package engine import ( "testing" "github.com/kevin/go-jdenticon/internal/util" ) func TestNewGenerator(t *testing.T) { config := DefaultColorConfig() generator := NewGenerator(config) if generator == nil { t.Fatal("NewGenerator returned nil") } if generator.config.IconPadding != config.IconPadding { t.Errorf("Expected icon padding %f, got %f", config.IconPadding, generator.config.IconPadding) } if generator.cache == nil { t.Error("Generator cache was not initialized") } } func TestNewDefaultGenerator(t *testing.T) { generator := NewDefaultGenerator() if generator == nil { t.Fatal("NewDefaultGenerator returned nil") } expectedConfig := DefaultColorConfig() if generator.config.IconPadding != expectedConfig.IconPadding { t.Errorf("Expected icon padding %f, got %f", expectedConfig.IconPadding, generator.config.IconPadding) } } func TestGenerateValidHash(t *testing.T) { generator := NewDefaultGenerator() hash := "abcdef123456789" size := 64.0 icon, err := generator.Generate(hash, size) if err != nil { t.Fatalf("Generate failed with error: %v", err) } if icon == nil { t.Fatal("Generate returned nil icon") } if icon.Hash != hash { t.Errorf("Expected hash %s, got %s", hash, icon.Hash) } if icon.Size != size { t.Errorf("Expected size %f, got %f", size, icon.Size) } if len(icon.Shapes) == 0 { t.Error("Generated icon has no shapes") } } func TestGenerateInvalidInputs(t *testing.T) { generator := NewDefaultGenerator() tests := []struct { name string hash string size float64 wantErr bool }{ { name: "empty hash", hash: "", size: 64.0, wantErr: true, }, { name: "zero size", hash: "abcdef123456789", size: 0.0, wantErr: true, }, { name: "negative size", hash: "abcdef123456789", size: -10.0, wantErr: true, }, { name: "short hash", hash: "abc", size: 64.0, wantErr: true, }, { name: "invalid hex characters", hash: "xyz123456789abc", size: 64.0, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := generator.Generate(tt.hash, tt.size) if (err != nil) != tt.wantErr { t.Errorf("Generate() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestGenerateCaching(t *testing.T) { generator := NewDefaultGenerator() hash := "abcdef123456789" size := 64.0 // Generate icon first time icon1, err := generator.Generate(hash, size) if err != nil { t.Fatalf("First generate failed: %v", err) } // Check cache size if generator.GetCacheSize() != 1 { t.Errorf("Expected cache size 1, got %d", generator.GetCacheSize()) } // Generate same icon again icon2, err := generator.Generate(hash, size) if err != nil { t.Fatalf("Second generate failed: %v", err) } // Should be the same instance from cache if icon1 != icon2 { t.Error("Second generate did not return cached instance") } // Cache size should still be 1 if generator.GetCacheSize() != 1 { t.Errorf("Expected cache size 1 after second generate, got %d", generator.GetCacheSize()) } } func TestClearCache(t *testing.T) { generator := NewDefaultGenerator() hash := "abcdef123456789" size := 64.0 // Generate an icon to populate cache _, err := generator.Generate(hash, size) if err != nil { t.Fatalf("Generate failed: %v", err) } // Verify cache has content if generator.GetCacheSize() == 0 { t.Error("Cache should not be empty after generate") } // Clear cache generator.ClearCache() // Verify cache is empty if generator.GetCacheSize() != 0 { t.Errorf("Expected cache size 0 after clear, got %d", generator.GetCacheSize()) } } func TestSetConfig(t *testing.T) { generator := NewDefaultGenerator() hash := "abcdef123456789" size := 64.0 // Generate an icon to populate cache _, err := generator.Generate(hash, size) if err != nil { t.Fatalf("Generate failed: %v", err) } // Verify cache has content if generator.GetCacheSize() == 0 { t.Error("Cache should not be empty after generate") } // Set new config newConfig := DefaultColorConfig() newConfig.IconPadding = 0.1 generator.SetConfig(newConfig) // Verify config was updated if generator.GetConfig().IconPadding != 0.1 { t.Errorf("Expected icon padding 0.1, got %f", generator.GetConfig().IconPadding) } // Verify cache was cleared if generator.GetCacheSize() != 0 { t.Errorf("Expected cache size 0 after config change, got %d", generator.GetCacheSize()) } } func TestExtractHue(t *testing.T) { generator := NewDefaultGenerator() tests := []struct { name string hash string expected float64 tolerance float64 }{ { name: "all zeros", hash: "0000000000000000000", expected: 0.0, tolerance: 0.0001, }, { name: "all fs", hash: "ffffffffffffffffff", expected: 1.0, tolerance: 0.0001, }, { name: "half value", hash: "000000000007ffffff", expected: 0.5, tolerance: 0.001, // Allow small floating point variance }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := generator.extractHue(tt.hash) if err != nil { t.Fatalf("extractHue failed: %v", err) } diff := result - tt.expected if diff < 0 { diff = -diff } if diff > tt.tolerance { t.Errorf("Expected hue %f, got %f (tolerance %f)", tt.expected, result, tt.tolerance) } }) } } func TestSelectColors(t *testing.T) { generator := NewDefaultGenerator() hash := "123456789abcdef" // Create test color palette availableColors := []Color{ NewColorRGB(50, 50, 50), // 0: Dark gray NewColorRGB(100, 100, 200), // 1: Mid color NewColorRGB(200, 200, 200), // 2: Light gray NewColorRGB(150, 150, 255), // 3: Light color NewColorRGB(25, 25, 100), // 4: Dark color } selectedIndexes, err := generator.selectColors(hash, availableColors) if err != nil { t.Fatalf("selectColors failed: %v", err) } if len(selectedIndexes) != 3 { t.Fatalf("Expected 3 selected colors, got %d", len(selectedIndexes)) } for i, index := range selectedIndexes { if index < 0 || index >= len(availableColors) { t.Errorf("Color index %d at position %d is out of range [0, %d)", index, i, len(availableColors)) } } } func TestSelectColorsEmptyPalette(t *testing.T) { generator := NewDefaultGenerator() hash := "123456789abcdef" _, err := generator.selectColors(hash, []Color{}) if err == nil { t.Error("Expected error for empty color palette") } } func TestIsValidHash(t *testing.T) { tests := []struct { name string hash string valid bool }{ { name: "valid hash", hash: "abcdef123456789", valid: true, }, { name: "too short", hash: "abc", valid: false, }, { name: "invalid characters", hash: "xyz123456789abc", valid: false, }, { name: "uppercase valid", hash: "ABCDEF123456789", valid: true, }, { name: "mixed case valid", hash: "AbCdEf123456789", valid: true, }, { name: "empty", hash: "", valid: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := util.IsValidHash(tt.hash) if result != tt.valid { t.Errorf("Expected isValidHash(%s) = %v, got %v", tt.hash, tt.valid, result) } }) } } func TestParseHex(t *testing.T) { hash := "123456789abcdef" tests := []struct { name string start int octets int expected int wantErr bool }{ { name: "single character", start: 0, octets: 1, expected: 1, wantErr: false, }, { name: "two characters", start: 1, octets: 2, expected: 0x23, wantErr: false, }, { name: "negative index", start: -1, octets: 1, expected: 0xf, wantErr: false, }, { name: "out of bounds", start: 100, octets: 1, expected: 0, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := util.ParseHex(hash, tt.start, tt.octets) if tt.wantErr { if err == nil { t.Errorf("Expected an error, but got nil") } return // Test is done for error cases } if err != nil { t.Fatalf("parseHex failed unexpectedly: %v", err) } if result != tt.expected { t.Errorf("Expected %d, got %d", tt.expected, result) } }) } } func TestShapeCollector(t *testing.T) { collector := &shapeCollector{} // Test AddPolygon points := []Point{{X: 0, Y: 0}, {X: 1, Y: 0}, {X: 1, Y: 1}, {X: 0, Y: 1}} collector.AddPolygon(points) if len(collector.shapes) != 1 { t.Fatalf("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) != len(points) { t.Errorf("Expected %d points, got %d", len(points), len(shape.Points)) } // Test AddCircle center := Point{X: 5, Y: 5} radius := 2.5 collector.AddCircle(center, radius, false) if len(collector.shapes) != 2 { t.Fatalf("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) } // Verify circle fields are set correctly if circleShape.CircleX != center.X { t.Errorf("Expected CircleX %f, got %f", center.X, circleShape.CircleX) } if circleShape.CircleY != center.Y { t.Errorf("Expected CircleY %f, got %f", center.Y, circleShape.CircleY) } if circleShape.CircleSize != radius { t.Errorf("Expected CircleSize %f, got %f", radius, circleShape.CircleSize) } if circleShape.Invert != false { t.Errorf("Expected Invert false, got %t", circleShape.Invert) } } func BenchmarkGenerate(b *testing.B) { generator := NewDefaultGenerator() hash := "abcdef123456789" size := 64.0 b.ResetTimer() for i := 0; i < b.N; i++ { _, err := generator.Generate(hash, size) if err != nil { b.Fatalf("Generate failed: %v", err) } } } func BenchmarkGenerateWithCache(b *testing.B) { generator := NewDefaultGenerator() hash := "abcdef123456789" size := 64.0 // Pre-populate cache _, err := generator.Generate(hash, size) if err != nil { b.Fatalf("Initial generate failed: %v", err) } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := generator.Generate(hash, size) if err != nil { b.Fatalf("Generate failed: %v", err) } } } func TestConsistentGeneration(t *testing.T) { generator1 := NewDefaultGenerator() generator2 := NewDefaultGenerator() hash := "abcdef123456789" size := 64.0 icon1, err := generator1.Generate(hash, size) if err != nil { t.Fatalf("Generator1 failed: %v", err) } icon2, err := generator2.Generate(hash, size) if err != nil { t.Fatalf("Generator2 failed: %v", err) } // Icons should have same number of shape groups if len(icon1.Shapes) != len(icon2.Shapes) { t.Errorf("Different number of shape groups: %d vs %d", len(icon1.Shapes), len(icon2.Shapes)) } // Colors should be the same for i := range icon1.Shapes { if i >= len(icon2.Shapes) { break } if !icon1.Shapes[i].Color.Equals(icon2.Shapes[i].Color) { t.Errorf("Different colors at group %d", i) } } }