package challenge import ( "errors" "testing" "time" ) func TestNewConfig(t *testing.T) { tests := []struct { name string opts []configOption wantErr bool expectedErr error validate func(*Config) error }{ { name: "default config", opts: nil, wantErr: false, validate: func(c *Config) error { if c.DefaultDifficulty != 4 { return errors.New("expected default difficulty 4") } if c.RandomBytes != 6 { return errors.New("expected random bytes 6") } if len(c.HMACSecret) == 0 { return errors.New("expected HMAC secret to be generated") } return nil }, }, { name: "custom difficulty", opts: []configOption{WithDefaultDifficulty(8)}, wantErr: false, validate: func(c *Config) error { if c.DefaultDifficulty != 8 { return errors.New("expected custom difficulty 8") } return nil }, }, { name: "invalid random bytes - too low", opts: []configOption{WithRandomBytes(0)}, wantErr: true, expectedErr: ErrInvalidConfig, }, { name: "invalid random bytes - too high", opts: []configOption{WithRandomBytes(50)}, wantErr: true, expectedErr: ErrInvalidConfig, }, { name: "invalid difficulty - min too low", opts: []configOption{WithMinDifficulty(0)}, wantErr: true, expectedErr: ErrInvalidConfig, }, { name: "invalid difficulty - max less than min", opts: []configOption{ WithMinDifficulty(5), WithMaxDifficulty(3), }, wantErr: true, expectedErr: ErrInvalidConfig, }, { name: "invalid difficulty - default out of range", opts: []configOption{ WithMinDifficulty(5), WithMaxDifficulty(10), WithDefaultDifficulty(15), }, wantErr: true, expectedErr: ErrInvalidConfig, }, { name: "empty HMAC secret", opts: []configOption{WithHMACSecret(nil)}, wantErr: true, expectedErr: ErrInvalidConfig, }, { name: "negative TTL", opts: []configOption{WithChallengeTTL(-time.Minute)}, wantErr: true, expectedErr: ErrInvalidConfig, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config, err := NewConfig(tt.opts...) if tt.wantErr { if err == nil { t.Fatal("expected error but got none") } if !errors.Is(err, tt.expectedErr) { t.Fatalf("expected error %v, got %v", tt.expectedErr, err) } return } if err != nil { t.Fatalf("unexpected error: %v", err) } if tt.validate != nil { if err := tt.validate(config); err != nil { t.Fatal(err) } } }) } } func TestConfigOptions(t *testing.T) { secret := []byte("test-secret") config, err := NewConfig( WithDefaultDifficulty(6), WithMaxDifficulty(12), WithMinDifficulty(2), WithChallengeTTL(10*time.Minute), WithHMACSecret(secret), WithResource("api"), WithRandomBytes(8), WithLoadAdjustmentBits(2), WithFailurePenaltyBits(3), WithMaxFailurePenaltyBits(9), ) if err != nil { t.Fatalf("unexpected error: %v", err) } if config.DefaultDifficulty != 6 { t.Errorf("expected DefaultDifficulty 6, got %d", config.DefaultDifficulty) } if config.MaxDifficulty != 12 { t.Errorf("expected MaxDifficulty 12, got %d", config.MaxDifficulty) } if config.MinDifficulty != 2 { t.Errorf("expected MinDifficulty 2, got %d", config.MinDifficulty) } if config.ChallengeTTL != 10*time.Minute { t.Errorf("expected ChallengeTTL 10m, got %v", config.ChallengeTTL) } if string(config.HMACSecret) != string(secret) { t.Errorf("expected HMACSecret %s, got %s", secret, config.HMACSecret) } if config.Resource != "api" { t.Errorf("expected Resource 'api', got %s", config.Resource) } if config.RandomBytes != 8 { t.Errorf("expected RandomBytes 8, got %d", config.RandomBytes) } }