1
0
forked from baron/baron-sso

조직도 M2M조회 추가, 자동로그인 보완

This commit is contained in:
2026-05-13 13:44:30 +09:00
parent 72288f1d39
commit 8c2b2f71ef
29 changed files with 2985 additions and 81 deletions

View File

@@ -13,6 +13,7 @@ import (
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/gofiber/fiber/v2"
"github.com/stretchr/testify/assert"
@@ -213,6 +214,13 @@ func (m *MockUserProjectionRepoForHandler) MarkFailed(ctx context.Context, syncE
return args.Error(0)
}
func toJSONString(t *testing.T, value any) string {
t.Helper()
raw, err := json.Marshal(value)
require.NoError(t, err)
return string(raw)
}
func TestTenantHandler_CreateTenant(t *testing.T) {
app := fiber.New()
mockSvc := new(MockTenantService)
@@ -360,6 +368,121 @@ func TestTenantHandler_ListTenants(t *testing.T) {
}
}
func TestTenantHandler_GetOrgContextJSONDefaultsToHanmacFamilyForApiKey(t *testing.T) {
app := fiber.New()
mockSvc := new(MockTenantService)
mockUsers := new(MockUserRepoForHandler)
h := &TenantHandler{Service: mockSvc, UserRepo: mockUsers}
app.Use(func(c *fiber.Ctx) error {
c.Locals("apiKeyName", "orgfront-ssot-client")
return c.Next()
})
app.Get("/org-context", h.GetOrgContext)
now := time.Date(2026, 5, 13, 12, 0, 0, 0, time.UTC)
parent := func(id string) *string { return &id }
tenants := []domain.Tenant{
{ID: "root-other", Type: domain.TenantTypeCompanyGroup, Name: "다른그룹", Slug: "other-family", Status: domain.TenantStatusActive, CreatedAt: now, UpdatedAt: now},
{ID: "group-hanmac-family", Type: domain.TenantTypeCompanyGroup, Name: "한맥가족", Slug: "hanmac-family", Status: domain.TenantStatusActive, CreatedAt: now, UpdatedAt: now},
{ID: "company-hanmac", Type: domain.TenantTypeCompany, ParentID: parent("group-hanmac-family"), Name: "한맥기술", Slug: "hanmac", Status: domain.TenantStatusActive, CreatedAt: now, UpdatedAt: now},
{ID: "dept-platform", Type: domain.TenantTypeUserGroup, ParentID: parent("company-hanmac"), Name: "플랫폼실", Slug: "platform", Status: domain.TenantStatusActive, Config: domain.JSONMap{"orgUnitType": "실"}, CreatedAt: now, UpdatedAt: now},
{ID: "team-sso", Type: domain.TenantTypeUserGroup, ParentID: parent("dept-platform"), Name: "SSO팀", Slug: "sso", Status: domain.TenantStatusActive, CreatedAt: now, UpdatedAt: now},
{ID: "private-team", Type: domain.TenantTypeUserGroup, ParentID: parent("company-hanmac"), Name: "비공개", Slug: "private-team", Status: domain.TenantStatusActive, Config: domain.JSONMap{"visibility": "private"}, CreatedAt: now, UpdatedAt: now},
}
usersByTenantID := []domain.User{
{ID: "user-platform-lead", Email: "lead@example.com", Name: "플랫폼 리드", Status: domain.UserStatusActive, TenantID: parent("dept-platform"), CompanyCode: "platform", Grade: "책임", Position: "실장", CreatedAt: now, UpdatedAt: now},
}
usersBySlug := []domain.User{
{ID: "user-sso-member", Email: "member@example.com", Name: "SSO 구성원", Status: domain.UserStatusActive, CompanyCode: "sso", Grade: "선임", CreatedAt: now, UpdatedAt: now},
}
mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(len(tenants)), nil)
mockUsers.On("FindByTenantIDs", mock.Anything, []string{"group-hanmac-family", "company-hanmac", "dept-platform", "team-sso"}).Return(usersByTenantID, nil)
mockUsers.On("FindByCompanyCodes", mock.Anything, []string{"hanmac-family", "hanmac", "platform", "sso"}).Return(usersBySlug, nil)
req := httptest.NewRequest(http.MethodGet, "/org-context", nil)
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
var got map[string]any
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
require.Equal(t, "baron.org-context.v1", got["schemaVersion"])
scope := got["scope"].(map[string]any)
require.Equal(t, "group-hanmac-family", scope["tenantId"])
require.Equal(t, "hanmac-family", scope["tenantSlug"])
tenantsPayload := got["tenants"].([]any)
require.Len(t, tenantsPayload, 4)
require.Equal(t, "group-hanmac-family", tenantsPayload[0].(map[string]any)["id"])
require.Equal(t, "company-hanmac", tenantsPayload[1].(map[string]any)["id"])
require.Equal(t, "dept-platform", tenantsPayload[2].(map[string]any)["id"])
require.Equal(t, "team-sso", tenantsPayload[3].(map[string]any)["id"])
usersPayload := got["users"].([]any)
require.Len(t, usersPayload, 2)
require.Equal(t, "user-platform-lead", usersPayload[0].(map[string]any)["id"])
require.Equal(t, []any{"dept-platform"}, usersPayload[0].(map[string]any)["tenantIds"])
require.Equal(t, "user-sso-member", usersPayload[1].(map[string]any)["id"])
tree := got["tree"].(map[string]any)
require.Equal(t, "group-hanmac-family", tree["id"])
require.NotContains(t, toJSONString(t, got), "private-team")
require.NotContains(t, toJSONString(t, got), "root-other")
}
func TestTenantHandler_GetOrgContextJSONScopesByTenantSlug(t *testing.T) {
app := fiber.New()
mockSvc := new(MockTenantService)
mockUsers := new(MockUserRepoForHandler)
h := &TenantHandler{Service: mockSvc, UserRepo: mockUsers}
app.Use(func(c *fiber.Ctx) error {
c.Locals("apiKeyName", "orgfront-ssot-client")
return c.Next()
})
app.Get("/org-context", h.GetOrgContext)
now := time.Date(2026, 5, 13, 12, 0, 0, 0, time.UTC)
parent := func(id string) *string { return &id }
tenants := []domain.Tenant{
{ID: "group-hanmac-family", Type: domain.TenantTypeCompanyGroup, Name: "한맥가족", Slug: "hanmac-family", Status: domain.TenantStatusActive, CreatedAt: now, UpdatedAt: now},
{ID: "company-hanmac", Type: domain.TenantTypeCompany, ParentID: parent("group-hanmac-family"), Name: "한맥기술", Slug: "hanmac", Status: domain.TenantStatusActive, CreatedAt: now, UpdatedAt: now},
{ID: "dept-platform", Type: domain.TenantTypeUserGroup, ParentID: parent("company-hanmac"), Name: "플랫폼실", Slug: "platform", Status: domain.TenantStatusActive, CreatedAt: now, UpdatedAt: now},
{ID: "company-other", Type: domain.TenantTypeCompany, ParentID: parent("group-hanmac-family"), Name: "다른회사", Slug: "other", Status: domain.TenantStatusActive, CreatedAt: now, UpdatedAt: now},
}
mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(len(tenants)), nil)
mockUsers.On("FindByTenantIDs", mock.Anything, []string{"company-hanmac", "dept-platform"}).Return([]domain.User{}, nil)
mockUsers.On("FindByCompanyCodes", mock.Anything, []string{"hanmac", "platform"}).Return([]domain.User{}, nil)
req := httptest.NewRequest(http.MethodGet, "/org-context?tenantSlug=hanmac", nil)
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
var got map[string]any
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
scope := got["scope"].(map[string]any)
require.Equal(t, "company-hanmac", scope["tenantId"])
require.Equal(t, "hanmac", scope["tenantSlug"])
require.Contains(t, toJSONString(t, got), "dept-platform")
require.NotContains(t, toJSONString(t, got), "company-other")
}
func TestTenantHandler_GetOrgContextJSONRequiresApiKey(t *testing.T) {
app := fiber.New()
h := &TenantHandler{}
app.Get("/org-context", h.GetOrgContext)
req := httptest.NewRequest(http.MethodGet, "/org-context", nil)
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
}
func TestTenantHandler_ListTenantsReturnsServiceUnavailableWhenProjectionStatusFails(t *testing.T) {
app := fiber.New()
mockSvc := new(MockTenantService)
@@ -518,6 +641,62 @@ func TestTenantHandler_ExportTenantsCSV_OmitsIDsAndUsesParentSlug(t *testing.T)
mockSvc.AssertExpectations(t)
}
func TestTenantHandler_ExportTenantsCSV_FiltersDescendantsByParentIDWithIDs(t *testing.T) {
app := fiber.New()
mockSvc := new(MockTenantService)
h := &TenantHandler{Service: mockSvc}
app.Get("/tenants/export", h.ExportTenantsCSV)
parentID := "11111111-2222-4333-8444-555555555555"
childID := "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee"
grandchildID := "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff"
unrelatedID := "cccccccc-dddd-4eee-8fff-111111111111"
tenants := []domain.Tenant{
{
ID: parentID,
Name: "Parent Org",
Type: domain.TenantTypeCompany,
Slug: "parent-org",
},
{
ID: childID,
Name: "Child Org",
Type: domain.TenantTypeOrganization,
ParentID: &parentID,
Slug: "child-org",
},
{
ID: grandchildID,
Name: "Leaf Team",
Type: domain.TenantTypeUserGroup,
ParentID: &childID,
Slug: "leaf-team",
},
{
ID: unrelatedID,
Name: "Unrelated Org",
Type: domain.TenantTypeOrganization,
Slug: "unrelated-org",
},
}
mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(len(tenants)), nil)
req := httptest.NewRequest("GET", "/tenants/export?includeIds=true&parentId="+parentID, nil)
resp, _ := app.Test(req)
body, _ := io.ReadAll(resp.Body)
text := string(body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Contains(t, text, "tenant_id,name,type,parent_tenant_id,parent_tenant_slug,slug,memo,email_domain,visibility,org_unit_type")
assert.Contains(t, text, childID+",Child Org,ORGANIZATION,"+parentID+",parent-org,child-org,")
assert.Contains(t, text, grandchildID+",Leaf Team,USER_GROUP,"+childID+",child-org,leaf-team,")
assert.NotContains(t, text, unrelatedID)
assert.NotContains(t, text, "Parent Org")
mockSvc.AssertExpectations(t)
}
func TestTenantHandler_ImportTenantsCSVCreatesTenant(t *testing.T) {
app := fiber.New()
mockSvc := new(MockTenantService)