1
0
forked from baron/baron-sso

admin api test 코드

This commit is contained in:
2026-02-05 17:01:47 +09:00
parent 86613fac46
commit 8831208c08
5 changed files with 828 additions and 0 deletions

View 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)
}

View 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)
}

View 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)
}

View 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)
}

View 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)
}