package jdenticon import ( "context" "errors" "strings" "testing" "gitea.dockr.co/kev/go-jdenticon/internal/constants" ) // TestDoSProtection_InputLength tests protection against large input strings. func TestDoSProtection_InputLength(t *testing.T) { tests := []struct { name string inputLength int config Config expectError bool errorType string }{ { name: "normal input with default config", inputLength: 100, config: DefaultConfig(), expectError: false, }, { name: "maximum allowed input with default config", inputLength: constants.DefaultMaxInputLength, config: DefaultConfig(), expectError: false, }, { name: "oversized input with default config", inputLength: constants.DefaultMaxInputLength + 1, config: DefaultConfig(), expectError: true, errorType: "*jdenticon.ErrValueTooLarge", }, { name: "oversized input with custom smaller limit", inputLength: 1000, config: func() Config { c := DefaultConfig() c.MaxInputLength = 500 return c }(), expectError: true, errorType: "*jdenticon.ErrValueTooLarge", }, { name: "oversized input with disabled limit", inputLength: constants.DefaultMaxInputLength + 1000, config: func() Config { c := DefaultConfig() c.MaxInputLength = -1 // Disabled return c }(), expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create input string of specified length input := strings.Repeat("a", tt.inputLength) // Test ToSVGWithConfig _, err := ToSVGWithConfig(context.Background(), input, 100, tt.config) if tt.expectError { if err == nil { t.Errorf("ToSVGWithConfig: expected error but got none") return } // Check error type if specified if tt.errorType != "" { var valueErr *ErrValueTooLarge if !errors.As(err, &valueErr) { t.Errorf("ToSVGWithConfig: expected error type %s, got %T", tt.errorType, err) } else if valueErr.ParameterName != "InputLength" { t.Errorf("ToSVGWithConfig: expected InputLength error, got %s", valueErr.ParameterName) } } } else if err != nil { t.Errorf("ToSVGWithConfig: unexpected error: %v", err) } // Test ToPNGWithConfig _, err = ToPNGWithConfig(context.Background(), input, 100, tt.config) if tt.expectError { if err == nil { t.Errorf("ToPNGWithConfig: expected error but got none") } } else if err != nil { t.Errorf("ToPNGWithConfig: unexpected error: %v", err) } }) } } // TestDoSProtection_IconSize tests protection against large icon sizes. func TestDoSProtection_IconSize(t *testing.T) { tests := []struct { name string size int config Config expectError bool errorType string }{ { name: "normal size with default config", size: 256, config: DefaultConfig(), expectError: false, }, { name: "maximum allowed size with default config", size: constants.DefaultMaxIconSize, config: DefaultConfig(), expectError: false, }, { name: "oversized icon with default config", size: constants.DefaultMaxIconSize + 1, config: DefaultConfig(), expectError: true, errorType: "*jdenticon.ErrValueTooLarge", }, { name: "oversized icon with custom smaller limit", size: 2000, config: func() Config { c := DefaultConfig() c.MaxIconSize = 1000 return c }(), expectError: true, errorType: "*jdenticon.ErrValueTooLarge", }, { name: "oversized icon with disabled limit", size: constants.DefaultMaxIconSize + 1000, config: func() Config { c := DefaultConfig() c.MaxIconSize = -1 // Disabled return c }(), expectError: false, }, { name: "zero size", size: 0, config: DefaultConfig(), expectError: true, errorType: "jdenticon.ErrInvalidSize", }, { name: "negative size", size: -100, config: DefaultConfig(), expectError: true, errorType: "jdenticon.ErrInvalidSize", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { input := "test" // Test ToSVGWithConfig _, err := ToSVGWithConfig(context.Background(), input, tt.size, tt.config) if tt.expectError { if err == nil { t.Errorf("ToSVGWithConfig: expected error but got none") return } // Check error type if specified if tt.errorType != "" { switch tt.errorType { case "*jdenticon.ErrValueTooLarge": var valueErr *ErrValueTooLarge if !errors.As(err, &valueErr) { t.Errorf("ToSVGWithConfig: expected error type %s, got %T", tt.errorType, err) } else if valueErr.ParameterName != "IconSize" { t.Errorf("ToSVGWithConfig: expected IconSize error, got %s", valueErr.ParameterName) } case "jdenticon.ErrInvalidSize": var sizeErr ErrInvalidSize if !errors.As(err, &sizeErr) { t.Errorf("ToSVGWithConfig: expected error type %s, got %T", tt.errorType, err) } } } } else if err != nil { t.Errorf("ToSVGWithConfig: unexpected error: %v", err) } }) } } // TestDoSProtection_PNGEffectiveSize tests protection against PNG supersampling creating oversized effective images. func TestDoSProtection_PNGEffectiveSize(t *testing.T) { tests := []struct { name string size int supersampling int config Config expectError bool errorType string }{ { name: "normal PNG with default supersampling", size: 512, supersampling: 8, // 512 * 8 = 4096 (exactly at limit) expectError: false, }, { name: "oversized effective PNG size", size: 1024, supersampling: 8, // 1024 * 8 = 8192 (exceeds 4096 limit) expectError: true, errorType: "*jdenticon.ErrEffectiveSizeTooLarge", }, { name: "large PNG with low supersampling (within limit)", size: 2048, supersampling: 2, // 2048 * 2 = 4096 (exactly at limit) expectError: false, }, { name: "maximum PNG with 1x supersampling", size: 4096, supersampling: 1, // 4096 * 1 = 4096 (exactly at limit) expectError: false, }, { name: "PNG with disabled size limit", size: 2000, supersampling: 10, // Would exceed default limit but should be allowed config: func() Config { c := DefaultConfig() c.MaxIconSize = -1 // Disabled return c }(), expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config := tt.config if config.MaxIconSize == 0 && config.MaxInputLength == 0 { // Use default config if not specified config = DefaultConfig() } config.PNGSupersampling = tt.supersampling input := "test" _, err := ToPNGWithConfig(context.Background(), input, tt.size, config) if tt.expectError { if err == nil { t.Errorf("ToPNGWithConfig: expected error but got none") return } // Check error type if specified if tt.errorType == "*jdenticon.ErrEffectiveSizeTooLarge" { var effectiveErr *ErrEffectiveSizeTooLarge if !errors.As(err, &effectiveErr) { t.Errorf("ToPNGWithConfig: expected error type %s, got %T", tt.errorType, err) } else { expectedEffective := tt.size * tt.supersampling if effectiveErr.Actual != expectedEffective { t.Errorf("ToPNGWithConfig: expected effective size %d, got %d", expectedEffective, effectiveErr.Actual) } if effectiveErr.Size != tt.size { t.Errorf("ToPNGWithConfig: expected size %d, got %d", tt.size, effectiveErr.Size) } if effectiveErr.Supersampling != tt.supersampling { t.Errorf("ToPNGWithConfig: expected supersampling %d, got %d", tt.supersampling, effectiveErr.Supersampling) } } } } else if err != nil { t.Errorf("ToPNGWithConfig: unexpected error: %v", err) } }) } } // TestDoSProtection_DynamicSupersampling tests the dynamic supersampling feature in ToPNG. func TestDoSProtection_DynamicSupersampling(t *testing.T) { tests := []struct { name string size int expectError bool expectedMaxSS int // Expected maximum supersampling that should be used }{ { name: "small size uses full supersampling", size: 256, expectError: false, expectedMaxSS: 8, // 256 * 8 = 2048 < 4096, so full supersampling }, { name: "medium size uses reduced supersampling", size: 1024, expectError: false, expectedMaxSS: 4, // 1024 * 4 = 4096, reduced from default 8 }, { name: "large size uses minimal supersampling", size: 4096, expectError: false, expectedMaxSS: 1, // 4096 * 1 = 4096, minimal supersampling }, { name: "oversized even with minimal supersampling", size: 4097, expectError: true, // Even 4097 * 1 = 4097 > 4096 }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { input := "test" data, err := ToPNG(context.Background(), input, tt.size) if tt.expectError { if err == nil { t.Errorf("ToPNG: expected error but got none") } return } if err != nil { t.Errorf("ToPNG: unexpected error: %v", err) return } if len(data) == 0 { t.Errorf("ToPNG: expected PNG data but got empty result") } }) } } // TestDoSProtection_EmptyInput tests protection against empty input strings. func TestDoSProtection_EmptyInput(t *testing.T) { config := DefaultConfig() _, err := ToSVGWithConfig(context.Background(), "", 100, config) if err == nil { t.Errorf("ToSVGWithConfig: expected error for empty input but got none") return } var inputErr *ErrInvalidInput if !errors.As(err, &inputErr) { t.Errorf("ToSVGWithConfig: expected ErrInvalidInput, got %T", err) } else if inputErr.Field != "input" { t.Errorf("ToSVGWithConfig: expected input field error, got %s", inputErr.Field) } } // TestDoSProtection_ConfigurableLimits tests that the configurable limits work as expected. func TestDoSProtection_ConfigurableLimits(t *testing.T) { // Test custom limits customConfig := DefaultConfig() customConfig.MaxIconSize = 1000 // Much smaller than default customConfig.MaxInputLength = 10000 // Much smaller than default customConfig.PNGSupersampling = 4 // This should work with custom config _, err := ToSVGWithConfig(context.Background(), "test", 500, customConfig) if err != nil { t.Errorf("ToSVGWithConfig with custom config: unexpected error: %v", err) } // This should fail with custom config (size too large) _, err = ToSVGWithConfig(context.Background(), "test", 1001, customConfig) if err == nil { t.Errorf("ToSVGWithConfig with custom config: expected error for oversized icon but got none") } // Test disabled limits noLimitsConfig := DefaultConfig() noLimitsConfig.MaxIconSize = -1 // Disabled noLimitsConfig.MaxInputLength = -1 // Disabled noLimitsConfig.PNGSupersampling = 1 // This should work even with very large size (disabled limits) largeInput := strings.Repeat("x", constants.DefaultMaxInputLength+1000) _, err = ToSVGWithConfig(context.Background(), largeInput, constants.DefaultMaxIconSize+1000, noLimitsConfig) if err != nil { t.Errorf("ToSVGWithConfig with disabled limits: unexpected error: %v", err) } } // TestDoSProtection_GeneratorConsistency tests that Generator API respects configured limits. func TestDoSProtection_GeneratorConsistency(t *testing.T) { // Test generator with custom limits customConfig := DefaultConfig() customConfig.MaxIconSize = 1000 customConfig.MaxInputLength = 100 generator, err := NewGeneratorWithConfig(customConfig, 10) if err != nil { t.Fatalf("NewGeneratorWithConfig: unexpected error: %v", err) } // This should work within limits _, err = generator.Generate(context.Background(), "test", 500) if err != nil { t.Errorf("Generator.Generate within limits: unexpected error: %v", err) } // This should fail due to size limit _, err = generator.Generate(context.Background(), "test", 1001) if err == nil { t.Errorf("Generator.Generate with oversized icon: expected error but got none") } else { var valueErr *ErrValueTooLarge if !errors.As(err, &valueErr) { t.Errorf("Generator.Generate: expected ErrValueTooLarge, got %T", err) } else if valueErr.ParameterName != "IconSize" { t.Errorf("Generator.Generate: expected IconSize error, got %s", valueErr.ParameterName) } } // This should fail due to input length limit longInput := strings.Repeat("a", 101) _, err = generator.Generate(context.Background(), longInput, 100) if err == nil { t.Errorf("Generator.Generate with long input: expected error but got none") } else { var valueErr *ErrValueTooLarge if !errors.As(err, &valueErr) { t.Errorf("Generator.Generate: expected ErrValueTooLarge, got %T", err) } else if valueErr.ParameterName != "InputLength" { t.Errorf("Generator.Generate: expected InputLength error, got %s", valueErr.ParameterName) } } // Test generator with default limits defaultGenerator, err := NewGenerator() if err != nil { t.Fatalf("NewGenerator: unexpected error: %v", err) } // Should work with normal inputs _, err = defaultGenerator.Generate(context.Background(), "test", 256) if err != nil { t.Errorf("Default generator: unexpected error: %v", err) } // Should fail with oversized input _, err = defaultGenerator.Generate(context.Background(), "test", constants.DefaultMaxIconSize+1) if err == nil { t.Errorf("Default generator with oversized icon: expected error but got none") } }