package main import ( "context" "fmt" "os" "path/filepath" "runtime" "strings" "sync/atomic" "testing" "time" "gitea.dockr.co/kev/go-jdenticon/jdenticon" "github.com/spf13/cobra" "github.com/spf13/viper" ) // TestBatchCommand tests the batch command functionality func TestBatchCommand(t *testing.T) { // Create temporary directory for test files tempDir, err := os.MkdirTemp("", "jdenticon-batch-test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer os.RemoveAll(tempDir) // Create test input file inputFile := filepath.Join(tempDir, "input.txt") testInputs := []string{ "user1@example.com", "user2@example.com", "test-user", "unicode-üser", "", // Empty line should be skipped "special@chars!", } inputContent := strings.Join(testInputs, "\n") if err := os.WriteFile(inputFile, []byte(inputContent), 0644); err != nil { t.Fatalf("Failed to create input file: %v", err) } outputDir := filepath.Join(tempDir, "output") tests := []struct { name string args []string wantErr bool outputCheck func(t *testing.T, outputDir string) }{ { name: "batch generate SVG", args: []string{"batch", inputFile, "--output-dir", outputDir}, wantErr: false, outputCheck: func(t *testing.T, outputDir string) { // Check that SVG files were created expectedFiles := []string{ "user1_at_example.com.svg", "user2_at_example.com.svg", "test-user.svg", "unicode-_ser.svg", // Unicode characters get sanitized "special_at_chars_.svg", // Special chars get sanitized } for _, filename := range expectedFiles { filepath := filepath.Join(outputDir, filename) if _, err := os.Stat(filepath); os.IsNotExist(err) { t.Errorf("Expected file %s to be created", filename) continue } // Check file content content, err := os.ReadFile(filepath) if err != nil { t.Errorf("Failed to read file %s: %v", filename, err) continue } if !strings.Contains(string(content), " 200 { return longStr[:200] } return longStr }(), }, { input: "", expected: "", }, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { result := sanitizeFilename(tt.input) if result != tt.expected { t.Errorf("sanitizeFilename(%q) = %q, want %q", tt.input, result, tt.expected) } }) } } // TestBatchWithCustomConfig tests batch generation with custom configuration func TestBatchWithCustomConfig(t *testing.T) { tempDir, err := os.MkdirTemp("", "jdenticon-batch-config-test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer os.RemoveAll(tempDir) // Create simple input file inputFile := filepath.Join(tempDir, "input.txt") if err := os.WriteFile(inputFile, []byte("test@example.com\n"), 0644); err != nil { t.Fatalf("Failed to create input file: %v", err) } outputDir := filepath.Join(tempDir, "output") tests := []struct { name string args []string wantErr bool outputCheck func(t *testing.T, outputPath string) }{ { name: "custom size", args: []string{"batch", "--size", "128", inputFile, "--output-dir", outputDir + "-size"}, wantErr: false, outputCheck: func(t *testing.T, outputPath string) { content, err := os.ReadFile(outputPath) if err != nil { t.Fatalf("Failed to read output: %v", err) } if !strings.Contains(string(content), "width=\"128\"") { t.Error("Expected SVG to have custom size") } }, }, { name: "custom background color", args: []string{"batch", "--bg-color", "#ff0000", inputFile, "--output-dir", outputDir + "-bg"}, wantErr: false, outputCheck: func(t *testing.T, outputPath string) { content, err := os.ReadFile(outputPath) if err != nil { t.Fatalf("Failed to read output: %v", err) } if !strings.Contains(string(content), "#ff0000") { t.Error("Expected SVG to have custom background color") } }, }, { name: "custom padding", args: []string{"batch", "--padding", "0.2", inputFile, "--output-dir", outputDir + "-pad"}, wantErr: false, outputCheck: func(t *testing.T, outputPath string) { content, err := os.ReadFile(outputPath) if err != nil { t.Fatalf("Failed to read output: %v", err) } // Should still generate valid SVG if !strings.Contains(string(content), "", Short: "Generate multiple identicons from a list", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { return runConcurrentBatch(cmd, args) }, } // Add batch-specific flags batchCmd.Flags().StringP("output-dir", "d", "", "Output directory for generated identicons (required)") batchCmd.MarkFlagRequired("output-dir") // Concurrency control batchCmd.Flags().IntP("concurrency", "c", runtime.NumCPU(), fmt.Sprintf("Number of concurrent workers (default: %d)", runtime.NumCPU())) // Add to root command rootCmd.AddCommand(batchCmd) return rootCmd } // TestConcurrentBatchProcessing tests the concurrent batch processing functionality func TestConcurrentBatchProcessing(t *testing.T) { // Create temporary directory for test files tempDir, err := os.MkdirTemp("", "jdenticon-concurrent-test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer os.RemoveAll(tempDir) // Create test input file with more entries to test concurrency inputFile := filepath.Join(tempDir, "input.txt") var inputs []string for i := 0; i < 50; i++ { inputs = append(inputs, fmt.Sprintf("user%d@example.com", i)) } inputContent := strings.Join(inputs, "\n") if err := os.WriteFile(inputFile, []byte(inputContent), 0644); err != nil { t.Fatalf("Failed to create input file: %v", err) } outputDir := filepath.Join(tempDir, "output") tests := []struct { name string concurrency int expectFiles int }{ {"sequential", 1, 50}, {"small_pool", 2, 50}, {"medium_pool", 4, 50}, {"large_pool", runtime.NumCPU(), 50}, {"over_provisioned", runtime.NumCPU() * 2, 50}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Clean output directory os.RemoveAll(outputDir) // Test the concurrent batch command cmd := createTestBatchCommand() args := []string{"batch", inputFile, "--output-dir", outputDir, "--concurrency", fmt.Sprintf("%d", tt.concurrency)} cmd.SetArgs(args) start := time.Now() err := cmd.Execute() duration := time.Since(start) if err != nil { t.Fatalf("Command failed: %v", err) } // Verify output files files, err := os.ReadDir(outputDir) if err != nil { t.Fatalf("Failed to read output directory: %v", err) } if len(files) != tt.expectFiles { t.Errorf("Expected %d files, got %d", tt.expectFiles, len(files)) } // Verify all files are valid SVG for _, file := range files { if !strings.HasSuffix(file.Name(), ".svg") { t.Errorf("Expected SVG file, got %s", file.Name()) continue } content, err := os.ReadFile(filepath.Join(outputDir, file.Name())) if err != nil { t.Errorf("Failed to read file %s: %v", file.Name(), err) continue } if !strings.Contains(string(content), "