Files
common/common_test.go

271 lines
8.1 KiB
Go

package common
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"testing"
"github.com/sonr-io/common/webauthn"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Test WebAuthn Challenge generation
func TestNewChallenge(t *testing.T) {
challenge, err := NewChallenge()
require.NoError(t, err, "NewChallenge should not return an error")
assert.NotEmpty(t, challenge, "Challenge should not be empty")
// Decode to verify it's valid base64
decoded, err := base64.RawURLEncoding.DecodeString(challenge)
require.NoError(t, err, "Challenge should be valid base64 URL encoding")
assert.Len(t, decoded, 32, "Challenge should be 32 bytes when decoded")
}
func TestNewChallenge_Uniqueness(t *testing.T) {
challenge1, err1 := NewChallenge()
challenge2, err2 := NewChallenge()
require.NoError(t, err1)
require.NoError(t, err2)
assert.NotEqual(t, challenge1, challenge2, "Two challenges should be unique")
}
// Test Challenge Length
func TestChallengeLength(t *testing.T) {
length := ChallengeLength()
assert.Equal(t, 32, length, "Challenge length should be 32 bytes")
}
// Test VerifyOrigin
func TestVerifyOrigin(t *testing.T) {
tests := []struct {
name string
origin string
allowedOrigins []string
expectError bool
}{
{
name: "Valid origin",
origin: "https://example.com",
allowedOrigins: []string{"https://example.com", "https://test.com"},
expectError: false,
},
{
name: "Valid origin with port",
origin: "https://example.com:8080",
allowedOrigins: []string{"https://example.com:8080"},
expectError: false,
},
{
name: "Origin not in allowed list",
origin: "https://malicious.com",
allowedOrigins: []string{"https://example.com"},
expectError: true,
},
{
name: "Invalid origin format",
origin: "not-a-url",
allowedOrigins: []string{"https://example.com"},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := VerifyOrigin(tt.origin, tt.allowedOrigins)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
// Test Base64 URL Encoding/Decoding
func TestEncodeDecodeBase64URL(t *testing.T) {
testData := []byte("Hello, WebAuthn!")
// Encode
encoded := EncodeBase64URL(testData)
assert.NotEmpty(t, encoded, "Encoded data should not be empty")
assert.NotContains(t, encoded, "=", "URL-safe base64 should not contain padding")
// Decode
decoded, err := DecodeBase64URL(encoded)
require.NoError(t, err, "Decoding should not return an error")
assert.Equal(t, testData, decoded, "Decoded data should match original")
}
func TestDecodeBase64URL_InvalidInput(t *testing.T) {
invalidBase64 := "not valid base64!!!"
_, err := DecodeBase64URL(invalidBase64)
assert.Error(t, err, "Should return error for invalid base64")
}
// Test Credential Creation Marshal/Unmarshal
func TestMarshalUnmarshalCredentialCreation(t *testing.T) {
// Create a sample credential creation response
original := &webauthn.CredentialCreationResponse{
PublicKeyCredential: webauthn.PublicKeyCredential{
Credential: webauthn.Credential{
ID: "test-credential-id",
Type: "public-key",
},
RawID: webauthn.URLEncodedBase64("raw-id-bytes"),
},
}
// Marshal
data, err := MarshalCredentialCreation(original)
require.NoError(t, err, "Marshaling should not return an error")
assert.NotEmpty(t, data, "Marshaled data should not be empty")
// Verify it's valid JSON
var jsonCheck map[string]interface{}
err = json.Unmarshal(data, &jsonCheck)
require.NoError(t, err, "Marshaled data should be valid JSON")
// Unmarshal
unmarshaled, err := UnmarshalCredentialCreation(data)
require.NoError(t, err, "Unmarshaling should not return an error")
assert.Equal(t, original.ID, unmarshaled.ID, "Credential ID should match")
assert.Equal(t, original.Type, unmarshaled.Type, "Credential type should match")
}
func TestUnmarshalCredentialCreation_InvalidJSON(t *testing.T) {
invalidJSON := []byte("{invalid json")
_, err := UnmarshalCredentialCreation(invalidJSON)
assert.Error(t, err, "Should return error for invalid JSON")
}
// Test Credential Assertion Marshal/Unmarshal
func TestMarshalUnmarshalCredentialAssertion(t *testing.T) {
// Create a sample credential assertion response
original := &webauthn.CredentialAssertionResponse{
PublicKeyCredential: webauthn.PublicKeyCredential{
Credential: webauthn.Credential{
ID: "test-assertion-id",
Type: "public-key",
},
RawID: webauthn.URLEncodedBase64("assertion-raw-id"),
},
}
// Marshal
data, err := MarshalCredentialAssertion(original)
require.NoError(t, err, "Marshaling should not return an error")
assert.NotEmpty(t, data, "Marshaled data should not be empty")
// Verify it's valid JSON
var jsonCheck map[string]interface{}
err = json.Unmarshal(data, &jsonCheck)
require.NoError(t, err, "Marshaled data should be valid JSON")
// Unmarshal
unmarshaled, err := UnmarshalCredentialAssertion(data)
require.NoError(t, err, "Unmarshaling should not return an error")
assert.Equal(t, original.ID, unmarshaled.ID, "Assertion ID should match")
assert.Equal(t, original.Type, unmarshaled.Type, "Assertion type should match")
}
func TestUnmarshalCredentialAssertion_InvalidJSON(t *testing.T) {
invalidJSON := []byte("{invalid json")
_, err := UnmarshalCredentialAssertion(invalidJSON)
assert.Error(t, err, "Should return error for invalid JSON")
}
// Test ParseCredentialCreation with valid minimal data
func TestParseCredentialCreation(t *testing.T) {
// Create a minimal valid credential creation response
challenge := make([]byte, 32)
rand.Read(challenge)
clientDataJSON := map[string]interface{}{
"type": "webauthn.create",
"challenge": base64.RawURLEncoding.EncodeToString(challenge),
"origin": "https://example.com",
}
clientDataBytes, _ := json.Marshal(clientDataJSON)
// Create a minimal attestation object (this would normally come from an authenticator)
// For testing, we'll create a structure that can be parsed
credResponse := &webauthn.CredentialCreationResponse{
PublicKeyCredential: webauthn.PublicKeyCredential{
Credential: webauthn.Credential{
ID: base64.RawURLEncoding.EncodeToString([]byte("test-credential-id")),
Type: "public-key",
},
RawID: webauthn.URLEncodedBase64("test-credential-id"),
},
AttestationResponse: webauthn.AuthenticatorAttestationResponse{
AuthenticatorResponse: webauthn.AuthenticatorResponse{
ClientDataJSON: clientDataBytes,
},
// Note: In a real scenario, AttestationObject would need to be properly formatted
},
}
data, err := json.Marshal(credResponse)
require.NoError(t, err)
// This will fail because we don't have a valid attestation object,
// but it tests that the function is wired up correctly
_, err = ParseCredentialCreation(data)
// We expect an error because the attestation object is not valid
assert.Error(t, err, "Should return error for incomplete attestation data")
}
// Test ParseCredentialAssertion with invalid data
func TestParseCredentialAssertion_InvalidData(t *testing.T) {
invalidData := []byte(`{"id": "test", "type": "invalid"}`)
_, err := ParseCredentialAssertion(invalidData)
assert.Error(t, err, "Should return error for invalid assertion data")
}
// Benchmark tests
func BenchmarkNewChallenge(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = NewChallenge()
}
}
func BenchmarkEncodeBase64URL(b *testing.B) {
data := make([]byte, 32)
rand.Read(data)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = EncodeBase64URL(data)
}
}
func BenchmarkDecodeBase64URL(b *testing.B) {
data := make([]byte, 32)
rand.Read(data)
encoded := EncodeBase64URL(data)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = DecodeBase64URL(encoded)
}
}
func BenchmarkMarshalCredentialCreation(b *testing.B) {
cred := &webauthn.CredentialCreationResponse{
PublicKeyCredential: webauthn.PublicKeyCredential{
Credential: webauthn.Credential{
ID: "test-id",
Type: "public-key",
},
},
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = MarshalCredentialCreation(cred)
}
}