import { beforeEach, describe, expect, it, vi } from 'vitest' import { http } from '@/shared/api/http' import { approveEnterpriseReview, assignPlatformInternalUserRoles, changePlatformManagedUserStatus, changePlatformInternalUserStatus, changePlatformTenantStatus, changePlatformPermissionStatus, changePlatformRoleStatus, createPlatformPermission, createPlatformManagedUser, createPlatformRole, createPlatformTenant, deletePlatformPermission, deletePlatformRole, downloadTenantQualification, listPlatformTenants, listPlatformTenantOrderCounts, listTenantQualifications, listPlatformPermissions, listPlatformRoles, listPlatformInternalUsers, listPlatformManagedUsers, getPlatformUserOptions, qualificationFileName, replacePlatformRolePermissions, resetPlatformInternalUserPassword, resetPlatformManagedUserPassword, submitPlatformTenantReview, updatePlatformInternalUser, updatePlatformManagedUser, updatePlatformPermission, updatePlatformRole, updatePlatformTenant, uploadPlatformTenantQualification, type PlatformPermissionResource, type PlatformInternalUser, type PlatformManagedUser, type PlatformRoleResource, } from './platformIam' vi.mock('@/shared/api/http', () => ({ http: { get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn() }, unwrapApiResponse: (response: { data: T }) => response.data, })) const role: PlatformRoleResource = { id: 4000002, tenantId: 0, appCode: 'PLATFORM', roleCode: 'CUSTOMER_REVIEWER', roleName: '企业审核员', roleType: 'SYSTEM', status: 'ACTIVE', description: '企业资料与入驻审核', permissionIds: ['5100010'], } const permission: PlatformPermissionResource = { id: 5100010, tenantId: 0, appCode: 'PLATFORM', permissionType: 'MENU', parentId: 5100000, permissionCode: 'platform:customer:list', permissionName: '企业客户', routePath: '/customers', httpMethod: null, sortNo: 10, status: 'ACTIVE', builtIn: false, } const internalUser: PlatformInternalUser = { membershipId: 3000001, userId: 2000001, authUserId: 2000001, username: 'platform_admin', displayName: '平台管理员', phone: '13800138000', email: 'admin@example.com', memberType: 'INTERNAL', memberNo: 'PLATFORM-0001', status: 'ACTIVE', roleIds: [4000001], } const managedUser: PlatformManagedUser = { membershipId: '3000002', userId: '2000002', authUserId: '1000002', username: 'zhangwei', displayName: '张伟', phone: '13800138000', email: 'zhangwei@example.com', tenantId: '10001', tenantName: '华远贸易', memberType: 'EMPLOYEE', memberNo: 'MEMBER-3000002', status: 'ACTIVE', roleIds: ['40001'], roleNames: ['租户管理员'], lastLoginAt: '2026-06-01T10:30:00', protectedAccount: false, } describe('platform IAM roles', () => { beforeEach(() => { vi.mocked(http.get).mockReset() vi.mocked(http.post).mockReset() vi.mocked(http.put).mockReset() vi.mocked(http.delete).mockReset() }) it('loads roles from the platform IAM prefix', async () => { vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: [role] }) await expect(listPlatformRoles()).resolves.toEqual([role]) expect(http.get).toHaveBeenCalledWith('/platform/v1/iam/roles') }) it('creates a platform role with the documented payload', async () => { const payload = { roleCode: 'CUSTOMER_REVIEWER', roleName: '企业审核员', description: '企业资料与入驻审核', } vi.mocked(http.post).mockResolvedValue({ code: '200', message: 'success', data: role }) await expect(createPlatformRole(payload)).resolves.toEqual(role) expect(http.post).toHaveBeenCalledWith('/platform/v1/iam/roles', payload) }) it('updates status, permissions, and deletes roles with the implemented IAM routes', async () => { vi.mocked(http.put).mockResolvedValue({ code: '200', message: 'success', data: role }) vi.mocked(http.delete).mockResolvedValue({ code: '200', message: 'success', data: null }) await expect(updatePlatformRole(role.id, { roleName: '审核主管', description: '负责企业审核' })).resolves.toEqual(role) await expect(changePlatformRoleStatus(role.id, 'DISABLED')).resolves.toEqual(role) await expect(replacePlatformRolePermissions(role.id, ['5100010'])).resolves.toEqual(role) await expect(deletePlatformRole(role.id)).resolves.toBeNull() expect(http.put).toHaveBeenNthCalledWith(1, `/platform/v1/iam/roles/${role.id}`, { roleName: '审核主管', description: '负责企业审核' }) expect(http.put).toHaveBeenNthCalledWith(2, `/platform/v1/iam/roles/${role.id}/status`, { status: 'DISABLED' }) expect(http.put).toHaveBeenNthCalledWith(3, '/platform/v1/iam/roles/permissions', { roleId: role.id, permissionIds: ['5100010'] }) expect(http.delete).toHaveBeenCalledWith(`/platform/v1/iam/roles/${role.id}`) }) it('loads and creates permission resources under the platform IAM prefix', async () => { vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: [permission] }) vi.mocked(http.post).mockResolvedValue({ code: '200', message: 'success', data: permission }) const payload = { type: 'MENU' as const, code: permission.permissionCode, name: permission.permissionName, parentId: permission.parentId, routePath: permission.routePath, httpMethod: null, sortNo: permission.sortNo, } await expect(listPlatformPermissions()).resolves.toEqual([permission]) await expect(createPlatformPermission(payload)).resolves.toEqual(permission) expect(http.get).toHaveBeenCalledWith('/platform/v1/iam/permissions') expect(http.post).toHaveBeenCalledWith('/platform/v1/iam/permissions', payload) }) it('updates, changes status, and deletes custom permission resources with IAM routes', async () => { vi.mocked(http.put).mockResolvedValue({ code: '200', message: 'success', data: permission }) vi.mocked(http.delete).mockResolvedValue({ code: '200', message: 'success', data: null }) const payload = { name: '企业客户', parentId: permission.parentId, routePath: '/customers', httpMethod: null, sortNo: 10 } await expect(updatePlatformPermission(permission.id, payload)).resolves.toEqual(permission) await expect(changePlatformPermissionStatus(permission.id, 'DISABLED')).resolves.toEqual(permission) await expect(deletePlatformPermission(permission.id)).resolves.toBeNull() expect(http.put).toHaveBeenNthCalledWith(1, `/platform/v1/iam/permissions/${permission.id}`, payload) expect(http.put).toHaveBeenNthCalledWith(2, `/platform/v1/iam/permissions/${permission.id}/status`, { status: 'DISABLED' }) expect(http.delete).toHaveBeenCalledWith(`/platform/v1/iam/permissions/${permission.id}`) }) it('maintains internal users through their public membership ID', async () => { vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: [internalUser] }) vi.mocked(http.put).mockResolvedValue({ code: '200', message: 'success', data: internalUser }) const update = { displayName: '平台管理员', phone: '13800138000', email: 'admin@example.com', memberNo: 'PLATFORM-0001', roleIds: [4000001], } await expect(listPlatformInternalUsers()).resolves.toEqual([internalUser]) await expect(updatePlatformInternalUser(internalUser.membershipId, update)).resolves.toEqual(internalUser) await expect(changePlatformInternalUserStatus(internalUser.membershipId, 'DISABLED')).resolves.toEqual(internalUser) await expect(resetPlatformInternalUserPassword(internalUser.membershipId, 'ChangeMe123')).resolves.toEqual(internalUser) await expect(assignPlatformInternalUserRoles(internalUser.membershipId, [4000001])).resolves.toEqual(internalUser) expect(http.get).toHaveBeenCalledWith('/platform/v1/iam/internal-users') expect(http.put).toHaveBeenNthCalledWith(1, `/platform/v1/iam/internal-users/${internalUser.membershipId}`, update) expect(http.put).toHaveBeenNthCalledWith(2, `/platform/v1/iam/internal-users/${internalUser.membershipId}/status`, { status: 'DISABLED' }) expect(http.put).toHaveBeenNthCalledWith(3, `/platform/v1/iam/internal-users/${internalUser.membershipId}/password`, { newPassword: 'ChangeMe123' }) expect(http.put).toHaveBeenNthCalledWith(4, `/platform/v1/iam/internal-users/${internalUser.membershipId}/roles`, { roleIds: [4000001] }) }) it('maintains platform and tenant users through the unified user contract', async () => { const userOptions = { tenants: [{ id: '10001', name: '华远贸易', status: 'ACTIVE' }], roles: [{ id: '40001', tenantId: '10001', name: '租户管理员', status: 'ACTIVE' }], } const create = { username: 'zhangwei', displayName: '张伟', phone: '13800138000', email: 'zhangwei@example.com', tenantId: '10001', roleIds: ['40001'], password: 'Password1', status: 'ACTIVE' as const, } const update = { displayName: '张伟', phone: '13800138000', email: 'zhangwei@example.com', tenantId: '10001', roleIds: ['40001'], } vi.mocked(http.get).mockResolvedValueOnce({ code: '200', message: 'success', data: [managedUser] }) .mockResolvedValueOnce({ code: '200', message: 'success', data: userOptions }) vi.mocked(http.post).mockResolvedValue({ code: '200', message: 'success', data: managedUser }) vi.mocked(http.put).mockResolvedValue({ code: '200', message: 'success', data: managedUser }) await expect(listPlatformManagedUsers()).resolves.toEqual([managedUser]) await expect(getPlatformUserOptions()).resolves.toEqual(userOptions) await expect(createPlatformManagedUser(create)).resolves.toEqual(managedUser) await expect(updatePlatformManagedUser(managedUser.membershipId, update)).resolves.toEqual(managedUser) await expect(changePlatformManagedUserStatus(managedUser.membershipId, 'DISABLED')).resolves.toEqual(managedUser) await expect(resetPlatformManagedUserPassword(managedUser.membershipId, 'ChangeMe123')).resolves.toEqual(managedUser) expect(http.get).toHaveBeenNthCalledWith(1, '/platform/v1/iam/users') expect(http.get).toHaveBeenNthCalledWith(2, '/platform/v1/iam/users/options') expect(http.post).toHaveBeenCalledWith('/platform/v1/iam/users', create) expect(http.put).toHaveBeenNthCalledWith(1, `/platform/v1/iam/users/${managedUser.membershipId}`, update) expect(http.put).toHaveBeenNthCalledWith(2, `/platform/v1/iam/users/${managedUser.membershipId}/status`, { status: 'DISABLED' }) expect(http.put).toHaveBeenNthCalledWith(3, `/platform/v1/iam/users/${managedUser.membershipId}/password`, { newPassword: 'ChangeMe123' }) }) it('loads qualifications for the selected tenant', async () => { const qualification = { id: 12, tenantId: 8, qualificationType: 'BUSINESS_LICENSE', certificateNo: '91440300XXXXXXXXXX', documentUrl: '/api/merchant/v1/files/qualifications/license.png', status: 'PENDING' as const, reviewComment: null, } vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: [qualification] }) await expect(listTenantQualifications(8)).resolves.toEqual([qualification]) expect(http.get).toHaveBeenCalledWith('/platform/v1/iam/tenants/8/qualifications') }) it('loads paginated tenants with the documented filters', async () => { const page = { records: [], page: 2, size: 10, total: 11, pages: 2 } vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: { ...page, page: '2', size: '10', total: '11', pages: '2' } }) await expect(listPlatformTenants({ page: 2, size: 10, keyword: '柏悦', status: 'ACTIVE' })).resolves.toEqual(page) expect(http.get).toHaveBeenCalledWith('/platform/v1/iam/tenants', { params: { page: 2, size: 10, keyword: '柏悦', status: 'ACTIVE' }, }) }) it('creates, updates, and changes customer status with the platform tenant contract', async () => { const tenant = { id: '8', tenantCode: 'TEN8', tenantName: '柏悦物流', status: 'ACTIVE' } const create = { tenantName: '柏悦物流', legalName: '柏悦物流有限公司', legalRepresentative: '张三', unifiedSocialCreditCode: '91440300MA5ABCDE12', contactName: '张三', contactPhone: '13800138000', contactEmail: 'ops@example.com', servicePackage: '标准版', contactWechat: 'baiyue-ops', salesOwnerName: '李四', paymentTermDays: 30, enterpriseType: '有限责任公司', establishedDate: '2020-01-01', registeredCapital: 1000, registeredCapitalCurrency: 'CNY', registeredAddress: '深圳市宝安区', internalRemark: '重点客户', } vi.mocked(http.post).mockResolvedValue({ code: '200', message: 'success', data: tenant }) vi.mocked(http.put).mockResolvedValue({ code: '200', message: 'success', data: tenant }) await expect(createPlatformTenant(create)).resolves.toEqual(tenant) const { unifiedSocialCreditCode: _creditCode, ...update } = create await expect(updatePlatformTenant('8', update)).resolves.toEqual(tenant) await expect(changePlatformTenantStatus('8', 'DISABLED')).resolves.toEqual(tenant) expect(http.post).toHaveBeenCalledWith('/platform/v1/iam/tenants', create) expect(http.put).toHaveBeenNthCalledWith(1, '/platform/v1/iam/tenants/8', update) expect(http.put).toHaveBeenNthCalledWith(2, '/platform/v1/iam/tenants/8/status', { status: 'DISABLED' }) }) it('loads real order totals for the visible customer tenants', async () => { const counts = [{ tenantId: '8', orderCount: 12 }, { tenantId: '9', orderCount: 0 }] vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: counts }) await expect(listPlatformTenantOrderCounts(['8', '9'])).resolves.toEqual(counts) expect(http.get).toHaveBeenCalledWith('/platform/v1/orders/first-leg/tenant-counts?tenantIds=8,9') }) it('uploads qualification metadata before explicitly submitting the customer review', async () => { const uploadResult = { qualificationId: '91', reviewId: null, tenantStatus: 'PROFILE_REQUIRED', documentUrl: '/file.pdf' } const tenant = { id: '8', tenantCode: 'TEN8', tenantName: '柏悦物流', status: 'PENDING' } vi.mocked(http.post).mockResolvedValueOnce({ code: '200', message: 'success', data: uploadResult }) .mockResolvedValueOnce({ code: '200', message: 'success', data: tenant }) const file = new File(['%PDF-1.4'], '营业执照.pdf', { type: 'application/pdf' }) await expect(uploadPlatformTenantQualification('8', 'BUSINESS_LICENSE', file, '2030-12-31', '91440300MA5ABCDE12', true)).resolves.toEqual(uploadResult) await expect(submitPlatformTenantReview('8')).resolves.toEqual(tenant) const form = vi.mocked(http.post).mock.calls[0]?.[1] as FormData expect(http.post).toHaveBeenNthCalledWith(1, '/platform/v1/files/qualifications/8', expect.any(FormData)) expect(form.get('file')).toBe(file) expect(form.get('qualificationType')).toBe('BUSINESS_LICENSE') expect(form.get('validTo')).toBe('2030-12-31') expect(form.get('certificateNo')).toBe('91440300MA5ABCDE12') expect(form.get('replace')).toBe('true') expect(http.post).toHaveBeenNthCalledWith(2, '/platform/v1/iam/tenants/8/submit-review', {}) }) it('preserves long review identifiers and returns one-time administrator credentials', async () => { const reviewId = '190000000000000001' const approval = { tenantId: '190000000000000099', administratorCreated: true, administratorUsername: 'merchant_owner', temporaryPassword: 'Temporary#2026', } vi.mocked(http.post).mockResolvedValue({ code: '200', message: 'success', data: approval }) await expect(approveEnterpriseReview(reviewId, '资料完整')).resolves.toEqual(approval) expect(http.post).toHaveBeenCalledWith(`/platform/v1/iam/reviews/${reviewId}/approve`, { comment: '资料完整' }) }) it('downloads a qualification with the platform file endpoint and a safe filename', async () => { const blob = new Blob(['image'], { type: 'image/png' }) vi.mocked(http.get).mockResolvedValue(blob) expect(qualificationFileName('/api/merchant/v1/files/qualifications/%E8%90%A5%E4%B8%9A%E6%89%A7%E7%85%A7.png?token=ignored')).toBe('营业执照.png') await expect(downloadTenantQualification(8, '/api/merchant/v1/files/qualifications/%E8%90%A5%E4%B8%9A%E6%89%A7%E7%85%A7.png')).resolves.toBe(blob) expect(http.get).toHaveBeenCalledWith( '/platform/v1/files/qualifications/8/%E8%90%A5%E4%B8%9A%E6%89%A7%E7%85%A7.png', { responseType: 'blob' }, ) expect(() => qualificationFileName('/api/merchant/v1/files/qualifications/%2Fetc%2Fpasswd')).toThrow('资质文件地址无效') }) })