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 7c85ff1 commit 90f0537
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 36 deletions.
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ const (
```
<a name="APIKey"></a>
## type [APIKey](<https://github.com/agentstation/uuidkey/blob/master/apikey.go#L74-L79>)
## type [APIKey](<https://github.com/agentstation/uuidkey/blob/master/apikey.go#L89-L94>)
APIKey represents a compound key consisting of four parts or segments: \- Prefix: A company or application identifier \(e.g., "AGNTSTN"\) \- Key: A UUID\-based identifier encoded in Base32\-Crockford \- Entropy: Additional segment of random data for increased uniqueness \- Checksum: CRC32 checksum of the previous components \(8 characters\)
Expand All @@ -253,7 +253,7 @@ type APIKey struct {
```
<a name="NewAPIKey"></a>
### func [NewAPIKey](<https://github.com/agentstation/uuidkey/blob/master/apikey.go#L100>)
### func [NewAPIKey](<https://github.com/agentstation/uuidkey/blob/master/apikey.go#L115>)
```go
func NewAPIKey(prefix, uuid string, opts ...Option) APIKey
Expand All @@ -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#L163>)
### func [NewAPIKeyFromBytes](<https://github.com/agentstation/uuidkey/blob/master/apikey.go#L178>)
```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#L218>)
### func [ParseAPIKey](<https://github.com/agentstation/uuidkey/blob/master/apikey.go#L233>)
```go
func ParseAPIKey(apikey string) (APIKey, error)
Expand All @@ -280,7 +280,7 @@ func ParseAPIKey(apikey string) (APIKey, error)
ParseAPIKey will parse a given APIKey string into an APIKey type.
<a name="APIKey.String"></a>
### func \(APIKey\) [String](<https://github.com/agentstation/uuidkey/blob/master/apikey.go#L92>)
### func \(APIKey\) [String](<https://github.com/agentstation/uuidkey/blob/master/apikey.go#L107>)
```go
func (a APIKey) String() string
Expand All @@ -298,7 +298,7 @@ type Key string
```
<a name="Encode"></a>
### func [Encode](<https://github.com/agentstation/uuidkey/blob/master/key.go#L171>)
### func [Encode](<https://github.com/agentstation/uuidkey/blob/master/key.go#L156>)
```go
func Encode(uuid string, opts ...Option) (Key, error)
Expand All @@ -307,7 +307,7 @@ func Encode(uuid string, opts ...Option) (Key, error)
Encode will encode a given UUID string into a Key. It pre\-allocates the exact string capacity needed for better performance.
<a name="EncodeBytes"></a>
### func [EncodeBytes](<https://github.com/agentstation/uuidkey/blob/master/key.go#L229>)
### func [EncodeBytes](<https://github.com/agentstation/uuidkey/blob/master/key.go#L214>)
```go
func EncodeBytes(uuid [16]byte, opts ...Option) (Key, error)
Expand All @@ -316,7 +316,7 @@ func EncodeBytes(uuid [16]byte, opts ...Option) (Key, error)
EncodeBytes encodes a \[16\]byte UUID into a Key.
<a name="Parse"></a>
### func [Parse](<https://github.com/agentstation/uuidkey/blob/master/key.go#L89>)
### func [Parse](<https://github.com/agentstation/uuidkey/blob/master/key.go#L74>)
```go
func Parse(key string) (Key, error)
Expand All @@ -325,7 +325,7 @@ func Parse(key string) (Key, error)
Parse converts a Key formatted string into a Key type.
<a name="Key.Bytes"></a>
### func \(Key\) [Bytes](<https://github.com/agentstation/uuidkey/blob/master/key.go#L331>)
### func \(Key\) [Bytes](<https://github.com/agentstation/uuidkey/blob/master/key.go#L316>)
```go
func (k Key) Bytes() ([16]byte, error)
Expand All @@ -334,7 +334,7 @@ func (k Key) Bytes() ([16]byte, error)
Bytes converts a Key to a \[16\]byte UUID.
<a name="Key.Decode"></a>
### func \(Key\) [Decode](<https://github.com/agentstation/uuidkey/blob/master/key.go#L273>)
### func \(Key\) [Decode](<https://github.com/agentstation/uuidkey/blob/master/key.go#L258>)
```go
func (k Key) Decode() (string, error)
Expand All @@ -343,7 +343,7 @@ func (k Key) Decode() (string, error)
Decode will decode a given Key into a UUID string with basic length validation.
<a name="Key.IsValid"></a>
### func \(Key\) [IsValid](<https://github.com/agentstation/uuidkey/blob/master/key.go#L105>)
### func \(Key\) [IsValid](<https://github.com/agentstation/uuidkey/blob/master/key.go#L90>)
```go
func (k Key) IsValid() bool
Expand All @@ -368,7 +368,7 @@ func (k Key) String() string
String will convert your Key into a string.
<a name="Key.UUID"></a>
### func \(Key\) [UUID](<https://github.com/agentstation/uuidkey/blob/master/key.go#L144>)
### func \(Key\) [UUID](<https://github.com/agentstation/uuidkey/blob/master/key.go#L129>)
```go
func (k Key) UUID() (string, error)
Expand Down
25 changes: 17 additions & 8 deletions apikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ const (
checksumLength = 8
)

// With128BitEntropy expects 128 bits of entropy in the APIKey
var With128BitEntropy Option = func(c *config) {
c.entropySize = EntropyBits128
}

// With160BitEntropy expects 160 bits of entropy in the APIKey
var With160BitEntropy Option = func(c *config) {
c.entropySize = EntropyBits160
}

// With256BitEntropy expects 256 bits of entropy in the APIKey
var With256BitEntropy Option = func(c *config) {
c.entropySize = EntropyBits256
}

// APIKey represents a compound key consisting of four parts or segments:
// - Prefix: A company or application identifier (e.g., "AGNTSTN")
// - Key: A UUID-based identifier encoded in Base32-Crockford
Expand Down Expand Up @@ -238,13 +253,6 @@ func ParseAPIKey(apikey string) (APIKey, error) {
// 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 @@ -258,7 +266,8 @@ func ParseAPIKey(apikey string) (APIKey, error) {
return APIKey{}, fmt.Errorf("invalid checksum format: must be 8 hexadecimal characters")
}
for _, c := range checksum {
if !strings.ContainsRune("0123456789ABCDEF", c) {
// Check if character is 0-9 or A-F
if !((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) {
return APIKey{}, fmt.Errorf("invalid checksum format: must be 8 hexadecimal characters")
}
}
Expand Down
56 changes: 55 additions & 1 deletion apikey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ func TestAPIKey(t *testing.T) {
}
},
},
{
name: "160-bit entropy",
prefix: "TEST",
uuid: "d1756360-5da0-40df-9926-a76abff5601d",
options: []Option{With160BitEntropy},
wantLen: 21,
validate: func(t *testing.T, key APIKey) {
if len(key.Entropy) != 21 {
t.Errorf("wrong entropy length, got %d, want 21", len(key.Entropy))
}
},
},
{
name: "Empty prefix",
prefix: "",
Expand All @@ -75,6 +87,32 @@ func TestAPIKey(t *testing.T) {
}
},
},
{
name: "Entropy padding check",
prefix: "TEST",
uuid: "d1756360-5da0-40df-9926-a76abff5601d",
options: []Option{With128BitEntropy},
wantLen: 14,
validate: func(t *testing.T, key APIKey) {
if len(key.Entropy) != 14 {
t.Errorf("wrong entropy length, got %d, want 14", len(key.Entropy))
}
// Ensure any leading zeros are properly handled
for _, c := range key.Entropy {
if !strings.ContainsRune("0123456789ABCDEFGHJKMNPQRSTVWXYZ", c) {
t.Errorf("invalid character in entropy: %c", c)
}
}
// Parse and verify the key can be reconstructed
parsed, err := ParseAPIKey(key.String())
if err != nil {
t.Errorf("failed to parse key with padded entropy: %v", err)
}
if parsed.Entropy != key.Entropy {
t.Errorf("entropy mismatch after parsing, got %s, want %s", parsed.Entropy, key.Entropy)
}
},
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -266,7 +304,7 @@ func TestParseAPIKeyErrors(t *testing.T) {
name: "Invalid characters in key",
// Using proper length but invalid characters
input: "TEST_38QARV01ET0G6Z2CJD9VA2ZZ!R0XAAAAAA_12345678",
wantErr: "invalid Key format: contains invalid characters",
wantErr: "invalid Key format: invalid UUID Key",
},
{
name: "Invalid checksum length",
Expand All @@ -278,6 +316,22 @@ func TestParseAPIKeyErrors(t *testing.T) {
input: "TEST_38QARV01ET0G6Z2CJD9VA2ZZAR0X_12345678",
wantErr: "invalid checksum: expected ", // actual value will vary
},
{
name: "Invalid UUID format in key",
// Using invalid characters that will fail UUID parsing
input: "TEST_IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII_12345678",
wantErr: "invalid Key format: invalid UUID Key",
},
{
name: "Invalid checksum characters (lowercase)",
input: "TEST_38QARV01ET0G6Z2CJD9VA2ZZAR0X_1234abcd",
wantErr: "invalid checksum format: must be 8 hexadecimal characters",
},
{
name: "Invalid checksum characters (non-hex)",
input: "TEST_38QARV01ET0G6Z2CJD9VA2ZZAR0X_1234WXYZ",
wantErr: "invalid checksum format: must be 8 hexadecimal characters",
},
}

for _, tt := range tests {
Expand Down
15 changes: 0 additions & 15 deletions key.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,21 +70,6 @@ var WithoutHyphens Option = func(c *config) {
c.hyphens = false
}

// With128BitEntropy expects 128 bits of entropy in the APIKey
var With128BitEntropy Option = func(c *config) {
c.entropySize = EntropyBits128
}

// With160BitEntropy expects 160 bits of entropy in the APIKey
var With160BitEntropy Option = func(c *config) {
c.entropySize = EntropyBits160
}

// With256BitEntropy expects 256 bits of entropy in the APIKey
var With256BitEntropy Option = func(c *config) {
c.entropySize = EntropyBits256
}

// Parse converts a Key formatted string into a Key type.
func Parse(key string) (Key, error) {
k := Key(key)
Expand Down
100 changes: 100 additions & 0 deletions key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ func TestValid(t *testing.T) {
"38QARV01ET0G6Z-2CJD9VA2ZZAR0X", // Unexpected hyphen in middle
"38QARV01ET0G6Z2CJD9VA-2ZZAR0X", // Unexpected hyphen near end
"38QARV01ET0G6Z2CJD9VA2ZZAR0X-", // Unexpected hyphen at end
// Additional length validation cases
"38QARV0-1ET0G6-2CJD9VA-2ZZAR0X", // Second part too short (6 chars)
"38QARV0-1ET0G6ZZ-2CJD9VA-2ZZAR0X", // Second part too long (8 chars)
"38QAR-1ET0G6Z-2CJD9VA-2ZZAR0X", // First part too short (5 chars)
"38QARV0Z-1ET0G6Z-2CJD9VA-2ZZAR0X", // First part too long (8 chars)

// Without hyphens length validation
"38QAR01ET0G6Z2CJD9VA2ZZAR0X", // First part too short (5 chars)
"38QARV0Z1ET0G6Z2CJD9VA2ZZAR0X", // First part too long (8 chars)
"38QARV01ET0G2CJD9VA2ZZAR0X", // Second part too short (5 chars)
"38QARV01ET0G6ZZ2CJD9VA2ZZAR0X", // Second part too long (8 chars)
}

for _, k := range validKeys {
Expand Down Expand Up @@ -678,3 +689,92 @@ func TestKeyBytes(t *testing.T) {
})
}
}

func TestDecodeErrorMessage(t *testing.T) {
tests := []struct {
name string
key Key
expectedError string
}{
{
name: "Too short key",
key: "ABC",
expectedError: "invalid Key length: expected 28 or 31 characters, got 3",
},
{
name: "Too long key",
key: "38QARV0-1ET0G6Z-2CJD9VA-2ZZAR0XX",
expectedError: "invalid Key length: expected 28 or 31 characters, got 32",
},
{
name: "Empty key",
key: "",
expectedError: "invalid Key length: expected 28 or 31 characters, got 0",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := tt.key.Decode()
if err == nil {
t.Error("Expected error, got nil")
return
}
if err.Error() != tt.expectedError {
t.Errorf("Expected error message %q, got %q", tt.expectedError, err.Error())
}
})
}
}

