forked from baron/baron-sso
admin api test 코드
This commit is contained in:
59
backend/internal/handler/api_key_handler_test.go
Normal file
59
backend/internal/handler/api_key_handler_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Mock DB for ApiKey tests using a real GORM instance but with a hijacked connection
|
||||
// or just a simple mock if we only check nil.
|
||||
// For ApiKeyHandler, it uses DB for Create/List/Delete.
|
||||
|
||||
func TestApiKeyHandler_CreateApiKey(t *testing.T) {
|
||||
app := fiber.New()
|
||||
// ApiKeyHandler requires a valid DB connection to perform h.DB.Create
|
||||
// Since we don't have a real DB here, we'll check if it fails gracefully
|
||||
// or we can use sqlite in-memory for a more realistic test.
|
||||
h := &ApiKeyHandler{DB: nil} // Testing ServiceUnavailable
|
||||
|
||||
app.Post("/api-keys", h.CreateApiKey)
|
||||
|
||||
input := map[string]interface{}{
|
||||
"name": "M2M Test",
|
||||
"scopes": []string{"read", "write"},
|
||||
}
|
||||
body, _ := json.Marshal(input)
|
||||
|
||||
req := httptest.NewRequest("POST", "/api-keys", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, _ := app.Test(req)
|
||||
|
||||
assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestApiKeyHandler_Validation(t *testing.T) {
|
||||
app := fiber.New()
|
||||
// Using a dummy DB pointer to pass the nil check
|
||||
h := &ApiKeyHandler{DB: &gorm.DB{}}
|
||||
|
||||
app.Post("/api-keys", h.CreateApiKey)
|
||||
|
||||
// Missing name
|
||||
input := map[string]interface{}{
|
||||
"scopes": []string{"read"},
|
||||
}
|
||||
body, _ := json.Marshal(input)
|
||||
|
||||
req := httptest.NewRequest("POST", "/api-keys", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, _ := app.Test(req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
}
|
||||
106
backend/internal/handler/tenant_handler_test.go
Normal file
106
backend/internal/handler/tenant_handler_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"baron-sso-backend/internal/service"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// MockTenantService is a mock for service.TenantService
|
||||
type MockTenantService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockTenantService) RegisterTenant(ctx context.Context, name, slug, description string, domains []string) (*domain.Tenant, error) {
|
||||
args := m.Called(ctx, name, slug, description, domains)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*domain.Tenant), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTenantService) GetTenantByDomain(ctx context.Context, domainName string) (*domain.Tenant, error) {
|
||||
args := m.Called(ctx, domainName)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*domain.Tenant), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTenantService) ApproveTenant(ctx context.Context, tenantID string) error {
|
||||
args := m.Called(ctx, tenantID)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockTenantService) RequestRegistration(ctx context.Context, name, slug, description, domainName, adminEmail string) (*domain.Tenant, error) {
|
||||
args := m.Called(ctx, name, slug, description, domainName, adminEmail)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*domain.Tenant), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTenantService) GetTenantBySlug(ctx context.Context, slug string) (*domain.Tenant, error) {
|
||||
args := m.Called(ctx, slug)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*domain.Tenant), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTenantService) SetKetoService(keto service.KetoService) {
|
||||
m.Called(keto)
|
||||
}
|
||||
|
||||
func TestTenantHandler_CreateTenant(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockSvc := new(MockTenantService)
|
||||
// CreateTenant checks h.DB != nil
|
||||
h := &TenantHandler{Service: mockSvc, DB: &gorm.DB{}}
|
||||
|
||||
app.Post("/tenants", h.CreateTenant)
|
||||
|
||||
input := map[string]interface{}{
|
||||
"name": "Test Tenant",
|
||||
"slug": "test-tenant",
|
||||
"domains": []string{"test.com"},
|
||||
}
|
||||
body, _ := json.Marshal(input)
|
||||
|
||||
mockSvc.On("RegisterTenant", mock.Anything, "Test Tenant", "test-tenant", "", []string{"test.com"}).
|
||||
Return(&domain.Tenant{ID: "t1", Name: "Test Tenant", Slug: "test-tenant"}, nil)
|
||||
|
||||
req := httptest.NewRequest("POST", "/tenants", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, _ := app.Test(req)
|
||||
|
||||
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||
var got map[string]interface{}
|
||||
json.NewDecoder(resp.Body).Decode(&got)
|
||||
assert.Equal(t, "t1", got["id"])
|
||||
}
|
||||
|
||||
func TestTenantHandler_ApproveTenant(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockSvc := new(MockTenantService)
|
||||
h := &TenantHandler{Service: mockSvc}
|
||||
|
||||
app.Post("/tenants/:id/approve", h.ApproveTenant)
|
||||
|
||||
mockSvc.On("ApproveTenant", mock.Anything, "t1").Return(nil)
|
||||
|
||||
req := httptest.NewRequest("POST", "/tenants/t1/approve", nil)
|
||||
resp, _ := app.Test(req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
}
|
||||
193
backend/internal/middleware/rbac_test.go
Normal file
193
backend/internal/middleware/rbac_test.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"baron-sso-backend/internal/service"
|
||||
"context"
|
||||
"errors"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockAuthProvider is a mock for AuthProvider interface
|
||||
type MockAuthProvider struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockAuthProvider) GetEnrichedProfile(c *fiber.Ctx) (*domain.UserProfileResponse, error) {
|
||||
args := m.Called(c)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*domain.UserProfileResponse), args.Error(1)
|
||||
}
|
||||
|
||||
// MockKetoService is a mock for KetoService interface
|
||||
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 {
|
||||
args := m.Called(ctx, namespace, object, relation, subject)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockKetoService) DeleteRelation(ctx context.Context, namespace, object, relation, subject string) error {
|
||||
args := m.Called(ctx, namespace, object, relation, subject)
|
||||
return args.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)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]service.RelationTuple), args.Error(1)
|
||||
}
|
||||
|
||||
// Fixed MockKetoService to match service.KetoService exactly if possible.
|
||||
// Wait, middleware/rbac.go imports baron-sso-backend/internal/service.
|
||||
// So I should use service.RelationTuple.
|
||||
|
||||
func TestRequireRole_Success(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockAuth := new(MockAuthProvider)
|
||||
config := RBACConfig{
|
||||
AllowedRoles: []string{"admin"},
|
||||
AuthHandler: mockAuth,
|
||||
}
|
||||
|
||||
mockAuth.On("GetEnrichedProfile", mock.Anything).Return(&domain.UserProfileResponse{
|
||||
ID: "user1",
|
||||
Role: "admin",
|
||||
}, nil)
|
||||
|
||||
app.Get("/test", RequireRole(config), func(c *fiber.Ctx) error {
|
||||
return c.SendString("ok")
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/test", nil)
|
||||
resp, _ := app.Test(req)
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestRequireRole_Forbidden(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockAuth := new(MockAuthProvider)
|
||||
config := RBACConfig{
|
||||
AllowedRoles: []string{"admin"},
|
||||
AuthHandler: mockAuth,
|
||||
}
|
||||
|
||||
mockAuth.On("GetEnrichedProfile", mock.Anything).Return(&domain.UserProfileResponse{
|
||||
ID: "user1",
|
||||
Role: "user",
|
||||
}, nil)
|
||||
|
||||
app.Get("/test", RequireRole(config), func(c *fiber.Ctx) error {
|
||||
return c.SendString("ok")
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/test", nil)
|
||||
resp, _ := app.Test(req)
|
||||
|
||||
assert.Equal(t, 403, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestRequireKetoPermission_Success(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockAuth := new(MockAuthProvider)
|
||||
mockKeto := new(MockKetoService)
|
||||
config := RBACConfig{
|
||||
AuthHandler: mockAuth,
|
||||
KetoService: mockKeto,
|
||||
}
|
||||
|
||||
profile := &domain.UserProfileResponse{ID: "user1", Role: "user"}
|
||||
mockAuth.On("GetEnrichedProfile", mock.Anything).Return(profile, nil)
|
||||
mockKeto.On("CheckPermission", mock.Anything, "user1", "tenants", "tenant1", "read").Return(true, nil)
|
||||
|
||||
app.Get("/tenants/:id", RequireKetoPermission(config, "tenants", "read"), func(c *fiber.Ctx) error {
|
||||
return c.SendString("ok")
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/tenants/tenant1", nil)
|
||||
resp, _ := app.Test(req)
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestRequireTenantMatch_SuperAdmin(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockAuth := new(MockAuthProvider)
|
||||
config := RBACConfig{
|
||||
AuthHandler: mockAuth,
|
||||
}
|
||||
|
||||
mockAuth.On("GetEnrichedProfile", mock.Anything).Return(&domain.UserProfileResponse{
|
||||
ID: "admin1",
|
||||
Role: domain.RoleSuperAdmin,
|
||||
}, nil)
|
||||
|
||||
app.Get("/tenants/:tenantId/data", RequireTenantMatch(config), func(c *fiber.Ctx) error {
|
||||
return c.SendString("ok")
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/tenants/any-tenant/data", nil)
|
||||
resp, _ := app.Test(req)
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestRequireTenantMatch_Forbidden(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockAuth := new(MockAuthProvider)
|
||||
config := RBACConfig{
|
||||
AuthHandler: mockAuth,
|
||||
}
|
||||
|
||||
tenant1 := "tenant1"
|
||||
mockAuth.On("GetEnrichedProfile", mock.Anything).Return(&domain.UserProfileResponse{
|
||||
ID: "user1",
|
||||
Role: domain.RoleTenantAdmin,
|
||||
TenantID: &tenant1,
|
||||
}, nil)
|
||||
|
||||
app.Get("/tenants/:tenantId/data", RequireTenantMatch(config), func(c *fiber.Ctx) error {
|
||||
return c.SendString("ok")
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/tenants/tenant2/data", nil)
|
||||
resp, _ := app.Test(req)
|
||||
|
||||
assert.Equal(t, 403, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestRequireRole_Unauthorized(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockAuth := new(MockAuthProvider)
|
||||
config := RBACConfig{
|
||||
AuthHandler: mockAuth,
|
||||
}
|
||||
|
||||
mockAuth.On("GetEnrichedProfile", mock.Anything).Return(nil, errors.New("unauthorized"))
|
||||
|
||||
app.Get("/test", RequireRole(config), func(c *fiber.Ctx) error {
|
||||
return c.SendString("ok")
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/test", nil)
|
||||
resp, _ := app.Test(req)
|
||||
|
||||
assert.Equal(t, 401, resp.StatusCode)
|
||||
}
|
||||
312
backend/internal/service/hydra_admin_service_test.go
Normal file
312
backend/internal/service/hydra_admin_service_test.go
Normal file
@@ -0,0 +1,312 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHydraAdminService_ListClients(t *testing.T) {
|
||||
clients := []domain.HydraClient{
|
||||
{ClientID: "client1", ClientName: "Client 1"},
|
||||
{ClientID: "client2", ClientName: "Client 2"},
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/clients", r.URL.Path)
|
||||
assert.Equal(t, "GET", r.Method)
|
||||
assert.Equal(t, "10", r.URL.Query().Get("limit"))
|
||||
assert.Equal(t, "5", r.URL.Query().Get("offset"))
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(clients)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &HydraAdminService{
|
||||
AdminURL: server.URL,
|
||||
}
|
||||
|
||||
result, err := s.ListClients(context.Background(), 10, 5)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, clients, result)
|
||||
}
|
||||
|
||||
func TestHydraAdminService_GetClient(t *testing.T) {
|
||||
client := domain.HydraClient{ClientID: "test-client", ClientName: "Test Client"}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/clients/test-client", r.URL.Path)
|
||||
assert.Equal(t, "GET", r.Method)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(client)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &HydraAdminService{
|
||||
AdminURL: server.URL,
|
||||
}
|
||||
|
||||
result, err := s.GetClient(context.Background(), "test-client")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &client, result)
|
||||
}
|
||||
|
||||
func TestHydraAdminService_CreateClient(t *testing.T) {
|
||||
client := domain.HydraClient{ClientName: "New Client"}
|
||||
created := domain.HydraClient{ClientID: "new-id", ClientName: "New Client"}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/clients", r.URL.Path)
|
||||
assert.Equal(t, "POST", r.Method)
|
||||
|
||||
var received domain.HydraClient
|
||||
json.NewDecoder(r.Body).Decode(&received)
|
||||
assert.Equal(t, client.ClientName, received.ClientName)
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(created)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &HydraAdminService{
|
||||
AdminURL: server.URL,
|
||||
}
|
||||
|
||||
result, err := s.CreateClient(context.Background(), client)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &created, result)
|
||||
}
|
||||
|
||||
func TestHydraAdminService_DeleteClient(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/clients/to-delete", r.URL.Path)
|
||||
assert.Equal(t, "DELETE", r.Method)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &HydraAdminService{
|
||||
AdminURL: server.URL,
|
||||
}
|
||||
|
||||
err := s.DeleteClient(context.Background(), "to-delete")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestHydraAdminService_GetConsentRequest(t *testing.T) {
|
||||
challenge := "challenge123"
|
||||
consentReq := domain.HydraConsentRequest{Challenge: challenge, Subject: "user1"}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/oauth2/auth/requests/consent", r.URL.Path)
|
||||
assert.Equal(t, challenge, r.URL.Query().Get("consent_challenge"))
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(consentReq)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &HydraAdminService{
|
||||
AdminURL: server.URL,
|
||||
}
|
||||
|
||||
result, err := s.GetConsentRequest(context.Background(), challenge)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &consentReq, result)
|
||||
}
|
||||
|
||||
func TestHydraAdminService_PatchClientStatus(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/clients/test-client", r.URL.Path)
|
||||
assert.Equal(t, "PATCH", r.Method)
|
||||
assert.Equal(t, "application/json-patch+json", r.Header.Get("Content-Type"))
|
||||
|
||||
var payload []map[string]interface{}
|
||||
json.NewDecoder(r.Body).Decode(&payload)
|
||||
assert.Equal(t, "replace", payload[0]["op"])
|
||||
assert.Equal(t, "/metadata/status", payload[0]["path"])
|
||||
assert.Equal(t, "inactive", payload[0]["value"])
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(domain.HydraClient{ClientID: "test-client"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &HydraAdminService{AdminURL: server.URL}
|
||||
_, err := s.PatchClientStatus(context.Background(), "test-client", "inactive")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestHydraAdminService_UpdateClient(t *testing.T) {
|
||||
client := domain.HydraClient{ClientName: "Updated Name"}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/clients/test-client", r.URL.Path)
|
||||
assert.Equal(t, "PUT", r.Method)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(client)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &HydraAdminService{AdminURL: server.URL}
|
||||
_, err := s.UpdateClient(context.Background(), "test-client", client)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestHydraAdminService_ListConsentSessions(t *testing.T) {
|
||||
sessions := []domain.HydraConsentSession{{Subject: "user1"}}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/oauth2/auth/sessions/consent", r.URL.Path)
|
||||
assert.Equal(t, "user1", r.URL.Query().Get("subject"))
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(sessions)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &HydraAdminService{AdminURL: server.URL}
|
||||
result, err := s.ListConsentSessions(context.Background(), "user1", "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, sessions, result)
|
||||
}
|
||||
|
||||
func TestHydraAdminService_RevokeConsentSessions(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/oauth2/auth/sessions/consent", r.URL.Path)
|
||||
assert.Equal(t, "DELETE", r.Method)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &HydraAdminService{AdminURL: server.URL}
|
||||
err := s.RevokeConsentSessions(context.Background(), "user1", "")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestHydraAdminService_RejectConsentRequest(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/oauth2/auth/requests/consent/reject", r.URL.Path)
|
||||
assert.Equal(t, "PUT", r.Method)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{"redirect_to": "http://reject"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &HydraAdminService{AdminURL: server.URL}
|
||||
resp, err := s.RejectConsentRequest(context.Background(), "challenge")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "http://reject", resp.RedirectTo)
|
||||
}
|
||||
|
||||
func TestHydraAdminService_RejectLoginRequest(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/oauth2/auth/requests/login/reject", r.URL.Path)
|
||||
assert.Equal(t, "PUT", r.Method)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{"redirect_to": "http://reject-login"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &HydraAdminService{AdminURL: server.URL}
|
||||
resp, err := s.RejectLoginRequest(context.Background(), "challenge", "error", "desc")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "http://reject-login", resp.RedirectTo)
|
||||
}
|
||||
|
||||
func TestHydraAdminService_GetLoginRequest(t *testing.T) {
|
||||
loginReq := domain.HydraLoginRequest{Challenge: "challenge"}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/oauth2/auth/requests/login", r.URL.Path)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(loginReq)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &HydraAdminService{AdminURL: server.URL}
|
||||
result, err := s.GetLoginRequest(context.Background(), "challenge")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &loginReq, result)
|
||||
}
|
||||
|
||||
func TestHydraAdminService_AcceptConsentRequest(t *testing.T) {
|
||||
grant := &domain.HydraConsentRequest{RequestedScope: []string{"openid"}}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/oauth2/auth/requests/consent/accept", r.URL.Path)
|
||||
assert.Equal(t, "PUT", r.Method)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{"redirect_to": "http://accept"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &HydraAdminService{AdminURL: server.URL}
|
||||
resp, err := s.AcceptConsentRequest(context.Background(), "challenge", grant, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "http://accept", resp.RedirectTo)
|
||||
}
|
||||
|
||||
func TestHydraAdminService_AcceptLoginRequest(t *testing.T) {
|
||||
challenge := "login_challenge"
|
||||
subject := "user@example.com"
|
||||
redirectTo := "http://hydra/auth/confirm"
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/oauth2/auth/requests/login/accept", r.URL.Path)
|
||||
assert.Equal(t, challenge, r.URL.Query().Get("login_challenge"))
|
||||
|
||||
var body map[string]interface{}
|
||||
json.NewDecoder(r.Body).Decode(&body)
|
||||
assert.Equal(t, subject, body["subject"])
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{"redirect_to": redirectTo})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &HydraAdminService{
|
||||
AdminURL: server.URL,
|
||||
}
|
||||
|
||||
result, err := s.AcceptLoginRequest(context.Background(), challenge, subject)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, redirectTo, result.RedirectTo)
|
||||
}
|
||||
|
||||
func TestHydraAdminService_ErrorHandling(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("bad request"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &HydraAdminService{AdminURL: server.URL}
|
||||
|
||||
_, err := s.GetClient(context.Background(), "invalid")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "status=400")
|
||||
|
||||
err = s.DeleteClient(context.Background(), "invalid")
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = s.ListClients(context.Background(), 10, 0)
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = s.PatchClientStatus(context.Background(), "invalid", "active")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestHydraAdminService_NotFound(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &HydraAdminService{AdminURL: server.URL}
|
||||
|
||||
_, err := s.GetClient(context.Background(), "none")
|
||||
assert.Equal(t, ErrHydraNotFound, err)
|
||||
}
|
||||
158
backend/internal/service/keto_service_test.go
Normal file
158
backend/internal/service/keto_service_test.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestKetoService_CheckPermission(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/relation-tuples/check", r.URL.Path)
|
||||
assert.Equal(t, "user1", r.URL.Query().Get("subject_id"))
|
||||
assert.Equal(t, "tenants", r.URL.Query().Get("namespace"))
|
||||
assert.Equal(t, "tenant1", r.URL.Query().Get("object"))
|
||||
assert.Equal(t, "admin", r.URL.Query().Get("relation"))
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(checkResponse{Allowed: true})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &ketoService{
|
||||
readURL: server.URL,
|
||||
client: &http.Client{},
|
||||
}
|
||||
|
||||
allowed, err := s.CheckPermission(context.Background(), "user1", "tenants", "tenant1", "admin")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, allowed)
|
||||
}
|
||||
|
||||
func TestKetoService_CreateRelation(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/admin/relation-tuples", r.URL.Path)
|
||||
assert.Equal(t, "PUT", r.Method)
|
||||
|
||||
var body map[string]interface{}
|
||||
json.NewDecoder(r.Body).Decode(&body)
|
||||
assert.Equal(t, "tenants", body["namespace"])
|
||||
assert.Equal(t, "tenant1", body["object"])
|
||||
assert.Equal(t, "admin", body["relation"])
|
||||
assert.Equal(t, "user1", body["subject_id"])
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &ketoService{
|
||||
writeURL: server.URL,
|
||||
client: &http.Client{},
|
||||
}
|
||||
|
||||
err := s.CreateRelation(context.Background(), "tenants", "tenant1", "admin", "user1")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestKetoService_DeleteRelation(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/relation-tuples", r.URL.Path)
|
||||
assert.Equal(t, "DELETE", r.Method)
|
||||
assert.Equal(t, "user1", r.URL.Query().Get("subject_id"))
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &ketoService{
|
||||
writeURL: server.URL,
|
||||
client: &http.Client{},
|
||||
}
|
||||
|
||||
err := s.DeleteRelation(context.Background(), "tenants", "tenant1", "admin", "user1")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestKetoService_ListRelations(t *testing.T) {
|
||||
tuples := []RelationTuple{
|
||||
{Namespace: "tenants", Object: "tenant1", Relation: "admin", SubjectID: "user1"},
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/relation-tuples", r.URL.Path)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(relationTuplesResponse{RelationTuples: tuples})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &ketoService{
|
||||
readURL: server.URL,
|
||||
client: &http.Client{},
|
||||
}
|
||||
|
||||
result, err := s.ListRelations(context.Background(), "tenants", "tenant1", "admin", "user1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tuples, result)
|
||||
}
|
||||
|
||||
func TestKetoService_ErrorHandling(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal error"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &ketoService{
|
||||
readURL: server.URL,
|
||||
writeURL: server.URL,
|
||||
client: &http.Client{},
|
||||
}
|
||||
|
||||
_, err := s.CheckPermission(context.Background(), "u", "n", "o", "r")
|
||||
assert.Error(t, err)
|
||||
|
||||
err = s.DeleteRelation(context.Background(), "n", "o", "r", "s")
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = s.ListRelations(context.Background(), "n", "o", "r", "s")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestKetoService_CheckPermission_Forbidden(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &ketoService{readURL: server.URL, client: &http.Client{}}
|
||||
allowed, err := s.CheckPermission(context.Background(), "u", "n", "o", "r")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, allowed)
|
||||
}
|
||||
|
||||
func TestKetoService_CreateRelation_Retry(t *testing.T) {
|
||||
attempts := 0
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
attempts++
|
||||
if attempts < 2 {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := &ketoService{
|
||||
writeURL: server.URL,
|
||||
client: &http.Client{},
|
||||
}
|
||||
|
||||
err := s.CreateRelation(context.Background(), "n", "o", "r", "s")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, attempts)
|
||||
}
|
||||
Reference in New Issue
Block a user