1
0
forked from baron/baron-sso

test 코드 수정

This commit is contained in:
2026-02-13 15:34:31 +09:00
parent f0b1a88005
commit 837883756f
9 changed files with 129 additions and 20 deletions

View File

@@ -1,16 +1,19 @@
package handler package handler
import ( import (
"baron-sso-backend/internal/service"
"runtime" "runtime"
"time" "time"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
type AdminHandler struct{} type AdminHandler struct {
Keto service.KetoService
}
func NewAdminHandler() *AdminHandler { func NewAdminHandler(keto service.KetoService) *AdminHandler {
return &AdminHandler{} return &AdminHandler{Keto: keto}
} }
func (h *AdminHandler) CheckAuth(c *fiber.Ctx) error { func (h *AdminHandler) CheckAuth(c *fiber.Ctx) error {

View File

@@ -24,7 +24,7 @@ type DevHandler struct {
ConsentRepo repository.ClientConsentRepository ConsentRepo repository.ClientConsentRepository
} }
func NewDevHandler(redis domain.RedisRepository, secretRepo domain.ClientSecretRepository, consentRepo repository.ClientConsentRepository) *DevHandler { func NewDevHandler(redis domain.RedisRepository, secretRepo domain.ClientSecretRepository, consentRepo repository.ClientConsentRepository, rpSvc service.RelyingPartyService) *DevHandler {
return &DevHandler{ return &DevHandler{
Hydra: service.NewHydraAdminService(), Hydra: service.NewHydraAdminService(),
Redis: redis, Redis: redis,

View File

@@ -9,11 +9,12 @@ import (
) )
type RelyingPartyHandler struct { type RelyingPartyHandler struct {
Service service.RelyingPartyService Service service.RelyingPartyService
KratosAdmin *service.KratosAdminService
} }
func NewRelyingPartyHandler(s service.RelyingPartyService) *RelyingPartyHandler { func NewRelyingPartyHandler(s service.RelyingPartyService, kratos *service.KratosAdminService) *RelyingPartyHandler {
return &RelyingPartyHandler{Service: s} return &RelyingPartyHandler{Service: s, KratosAdmin: kratos}
} }
func (h *RelyingPartyHandler) Create(c *fiber.Ctx) error { func (h *RelyingPartyHandler) Create(c *fiber.Ctx) error {

View File

@@ -12,12 +12,19 @@ import (
) )
type TenantHandler struct { type TenantHandler struct {
DB *gorm.DB DB *gorm.DB
Service service.TenantService Service service.TenantService
Keto service.KetoService
KratosAdmin *service.KratosAdminService
} }
func NewTenantHandler(db *gorm.DB, svc service.TenantService) *TenantHandler { func NewTenantHandler(db *gorm.DB, svc service.TenantService, keto service.KetoService, kratos *service.KratosAdminService) *TenantHandler {
return &TenantHandler{DB: db, Service: svc} return &TenantHandler{
DB: db,
Service: svc,
Keto: keto,
KratosAdmin: kratos,
}
} }
type tenantSummary struct { type tenantSummary struct {
@@ -301,6 +308,85 @@ func (h *TenantHandler) DeleteTenant(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusNoContent) return c.SendStatus(fiber.StatusNoContent)
} }
func (h *TenantHandler) ListAdmins(c *fiber.Ctx) error {
tenantID := c.Params("id")
if tenantID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "tenant id is required"})
}
// Fetch admins from Keto
relations, err := h.Keto.ListRelations(c.Context(), "Tenant", tenantID, "admin", "")
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
type adminInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
admins := []adminInfo{}
for _, rel := range relations {
if !strings.HasPrefix(rel.SubjectID, "User:") {
continue
}
userID := strings.TrimPrefix(rel.SubjectID, "User:")
// Fetch user details from Kratos
identity, err := h.KratosAdmin.GetIdentity(c.Context(), userID)
if err != nil {
admins = append(admins, adminInfo{ID: userID, Name: "Unknown", Email: "Unknown"})
continue
}
name := ""
if n, ok := identity.Traits["name"].(string); ok {
name = n
}
email := ""
if e, ok := identity.Traits["email"].(string); ok {
email = e
}
admins = append(admins, adminInfo{
ID: userID,
Name: name,
Email: email,
})
}
return c.JSON(admins)
}
func (h *TenantHandler) AddAdmin(c *fiber.Ctx) error {
tenantID := c.Params("id")
userID := c.Params("userId")
if tenantID == "" || userID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "tenantId and userId are required"})
}
if err := h.Keto.CreateRelation(c.Context(), "Tenant", tenantID, "admin", "User:"+userID); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.SendStatus(fiber.StatusOK)
}
func (h *TenantHandler) RemoveAdmin(c *fiber.Ctx) error {
tenantID := c.Params("id")
userID := c.Params("userId")
if tenantID == "" || userID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "tenantId and userId are required"})
}
if err := h.Keto.DeleteRelation(c.Context(), "Tenant", tenantID, "admin", "User:"+userID); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.SendStatus(fiber.StatusNoContent)
}
func mapTenantSummary(t domain.Tenant) tenantSummary { func mapTenantSummary(t domain.Tenant) tenantSummary {
domains := make([]string, 0, len(t.Domains)) domains := make([]string, 0, len(t.Domains))
for _, d := range t.Domains { for _, d := range t.Domains {

View File

@@ -54,6 +54,14 @@ func (m *MockKetoService) ListRelations(ctx context.Context, namespace, object,
return args.Get(0).([]service.RelationTuple), args.Error(1) 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)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).([]string), args.Error(1)
}
// Fixed MockKetoService to match service.KetoService exactly if possible. // Fixed MockKetoService to match service.KetoService exactly if possible.
// Wait, middleware/rbac.go imports baron-sso-backend/internal/service. // Wait, middleware/rbac.go imports baron-sso-backend/internal/service.
// So I should use service.RelationTuple. // So I should use service.RelationTuple.

View File

@@ -4,4 +4,8 @@ import 'locale_storage_stub.dart'
abstract class LocaleStorage { abstract class LocaleStorage {
static String? read() => localeStorage.read(); static String? read() => localeStorage.read();
static void write(String locale) => localeStorage.write(locale); static void write(String locale) => localeStorage.write(locale);
static void forceMemoryStorageForTests(bool value) =>
localeStorage.forceMemoryStorageForTests(value);
static void forceSessionStorageForTests(bool value) =>
localeStorage.forceSessionStorageForTests(value);
} }

View File

@@ -6,6 +6,14 @@ class LocaleStorageImpl {
void write(String locale) { void write(String locale) {
_locale = locale; _locale = locale;
} }
void forceMemoryStorageForTests(bool value) {
// Stub
}
void forceSessionStorageForTests(bool value) {
// Stub
}
} }
final localeStorage = LocaleStorageImpl(); final localeStorage = LocaleStorageImpl();

View File

@@ -11,7 +11,7 @@ class LocaleStorageImpl {
static bool _forceSession = false; static bool _forceSession = false;
@visibleForTesting @visibleForTesting
static void forceMemoryStorageForTests(bool value) { void forceMemoryStorageForTests(bool value) {
_forceMemory = value; _forceMemory = value;
if (!value) { if (!value) {
_memory.clear(); _memory.clear();
@@ -19,7 +19,7 @@ class LocaleStorageImpl {
} }
@visibleForTesting @visibleForTesting
static void forceSessionStorageForTests(bool value) { void forceSessionStorageForTests(bool value) {
_forceSession = value; _forceSession = value;
} }

View File

@@ -1,13 +1,12 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:userfront/core/i18n/locale_storage.dart'; import 'package:userfront/core/i18n/locale_storage.dart';
import 'package:userfront/core/i18n/locale_storage_web.dart' as locale_web;
import 'helpers/web_storage.dart'; import 'helpers/web_storage.dart';
void main() { void main() {
setUp(() { setUp(() {
locale_web.LocaleStorageImpl.forceMemoryStorageForTests(false); LocaleStorage.forceMemoryStorageForTests(false);
locale_web.LocaleStorageImpl.forceSessionStorageForTests(false); LocaleStorage.forceSessionStorageForTests(false);
if (webStorage.isWeb) { if (webStorage.isWeb) {
webStorage.clear(); webStorage.clear();
webStorage.clearSession(); webStorage.clearSession();
@@ -15,8 +14,8 @@ void main() {
}); });
tearDown(() { tearDown(() {
locale_web.LocaleStorageImpl.forceMemoryStorageForTests(false); LocaleStorage.forceMemoryStorageForTests(false);
locale_web.LocaleStorageImpl.forceSessionStorageForTests(false); LocaleStorage.forceSessionStorageForTests(false);
if (webStorage.isWeb) { if (webStorage.isWeb) {
webStorage.clear(); webStorage.clear();
webStorage.clearSession(); webStorage.clearSession();
@@ -59,7 +58,7 @@ void main() {
return; return;
} }
locale_web.LocaleStorageImpl.forceMemoryStorageForTests(true); LocaleStorage.forceMemoryStorageForTests(true);
LocaleStorage.write('en'); LocaleStorage.write('en');
expect(webStorage.get('locale'), isNull); expect(webStorage.get('locale'), isNull);
@@ -76,7 +75,7 @@ void main() {
return; return;
} }
locale_web.LocaleStorageImpl.forceSessionStorageForTests(true); LocaleStorage.forceSessionStorageForTests(true);
LocaleStorage.write('ko'); LocaleStorage.write('ko');
expect(webStorage.get('locale'), isNull); expect(webStorage.get('locale'), isNull);