Skip to content

Commit

Permalink
update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jackspirou committed Jan 12, 2025
1 parent bb989ca commit 7c85ff1
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 12 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ func NewAPIKey(prefix, uuid string, opts ...Option) APIKey
NewAPIKey creates a new APIKey from a string prefix, string UUID, and options.
<a name="NewAPIKeyFromBytes"></a>
### func [NewAPIKeyFromBytes](<https://github.com/agentstation/uuidkey/blob/master/apikey.go#L159>)
### func [NewAPIKeyFromBytes](<https://github.com/agentstation/uuidkey/blob/master/apikey.go#L163>)
```go
func NewAPIKeyFromBytes(prefix string, uuid [16]byte, opts ...Option) APIKey
Expand All @@ -271,7 +271,7 @@ func NewAPIKeyFromBytes(prefix string, uuid [16]byte, opts ...Option) APIKey
NewAPIKeyFromBytes creates a new APIKey from a string prefix, \[16\]byte UUID, and options.
<a name="ParseAPIKey"></a>
### func [ParseAPIKey](<https://github.com/agentstation/uuidkey/blob/master/apikey.go#L214>)
### func [ParseAPIKey](<https://github.com/agentstation/uuidkey/blob/master/apikey.go#L218>)
```go
func ParseAPIKey(apikey string) (APIKey, error)
Expand Down
34 changes: 28 additions & 6 deletions apikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ func (a APIKey) calculateChecksum() string {
// Combine all parts except checksum
data := a.Prefix + "_" + a.Key.String() + a.Entropy

// Calculate CRC32 checksum and return as hex
// Calculate CRC32 checksum and return as uppercase hex
crc := crc32.ChecksumIEEE([]byte(data))
return fmt.Sprintf("%08x", crc) // Always 8 chars, zero-padded
return fmt.Sprintf("%08X", crc) // Always 8 chars, zero-padded, uppercase
}

