package engine import ( "context" "fmt" "testing" "github.com/ungluedlabs/go-jdenticon/internal/util" ) func TestNewGenerator(t *testing.T) { config := DefaultColorConfig() generator, err := NewGenerator(config) if err != nil { t.Fatalf("NewGenerator failed: %v", err) } if generator == nil { t.Fatal("NewGenerator returned nil") } if generator.config.ColorConfig.IconPadding != config.IconPadding { t.Errorf("Expected icon padding %f, got %f", config.IconPadding, generator.config.ColorConfig.IconPadding) } if generator.cache == nil { t.Error("Generator cache was not initialized") } } func TestNewDefaultGenerator(t *testing.T) { generator, err := NewDefaultGenerator() if err != nil { t.Fatalf("NewDefaultGenerator failed: %v", err) } if generator == nil { t.Fatal("NewDefaultGenerator returned nil") } expectedConfig := DefaultColorConfig() if generator.config.ColorConfig.IconPadding != expectedConfig.IconPadding { t.Errorf("Expected icon padding %f, got %f", expectedConfig.IconPadding, generator.config.ColorConfig.IconPadding) } } func TestNewGeneratorWithConfig(t *testing.T) { config := GeneratorConfig{ ColorConfig: DefaultColorConfig(), CacheSize: 500, } generator, err := NewGeneratorWithConfig(config) if err != nil { t.Fatalf("NewGeneratorWithConfig failed: %v", err) } if generator == nil { t.Fatal("NewGeneratorWithConfig returned nil") } if generator.config.CacheSize != 500 { t.Errorf("Expected cache size 500, got %d", generator.config.CacheSize) } } func TestDefaultGeneratorConfig(t *testing.T) { config := DefaultGeneratorConfig() if config.CacheSize != 1000 { t.Errorf("Expected default cache size 1000, got %d", config.CacheSize) } if config.MaxComplexity != 0 { t.Errorf("Expected default max complexity 0, got %d", config.MaxComplexity) } } func TestExtractHue(t *testing.T) { generator, err := NewDefaultGenerator() if err != nil { t.Fatalf("NewDefaultGenerator failed: %v", err) } tests := []struct { name string hash string expectedHue float64 expectsError bool }{ { name: "Valid 40-character hash", hash: "abcdef1234567890abcdef1234567890abcdef12", expectedHue: float64(0xbcdef12) / float64(0xfffffff), expectsError: false, }, { name: "Valid hash with different values", hash: "1234567890abcdef1234567890abcdef12345678", expectedHue: float64(0x2345678) / float64(0xfffffff), expectsError: false, }, { name: "Hash too short", hash: "abc", expectedHue: 0, expectsError: true, }, { name: "Invalid hex characters", hash: "abcdef1234567890abcdef1234567890abcdefgh", expectedHue: 0, expectsError: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { hue, err := generator.extractHue(test.hash) if test.expectsError { if err == nil { t.Errorf("Expected error for hash %s, but got none", test.hash) } return } if err != nil { t.Errorf("Unexpected error for hash %s: %v", test.hash, err) return } if fmt.Sprintf("%.6f", hue) != fmt.Sprintf("%.6f", test.expectedHue) { t.Errorf("Expected hue %.6f, got %.6f", test.expectedHue, hue) } }) } } func TestSelectColors(t *testing.T) { generator, err := NewDefaultGenerator() if err != nil { t.Fatalf("NewDefaultGenerator failed: %v", err) } availableColors := []Color{ {H: 0.0, S: 1.0, L: 0.5, A: 255}, // Red {H: 0.33, S: 1.0, L: 0.5, A: 255}, // Green {H: 0.67, S: 1.0, L: 0.5, A: 255}, // Blue {H: 0.17, S: 1.0, L: 0.5, A: 255}, // Yellow {H: 0.0, S: 0.0, L: 0.5, A: 255}, // Gray } hash := "abcdef1234567890abcdef1234567890abcdef12" selectedIndexes, err := generator.selectColors(hash, availableColors) if err != nil { t.Fatalf("selectColors failed: %v", err) } if len(selectedIndexes) != 3 { t.Errorf("Expected 3 selected color indexes, got %d", len(selectedIndexes)) } for i, index := range selectedIndexes { if index < 0 || index >= len(availableColors) { t.Errorf("Selected index %d at position %d is out of range [0, %d)", index, i, len(availableColors)) } } } func TestSelectColorsEmptyPalette(t *testing.T) { generator, err := NewDefaultGenerator() if err != nil { t.Fatalf("NewDefaultGenerator failed: %v", err) } hash := "abcdef1234567890abcdef1234567890abcdef12" _, err = generator.selectColors(hash, []Color{}) if err == nil { t.Error("Expected error for empty color palette, but got none") } } func TestConsistentGeneration(t *testing.T) { generator, err := NewDefaultGenerator() if err != nil { t.Fatalf("NewDefaultGenerator failed: %v", err) } hash := "abcdef1234567890abcdef1234567890abcdef12" size := 64.0 icon1, err := generator.GenerateWithoutCache(context.Background(), hash, size) if err != nil { t.Fatalf("First generation failed: %v", err) } icon2, err := generator.GenerateWithoutCache(context.Background(), hash, size) if err != nil { t.Fatalf("Second generation failed: %v", err) } if icon1.Hash != icon2.Hash { t.Error("Icons have different hashes") } if icon1.Size != icon2.Size { t.Error("Icons have different sizes") } if len(icon1.Shapes) != len(icon2.Shapes) { t.Errorf("Icons have different number of shape groups: %d vs %d", len(icon1.Shapes), len(icon2.Shapes)) } for i, group1 := range icon1.Shapes { group2 := icon2.Shapes[i] if len(group1.Shapes) != len(group2.Shapes) { t.Errorf("Shape group %d has different number of shapes: %d vs %d", i, len(group1.Shapes), len(group2.Shapes)) } } } func TestIsColorInForbiddenSet(t *testing.T) { tests := []struct { name string index int forbidden []int expected bool }{ { name: "Index in forbidden set", index: 2, forbidden: []int{0, 2, 4}, expected: true, }, { name: "Index not in forbidden set", index: 1, forbidden: []int{0, 2, 4}, expected: false, }, { name: "Empty forbidden set", index: 1, forbidden: []int{}, expected: false, }, { name: "Single element forbidden set - match", index: 5, forbidden: []int{5}, expected: true, }, { name: "Single element forbidden set - no match", index: 3, forbidden: []int{5}, expected: false, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { result := isColorInForbiddenSet(test.index, test.forbidden) if result != test.expected { t.Errorf("Expected %v, got %v", test.expected, result) } }) } } func TestHasSelectedColorInForbiddenSet(t *testing.T) { tests := []struct { name string selected []int forbidden []int expected bool }{ { name: "No overlap", selected: []int{1, 3, 5}, forbidden: []int{0, 2, 4}, expected: false, }, { name: "Partial overlap", selected: []int{1, 2, 5}, forbidden: []int{0, 2, 4}, expected: true, }, { name: "Complete overlap", selected: []int{0, 2, 4}, forbidden: []int{0, 2, 4}, expected: true, }, { name: "Empty selected", selected: []int{}, forbidden: []int{0, 2, 4}, expected: false, }, { name: "Empty forbidden", selected: []int{1, 3, 5}, forbidden: []int{}, expected: false, }, { name: "Both empty", selected: []int{}, forbidden: []int{}, expected: false, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { result := hasSelectedColorInForbiddenSet(test.selected, test.forbidden) if result != test.expected { t.Errorf("Expected %v, got %v", test.expected, result) } }) } } func TestIsDuplicateColorRefactored(t *testing.T) { generator, err := NewDefaultGenerator() if err != nil { t.Fatalf("NewDefaultGenerator failed: %v", err) } tests := []struct { name string index int selected []int forbidden []int expected bool }{ { name: "Index not in forbidden set", index: 1, selected: []int{0, 4}, forbidden: []int{0, 4}, expected: false, }, { name: "Index in forbidden set, no selected colors in forbidden set", index: 0, selected: []int{1, 3}, forbidden: []int{0, 4}, expected: false, }, { name: "Index in forbidden set, has selected colors in forbidden set", index: 0, selected: []int{1, 4}, forbidden: []int{0, 4}, expected: true, }, { name: "Dark gray and dark main conflict", index: colorDarkGray, selected: []int{colorDarkMain}, forbidden: []int{colorDarkGray, colorDarkMain}, expected: true, }, { name: "Light gray and light main conflict", index: colorLightGray, selected: []int{colorLightMain}, forbidden: []int{colorLightGray, colorLightMain}, expected: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { result := generator.isDuplicateColor(test.index, test.selected, test.forbidden) if result != test.expected { t.Errorf("Expected %v, got %v", test.expected, result) } }) } } func TestShapeCollector(t *testing.T) { collector := &shapeCollector{} // Test initial state if len(collector.shapes) != 0 { t.Error("Expected empty shapes slice initially") } // Test AddPolygon points := []Point{{X: 0, Y: 0}, {X: 10, Y: 0}, {X: 5, Y: 10}} collector.AddPolygon(points) if len(collector.shapes) != 1 { t.Errorf("Expected 1 shape after AddPolygon, got %d", len(collector.shapes)) } shape := collector.shapes[0] if shape.Type != "polygon" { t.Errorf("Expected shape type 'polygon', got '%s'", shape.Type) } if len(shape.Points) != 3 { t.Errorf("Expected 3 points, got %d", len(shape.Points)) } // Test AddCircle collector.AddCircle(Point{X: 5, Y: 5}, 20, false) if len(collector.shapes) != 2 { t.Errorf("Expected 2 shapes after AddCircle, got %d", len(collector.shapes)) } circleShape := collector.shapes[1] if circleShape.Type != "circle" { t.Errorf("Expected shape type 'circle', got '%s'", circleShape.Type) } if circleShape.CircleX != 5 { t.Errorf("Expected CircleX 5, got %f", circleShape.CircleX) } if circleShape.CircleY != 5 { t.Errorf("Expected CircleY 5, got %f", circleShape.CircleY) } if circleShape.CircleSize != 20 { t.Errorf("Expected CircleSize 20, got %f", circleShape.CircleSize) } if circleShape.Invert != false { t.Errorf("Expected Invert false, got %v", circleShape.Invert) } // Test Reset collector.Reset() if len(collector.shapes) != 0 { t.Errorf("Expected empty shapes slice after Reset, got %d", len(collector.shapes)) } // Test that we can add shapes again after reset collector.AddPolygon([]Point{{X: 1, Y: 1}}) if len(collector.shapes) != 1 { t.Errorf("Expected 1 shape after Reset and AddPolygon, got %d", len(collector.shapes)) } } func TestIsValidHash(t *testing.T) { tests := []struct { name string hash string expected bool }{ { name: "Valid 40-character hex hash", hash: "abcdef1234567890abcdef1234567890abcdef12", expected: true, }, { name: "Valid 32-character hex hash", hash: "abcdef1234567890abcdef1234567890", expected: true, }, { name: "Empty hash", hash: "", expected: false, }, { name: "Hash too short", hash: "abc", expected: false, }, { name: "Hash with invalid characters", hash: "abcdef1234567890abcdef1234567890abcdefgh", expected: false, }, { name: "Hash with uppercase letters", hash: "ABCDEF1234567890ABCDEF1234567890ABCDEF12", expected: true, }, { name: "Mixed case hash", hash: "AbCdEf1234567890aBcDeF1234567890AbCdEf12", expected: true, }, { name: "Hash with spaces", hash: "abcdef12 34567890abcdef1234567890abcdef12", expected: false, }, { name: "All zeros", hash: "0000000000000000000000000000000000000000", expected: true, }, { name: "All f's", hash: "ffffffffffffffffffffffffffffffffffffffff", expected: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { result := util.IsValidHash(test.hash) if result != test.expected { t.Errorf("Expected %v for hash '%s', got %v", test.expected, test.hash, result) } }) } } func TestParseHex(t *testing.T) { tests := []struct { name string hash string position int octets int expected int expectsError bool }{ { name: "Valid single octet", hash: "abcdef1234567890", position: 0, octets: 1, expected: 0xa, expectsError: false, }, { name: "Valid two octets", hash: "abcdef1234567890", position: 1, octets: 2, expected: 0xbc, expectsError: false, }, { name: "Position at end of hash", hash: "abcdef12", position: 7, octets: 1, expected: 0x2, expectsError: false, }, { name: "Position beyond hash length", hash: "abc", position: 5, octets: 1, expected: 0, expectsError: true, }, { name: "Octets extend beyond hash", hash: "abcdef12", position: 6, octets: 3, expected: 0x12, // Should read to end of hash expectsError: false, }, { name: "Zero octets", hash: "abcdef12", position: 0, octets: 0, expected: 0xabcdef12, // Should read to end when octets is 0 expectsError: false, }, { name: "Negative position", hash: "abcdef12", position: -1, octets: 1, expected: 0x2, // Should read from end expectsError: false, }, { name: "Empty hash", hash: "", position: 0, octets: 1, expected: 0, expectsError: true, }, { name: "All f's", hash: "ffffffff", position: 0, octets: 4, expected: 0xffff, expectsError: false, }, { name: "Mixed case", hash: "AbCdEf12", position: 2, octets: 2, expected: 0xcd, expectsError: false, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { result, err := util.ParseHex(test.hash, test.position, test.octets) if test.expectsError { if err == nil { t.Errorf("Expected error for ParseHex(%s, %d, %d), but got none", test.hash, test.position, test.octets) } return } if err != nil { t.Errorf("Unexpected error for ParseHex(%s, %d, %d): %v", test.hash, test.position, test.octets, err) return } if result != test.expected { t.Errorf("Expected %d (0x%x), got %d (0x%x)", test.expected, test.expected, result, result) } }) } }