1
0
forked from baron/baron-sso
Files
baron-sso/backend/internal/handler/tenant_handler_relations_test.go

270 lines
8.0 KiB
Go

package handler
import (
"baron-sso-backend/internal/domain"
"baron-sso-backend/internal/repository"
"baron-sso-backend/internal/service"
"baron-sso-backend/internal/testsupport"
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gofiber/fiber/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestTenantHandler_Relations(t *testing.T) {
if !testsupport.DockerAvailable() {
t.Skip("Docker provider is unavailable in this environment")
}
db := newTenantHandlerSeedDeleteDB(t)
if err := db.AutoMigrate(&domain.TenantDomain{}, &domain.KetoOutbox{}); err != nil {
t.Fatalf("failed to migrate tenant domains or outbox: %v", err)
}
// Create a test tenant in DB with a valid UUID
tenantID := "00000000-0000-0000-0000-000000000030"
tenant := domain.Tenant{
ID: tenantID,
Name: "Relation Test Tenant",
Slug: "relation-test-tenant",
Type: domain.TenantTypeCompany,
Status: domain.TenantStatusActive,
}
if err := db.Create(&tenant).Error; err != nil {
t.Fatalf("failed to create tenant: %v", err)
}
mockSvc := new(MockTenantService)
mockKeto := new(devMockKetoService)
realOutbox := repository.NewKetoOutboxRepository(db)
h := &TenantHandler{
DB: db,
Service: mockSvc,
Keto: mockKeto,
KetoOutbox: realOutbox,
}
userID := "user-relation-1"
t.Run("ListRelations - Returns correct relations aggregated by user", func(t *testing.T) {
app := fiber.New()
app.Get("/tenants/:id/relations", h.ListRelations)
mockKeto.On("ListRelations", mock.Anything, "Tenant", tenantID, "", "").Return([]service.RelationTuple{
{
Namespace: "Tenant",
Object: tenantID,
Relation: "schema_managers",
SubjectID: "User:" + userID,
},
{
Namespace: "Tenant",
Object: tenantID,
Relation: "profile_viewers",
SubjectID: "User:" + userID,
},
{
Namespace: "Tenant",
Object: tenantID,
Relation: "unrelated_relation", // Should be filtered out
SubjectID: "User:" + userID,
},
}, nil).Once()
req := httptest.NewRequest("GET", "/tenants/"+tenantID+"/relations", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf("request failed: %v", err)
}
assert.Equal(t, http.StatusOK, resp.StatusCode)
var got struct {
Items []struct {
UserID string `json:"userId"`
Name string `json:"name"`
Email string `json:"email"`
Relations []string `json:"relations"`
} `json:"items"`
}
err = json.NewDecoder(resp.Body).Decode(&got)
if err != nil {
t.Fatalf("failed to decode response: %v", err)
}
assert.Len(t, got.Items, 1)
assert.Equal(t, userID, got.Items[0].UserID)
assert.Contains(t, got.Items[0].Relations, "schema_managers")
assert.Contains(t, got.Items[0].Relations, "profile_viewers")
assert.NotContains(t, got.Items[0].Relations, "unrelated_relation")
mockKeto.AssertExpectations(t)
})
t.Run("AddRelation - Inserts into KetoOutbox DB table", func(t *testing.T) {
app := fiber.New()
app.Post("/tenants/:id/relations", h.AddRelation)
mockKeto.On("ListRelations", mock.Anything, "Tenant", tenantID, "schema_managers", "User:"+userID).Return([]service.RelationTuple{}, nil).Once()
body, _ := json.Marshal(map[string]string{
"userId": userID,
"relation": "schema_managers",
})
req := httptest.NewRequest("POST", "/tenants/"+tenantID+"/relations", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
if err != nil {
t.Fatalf("request failed: %v", err)
}
assert.Equal(t, http.StatusOK, resp.StatusCode)
// Verify row was written to the keto_outboxes DB table
var outboxEntries []domain.KetoOutbox
if err := db.Where("object = ? AND relation = ? AND action = ?", tenantID, "schema_managers", domain.KetoOutboxActionCreate).Find(&outboxEntries).Error; err != nil {
t.Fatalf("failed to query outbox: %v", err)
}
assert.Len(t, outboxEntries, 1)
assert.Equal(t, "Tenant", outboxEntries[0].Namespace)
assert.Equal(t, "User:"+userID, outboxEntries[0].Subject)
mockKeto.AssertExpectations(t)
})
t.Run("RemoveRelation - Inserts delete action into KetoOutbox DB table", func(t *testing.T) {
app := fiber.New()
app.Delete("/tenants/:id/relations", h.RemoveRelation)
body, _ := json.Marshal(map[string]string{
"userId": userID,
"relation": "schema_managers",
})
req := httptest.NewRequest("DELETE", "/tenants/"+tenantID+"/relations", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
if err != nil {
t.Fatalf("request failed: %v", err)
}
assert.Equal(t, http.StatusOK, resp.StatusCode)
// Verify delete action row was written to the keto_outboxes DB table
var outboxEntries []domain.KetoOutbox
if err := db.Where("object = ? AND relation = ? AND action = ?", tenantID, "schema_managers", domain.KetoOutboxActionDelete).Find(&outboxEntries).Error; err != nil {
t.Fatalf("failed to query outbox: %v", err)
}
assert.Len(t, outboxEntries, 1)
assert.Equal(t, "Tenant", outboxEntries[0].Namespace)
assert.Equal(t, "User:"+userID, outboxEntries[0].Subject)
})
}
func TestTenantHandler_SystemRelations(t *testing.T) {
if !testsupport.DockerAvailable() {
t.Skip("Docker provider is unavailable in this environment")
}
db := newTenantHandlerSeedDeleteDB(t)
if err := db.AutoMigrate(&domain.KetoOutbox{}); err != nil {
t.Fatalf("failed to migrate outbox: %v", err)
}
mockSvc := new(MockTenantService)
mockKeto := new(devMockKetoService)
realOutbox := repository.NewKetoOutboxRepository(db)
h := &TenantHandler{
DB: db,
Service: mockSvc,
Keto: mockKeto,
KetoOutbox: realOutbox,
}
userID := "user-system-1"
t.Run("ListSystemRelations - Returns correct system relations", func(t *testing.T) {
app := fiber.New()
app.Get("/system/relations", h.ListSystemRelations)
mockKeto.On("ListRelations", mock.Anything, "System", "system", "", "").Return([]service.RelationTuple{
{
Namespace: "System",
Object: "system",
Relation: "ory_ssot_viewers",
SubjectID: "User:" + userID,
},
{
Namespace: "System",
Object: "system",
Relation: "audit_logs_viewers",
SubjectID: "User:" + userID,
},
}, nil).Once()
req := httptest.NewRequest("GET", "/system/relations", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf("request failed: %v", err)
}
assert.Equal(t, http.StatusOK, resp.StatusCode)
var got struct {
Items []struct {
UserID string `json:"userId"`
Relations []string `json:"relations"`
} `json:"items"`
}
err = json.NewDecoder(resp.Body).Decode(&got)
if err != nil {
t.Fatalf("failed to decode response: %v", err)
}
assert.Len(t, got.Items, 1)
assert.Equal(t, userID, got.Items[0].UserID)
assert.Contains(t, got.Items[0].Relations, "ory_ssot_viewers")
assert.Contains(t, got.Items[0].Relations, "audit_logs_viewers")
mockKeto.AssertExpectations(t)
})
t.Run("AddSystemRelation - Inserts into KetoOutbox DB table with System namespace", func(t *testing.T) {
app := fiber.New()
app.Post("/system/relations", h.AddSystemRelation)
mockKeto.On("ListRelations", mock.Anything, "System", "system", "ory_ssot_viewers", "User:"+userID).Return([]service.RelationTuple{}, nil).Once()
body, _ := json.Marshal(map[string]string{
"userId": userID,
"relation": "ory_ssot_viewers",
})
req := httptest.NewRequest("POST", "/system/relations", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
if err != nil {
t.Fatalf("request failed: %v", err)
}
assert.Equal(t, http.StatusOK, resp.StatusCode)
var outboxEntries []domain.KetoOutbox
if err := db.Where("object = ? AND relation = ? AND action = ?", "system", "ory_ssot_viewers", domain.KetoOutboxActionCreate).Find(&outboxEntries).Error; err != nil {
t.Fatalf("failed to query outbox: %v", err)
}
assert.Len(t, outboxEntries, 1)
assert.Equal(t, "System", outboxEntries[0].Namespace)
assert.Equal(t, "User:"+userID, outboxEntries[0].Subject)
mockKeto.AssertExpectations(t)
})
}