forked from baron/baron-sso
Implement tenant import and RP auto login policies
This commit is contained in:
@@ -189,7 +189,7 @@ func TestTenantHandler_CreateTenant(t *testing.T) {
|
||||
}
|
||||
body, _ := json.Marshal(input)
|
||||
|
||||
mockSvc.On("RegisterTenant", mock.Anything, "Test Tenant", "test-tenant", domain.TenantTypeCompany, "", []string{"test.com"}, (*string)(nil), "").
|
||||
mockSvc.On("RegisterTenant", mock.Anything, "Test Tenant", "test-tenant", domain.TenantTypeCompany, "", []string(nil), (*string)(nil), "").
|
||||
Return(&domain.Tenant{ID: "t1", Name: "Test Tenant", Slug: "test-tenant"}, nil)
|
||||
|
||||
req := httptest.NewRequest("POST", "/tenants", bytes.NewReader(body))
|
||||
@@ -278,15 +278,55 @@ func TestTenantHandler_ExportTenantsCSV(t *testing.T) {
|
||||
|
||||
mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(1), nil)
|
||||
|
||||
req := httptest.NewRequest("GET", "/tenants/export", nil)
|
||||
req := httptest.NewRequest("GET", "/tenants/export?includeIds=true", nil)
|
||||
resp, _ := app.Test(req)
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
assert.Contains(t, resp.Header.Get("Content-Disposition"), "tenants.csv")
|
||||
assert.Equal(t, "text/csv", strings.Split(resp.Header.Get("Content-Type"), ";")[0])
|
||||
assert.Contains(t, string(body), "tenant_id,name,type,parent_tenant_id,slug,memo,email_domain")
|
||||
assert.Contains(t, string(body), "t1,Tenant A,COMPANY,parent-1,tenant-a,Primary tenant,tenant-a.example.com;login.tenant-a.example.com")
|
||||
assert.Contains(t, string(body), "tenant_id,name,type,parent_tenant_id,parent_tenant_slug,slug,memo,email_domain")
|
||||
assert.Contains(t, string(body), "t1,Tenant A,COMPANY,parent-1,,tenant-a,Primary tenant,tenant-a.example.com;login.tenant-a.example.com")
|
||||
}
|
||||
|
||||
func TestTenantHandler_ExportTenantsCSV_OmitsIDsAndUsesParentSlug(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockSvc := new(MockTenantService)
|
||||
h := &TenantHandler{Service: mockSvc}
|
||||
|
||||
app.Get("/tenants/export", h.ExportTenantsCSV)
|
||||
|
||||
parentID := "parent-1"
|
||||
tenants := []domain.Tenant{
|
||||
{
|
||||
ID: parentID,
|
||||
Name: "Parent Tenant",
|
||||
Type: domain.TenantTypeCompanyGroup,
|
||||
Slug: "parent-tenant",
|
||||
},
|
||||
{
|
||||
ID: "child-1",
|
||||
Name: "Child Tenant",
|
||||
Type: domain.TenantTypeUserGroup,
|
||||
ParentID: &parentID,
|
||||
Slug: "child-tenant",
|
||||
},
|
||||
}
|
||||
|
||||
mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(2), nil)
|
||||
|
||||
req := httptest.NewRequest("GET", "/tenants/export?includeIds=false", nil)
|
||||
resp, _ := app.Test(req)
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
text := string(body)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
assert.Contains(t, text, "name,type,parent_tenant_slug,slug,memo,email_domain")
|
||||
assert.Contains(t, text, "Child Tenant,USER_GROUP,parent-tenant,child-tenant,,")
|
||||
assert.NotContains(t, text, "tenant_id")
|
||||
assert.NotContains(t, text, "parent_tenant_id")
|
||||
assert.NotContains(t, text, "child-1")
|
||||
mockSvc.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestTenantHandler_ImportTenantsCSVCreatesTenant(t *testing.T) {
|
||||
@@ -304,6 +344,7 @@ func TestTenantHandler_ImportTenantsCSVCreatesTenant(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, writer.Close())
|
||||
|
||||
mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return([]domain.Tenant{}, int64(0), nil).Once()
|
||||
mockSvc.On(
|
||||
"RegisterTenant",
|
||||
mock.Anything,
|
||||
@@ -331,6 +372,127 @@ func TestTenantHandler_ImportTenantsCSVCreatesTenant(t *testing.T) {
|
||||
mockSvc.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestTenantHandler_ImportTenantsCSVResolvesParentSlugToID(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockSvc := new(MockTenantService)
|
||||
h := &TenantHandler{Service: mockSvc}
|
||||
|
||||
app.Post("/tenants/import", h.ImportTenantsCSV)
|
||||
|
||||
var body bytes.Buffer
|
||||
writer := multipart.NewWriter(&body)
|
||||
part, err := writer.CreateFormFile("file", "tenants.csv")
|
||||
assert.NoError(t, err)
|
||||
_, err = part.Write([]byte("name,type,parent_tenant_slug,slug,memo,email_domain\nParent Tenant,COMPANY,,parent-slug,,\nChild Tenant,USER_GROUP,parent-slug,child-slug,,\n"))
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, writer.Close())
|
||||
|
||||
parentID := "parent-id"
|
||||
mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return([]domain.Tenant{}, int64(0), nil).Once()
|
||||
mockSvc.On(
|
||||
"RegisterTenant",
|
||||
mock.Anything,
|
||||
"Parent Tenant",
|
||||
"parent-slug",
|
||||
domain.TenantTypeCompany,
|
||||
"",
|
||||
[]string{},
|
||||
(*string)(nil),
|
||||
"",
|
||||
).Return(&domain.Tenant{ID: parentID, Name: "Parent Tenant", Slug: "parent-slug"}, nil).Once()
|
||||
mockSvc.On(
|
||||
"RegisterTenant",
|
||||
mock.Anything,
|
||||
"Child Tenant",
|
||||
"child-slug",
|
||||
domain.TenantTypeUserGroup,
|
||||
"",
|
||||
[]string{},
|
||||
mock.MatchedBy(func(got *string) bool {
|
||||
return got != nil && *got == parentID
|
||||
}),
|
||||
"",
|
||||
).Return(&domain.Tenant{ID: "child-id", Name: "Child Tenant", Slug: "child-slug"}, nil).Once()
|
||||
|
||||
req := httptest.NewRequest("POST", "/tenants/import", &body)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
resp, _ := app.Test(req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
var got map[string]interface{}
|
||||
json.NewDecoder(resp.Body).Decode(&got)
|
||||
assert.Equal(t, float64(2), got["created"])
|
||||
assert.Equal(t, float64(0), got["failed"])
|
||||
mockSvc.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestTenantCSVAllowedDomainsRoundTrip(t *testing.T) {
|
||||
records, err := parseTenantCSVRecords(strings.NewReader(
|
||||
"name,type,parent_tenant_slug,slug,memo,email_domain\n" +
|
||||
"Hanmac,COMPANY,,hanmac,,\"samaneng.com, hanmaceng.co.kr;login.hmac.kr\"\n",
|
||||
))
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, records, 1)
|
||||
assert.Equal(t, []string{"samaneng.com", "hanmaceng.co.kr", "login.hmac.kr"}, records[0].Domains)
|
||||
}
|
||||
|
||||
func TestNormalizeTenantDomainInputsSplitsCommaAndWhitespace(t *testing.T) {
|
||||
got := normalizeTenantDomainInputs([]string{
|
||||
"samaneng.com, hanmaceng.co.kr",
|
||||
" LOGIN.HMAC.KR\nportal.hmac.kr ",
|
||||
"samaneng.com",
|
||||
})
|
||||
|
||||
assert.Equal(t, []string{
|
||||
"samaneng.com",
|
||||
"hanmaceng.co.kr",
|
||||
"login.hmac.kr",
|
||||
"portal.hmac.kr",
|
||||
}, got)
|
||||
}
|
||||
|
||||
func TestNormalizeTenantConfigForcesIndexedForLoginIDFields(t *testing.T) {
|
||||
config, err := normalizeTenantConfig(map[string]any{
|
||||
"userSchema": []any{
|
||||
map[string]any{
|
||||
"key": "emp_no",
|
||||
"label": "사번",
|
||||
"type": "text",
|
||||
"indexed": false,
|
||||
"isLoginId": true,
|
||||
"maxLength": 20,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
fields, ok := config["userSchema"].([]any)
|
||||
assert.True(t, ok)
|
||||
assert.Len(t, fields, 1)
|
||||
|
||||
field, ok := fields[0].(map[string]any)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, true, field["indexed"])
|
||||
assert.Equal(t, true, field["isLoginId"])
|
||||
assert.NotContains(t, field, "maxLength")
|
||||
}
|
||||
|
||||
func TestNormalizeTenantConfigRejectsNonTextLoginIDFields(t *testing.T) {
|
||||
_, err := normalizeTenantConfig(map[string]any{
|
||||
"userSchema": []any{
|
||||
map[string]any{
|
||||
"key": "emp_no",
|
||||
"type": "number",
|
||||
"isLoginId": true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "login ID fields must be text")
|
||||
}
|
||||
|
||||
func TestTenantHandler_ApproveTenant(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockSvc := new(MockTenantService)
|
||||
|
||||
Reference in New Issue
Block a user