amaniquery-agent / internal /security /security_test.go
Deployment
Automated deployment update
4b1daed
Raw
History Blame Contribute Delete
7.73 kB
package security_test
import (
"context"
"testing"
"time"
"github.com/AmaniQuery/amaniquery/internal/security"
)
// TestJWTAuthenticator_GenerateAndValidate tests the full JWT lifecycle
func TestJWTAuthenticator_GenerateAndValidate(t *testing.T) {
cfg := security.JWTConfig{
Secret: []byte("test-secret-key-at-least-32-chars!!"),
Issuer: "test-issuer",
Audience: "test-audience",
TokenDuration: time.Hour,
}
auth := security.NewJWTAuthenticator(cfg)
// Generate token
token, err := auth.GenerateToken("user-123", "test@example.com", []string{"user", "admin"}, "tenant-1")
if err != nil {
t.Fatalf("GenerateToken failed: %v", err)
}
if token == "" {
t.Error("Expected non-empty token")
}
// Validate token
claims, err := auth.ValidateToken(token)
if err != nil {
t.Fatalf("ValidateToken failed: %v", err)
}
if claims.UserID != "user-123" {
t.Errorf("Expected UserID 'user-123', got '%s'", claims.UserID)
}
if claims.Email != "test@example.com" {
t.Errorf("Expected Email 'test@example.com', got '%s'", claims.Email)
}
if len(claims.Roles) != 2 {
t.Errorf("Expected 2 roles, got %d", len(claims.Roles))
}
if claims.TenantID != "tenant-1" {
t.Errorf("Expected TenantID 'tenant-1', got '%s'", claims.TenantID)
}
}
// TestJWTAuthenticator_InvalidToken tests handling of invalid tokens
func TestJWTAuthenticator_InvalidToken(t *testing.T) {
cfg := security.JWTConfig{
Secret: []byte("test-secret-key-at-least-32-chars!!"),
Issuer: "test-issuer",
TokenDuration: time.Hour,
}
auth := security.NewJWTAuthenticator(cfg)
testCases := []struct {
name string
token string
}{
{"empty token", ""},
{"garbage token", "not-a-valid-token"},
{"tampered token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := auth.ValidateToken(tc.token)
if err == nil {
t.Error("Expected error for invalid token")
}
})
}
}
// TestJWTAuthenticator_ExpiredToken tests handling of expired tokens
func TestJWTAuthenticator_ExpiredToken(t *testing.T) {
cfg := security.JWTConfig{
Secret: []byte("test-secret-key-at-least-32-chars!!"),
Issuer: "test-issuer",
TokenDuration: -time.Hour, // Already expired
}
auth := security.NewJWTAuthenticator(cfg)
token, err := auth.GenerateToken("user-123", "test@example.com", []string{"user"}, "")
if err != nil {
t.Fatalf("GenerateToken failed: %v", err)
}
// Validation should fail due to expiration
_, err = auth.ValidateToken(token)
if err == nil {
t.Error("Expected error for expired token")
}
}
// TestJWTAuthenticator_DifferentSecrets tests that different secrets fail validation
func TestJWTAuthenticator_DifferentSecrets(t *testing.T) {
cfg1 := security.JWTConfig{
Secret: []byte("first-secret-key-at-least-32-chars!"),
Issuer: "test-issuer",
TokenDuration: time.Hour,
}
cfg2 := security.JWTConfig{
Secret: []byte("second-secret-key-at-least-32-chars"),
Issuer: "test-issuer",
TokenDuration: time.Hour,
}
auth1 := security.NewJWTAuthenticator(cfg1)
auth2 := security.NewJWTAuthenticator(cfg2)
token, err := auth1.GenerateToken("user-123", "test@example.com", []string{"user"}, "")
if err != nil {
t.Fatalf("GenerateToken failed: %v", err)
}
// Should fail with different secret
_, err = auth2.ValidateToken(token)
if err == nil {
t.Error("Expected error when validating with different secret")
}
}
// TestJWTAuthenticator_IssuerValidation tests issuer validation
func TestJWTAuthenticator_IssuerValidation(t *testing.T) {
cfg1 := security.JWTConfig{
Secret: []byte("test-secret-key-at-least-32-chars!!"),
Issuer: "issuer-1",
TokenDuration: time.Hour,
}
cfg2 := security.JWTConfig{
Secret: []byte("test-secret-key-at-least-32-chars!!"),
Issuer: "issuer-2",
TokenDuration: time.Hour,
}
auth1 := security.NewJWTAuthenticator(cfg1)
auth2 := security.NewJWTAuthenticator(cfg2)
token, err := auth1.GenerateToken("user-123", "test@example.com", []string{"user"}, "")
if err != nil {
t.Fatalf("GenerateToken failed: %v", err)
}
// Should fail with different issuer
_, err = auth2.ValidateToken(token)
if err == nil {
t.Error("Expected error for mismatched issuer")
}
}
// TestContextWithClaims tests claims context operations
func TestContextWithClaims(t *testing.T) {
claims := &security.Claims{
UserID: "user-123",
Email: "test@example.com",
Roles: []string{"admin"},
}
ctx := security.ContextWithClaims(context.Background(), claims)
extracted, ok := security.ClaimsFromContext(ctx)
if !ok {
t.Fatal("Expected to extract claims from context")
}
if extracted.UserID != claims.UserID {
t.Errorf("Expected UserID %s, got %s", claims.UserID, extracted.UserID)
}
}
// TestClaimsFromContext_NoClaims tests extracting from context without claims
func TestClaimsFromContext_NoClaims(t *testing.T) {
ctx := context.Background()
_, ok := security.ClaimsFromContext(ctx)
if ok {
t.Error("Expected no claims in empty context")
}
}
// TestRequireRole tests role requirement checking
func TestRequireRole(t *testing.T) {
claims := &security.Claims{
UserID: "user-123",
Roles: []string{"user", "editor"},
}
ctx := security.ContextWithClaims(context.Background(), claims)
// Should pass for existing role
err := security.RequireRole(ctx, "user")
if err != nil {
t.Errorf("Expected no error for existing role: %v", err)
}
// Should fail for missing role
err = security.RequireRole(ctx, "superadmin")
if err == nil {
t.Error("Expected error for missing role")
}
}
// TestRequireRole_AdminBypass tests admin role bypass
func TestRequireRole_AdminBypass(t *testing.T) {
claims := &security.Claims{
UserID: "user-123",
Roles: []string{"admin"},
}
ctx := security.ContextWithClaims(context.Background(), claims)
// Admin should have access to any role
err := security.RequireRole(ctx, "any-role")
if err != nil {
t.Errorf("Expected admin to bypass role check: %v", err)
}
}
// TestRequireAnyRole tests multiple role checking
func TestRequireAnyRole(t *testing.T) {
claims := &security.Claims{
UserID: "user-123",
Roles: []string{"viewer"},
}
ctx := security.ContextWithClaims(context.Background(), claims)
// Should pass if user has any of the roles
err := security.RequireAnyRole(ctx, "editor", "viewer", "admin")
if err != nil {
t.Errorf("Expected no error when user has one of the roles: %v", err)
}
// Should fail if user has none of the roles
err = security.RequireAnyRole(ctx, "editor", "admin")
if err == nil {
t.Error("Expected error when user has none of the roles")
}
}
// BenchmarkJWT_Generate benchmarks token generation
func BenchmarkJWT_Generate(b *testing.B) {
cfg := security.JWTConfig{
Secret: []byte("test-secret-key-at-least-32-chars!!"),
Issuer: "test-issuer",
TokenDuration: time.Hour,
}
auth := security.NewJWTAuthenticator(cfg)
b.ResetTimer()
for i := 0; i < b.N; i++ {
auth.GenerateToken("user-123", "test@example.com", []string{"admin"}, "tenant-1")
}
}
// BenchmarkJWT_Validate benchmarks token validation
func BenchmarkJWT_Validate(b *testing.B) {
cfg := security.JWTConfig{
Secret: []byte("test-secret-key-at-least-32-chars!!"),
Issuer: "test-issuer",
TokenDuration: time.Hour,
}
auth := security.NewJWTAuthenticator(cfg)
token, _ := auth.GenerateToken("user-123", "test@example.com", []string{"admin"}, "")
b.ResetTimer()
for i := 0; i < b.N; i++ {
auth.ValidateToken(token)
}
}