package handler import ( "baron-sso-backend/internal/domain" "baron-sso-backend/internal/middleware" "baron-sso-backend/internal/service" "context" "net/http" "net/http/httptest" "testing" "github.com/gofiber/fiber/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) // Reusing MockKetoService from previous step or defining here if needed type MockKetoService struct { mock.Mock } func (m *MockKetoService) CheckPermission(ctx context.Context, subject, namespace, object, relation string) (bool, error) { args := m.Called(ctx, subject, namespace, object, relation) return args.Bool(0), args.Error(1) } func (m *MockKetoService) CreateRelation(ctx context.Context, namespace, object, relation, subject string) error { return m.Called(ctx, namespace, object, relation, subject).Error(0) } func (m *MockKetoService) DeleteRelation(ctx context.Context, namespace, object, relation, subject string) error { return m.Called(ctx, namespace, object, relation, subject).Error(0) } func (m *MockKetoService) ListRelations(ctx context.Context, namespace, object, relation, subject string) ([]service.RelationTuple, error) { args := m.Called(ctx, namespace, object, relation, subject) return args.Get(0).([]service.RelationTuple), args.Error(1) } func (m *MockKetoService) ListObjects(ctx context.Context, namespace, relation, subject string) ([]string, error) { args := m.Called(ctx, namespace, relation, subject) return args.Get(0).([]string), args.Error(1) } // MockAuthHandler implements middleware.AuthProfileProvider type MockAuthHandler struct { mock.Mock } func (m *MockAuthHandler) GetEnrichedProfile(c *fiber.Ctx) (*domain.UserProfileResponse, error) { args := m.Called(c) return args.Get(0).(*domain.UserProfileResponse), args.Error(1) } func TestRequireKetoPermission_Tenant_AuditContext(t *testing.T) { app := fiber.New() mockKeto := new(MockKetoService) mockAuth := new(MockAuthHandler) config := middleware.RBACConfig{ AuthHandler: mockAuth, KetoService: mockKeto, } userID := "user-1" tenantID := "tenant-abc" // Mock user profile mockAuth.On("GetEnrichedProfile", mock.Anything).Return(&domain.UserProfileResponse{ ID: userID, Role: domain.RoleTenantAdmin, }, nil) // Mock Keto: Allow access mockKeto.On("CheckPermission", mock.Anything, userID, "Tenant", tenantID, "manage").Return(true, nil) // Route with middleware app.Get("/test/tenants/:id", middleware.RequireKetoPermission(config, "Tenant", "manage"), func(c *fiber.Ctx) error { // Verify that tenant_id was injected into Locals for audit log assert.Equal(t, tenantID, c.Locals("tenant_id")) return c.SendStatus(fiber.StatusOK) }) // Execute req := httptest.NewRequest("GET", "/test/tenants/"+tenantID, nil) resp, _ := app.Test(req) // Verify assert.Equal(t, http.StatusOK, resp.StatusCode) mockKeto.AssertExpectations(t) mockAuth.AssertExpectations(t) } func TestRequireKetoPermission_Deny(t *testing.T) { app := fiber.New() mockKeto := new(MockKetoService) mockAuth := new(MockAuthHandler) config := middleware.RBACConfig{ AuthHandler: mockAuth, KetoService: mockKeto, } userID := "user-bad" tenantID := "tenant-secret" mockAuth.On("GetEnrichedProfile", mock.Anything).Return(&domain.UserProfileResponse{ ID: userID, Role: domain.RoleUser, }, nil) // Mock Keto: Deny access mockKeto.On("CheckPermission", mock.Anything, userID, "Tenant", tenantID, "view").Return(false, nil) app.Get("/test/tenants/:id", middleware.RequireKetoPermission(config, "Tenant", "view"), func(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) }) req := httptest.NewRequest("GET", "/test/tenants/"+tenantID, nil) resp, _ := app.Test(req) assert.Equal(t, http.StatusForbidden, resp.StatusCode) }