package jdenticon import ( "bytes" "fmt" "strings" "testing" ) func TestGenerate(t *testing.T) { tests := []struct { name string value string size int }{ { name: "email address", value: "test@example.com", size: 64, }, { name: "username", value: "johndoe", size: 32, }, { name: "large icon", value: "large-icon-test", size: 256, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { icon, err := Generate(tt.value, tt.size) if err != nil { t.Fatalf("Generate failed: %v", err) } if icon == nil { t.Fatal("Generate returned nil icon") } // Test SVG generation svg, err := icon.ToSVG() if err != nil { t.Fatalf("ToSVG failed: %v", err) } if svg == "" { t.Error("ToSVG returned empty string") } // Basic SVG validation if !strings.Contains(svg, "") { t.Error("SVG output does not contain closing svg tag") } // Test PNG generation png, err := icon.ToPNG() if err != nil { t.Fatalf("ToPNG failed: %v", err) } if len(png) == 0 { t.Error("ToPNG returned empty data") } // Basic PNG validation (check PNG signature) if len(png) < 8 || string(png[1:4]) != "PNG" { t.Error("PNG output does not have valid PNG signature") } }) } } func TestGenerateConsistency(t *testing.T) { value := "consistency-test" size := 64 // Generate the same icon multiple times icon1, err := Generate(value, size) if err != nil { t.Fatalf("First generate failed: %v", err) } icon2, err := Generate(value, size) if err != nil { t.Fatalf("Second generate failed: %v", err) } // SVG should be identical svg1, err := icon1.ToSVG() if err != nil { t.Fatalf("First ToSVG failed: %v", err) } svg2, err := icon2.ToSVG() if err != nil { t.Fatalf("Second ToSVG failed: %v", err) } if svg1 != svg2 { t.Error("SVG outputs are not consistent for same input") } // PNG should be identical png1, err := icon1.ToPNG() if err != nil { t.Fatalf("First ToPNG failed: %v", err) } png2, err := icon2.ToPNG() if err != nil { t.Fatalf("Second ToPNG failed: %v", err) } if !bytes.Equal(png1, png2) { t.Error("PNG outputs are not consistent for same input") } } func TestGenerateInvalidInputs(t *testing.T) { tests := []struct { name string value string size int }{ { name: "zero size", value: "test", size: 0, }, { name: "negative size", value: "test", size: -10, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := Generate(tt.value, tt.size) if err == nil { t.Error("Expected error for invalid input") } }) } } func TestGenerateVariety(t *testing.T) { // Test that different inputs produce different outputs values := []string{"value1", "value2", "value3", "test@example.com", "another-test"} size := 64 svgs := make([]string, len(values)) for i, value := range values { icon, err := Generate(value, size) if err != nil { t.Fatalf("Generate failed for %s: %v", value, err) } svg, err := icon.ToSVG() if err != nil { t.Fatalf("ToSVG failed for %s: %v", value, err) } svgs[i] = svg } // Check that all SVGs are different for i := 0; i < len(svgs); i++ { for j := i + 1; j < len(svgs); j++ { if svgs[i] == svgs[j] { t.Errorf("SVG outputs are identical for different inputs: %s and %s", values[i], values[j]) } } } } func BenchmarkGenerate(b *testing.B) { value := "benchmark-test" size := 64 b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { _, err := Generate(value, size) if err != nil { b.Fatalf("Generate failed: %v", err) } } } // BenchmarkGenerateVariousSizes tests generation performance across different icon sizes func BenchmarkGenerateVariousSizes(b *testing.B) { sizes := []int{64, 128, 256, 512, 1024} for _, size := range sizes { b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := Generate("benchmark@test.com", size) if err != nil { b.Fatalf("Generate failed for size %d: %v", size, err) } } }) } } // BenchmarkGenerateVariousInputs tests generation performance with different input types func BenchmarkGenerateVariousInputs(b *testing.B) { inputs := []struct { name string value string }{ {"email", "user@example.com"}, {"username", "john_doe_123"}, {"uuid", "550e8400-e29b-41d4-a716-446655440000"}, {"short", "abc"}, {"long", "this_is_a_very_long_identifier_that_might_be_used_for_generating_identicons_in_some_applications"}, {"special_chars", "user+test@domain.co.uk"}, {"numbers", "12345678901234567890"}, } for _, input := range inputs { b.Run(input.name, func(b *testing.B) { b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := Generate(input.value, 128) if err != nil { b.Fatalf("Generate failed for input %s: %v", input.name, err) } } }) } } func BenchmarkToSVG(b *testing.B) { icon, err := Generate("benchmark-test", 64) if err != nil { b.Fatalf("Generate failed: %v", err) } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { _, err := icon.ToSVG() if err != nil { b.Fatalf("ToSVG failed: %v", err) } } } func BenchmarkToPNG(b *testing.B) { icon, err := Generate("benchmark-test", 64) if err != nil { b.Fatalf("Generate failed: %v", err) } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { _, err := icon.ToPNG() if err != nil { b.Fatalf("ToPNG failed: %v", err) } } } // BenchmarkHashGeneration benchmarks just hash computation performance func BenchmarkHashGeneration(b *testing.B) { inputs := []string{ "user@example.com", "john_doe_123", "550e8400-e29b-41d4-a716-446655440000", "abc", "this_is_a_very_long_identifier_that_might_be_used_for_generating_identicons", } for _, input := range inputs { b.Run(fmt.Sprintf("len_%d", len(input)), func(b *testing.B) { b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _ = ComputeHash(input) } }) } } // BenchmarkSVGRenderingVariousSizes benchmarks SVG rendering across different sizes func BenchmarkSVGRenderingVariousSizes(b *testing.B) { sizes := []int{64, 128, 256, 512} icons := make(map[int]*Icon) // Pre-generate icons for _, size := range sizes { icon, err := Generate("benchmark@test.com", size) if err != nil { b.Fatalf("Failed to generate icon for size %d: %v", size, err) } icons[size] = icon } for _, size := range sizes { b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { icon := icons[size] b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := icon.ToSVG() if err != nil { b.Fatalf("ToSVG failed for size %d: %v", size, err) } } }) } } // BenchmarkPNGRenderingVariousSizes benchmarks PNG rendering across different sizes func BenchmarkPNGRenderingVariousSizes(b *testing.B) { sizes := []int{64, 128, 256} // Smaller range for PNG due to higher memory usage icons := make(map[int]*Icon) // Pre-generate icons for _, size := range sizes { icon, err := Generate("benchmark@test.com", size) if err != nil { b.Fatalf("Failed to generate icon for size %d: %v", size, err) } icons[size] = icon } for _, size := range sizes { b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { icon := icons[size] b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := icon.ToPNG() if err != nil { b.Fatalf("ToPNG failed for size %d: %v", size, err) } } }) } } // BenchmarkWithCustomConfig benchmarks generation with custom configuration func BenchmarkWithCustomConfig(b *testing.B) { config, err := Configure( WithHueRestrictions([]float64{0.0, 0.33, 0.66}), WithColorSaturation(0.6), WithBackgroundColor("#ffffff"), WithPadding(0.1), ) if err != nil { b.Fatalf("Configure failed: %v", err) } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := ToSVG("benchmark@test.com", 128, config) if err != nil { b.Fatalf("ToSVG with config failed: %v", err) } } } // BenchmarkWithCustomConfigPNG benchmarks PNG generation with custom configuration func BenchmarkWithCustomConfigPNG(b *testing.B) { config, err := Configure( WithColorSaturation(0.6), WithBackgroundColor("#123456"), WithPadding(0.1), ) if err != nil { b.Fatalf("Configure failed: %v", err) } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := ToPNG("benchmark-png-config@test.com", 128, config) if err != nil { b.Fatalf("ToPNG with config failed: %v", err) } } } // BenchmarkConcurrentGeneration tests performance under concurrent load func BenchmarkConcurrentGeneration(b *testing.B) { b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { i := 0 for pb.Next() { value := fmt.Sprintf("concurrent_user_%d@example.com", i) _, err := Generate(value, 128) if err != nil { b.Fatalf("Generate failed: %v", err) } i++ } }) } // BenchmarkBatchGeneration tests memory allocation patterns for batch generation func BenchmarkBatchGeneration(b *testing.B) { const batchSize = 100 b.SetBytes(batchSize) // Report throughput as items/sec b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { for j := 0; j < batchSize; j++ { value := fmt.Sprintf("batch_user_%d@example.com", j) _, err := Generate(value, 64) if err != nil { b.Fatalf("Generate failed: %v", err) } } } } // Tests for new public API functions func TestToSVG(t *testing.T) { tests := []struct { name string value interface{} size int valid bool }{ {"string input", "test@example.com", 64, true}, {"int input", 12345, 64, true}, {"float input", 123.45, 64, true}, {"bool input", true, 64, true}, {"nil input", nil, 64, true}, {"byte slice", []byte("hello"), 64, true}, {"zero size", "test", 0, false}, {"negative size", "test", -10, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { svg, err := ToSVG(tt.value, tt.size) if tt.valid { if err != nil { t.Fatalf("ToSVG failed: %v", err) } if svg == "" { t.Error("ToSVG returned empty string") } // Basic SVG validation if !strings.Contains(svg, "") { t.Error("SVG output does not contain closing svg tag") } } else { if err == nil { t.Error("Expected error for invalid input") } } }) } } func TestToPNG(t *testing.T) { tests := []struct { name string value interface{} size int valid bool }{ {"string input", "test@example.com", 64, true}, {"int input", 12345, 64, true}, {"float input", 123.45, 64, true}, {"bool input", false, 64, true}, {"nil input", nil, 64, true}, {"byte slice", []byte("hello"), 64, true}, {"zero size", "test", 0, false}, {"negative size", "test", -10, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { png, err := ToPNG(tt.value, tt.size) if tt.valid { if err != nil { t.Fatalf("ToPNG failed: %v", err) } if len(png) == 0 { t.Error("ToPNG returned empty data") } // Basic PNG validation (check PNG signature) if len(png) < 8 || string(png[1:4]) != "PNG" { t.Error("PNG output does not have valid PNG signature") } } else { if err == nil { t.Error("Expected error for invalid input") } } }) } } func TestToHash(t *testing.T) { tests := []struct { name string value interface{} expected string }{ {"string", "test", ComputeHash("test")}, {"int", 123, ComputeHash("123")}, {"float", 123.45, ComputeHash("123.45")}, {"bool true", true, ComputeHash("true")}, {"bool false", false, ComputeHash("false")}, {"nil", nil, ComputeHash("")}, {"byte slice", []byte("hello"), ComputeHash("hello")}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { hash := ToHash(tt.value) if hash != tt.expected { t.Errorf("ToHash(%v) = %s, expected %s", tt.value, hash, tt.expected) } // Hash should be non-empty and valid hex if hash == "" { t.Error("ToHash returned empty string") } // Should be valid hex characters for _, c := range hash { if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) { t.Errorf("Hash contains invalid character: %c", c) break } } }) } } func TestToSVGWithConfig(t *testing.T) { value := "config-test" size := 64 // Test with custom configuration config, err := Configure( WithHueRestrictions([]float64{120, 240}), // Blue/green hues WithColorSaturation(0.8), WithBackgroundColor("#ffffff"), WithPadding(0.1), ) if err != nil { t.Fatalf("Configure failed: %v", err) } svg, err := ToSVG(value, size, config) if err != nil { t.Fatalf("ToSVG with config failed: %v", err) } if svg == "" { t.Error("ToSVG with config returned empty string") } // Test that it's different from default defaultSvg, err := ToSVG(value, size) if err != nil { t.Fatalf("ToSVG default failed: %v", err) } if svg == defaultSvg { t.Error("SVG with config is identical to default SVG") } } func TestToPNGWithConfig(t *testing.T) { value := "config-test" size := 64 // Test with custom configuration config, err := Configure( WithHueRestrictions([]float64{60, 180}), // Yellow/cyan hues WithColorSaturation(0.9), WithBackgroundColor("#000000"), WithPadding(0.05), ) if err != nil { t.Fatalf("Configure failed: %v", err) } png, err := ToPNG(value, size, config) if err != nil { t.Fatalf("ToPNG with config failed: %v", err) } if len(png) == 0 { t.Error("ToPNG with config returned empty data") } // Test that it's different from default defaultPng, err := ToPNG(value, size) if err != nil { t.Fatalf("ToPNG default failed: %v", err) } if bytes.Equal(png, defaultPng) { t.Error("PNG with config is identical to default PNG") } } func TestPublicAPIConsistency(t *testing.T) { value := "consistency-test" size := 64 // Generate with both old and new APIs icon, err := Generate(value, size) if err != nil { t.Fatalf("Generate failed: %v", err) } oldSvg, err := icon.ToSVG() if err != nil { t.Fatalf("Icon.ToSVG failed: %v", err) } oldPng, err := icon.ToPNG() if err != nil { t.Fatalf("Icon.ToPNG failed: %v", err) } newSvg, err := ToSVG(value, size) if err != nil { t.Fatalf("ToSVG failed: %v", err) } newPng, err := ToPNG(value, size) if err != nil { t.Fatalf("ToPNG failed: %v", err) } // Results should be identical if oldSvg != newSvg { t.Error("SVG output differs between old and new APIs") } if !bytes.Equal(oldPng, newPng) { t.Error("PNG output differs between old and new APIs") } } func TestTypeConversion(t *testing.T) { tests := []struct { name string input interface{} expected string }{ {"string", "hello", "hello"}, {"int", 42, "42"}, {"int64", int64(42), "42"}, {"float64", 3.14, "3.14"}, {"bool true", true, "true"}, {"bool false", false, "false"}, {"nil", nil, ""}, {"byte slice", []byte("test"), "test"}, {"struct", struct{ Name string }{"test"}, "{test}"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := convertToString(tt.input) if result != tt.expected { t.Errorf("convertToString(%v) = %s, expected %s", tt.input, result, tt.expected) } }) } } // Helper function for min func min(a, b int) int { if a < b { return a } return b }