forked from baron/baron-sso
Merge branch 'dev' into feature/rbac-simplification-and-remove-dev-switcher
This commit is contained in:
@@ -37,6 +37,7 @@ type InitialTenantConfig struct {
|
||||
ParentSlug string
|
||||
Description string
|
||||
Domains []string
|
||||
Config domain.JSONMap
|
||||
}
|
||||
|
||||
func SeedTenants(db *gorm.DB) error {
|
||||
@@ -149,6 +150,9 @@ func seedTenantConfigs(db *gorm.DB, configs []InitialTenantConfig) error {
|
||||
return err
|
||||
}
|
||||
tenant.Status = domain.TenantStatusActive
|
||||
if len(config.Config) > 0 {
|
||||
tenant.Config = config.Config
|
||||
}
|
||||
if err := db.Save(tenant).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -265,6 +269,11 @@ func parseSeedTenantCSV(r io.Reader) ([]InitialTenantConfig, error) {
|
||||
return nil, fmt.Errorf("row %d: slug is required", i+2)
|
||||
}
|
||||
|
||||
config, err := seedTenantCSVRecordConfig(row, header)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("row %d: %w", i+2, err)
|
||||
}
|
||||
|
||||
configs = append(configs, InitialTenantConfig{
|
||||
TenantID: seedTenantCSVValue(row, header, "tenant_id"),
|
||||
Name: name,
|
||||
@@ -273,6 +282,7 @@ func parseSeedTenantCSV(r io.Reader) ([]InitialTenantConfig, error) {
|
||||
Slug: slug,
|
||||
Description: seedTenantCSVValue(row, header, "memo"),
|
||||
Domains: splitSeedTenantCSVDomains(seedTenantCSVValue(row, header, "email_domain")),
|
||||
Config: config,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -298,6 +308,18 @@ func seedTenantCSVHeaderIndex(header []string) map[string]int {
|
||||
"email_domain": "email_domain",
|
||||
"domain": "email_domain",
|
||||
"domains": "email_domain",
|
||||
"visibility": "visibility",
|
||||
"public_setting": "visibility",
|
||||
"publicsetting": "visibility",
|
||||
"org_unit_type": "org_unit_type",
|
||||
"orgunittype": "org_unit_type",
|
||||
"organization_type": "org_unit_type",
|
||||
"organizationtype": "org_unit_type",
|
||||
"worksmobile": "worksmobile_sync",
|
||||
"worksmobilesync": "worksmobile_sync",
|
||||
"worksmobile_sync": "worksmobile_sync",
|
||||
"works_sync": "worksmobile_sync",
|
||||
"works": "worksmobile_sync",
|
||||
}
|
||||
for i, column := range header {
|
||||
key := strings.ToLower(strings.TrimSpace(column))
|
||||
@@ -317,6 +339,67 @@ func seedTenantCSVValue(row []string, header map[string]int, key string) string
|
||||
return strings.TrimSpace(row[idx])
|
||||
}
|
||||
|
||||
func seedTenantCSVRecordConfig(row []string, header map[string]int) (domain.JSONMap, error) {
|
||||
config := domain.JSONMap{}
|
||||
visibility := strings.TrimSpace(seedTenantCSVValue(row, header, "visibility"))
|
||||
if visibility != "" {
|
||||
normalizedVisibility, err := normalizeSeedTenantVisibility(visibility)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config["visibility"] = normalizedVisibility
|
||||
}
|
||||
orgUnitType := strings.TrimSpace(seedTenantCSVValue(row, header, "org_unit_type"))
|
||||
if orgUnitType != "" {
|
||||
if !isAllowedSeedTenantOrgUnitType(orgUnitType) {
|
||||
return nil, errors.New("orgUnitType must be one of 실, 팀, TF, TF팀, 센터, 디비전, 셀, 본부, 지역본부, 부, 임원직속")
|
||||
}
|
||||
config["orgUnitType"] = orgUnitType
|
||||
}
|
||||
if worksmobileSync := strings.TrimSpace(seedTenantCSVValue(row, header, "worksmobile_sync")); worksmobileSync != "" {
|
||||
excluded, err := normalizeSeedTenantWorksmobileExcluded(worksmobileSync)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config["worksmobileExcluded"] = excluded
|
||||
}
|
||||
if len(config) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func normalizeSeedTenantWorksmobileExcluded(value string) (bool, error) {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "", "yes", "y", "true", "1", "on", "sync", "linked", "연동":
|
||||
return false, nil
|
||||
case "no", "n", "false", "0", "off", "none", "excluded", "exclude", "not_sync", "not-synced", "미연동", "연동안함", "제외":
|
||||
return true, nil
|
||||
default:
|
||||
return false, errors.New("worksmobile_sync must be yes or no")
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeSeedTenantVisibility(value string) (string, error) {
|
||||
visibility := strings.ToLower(strings.TrimSpace(value))
|
||||
if visibility == "" || visibility == "public" {
|
||||
return "public", nil
|
||||
}
|
||||
if visibility != "internal" && visibility != "private" {
|
||||
return "", errors.New("visibility must be public, internal, or private")
|
||||
}
|
||||
return visibility, nil
|
||||
}
|
||||
|
||||
func isAllowedSeedTenantOrgUnitType(value string) bool {
|
||||
switch strings.TrimSpace(value) {
|
||||
case "실", "팀", "TF", "TF팀", "센터", "디비전", "셀", "본부", "지역본부", "부", "임원직속":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func seedTenantCSVRowIsEmpty(row []string) bool {
|
||||
for _, value := range row {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
@@ -405,6 +488,7 @@ func createSeedTenant(
|
||||
Description: config.Description,
|
||||
Status: domain.TenantStatusActive,
|
||||
ParentID: parentID,
|
||||
Config: config.Config,
|
||||
}
|
||||
|
||||
if err := repo.Create(ctx, tenant); err != nil {
|
||||
|
||||
@@ -61,6 +61,7 @@ func TestSeedTenantCSVDefinesWorksmobileDomainClassTenants(t *testing.T) {
|
||||
slug: "baron-group",
|
||||
tenantType: domain.TenantTypeCompanyGroup,
|
||||
parentSlug: "hanmac-family",
|
||||
domains: []string{"brsw.kr"},
|
||||
},
|
||||
{
|
||||
name: "(주)장헌",
|
||||
@@ -78,10 +79,10 @@ func TestSeedTenantCSVDefinesWorksmobileDomainClassTenants(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "한라산업개발",
|
||||
slug: "hanlla",
|
||||
slug: "halla",
|
||||
tenantType: domain.TenantTypeCompany,
|
||||
parentSlug: "baron-group",
|
||||
domains: []string{"hanllasanup.co.kr"},
|
||||
parentSlug: "hanmac-family",
|
||||
domains: []string{"hallasanup.com"},
|
||||
},
|
||||
{
|
||||
name: "(주)피티씨",
|
||||
@@ -97,30 +98,64 @@ func TestSeedTenantCSVDefinesWorksmobileDomainClassTenants(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
if len(configs) != len(expected) {
|
||||
t.Fatalf("expected %d seed tenants, got %d", len(expected), len(configs))
|
||||
if len(configs) < len(expected) {
|
||||
t.Fatalf("expected at least %d seed tenants, got %d", len(expected), len(configs))
|
||||
}
|
||||
|
||||
for i, want := range expected {
|
||||
got := configs[i]
|
||||
wantFamilyChildOrder := []string{
|
||||
"gpdtdc",
|
||||
"saman",
|
||||
"hanmac",
|
||||
"baron-group",
|
||||
"halla",
|
||||
}
|
||||
policyFamilyChildSlugs := map[string]bool{}
|
||||
for _, slug := range wantFamilyChildOrder {
|
||||
policyFamilyChildSlugs[slug] = true
|
||||
}
|
||||
gotFamilyChildOrder := make([]string, 0, len(wantFamilyChildOrder))
|
||||
for _, config := range configs {
|
||||
if config.ParentSlug == "hanmac-family" && policyFamilyChildSlugs[config.Slug] {
|
||||
gotFamilyChildOrder = append(gotFamilyChildOrder, config.Slug)
|
||||
}
|
||||
}
|
||||
if len(gotFamilyChildOrder) != len(wantFamilyChildOrder) {
|
||||
t.Fatalf("hanmac-family child order = %#v, want %#v", gotFamilyChildOrder, wantFamilyChildOrder)
|
||||
}
|
||||
for i, wantSlug := range wantFamilyChildOrder {
|
||||
if gotFamilyChildOrder[i] != wantSlug {
|
||||
t.Fatalf("hanmac-family child order[%d] = %q, want %q", i, gotFamilyChildOrder[i], wantSlug)
|
||||
}
|
||||
}
|
||||
|
||||
configBySlug := make(map[string]InitialTenantConfig, len(configs))
|
||||
for _, config := range configs {
|
||||
configBySlug[config.Slug] = config
|
||||
}
|
||||
|
||||
for _, want := range expected {
|
||||
got, ok := configBySlug[want.slug]
|
||||
if !ok {
|
||||
t.Fatalf("tenant slug %q not found in seed configs", want.slug)
|
||||
}
|
||||
if got.Name != want.name {
|
||||
t.Fatalf("tenant[%d] name = %q, want %q", i, got.Name, want.name)
|
||||
t.Fatalf("tenant[%s] name = %q, want %q", want.slug, got.Name, want.name)
|
||||
}
|
||||
if got.Slug != want.slug {
|
||||
t.Fatalf("tenant[%d] slug = %q, want %q", i, got.Slug, want.slug)
|
||||
t.Fatalf("tenant[%s] slug = %q, want %q", want.slug, got.Slug, want.slug)
|
||||
}
|
||||
if got.Type != want.tenantType {
|
||||
t.Fatalf("tenant[%d] type = %q, want %q", i, got.Type, want.tenantType)
|
||||
t.Fatalf("tenant[%s] type = %q, want %q", want.slug, got.Type, want.tenantType)
|
||||
}
|
||||
if got.ParentSlug != want.parentSlug {
|
||||
t.Fatalf("tenant[%d] parent slug = %q, want %q", i, got.ParentSlug, want.parentSlug)
|
||||
t.Fatalf("tenant[%s] parent slug = %q, want %q", want.slug, got.ParentSlug, want.parentSlug)
|
||||
}
|
||||
if len(got.Domains) != len(want.domains) {
|
||||
t.Fatalf("tenant[%d] domains = %#v, want %#v", i, got.Domains, want.domains)
|
||||
t.Fatalf("tenant[%s] domains = %#v, want %#v", want.slug, got.Domains, want.domains)
|
||||
}
|
||||
for j, wantDomain := range want.domains {
|
||||
if got.Domains[j] != wantDomain {
|
||||
t.Fatalf("tenant[%d] domain[%d] = %q, want %q", i, j, got.Domains[j], wantDomain)
|
||||
t.Fatalf("tenant[%s] domain[%d] = %q, want %q", want.slug, j, got.Domains[j], wantDomain)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,9 +170,9 @@ func TestNormalizeSeedTenantTypeAllowsOrganization(t *testing.T) {
|
||||
func TestLoadSeedTenantConfigsUsesConfiguredCSVPath(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "seed-tenant.csv")
|
||||
csv := "name,type,parent_tenant_slug,slug,memo,email_domain\n" +
|
||||
"Root,COMPANY_GROUP,,root,Root memo,\n" +
|
||||
"Child,COMPANY,root,child,Child memo,child.example.com\n"
|
||||
csv := "name,type,parent_tenant_slug,slug,memo,email_domain,visibility,org_unit_type,worksmobile_sync\n" +
|
||||
"Root,COMPANY_GROUP,,root,Root memo,,,,\n" +
|
||||
"Child,USER_GROUP,root,child,Child memo,child.example.com,private,팀,no\n"
|
||||
if err := os.WriteFile(path, []byte(csv), 0o600); err != nil {
|
||||
t.Fatalf("failed to write seed csv: %v", err)
|
||||
}
|
||||
@@ -156,6 +191,41 @@ func TestLoadSeedTenantConfigsUsesConfiguredCSVPath(t *testing.T) {
|
||||
if len(configs[1].Domains) != 1 || configs[1].Domains[0] != "child.example.com" {
|
||||
t.Fatalf("child domains = %#v, want child.example.com", configs[1].Domains)
|
||||
}
|
||||
if configs[1].Config["visibility"] != "private" {
|
||||
t.Fatalf("child visibility = %#v, want private", configs[1].Config["visibility"])
|
||||
}
|
||||
if configs[1].Config["orgUnitType"] != "팀" {
|
||||
t.Fatalf("child orgUnitType = %#v, want 팀", configs[1].Config["orgUnitType"])
|
||||
}
|
||||
if configs[1].Config["worksmobileExcluded"] != true {
|
||||
t.Fatalf("child worksmobileExcluded = %#v, want true", configs[1].Config["worksmobileExcluded"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedTenantCSVDefinesMHDAsPrivateUserGroup(t *testing.T) {
|
||||
configs, err := loadSeedTenantConfigs()
|
||||
if err != nil {
|
||||
t.Fatalf("loadSeedTenantConfigs returned error: %v", err)
|
||||
}
|
||||
|
||||
configBySlug := make(map[string]InitialTenantConfig, len(configs))
|
||||
for _, config := range configs {
|
||||
configBySlug[config.Slug] = config
|
||||
}
|
||||
|
||||
mhd, ok := configBySlug["mhd"]
|
||||
if !ok {
|
||||
t.Fatal("mhd seed tenant not found")
|
||||
}
|
||||
if mhd.Type != domain.TenantTypeUserGroup {
|
||||
t.Fatalf("mhd type = %q, want %q", mhd.Type, domain.TenantTypeUserGroup)
|
||||
}
|
||||
if mhd.Config["visibility"] != "private" {
|
||||
t.Fatalf("mhd visibility = %#v, want private", mhd.Config["visibility"])
|
||||
}
|
||||
if mhd.Config["worksmobileExcluded"] != true {
|
||||
t.Fatalf("mhd worksmobileExcluded = %#v, want true", mhd.Config["worksmobileExcluded"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsSeedTenantSlugUsesConfiguredCSVPath(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user