package service import ( "fmt" "strings" "testing" "affiliate_dash/internal/model" ) func TestCreateAPIClientEnforcesPerMerchantLimit(t *testing.T) { db := newServiceTestDB(t) codec, err := NewSecretCodec("test-master-key") if err != nil { t.Fatalf("codec: %v", err) } svc := NewMerchantService(db, codec, NewTenantService(db)) merchant := model.Merchant{Code: "merchant-api-limit", Name: "限数商户", Status: model.MerchantStatusActive} if err := db.Create(&merchant).Error; err != nil { t.Fatalf("create merchant: %v", err) } for i := 0; i < MaxAPIClientsPerMerchant; i++ { if _, err := svc.CreateAPIClient(merchant.ID, CreateAPIClientInput{ Name: fmt.Sprintf("key-%d", i), Scopes: "products:read", }, 1); err != nil { t.Fatalf("create client %d: %v", i, err) } } _, err = svc.CreateAPIClient(merchant.ID, CreateAPIClientInput{ Name: "overflow", Scopes: "products:read", }, 1) if err == nil || !strings.Contains(err.Error(), fmt.Sprintf("最多可创建 %d 个", MaxAPIClientsPerMerchant)) { t.Fatalf("expected per-merchant limit error, got %v", err) } } func TestMerchantCustomRoleIsScopedAndCanCreateEmployee(t *testing.T) { db := newServiceTestDB(t) merchantA := model.Merchant{Code: "team-merchant-a", Name: "团队商户 A", Status: model.MerchantStatusActive} merchantB := model.Merchant{Code: "team-merchant-b", Name: "团队商户 B", Status: model.MerchantStatusActive} actor := model.User{Username: "team-owner", PasswordHash: "hash", Role: model.RoleMerchant, Status: 1} if err := db.Create(&merchantA).Error; err != nil { t.Fatalf("create merchant A: %v", err) } if err := db.Create(&merchantB).Error; err != nil { t.Fatalf("create merchant B: %v", err) } if err := db.Create(&actor).Error; err != nil { t.Fatalf("create actor: %v", err) } svc := NewMerchantService(db, nil, NewTenantService(db)) role, err := svc.CreateRole(merchantA.ID, MerchantRoleInput{ Code: "after-sales", Name: "客服", Permissions: []string{model.PermissionOrdersManage}, }, actor.ID) if err != nil { t.Fatalf("create custom role: %v", err) } member, err := svc.AddMember(merchantA.ID, AddMemberInput{ Username: "merchant-a-support", Password: "password123", Nickname: "客服小李", Role: role.Code, }, actor.ID) if err != nil { t.Fatalf("create employee: %v", err) } if member.User == nil || member.User.Username != "merchant-a-support" || member.Role != "after-sales" { t.Fatalf("unexpected member: %+v", member) } if !member.IsDefault { t.Fatal("new employee should default to the merchant that created it") } if _, err := svc.AddMember(merchantB.ID, AddMemberInput{ Username: "merchant-b-support", Password: "password123", Role: role.Code, }, actor.ID); err == nil { t.Fatal("expected merchant A role to be rejected for merchant B") } } func TestDefaultMerchantRolesCanBeEdited(t *testing.T) { db := newServiceTestDB(t) merchant := model.Merchant{Code: "default-roles", Name: "默认角色商户", Status: model.MerchantStatusActive} actor := model.User{Username: "default-role-owner", PasswordHash: "hash", Role: model.RoleMerchant, Status: 1} if err := db.Create(&merchant).Error; err != nil { t.Fatalf("create merchant: %v", err) } if err := db.Create(&actor).Error; err != nil { t.Fatalf("create actor: %v", err) } if err := createDefaultMerchantRoles(db, merchant.ID); err != nil { t.Fatalf("create default roles: %v", err) } svc := NewMerchantService(db, nil, NewTenantService(db)) roles, err := svc.ListRoles(merchant.ID) if err != nil { t.Fatalf("list roles: %v", err) } var finance model.MerchantRole var hasSupport bool for _, role := range roles { if role.Code == model.MemberRoleFinance { finance = role } if role.Code == "support" && role.Name == "客服" { hasSupport = true } } if finance.ID == 0 || !hasSupport { t.Fatalf("expected editable finance and support roles, got %+v", roles) } updated, err := svc.UpdateRole(merchant.ID, finance.ID, MerchantRoleInput{ Name: "财务主管", Permissions: []string{model.PermissionWalletView}, }, actor.ID) if err != nil { t.Fatalf("update finance role: %v", err) } if updated.Name != "财务主管" || updated.Permissions != model.PermissionWalletView { t.Fatalf("unexpected updated role: %+v", updated) } }