Files
go-jdenticon/jdenticon/security_test.go
Kevin McIntyre f1544ef49c
Some checks failed
CI / Test (Go 1.24.x, ubuntu-latest) (push) Successful in 1m53s
CI / Code Quality (push) Failing after 26s
CI / Security Scan (push) Failing after 11s
CI / Test Coverage (push) Successful in 1m13s
CI / Benchmarks (push) Failing after 10m22s
CI / Build CLI (push) Failing after 8s
Benchmarks / Run Benchmarks (push) Failing after 10m13s
Release / Test (push) Successful in 55s
Release / Build (amd64, darwin, ) (push) Failing after 12s
Release / Build (amd64, linux, ) (push) Failing after 6s
Release / Build (amd64, windows, .exe) (push) Failing after 12s
Release / Build (arm64, darwin, ) (push) Failing after 12s
Release / Build (arm64, linux, ) (push) Failing after 12s
Release / Release (push) Has been skipped
CI / Test (Go 1.24.x, macos-latest) (push) Has been cancelled
CI / Test (Go 1.24.x, windows-latest) (push) Has been cancelled
chore: update module path to gitea.dockr.co/kev/go-jdenticon
Move hosting from GitHub to private Gitea instance.
2026-02-10 10:07:57 -05:00

469 lines
14 KiB
Go

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")
}
}