diff --git a/internal/rbac/middleware_endpoint_test.go b/internal/rbac/middleware_endpoint_test.go new file mode 100644 index 00000000..9e377b26 --- /dev/null +++ b/internal/rbac/middleware_endpoint_test.go @@ -0,0 +1,228 @@ +package rbac_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "queryorchestration/internal/rbac" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" +) + +// testSetup creates a new Echo instance and key provider for testing +func testSetup(t *testing.T) (*echo.Echo, *rbac.LocalKeyProvider) { + // Use local key files + privateKeyPath := "./private_key.pem" + publicKeyPath := "./public_key.pem" + + // Create and initialize key provider + keyProvider := rbac.NewLocalKeyProvider(privateKeyPath, publicKeyPath) + //TestKeyProvider(t, privateKeyPath, publicKeyPath) + + // Initialize Echo instance + e := echo.New() + + return e, keyProvider +} + +// createTestEndpoint creates a test endpoint with the given JWT config +func createTestEndpoint(e *echo.Echo, config rbac.JWTConfig) { + e.GET("/test", func(c echo.Context) error { + return c.String(http.StatusOK, "authorized") + }, rbac.JWTWithConfig(config)) +} + +// makeTestRequest creates and executes a test request with the given token +func makeTestRequest(t *testing.T, e *echo.Echo, token string, setupReq func(*http.Request)) *httptest.ResponseRecorder { + req := httptest.NewRequest(http.MethodGet, "/test", nil) + if setupReq != nil { + setupReq(req) + } else { + req.Header.Set("Authorization", "Bearer "+token) + } + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + return rec +} + +// TestJWTMiddleware tests the core JWT middleware functionality with group-based authorization. +// It validates: +// 1. A user with the required 'foo' group can access a protected endpoint +// 2. A user with only 'bar' group (and not 'foo') is properly denied access +// 3. A user with multiple groups including the required 'foo' group is granted access +// This test demonstrates the basic setup for protecting routes with the JWT middleware +// and ensuring only users with appropriate group membership can access restricted resources. +func TestJWTMiddleware(t *testing.T) { + e, keyProvider := testSetup(t) + + // Set up a restricted endpoint that requires the 'foo' group + createTestEndpoint(e, rbac.JWTConfig{ + KeyProvider: keyProvider, + RequiredGroups: []string{"foo"}, + }) + + // Test cases + tests := []struct { + name string + groups []string + wantStatus int + }{ + { + name: "Authorized user with 'foo' group", + groups: []string{"foo"}, + wantStatus: http.StatusOK, + }, + { + name: "Unauthorized user with 'bar' group", + groups: []string{"bar"}, + wantStatus: http.StatusForbidden, + }, + { + name: "User with multiple groups including required", + groups: []string{"bar", "foo", "baz"}, + wantStatus: http.StatusOK, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Generate JWT token with test groups + token, err := rbac.GenerateTestJWT(keyProvider, "test-user-id", "test@example.com", tt.groups) + if err != nil { + t.Fatalf("Failed to generate JWT: %v", err) + } + + // Make test request and verify response + rec := makeTestRequest(t, e, token, nil) + assert.Equal(t, tt.wantStatus, rec.Code) + if tt.wantStatus == http.StatusOK { + assert.Equal(t, "authorized", rec.Body.String()) + } + }) + } +} + +// TestTokenExtraction validates different methods for extracting JWT tokens: +// 1. From Authorization header (standard Bearer token) +// 2. From URL query parameters +// 3. From cookies +// Each method is configured through the TokenLookup field in JWTConfig. +// The test confirms that the middleware can successfully authenticate a user +// regardless of where the token is provided, as long as it's valid and +// contains the required group membership. +func TestTokenExtraction(t *testing.T) { + _, keyProvider := testSetup(t) + + // Generate JWT token with 'foo' group + token, err := rbac.GenerateTestJWT(keyProvider, "test-user-id", "test@example.com", []string{"foo"}) + if err != nil { + t.Fatalf("Failed to generate JWT: %v", err) + } + + // Test different token extraction methods + tests := []struct { + name string + config rbac.JWTConfig + setupReq func(req *http.Request) + wantStatus int + }{ + { + name: "Extract from header", + config: rbac.JWTConfig{ + KeyProvider: keyProvider, + TokenLookup: "header:Authorization", + AuthScheme: "Bearer", + RequiredGroups: []string{"foo"}, + }, + setupReq: func(req *http.Request) { + req.Header.Set("Authorization", "Bearer "+token) + }, + wantStatus: http.StatusOK, + }, + { + name: "Extract from query parameter", + config: rbac.JWTConfig{ + KeyProvider: keyProvider, + TokenLookup: "query:token", + RequiredGroups: []string{"foo"}, + }, + setupReq: func(req *http.Request) { + q := req.URL.Query() + q.Add("token", token) + req.URL.RawQuery = q.Encode() + }, + wantStatus: http.StatusOK, + }, + { + name: "Extract from cookie", + config: rbac.JWTConfig{ + KeyProvider: keyProvider, + TokenLookup: "cookie:jwt", + RequiredGroups: []string{"foo"}, + }, + setupReq: func(req *http.Request) { + req.AddCookie(&http.Cookie{ + Name: "jwt", + Value: token, + }) + }, + wantStatus: http.StatusOK, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create new Echo instance for each test to avoid config conflicts + e := echo.New() + createTestEndpoint(e, tt.config) + + // Make test request and verify response + rec := makeTestRequest(t, e, token, tt.setupReq) + assert.Equal(t, tt.wantStatus, rec.Code) + if tt.wantStatus == http.StatusOK { + assert.Equal(t, "authorized", rec.Body.String()) + } + }) + } +} + +// TestCustomErrorHandler verifies that custom error handling works correctly. +// It tests: +// 1. Setting up a custom error handler function in the JWTConfig +// 2. Sending an invalid JWT token in the request +// 3. Confirming that the custom error handler is invoked and provides +// the expected response format and status code +// +// This allows for customized error responses when authentication fails, +// which is useful for providing more context or formatting errors +// according to API standards. +func TestCustomErrorHandler(t *testing.T) { + e, keyProvider := testSetup(t) + + // Custom error handler + customErrorHandler := func(c echo.Context, err error) error { + return c.JSON(http.StatusUnauthorized, map[string]string{ + "error": "Custom auth error: " + err.Error(), + }) + } + + // Set up a restricted endpoint with custom error handler + createTestEndpoint(e, rbac.JWTConfig{ + KeyProvider: keyProvider, + RequiredGroups: []string{"foo"}, + ErrorHandler: customErrorHandler, + }) + + // Create test request with invalid token and verify response + rec := makeTestRequest(t, e, "invalidtoken", nil) + assert.Equal(t, http.StatusUnauthorized, rec.Code) + assert.Contains(t, rec.Body.String(), "Custom auth error") +} + +// These tests require valid RSA key files (private_key.pem and public_key.pem) +// in the current directory. These keys are used to sign and verify JWT tokens +// during the tests. In a production environment, the public key would typically +// be retrieved from AWS Cognito's JWKS endpoint, but for testing, we use local files. +// Make sure to generate these key files before running the tests. diff --git a/internal/rbac/middleware_test.go b/internal/rbac/middleware_test.go index 9e377b26..f2b0ef6f 100644 --- a/internal/rbac/middleware_test.go +++ b/internal/rbac/middleware_test.go @@ -1,6 +1,8 @@ package rbac_test import ( + "crypto/rsa" + "math/big" "net/http" "net/http/httptest" "testing" @@ -11,218 +13,116 @@ import ( "github.com/stretchr/testify/assert" ) -// testSetup creates a new Echo instance and key provider for testing -func testSetup(t *testing.T) (*echo.Echo, *rbac.LocalKeyProvider) { - // Use local key files - privateKeyPath := "./private_key.pem" - publicKeyPath := "./public_key.pem" +// mockKeyProvider is a mock implementation of the KeyProvider interface +type mockKeyProvider struct{} - // Create and initialize key provider - keyProvider := rbac.NewLocalKeyProvider(privateKeyPath, publicKeyPath) - //TestKeyProvider(t, privateKeyPath, publicKeyPath) +// GetPublicKey returns a mock public key +func (m *mockKeyProvider) GetPublicKey(kid string) (*rsa.PublicKey, error) { + // Create a mock RSA public key with minimal valid structure + return &rsa.PublicKey{ + N: big.NewInt(123), // Just a dummy value + E: 65537, // Common RSA exponent + }, nil +} - // Initialize Echo instance +// createMockKeyProvider creates a mock KeyProvider for testing +func createMockKeyProvider() rbac.KeyProvider { + return &mockKeyProvider{} +} + +// TestDefaultErrorHandler tests the defaultErrorHandler function +func TestDefaultErrorHandler(t *testing.T) { + // Create a new Echo instance + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + // Create a test error + //testErr := errors.New("test error") + + // Access the defaultErrorHandler function directly + // In a real implementation, you might use an exported test helper function like: + // err := rbac.DefaultErrorHandlerTest(c, testErr) + + // In this case, we'll test the error handler through the Echo middleware + // by creating a config with our own error handler that logs what was called + var capturedError error + customErrorHandler := func(c echo.Context, err error) error { + capturedError = err + return echo.NewHTTPError(http.StatusUnauthorized, "captured: "+err.Error()) + } + + config := rbac.JWTConfig{ + KeyProvider: createMockKeyProvider(), + ErrorHandler: customErrorHandler, + } + + // Create middleware with our config + middleware := rbac.JWTWithConfig(config) + + // Create a handler that should never be called + handler := func(c echo.Context) error { + return c.String(http.StatusOK, "success") + } + + // Call the middleware with a bad request (no Authorization header) + middlewareHandler := middleware(handler) + err := middlewareHandler(c) + + // Verify that our error handler was called + assert.NotNil(t, capturedError) + assert.NotNil(t, err) + + // The error should be an HTTP error + httpErr, ok := err.(*echo.HTTPError) + assert.True(t, ok) + assert.Equal(t, http.StatusUnauthorized, httpErr.Code) +} + +// TestJWT tests the JWT function which creates middleware with default config +func TestJWT(t *testing.T) { + // Create a new Echo instance e := echo.New() - return e, keyProvider -} + // Create a simple test endpoint with the JWT middleware + e.GET("/protected", func(c echo.Context) error { + return c.String(http.StatusOK, "success") + }, rbac.JWT(createMockKeyProvider())) -// createTestEndpoint creates a test endpoint with the given JWT config -func createTestEndpoint(e *echo.Echo, config rbac.JWTConfig) { - e.GET("/test", func(c echo.Context) error { - return c.String(http.StatusOK, "authorized") - }, rbac.JWTWithConfig(config)) -} - -// makeTestRequest creates and executes a test request with the given token -func makeTestRequest(t *testing.T, e *echo.Echo, token string, setupReq func(*http.Request)) *httptest.ResponseRecorder { - req := httptest.NewRequest(http.MethodGet, "/test", nil) - if setupReq != nil { - setupReq(req) - } else { - req.Header.Set("Authorization", "Bearer "+token) - } + // Create a test request with an invalid token (format doesn't matter for this test) + // The mock key provider will accept any token + req := httptest.NewRequest(http.MethodGet, "/protected", nil) + req.Header.Set("Authorization", "Bearer valid-token") rec := httptest.NewRecorder() + + // Process the request (this will fail because our mock doesn't properly validate) e.ServeHTTP(rec, req) - return rec -} -// TestJWTMiddleware tests the core JWT middleware functionality with group-based authorization. -// It validates: -// 1. A user with the required 'foo' group can access a protected endpoint -// 2. A user with only 'bar' group (and not 'foo') is properly denied access -// 3. A user with multiple groups including the required 'foo' group is granted access -// This test demonstrates the basic setup for protecting routes with the JWT middleware -// and ensuring only users with appropriate group membership can access restricted resources. -func TestJWTMiddleware(t *testing.T) { - e, keyProvider := testSetup(t) - - // Set up a restricted endpoint that requires the 'foo' group - createTestEndpoint(e, rbac.JWTConfig{ - KeyProvider: keyProvider, - RequiredGroups: []string{"foo"}, - }) - - // Test cases - tests := []struct { - name string - groups []string - wantStatus int - }{ - { - name: "Authorized user with 'foo' group", - groups: []string{"foo"}, - wantStatus: http.StatusOK, - }, - { - name: "Unauthorized user with 'bar' group", - groups: []string{"bar"}, - wantStatus: http.StatusForbidden, - }, - { - name: "User with multiple groups including required", - groups: []string{"bar", "foo", "baz"}, - wantStatus: http.StatusOK, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Generate JWT token with test groups - token, err := rbac.GenerateTestJWT(keyProvider, "test-user-id", "test@example.com", tt.groups) - if err != nil { - t.Fatalf("Failed to generate JWT: %v", err) - } - - // Make test request and verify response - rec := makeTestRequest(t, e, token, nil) - assert.Equal(t, tt.wantStatus, rec.Code) - if tt.wantStatus == http.StatusOK { - assert.Equal(t, "authorized", rec.Body.String()) - } - }) - } -} - -// TestTokenExtraction validates different methods for extracting JWT tokens: -// 1. From Authorization header (standard Bearer token) -// 2. From URL query parameters -// 3. From cookies -// Each method is configured through the TokenLookup field in JWTConfig. -// The test confirms that the middleware can successfully authenticate a user -// regardless of where the token is provided, as long as it's valid and -// contains the required group membership. -func TestTokenExtraction(t *testing.T) { - _, keyProvider := testSetup(t) - - // Generate JWT token with 'foo' group - token, err := rbac.GenerateTestJWT(keyProvider, "test-user-id", "test@example.com", []string{"foo"}) - if err != nil { - t.Fatalf("Failed to generate JWT: %v", err) - } - - // Test different token extraction methods - tests := []struct { - name string - config rbac.JWTConfig - setupReq func(req *http.Request) - wantStatus int - }{ - { - name: "Extract from header", - config: rbac.JWTConfig{ - KeyProvider: keyProvider, - TokenLookup: "header:Authorization", - AuthScheme: "Bearer", - RequiredGroups: []string{"foo"}, - }, - setupReq: func(req *http.Request) { - req.Header.Set("Authorization", "Bearer "+token) - }, - wantStatus: http.StatusOK, - }, - { - name: "Extract from query parameter", - config: rbac.JWTConfig{ - KeyProvider: keyProvider, - TokenLookup: "query:token", - RequiredGroups: []string{"foo"}, - }, - setupReq: func(req *http.Request) { - q := req.URL.Query() - q.Add("token", token) - req.URL.RawQuery = q.Encode() - }, - wantStatus: http.StatusOK, - }, - { - name: "Extract from cookie", - config: rbac.JWTConfig{ - KeyProvider: keyProvider, - TokenLookup: "cookie:jwt", - RequiredGroups: []string{"foo"}, - }, - setupReq: func(req *http.Request) { - req.AddCookie(&http.Cookie{ - Name: "jwt", - Value: token, - }) - }, - wantStatus: http.StatusOK, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create new Echo instance for each test to avoid config conflicts - e := echo.New() - createTestEndpoint(e, tt.config) - - // Make test request and verify response - rec := makeTestRequest(t, e, token, tt.setupReq) - assert.Equal(t, tt.wantStatus, rec.Code) - if tt.wantStatus == http.StatusOK { - assert.Equal(t, "authorized", rec.Body.String()) - } - }) - } -} - -// TestCustomErrorHandler verifies that custom error handling works correctly. -// It tests: -// 1. Setting up a custom error handler function in the JWTConfig -// 2. Sending an invalid JWT token in the request -// 3. Confirming that the custom error handler is invoked and provides -// the expected response format and status code -// -// This allows for customized error responses when authentication fails, -// which is useful for providing more context or formatting errors -// according to API standards. -func TestCustomErrorHandler(t *testing.T) { - e, keyProvider := testSetup(t) - - // Custom error handler - customErrorHandler := func(c echo.Context, err error) error { - return c.JSON(http.StatusUnauthorized, map[string]string{ - "error": "Custom auth error: " + err.Error(), - }) - } - - // Set up a restricted endpoint with custom error handler - createTestEndpoint(e, rbac.JWTConfig{ - KeyProvider: keyProvider, - RequiredGroups: []string{"foo"}, - ErrorHandler: customErrorHandler, - }) - - // Create test request with invalid token and verify response - rec := makeTestRequest(t, e, "invalidtoken", nil) + // The response status should reflect whether validation succeeded + // In a real test, this would depend on token validity + // Here, we expect an error because our mock key provider doesn't correctly + // validate the token structure assert.Equal(t, http.StatusUnauthorized, rec.Code) - assert.Contains(t, rec.Body.String(), "Custom auth error") } -// These tests require valid RSA key files (private_key.pem and public_key.pem) -// in the current directory. These keys are used to sign and verify JWT tokens -// during the tests. In a production environment, the public key would typically -// be retrieved from AWS Cognito's JWKS endpoint, but for testing, we use local files. -// Make sure to generate these key files before running the tests. +// TestJWTWithMissingHeader tests JWT middleware with a missing Authorization header +func TestJWTWithMissingHeader(t *testing.T) { + // Create a new Echo instance + e := echo.New() + + // Create a simple test endpoint with the JWT middleware + e.GET("/protected", func(c echo.Context) error { + return c.String(http.StatusOK, "success") + }, rbac.JWT(createMockKeyProvider())) + + // Create a test request with no Authorization header + req := httptest.NewRequest(http.MethodGet, "/protected", nil) + rec := httptest.NewRecorder() + + // Process the request + e.ServeHTTP(rec, req) + + // This should fail with a 401 Unauthorized + assert.Equal(t, http.StatusUnauthorized, rec.Code) +} diff --git a/internal/serviceconfig/rbac/config_test.go b/internal/serviceconfig/rbac/config_test.go new file mode 100644 index 00000000..de53e48f --- /dev/null +++ b/internal/serviceconfig/rbac/config_test.go @@ -0,0 +1,280 @@ +package rbac_test + +import ( + "crypto/rsa" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "queryorchestration/internal/serviceconfig/rbac" +) + +// TestGetKeyProvider tests the GetKeyProvider method +func TestGetKeyProvider(t *testing.T) { + // Create a temporary directory for test key files + tmpDir, err := os.MkdirTemp("", "rbac-test") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer func(path string) { + err := os.RemoveAll(path) + if err != nil { + t.Fatalf("Failed to remove temp dir: %v", err) + } + }(tmpDir) + + // Create temporary key files with minimal PEM format + privateKeyPath := tmpDir + "/private_key.pem" + publicKeyPath := tmpDir + "/public_key.pem" + + // Write minimal PEM content to the key files + // These are not valid RSA keys, but that doesn't matter for this test + // as we're just testing that the config passes the paths correctly + err = os.WriteFile(privateKeyPath, []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA0Gzk05Pbnb12O7O+vCrwY9oMsEsKkJZ1hnMvP4JXOa0beJAw +DgYKAAAAAAAAAAA= +-----END RSA PRIVATE KEY-----`), 0600) + if err != nil { + t.Fatalf("Failed to write private key: %v", err) + } + + err = os.WriteFile(publicKeyPath, []byte(`-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Gzk05Pbnb12O7O+vCrw +Y9oMsEsKkJZ1hnMvP4JXOa0beJAwOJosFh5kAAs= +-----END PUBLIC KEY-----`), 0600) + if err != nil { + t.Fatalf("Failed to write public key: %v", err) + } + + // Create an AuthConfig with local provider + config := &rbac.AuthConfig{ + AuthType: "local", + PrivateKeyPath: privateKeyPath, + PublicKeyPath: publicKeyPath, + } + + // Since we can't successfully load invalid keys, we'll just test the GetKeyProvider method directly + // without initializing the provider + config.AuthProvider = &mockKeyProvider{} // Use our mock key provider + + // Test GetKeyProvider + provider := config.GetKeyProvider() + assert.NotNil(t, provider, "GetKeyProvider should return a non-nil provider") + assert.Equal(t, &mockKeyProvider{}, provider, "Provider should match what we set") +} + +// TestInitializeAuthProvider tests the InitializeAuthProvider function +func TestInitializeAuthProvider(t *testing.T) { + // Create a temporary directory for test key files + tmpDir, err := os.MkdirTemp("", "rbac-test") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer func(path string) { + err := os.RemoveAll(path) + if err != nil { + t.Fatalf("Failed to remove temp dir: %v", err) + } + }(tmpDir) + + // Create temporary key files + privateKeyPath := tmpDir + "/private_key.pem" + publicKeyPath := tmpDir + "/public_key.pem" + + // Write some dummy content to the key files - these are NOT valid keys + err = os.WriteFile(privateKeyPath, []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA0Gzk05Pbnb12O7O+vCrwY9oMsEsKkJZ1hnMvP4JXOa0beJAw +DgYKAAAAAAAAAAA= +-----END RSA PRIVATE KEY-----`), 0600) + if err != nil { + t.Fatalf("Failed to write private key: %v", err) + } + + err = os.WriteFile(publicKeyPath, []byte(`-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Gzk05Pbnb12O7O+vCrw +Y9oMsEsKkJZ1hnMvP4JXOa0beJAwOJosFh5kAAs= +-----END PUBLIC KEY-----`), 0600) + if err != nil { + t.Fatalf("Failed to write public key: %v", err) + } + + // Test cases for InitializeAuthProvider + testCases := []struct { + name string + config *rbac.AuthConfig + expectError bool + }{ + { + name: "Missing local key paths", + config: &rbac.AuthConfig{ + AuthType: "local", + // Missing key paths + }, + expectError: true, + }, + { + name: "Missing Cognito user pool ID", + config: &rbac.AuthConfig{ + AuthType: "cognito", + CognitoRegion: "us-east-1", + // Missing user pool ID + }, + expectError: true, + }, + { + name: "Invalid auth type", + config: &rbac.AuthConfig{ + AuthType: "invalid", + }, + expectError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := rbac.InitializeAuthProvider(tc.config) + + if tc.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +// TestAuthConfigInitializeAuthProvider tests the AuthConfig method version of InitializeAuthProvider +func TestAuthConfigInitializeAuthProvider(t *testing.T) { + // Since we can't load invalid keys but want to test the method itself, + // we'll test the error paths which don't require valid keys + + testCases := []struct { + name string + config *rbac.AuthConfig + expectError bool + }{ + { + name: "Missing local key paths", + config: &rbac.AuthConfig{ + AuthType: "local", + // Missing key paths + }, + expectError: true, + }, + { + name: "Missing Cognito user pool ID", + config: &rbac.AuthConfig{ + AuthType: "cognito", + CognitoRegion: "us-east-1", + // Missing user pool ID + }, + expectError: true, + }, + { + name: "Invalid auth type", + config: &rbac.AuthConfig{ + AuthType: "invalid", + }, + expectError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.config.InitializeAuthProvider() + + if tc.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +// TestPrintAuthConfig tests the PrintAuthConfig method +func TestPrintAuthConfig(t *testing.T) { + // Create an AuthConfig with some test values + config := &rbac.AuthConfig{ + AuthType: "cognito", + CognitoRegion: "us-east-1", + CognitoUserPoolID: "us-east-1_TestPool", + PrivateKeyPath: "/path/to/private.pem", + PublicKeyPath: "/path/to/public.pem", + } + + // Call PrintAuthConfig - there's no return value, so we're just ensuring it doesn't panic + config.PrintAuthConfig() +} + +// TestValidate tests the Validate method +func TestValidate(t *testing.T) { + testCases := []struct { + name string + config *rbac.AuthConfig + expectError bool + }{ + { + name: "Valid local config", + config: &rbac.AuthConfig{ + AuthType: "local", + PrivateKeyPath: "/path/to/private.pem", + PublicKeyPath: "/path/to/public.pem", + }, + expectError: false, + }, + { + name: "Valid Cognito config", + config: &rbac.AuthConfig{ + AuthType: "cognito", + CognitoRegion: "us-east-1", + CognitoUserPoolID: "us-east-1_TestPool", + }, + expectError: false, + }, + { + name: "Invalid auth type", + config: &rbac.AuthConfig{ + AuthType: "invalid", + }, + expectError: true, + }, + { + name: "Missing local key paths", + config: &rbac.AuthConfig{ + AuthType: "local", + // Missing key paths + }, + expectError: true, + }, + { + name: "Missing Cognito user pool ID", + config: &rbac.AuthConfig{ + AuthType: "cognito", + CognitoRegion: "us-east-1", + // Missing user pool ID + }, + expectError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.config.Validate() + + if tc.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +// Mock key provider for testing +type mockKeyProvider struct{} + +func (m *mockKeyProvider) GetPublicKey(kid string) (*rsa.PublicKey, error) { + return nil, nil +}