forked from baron/baron-sso
- Simplified RBAC system to two roles: super_admin and user. - Removed tenant_admin and rp_admin roles across backend and frontend. - Removed Dev Role Switcher feature from adminfront. - Updated all handlers, middlewares, and navigation to reflect the new role model. - Fixed backend build errors and updated tests.
197 lines
6.5 KiB
Go
197 lines
6.5 KiB
Go
package handler
|
|
|
|
import (
|
|
"baron-sso-backend/internal/domain"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type fakeDataIntegrityChecker struct {
|
|
calls int
|
|
listCalls int
|
|
deleteCalls int
|
|
deletedIDs []string
|
|
report domain.DataIntegrityReport
|
|
orphans []domain.OrphanUserLoginID
|
|
deleteResult domain.DeleteOrphanUserLoginIDsResult
|
|
err error
|
|
}
|
|
|
|
func (f *fakeDataIntegrityChecker) CheckDataIntegrity(ctx context.Context) (domain.DataIntegrityReport, error) {
|
|
f.calls++
|
|
return f.report, f.err
|
|
}
|
|
|
|
func (f *fakeDataIntegrityChecker) ListOrphanUserLoginIDs(ctx context.Context) ([]domain.OrphanUserLoginID, error) {
|
|
f.listCalls++
|
|
return f.orphans, f.err
|
|
}
|
|
|
|
func (f *fakeDataIntegrityChecker) DeleteOrphanUserLoginIDs(ctx context.Context, ids []string) (domain.DeleteOrphanUserLoginIDsResult, error) {
|
|
f.deleteCalls++
|
|
f.deletedIDs = append([]string(nil), ids...)
|
|
return f.deleteResult, f.err
|
|
}
|
|
|
|
func TestAdminHandler_GetDataIntegrityRequiresSuperAdmin(t *testing.T) {
|
|
checker := &fakeDataIntegrityChecker{}
|
|
h := &AdminHandler{IntegrityChecker: checker}
|
|
app := fiber.New()
|
|
app.Use(func(c *fiber.Ctx) error {
|
|
c.Locals("user_profile", &domain.UserProfileResponse{ID: "tenant-admin", Role: "tenant_admin"})
|
|
return c.Next()
|
|
})
|
|
app.Get("/api/v1/admin/integrity", h.GetDataIntegrity)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/integrity", nil)
|
|
resp, err := app.Test(req)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusForbidden, resp.StatusCode)
|
|
require.Equal(t, 0, checker.calls)
|
|
}
|
|
|
|
func TestAdminHandler_GetDataIntegrityReturnsReportForSuperAdmin(t *testing.T) {
|
|
checkedAt := time.Date(2026, 5, 14, 0, 0, 0, 0, time.UTC)
|
|
checker := &fakeDataIntegrityChecker{
|
|
report: domain.DataIntegrityReport{
|
|
Status: domain.DataIntegrityStatusFail,
|
|
CheckedAt: checkedAt,
|
|
Summary: domain.DataIntegritySummary{
|
|
TotalChecks: 1,
|
|
Failures: 1,
|
|
},
|
|
Sections: []domain.DataIntegritySection{
|
|
{
|
|
Key: "tenant_integrity",
|
|
Label: "테넌트 정합성",
|
|
Status: domain.DataIntegrityStatusFail,
|
|
Checks: []domain.DataIntegrityCheck{
|
|
{
|
|
Key: "duplicate_tenant_slugs",
|
|
Label: "중복 테넌트 slug",
|
|
Status: domain.DataIntegrityStatusFail,
|
|
Count: 1,
|
|
Severity: "error",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
h := &AdminHandler{IntegrityChecker: checker}
|
|
app := fiber.New()
|
|
app.Use(func(c *fiber.Ctx) error {
|
|
c.Locals("user_profile", &domain.UserProfileResponse{ID: "super", Role: domain.RoleSuperAdmin})
|
|
return c.Next()
|
|
})
|
|
app.Get("/api/v1/admin/integrity", h.GetDataIntegrity)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/integrity", nil)
|
|
resp, err := app.Test(req)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
require.Equal(t, 1, checker.calls)
|
|
|
|
var body domain.DataIntegrityReport
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&body))
|
|
require.Equal(t, domain.DataIntegrityStatusFail, body.Status)
|
|
require.Equal(t, int64(1), body.Summary.Failures)
|
|
require.Len(t, body.Sections, 1)
|
|
require.Equal(t, "tenant_integrity", body.Sections[0].Key)
|
|
}
|
|
|
|
func TestAdminHandler_ListOrphanUserLoginIDsReturnsTargetsForSuperAdmin(t *testing.T) {
|
|
checker := &fakeDataIntegrityChecker{
|
|
orphans: []domain.OrphanUserLoginID{
|
|
{
|
|
ID: "login-id-1",
|
|
UserID: "user-1",
|
|
TenantID: "tenant-1",
|
|
FieldKey: "emp_id",
|
|
LoginID: "EMP001",
|
|
Reasons: []string{"missing_tenant"},
|
|
},
|
|
},
|
|
}
|
|
h := &AdminHandler{IntegrityChecker: checker}
|
|
app := fiber.New()
|
|
app.Use(func(c *fiber.Ctx) error {
|
|
c.Locals("user_profile", &domain.UserProfileResponse{ID: "super", Role: domain.RoleSuperAdmin})
|
|
return c.Next()
|
|
})
|
|
app.Get("/api/v1/admin/integrity/orphan-user-login-ids", h.ListOrphanUserLoginIDs)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/integrity/orphan-user-login-ids", nil)
|
|
resp, err := app.Test(req)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
require.Equal(t, 1, checker.listCalls)
|
|
|
|
var body struct {
|
|
Items []domain.OrphanUserLoginID `json:"items"`
|
|
Total int `json:"total"`
|
|
}
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&body))
|
|
require.Equal(t, 1, body.Total)
|
|
require.Equal(t, "login-id-1", body.Items[0].ID)
|
|
require.Equal(t, []string{"missing_tenant"}, body.Items[0].Reasons)
|
|
}
|
|
|
|
func TestAdminHandler_DeleteOrphanUserLoginIDsRequiresSuperAdminAndDeletesSelectedTargets(t *testing.T) {
|
|
checker := &fakeDataIntegrityChecker{
|
|
deleteResult: domain.DeleteOrphanUserLoginIDsResult{
|
|
DeletedCount: 1,
|
|
Deleted: []domain.OrphanUserLoginID{
|
|
{ID: "login-id-1", LoginID: "EMP001", Reasons: []string{"missing_user"}},
|
|
},
|
|
SkippedIDs: []string{"valid-login-id"},
|
|
},
|
|
}
|
|
h := &AdminHandler{IntegrityChecker: checker}
|
|
app := fiber.New()
|
|
app.Use(func(c *fiber.Ctx) error {
|
|
c.Locals("user_profile", &domain.UserProfileResponse{ID: "super", Role: domain.RoleSuperAdmin})
|
|
return c.Next()
|
|
})
|
|
app.Delete("/api/v1/admin/integrity/orphan-user-login-ids", h.DeleteOrphanUserLoginIDs)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/admin/integrity/orphan-user-login-ids", strings.NewReader(`{"ids":["login-id-1","valid-login-id"]}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := app.Test(req)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
require.Equal(t, 1, checker.deleteCalls)
|
|
require.Equal(t, []string{"login-id-1", "valid-login-id"}, checker.deletedIDs)
|
|
|
|
var body domain.DeleteOrphanUserLoginIDsResult
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&body))
|
|
require.Equal(t, int64(1), body.DeletedCount)
|
|
require.Equal(t, []string{"valid-login-id"}, body.SkippedIDs)
|
|
}
|
|
|
|
func TestAdminHandler_DeleteOrphanUserLoginIDsRejectsTenantAdmin(t *testing.T) {
|
|
checker := &fakeDataIntegrityChecker{}
|
|
h := &AdminHandler{IntegrityChecker: checker}
|
|
app := fiber.New()
|
|
app.Use(func(c *fiber.Ctx) error {
|
|
c.Locals("user_profile", &domain.UserProfileResponse{ID: "tenant-admin", Role: "tenant_admin"})
|
|
return c.Next()
|
|
})
|
|
app.Delete("/api/v1/admin/integrity/orphan-user-login-ids", h.DeleteOrphanUserLoginIDs)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/admin/integrity/orphan-user-login-ids", strings.NewReader(`{"ids":["login-id-1"]}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := app.Test(req)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusForbidden, resp.StatusCode)
|
|
require.Equal(t, 0, checker.deleteCalls)
|
|
}
|