func TestKey_Bytes_Errors(t *testing.T) {
tests := []struct {
name string
key Key
wantErr string
}{
{
name: "invalid length - too short",
key: Key("ABC"),
wantErr: "invalid Key length: expected 28 or 31 characters, got 3",
},
{
name: "invalid characters in first group",
key: Key("@#$%^&*-1111111-2222222-3333333"),
wantErr: "failed to decode Key part: crock32.Decode: invalid character @",
},
{
name: "invalid characters in second group",
key: Key("1111111-@#$%^&*-2222222-3333333"),
wantErr: "failed to decode Key part: crock32.Decode: invalid character @",
},
{
name: "invalid characters in third group",
key: Key("1111111-2222222-@#$%^&*-3333333"),
wantErr: "failed to decode Key part: crock32.Decode: invalid character @",
},
{
name: "invalid characters in fourth group",
key: Key("1111111-2222222-3333333-@#$%^&*"),
wantErr: "failed to decode Key part: crock32.Decode: invalid character @",
},
{
name: "invalid characters without hyphens",
key: Key("1111111222222233333333@#$%^&"),
wantErr: "failed to decode Key part: crock32.Decode: invalid character @",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := tt.key.Bytes()
if err == nil {
t.Error("expected error, got nil")
return
}
if err.Error() != tt.wantErr {
t.Errorf("expected error %q, got %q", tt.wantErr, err.Error())
}
})
}
}

0 comments on commit 90f0537

Please sign in to comment.