// String returns the complete API key as a string with all components joined
Expand All @@ -98,6 +98,10 @@ func (a APIKey) String() string {

// NewAPIKey creates a new APIKey from a string prefix, string UUID, and options.
func NewAPIKey(prefix, uuid string, opts ...Option) APIKey {
if prefix == "" {
panic("prefix cannot be empty")
}

// apply the options to the default configuration
options := apply(opts...)

Expand Down Expand Up @@ -219,20 +223,28 @@ func ParseAPIKey(apikey string) (APIKey, error) {
}

prefix := parts[0]
if prefix == "" {
return APIKey{}, fmt.Errorf("invalid prefix: cannot be empty")
}

remainder := parts[1]
checksum := parts[2]

if len(checksum) != checksumLength {
return APIKey{}, fmt.Errorf("invalid checksum length: expected %d characters, got %d", checksumLength, len(checksum))
}

// The remainder should contain both the Key and Entropy parts
if len(remainder) < KeyLengthWithoutHyphens {
return APIKey{}, fmt.Errorf("invalid Key format: insufficient length")
}

// Extract the Key part (first KeyLengthWithoutHyphens characters)
keyPart := remainder[:KeyLengthWithoutHyphens]

// Validate key format first
for _, c := range keyPart {
if !strings.ContainsRune("0123456789ABCDEFGHJKMNPQRSTVWXYZ", c) {
return APIKey{}, fmt.Errorf("invalid Key format: contains invalid characters")
}
}

key, err := Parse(keyPart)
if err != nil {
return APIKey{}, fmt.Errorf("invalid Key format: %v", err)
Expand All @@ -241,6 +253,16 @@ func ParseAPIKey(apikey string) (APIKey, error) {
// The rest is entropy
entropy := remainder[KeyLengthWithoutHyphens:]

// Validate checksum format (must be 8 uppercase hexadecimal characters)
if len(checksum) != checksumLength {
return APIKey{}, fmt.Errorf("invalid checksum format: must be 8 hexadecimal characters")
}
for _, c := range checksum {
if !strings.ContainsRune("0123456789ABCDEF", c) {
return APIKey{}, fmt.Errorf("invalid checksum format: must be 8 hexadecimal characters")
}

Check warning on line 263 in apikey.go

View check run for this annotation

Codecov / codecov/patch

apikey.go#L262-L263

Added lines #L262 - L263 were not covered by tests
}

apiKey := APIKey{
Prefix: prefix,
Key: key,
Expand Down
124 changes: 120 additions & 4 deletions apikey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,58 @@ func TestAPIKey(t *testing.T) {
}
},
},
{
name: "Empty prefix",
prefix: "",
uuid: "d1756360-5da0-40df-9926-a76abff5601d",
wantErr: true,
validate: func(t *testing.T, key APIKey) {
// We shouldn't reach this point if wantErr is true
t.Error("expected error for empty prefix, got none")
},
},
{
name: "Multiple options",
prefix: "TEST",
uuid: "d1756360-5da0-40df-9926-a76abff5601d",
options: []Option{With128BitEntropy, With256BitEntropy}, // Last option should win
wantLen: 42,
validate: func(t *testing.T, key APIKey) {
if len(key.Entropy) != 42 {
t.Errorf("wrong entropy length, got %d, want 42", len(key.Entropy))
}
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Catch panics and convert to errors if we expect an error
defer func() {
if r := recover(); r != nil {
if !tt.wantErr {
t.Errorf("unexpected panic: %v", r)
}
}
}()

// Test NewAPIKey
key := NewAPIKey(tt.prefix, tt.uuid, tt.options...)

if tt.wantErr {
t.Error("expected error, got none")
return
}

// Validate entropy length
if len(key.Entropy) != tt.wantLen {
t.Errorf("entropy length mismatch, got %d, want %d", len(key.Entropy), tt.wantLen)
}

// Run additional validations
tt.validate(t, key)
// Run additional validations if provided
if tt.validate != nil {
tt.validate(t, key)
}

// Test String() method
str := key.String()
Expand Down Expand Up @@ -141,6 +179,23 @@ func TestNewAPIKeyFromBytes(t *testing.T) {
}
},
},
{
name: "256-bit entropy",
prefix: "PROD",
options: []Option{With256BitEntropy},
wantLen: 42,
validate: func(t *testing.T, key APIKey) {
if len(key.Entropy) != 42 {
t.Errorf("wrong entropy length, got %d, want 42", len(key.Entropy))
}
// Validate entropy characters are in valid Base32-Crockford range
for _, c := range key.Entropy {
if !strings.ContainsRune("0123456789ABCDEFGHJKMNPQRSTVWXYZ", c) {
t.Errorf("invalid character in entropy: %c", c)
}
}
},
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -198,14 +253,25 @@ func TestParseAPIKeyErrors(t *testing.T) {
wantErr: "invalid APIKey format: expected 3 parts, got 4",
},
{
name: "Invalid key format",
name: "Invalid prefix",
input: "_38QARV01ET0G6Z2CJD9VA2ZZAR0X_12345678",
wantErr: "invalid prefix: cannot be empty",
},
{
name: "Invalid key length",
input: "TEST_INVALID_12345678",
wantErr: "invalid Key format: insufficient length",
},
{
name: "Invalid characters in key",
// Using proper length but invalid characters
input: "TEST_38QARV01ET0G6Z2CJD9VA2ZZ!R0XAAAAAA_12345678",
wantErr: "invalid Key format: contains invalid characters",
},
{
name: "Invalid checksum length",
input: "TEST_38QARV01ET0G6Z2CJD9VA2ZZAR0X_123",
wantErr: "invalid checksum length: expected 8 characters, got 3",
wantErr: "invalid checksum format: must be 8 hexadecimal characters",
},
{
name: "Invalid checksum value",
Expand All @@ -226,3 +292,53 @@ func TestParseAPIKeyErrors(t *testing.T) {
})
}
}

func TestEntropyCharacterValidation(t *testing.T) {
tests := []struct {
name string
prefix string
options []Option
}{
{
name: "128-bit character validation",
prefix: "TEST",
options: []Option{With128BitEntropy},
},
{
name: "160-bit (default) character validation",
prefix: "TEST",
options: []Option{},
},
{
name: "256-bit character validation",
prefix: "TEST",
options: []Option{With256BitEntropy},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
key := NewAPIKey(tt.prefix, "d1756360-5da0-40df-9926-a76abff5601d", tt.options...)

// Validate all characters are in Base32-Crockford alphabet
for _, c := range key.Entropy {
if !strings.ContainsRune("0123456789ABCDEFGHJKMNPQRSTVWXYZ", c) {
t.Errorf("invalid character in entropy: %c", c)
}
}

// Validate checksum is valid uppercase hexadecimal (CRC32)
str := key.String()
parts := strings.Split(str, "_")
checksum := parts[2]
if len(checksum) != 8 {
t.Errorf("checksum length must be 8, got %d", len(checksum))
}
for _, c := range checksum {
if !strings.ContainsRune("0123456789ABCDEF", c) {
t.Errorf("invalid character in checksum: %c (must be uppercase hexadecimal)", c)
}
}
})
}
}

0 comments on commit 7c85ff1

Please sign in to comment.