test(admin): cover optional legal-person qualification
This commit is contained in:
@@ -117,6 +117,9 @@ describe('CustomerDetailView', () => {
|
||||
expect(document.querySelector('.overview-strip')).toBeNull()
|
||||
expect(document.body.textContent).toContain('merchant_admin')
|
||||
expect(document.body.textContent).toContain('企业管理员')
|
||||
expect(document.body.textContent).toContain('公司联系电话')
|
||||
expect(document.body.textContent).toContain('登录手机号')
|
||||
expect(document.body.textContent).toContain('修改会影响登录')
|
||||
expect(listPlatformTenantMembers).toHaveBeenCalledWith(tenant.id)
|
||||
expect(document.body.textContent).toContain('已替换')
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ElementPlus from 'element-plus'
|
||||
import ElementPlus, { ElMessageBox } from 'element-plus'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
@@ -7,6 +7,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
approveEnterpriseReview,
|
||||
archivePlatformTenant,
|
||||
createPlatformTenant,
|
||||
downloadTenantQualification,
|
||||
createPlatformTenantImpersonation,
|
||||
getPlatformTenant,
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
listTenantQualifications,
|
||||
rejectEnterpriseReview,
|
||||
resetPlatformTenantOwnerPassword,
|
||||
submitPlatformTenantReview,
|
||||
updatePlatformTenant,
|
||||
uploadPlatformTenantQualification,
|
||||
uploadPlatformTenantRequiredQualificationPackage,
|
||||
@@ -104,6 +106,12 @@ function tableButtonByText(text: string) {
|
||||
.find((button) => button.textContent?.trim() === text)
|
||||
}
|
||||
|
||||
function selectQualificationFile(index: number, file: File) {
|
||||
const input = document.querySelectorAll<HTMLInputElement>('.qualification-file input[type="file"]')[index]
|
||||
Object.defineProperty(input, 'files', { configurable: true, value: [file] })
|
||||
input.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
}
|
||||
|
||||
async function clickMoreAction(text: string) {
|
||||
document.querySelector<HTMLButtonElement>('.el-table__body button[aria-label^="更多客户操作:"]')?.click()
|
||||
await flushPromises()
|
||||
@@ -139,6 +147,10 @@ describe('CustomersView', () => {
|
||||
vi.mocked(getPlatformTenant).mockResolvedValue(fullTenant)
|
||||
vi.mocked(listTenantQualifications).mockResolvedValue([])
|
||||
vi.mocked(listEnterpriseReviews).mockResolvedValue([])
|
||||
vi.mocked(createPlatformTenant).mockResolvedValue({
|
||||
tenant: fullTenant, administratorUsername: 'merchant_owner', temporaryPassword: 'Temporary#2026',
|
||||
})
|
||||
vi.mocked(submitPlatformTenantReview).mockResolvedValue({ ...fullTenant, status: 'PENDING' })
|
||||
vi.mocked(updatePlatformTenant).mockResolvedValue(fullTenant)
|
||||
vi.mocked(uploadPlatformTenantQualification).mockResolvedValue({
|
||||
qualificationId: 'qualification-1', reviewId: null, tenantStatus: 'PROFILE_REQUIRED', documentUrl: '/files/license.png',
|
||||
@@ -187,7 +199,7 @@ describe('CustomersView', () => {
|
||||
await flushPromises()
|
||||
|
||||
expect(getPlatformTenant).toHaveBeenCalledWith(fullTenant.id)
|
||||
const phone = document.querySelector<HTMLInputElement>('input[placeholder="请输入手机号或固定电话"]')
|
||||
const phone = document.querySelector<HTMLInputElement>('input[placeholder="请输入公司联系电话"]')
|
||||
const email = document.querySelector<HTMLInputElement>('input[placeholder="如:contact@example.com"]')
|
||||
expect(phone?.value).toBe(fullTenant.contactPhone)
|
||||
expect(email?.value).toBe(fullTenant.contactEmail)
|
||||
@@ -212,19 +224,83 @@ describe('CustomersView', () => {
|
||||
expect(preview?.src).toBe('blob:qualification-preview')
|
||||
})
|
||||
|
||||
it('marks only the business license and legal-person ID as required qualifications', async () => {
|
||||
it('marks only the business license as a required file when creating a customer', async () => {
|
||||
const auth = useAuthStore()
|
||||
auth.permissions = [...auth.permissions, 'platform:customer:create']
|
||||
useAccessPreviewStore().initializeAfterLogin()
|
||||
mount(CustomersView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
|
||||
await flushPromises()
|
||||
tableButtonByText('编辑')?.click()
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.find((button) => button.textContent?.trim() === '新增客户')?.click()
|
||||
await flushPromises()
|
||||
|
||||
const labels = [...document.querySelectorAll<HTMLElement>('.qualification-label')]
|
||||
expect(labels.map((label) => label.textContent?.replace(/\s/g, ''))).toEqual([
|
||||
'*营业执照',
|
||||
'*法人身份证',
|
||||
'法人身份证选填',
|
||||
'货代资质证书选填',
|
||||
'银行开户许可证选填',
|
||||
])
|
||||
expect(document.body.textContent).toContain('营业执照和18位统一社会信用代码为必填业务项')
|
||||
})
|
||||
|
||||
it('creates a customer with a business license and no legal-person ID', async () => {
|
||||
const auth = useAuthStore()
|
||||
auth.permissions = [...auth.permissions, 'platform:customer:create']
|
||||
useAccessPreviewStore().initializeAfterLogin()
|
||||
const wrapper = mount(CustomersView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
|
||||
await flushPromises()
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.find((button) => button.textContent?.trim() === '新增客户')?.click()
|
||||
await flushPromises()
|
||||
|
||||
await wrapper.get('input[placeholder="请输入工商登记公司名称"]').setValue(fullTenant.tenantName)
|
||||
await wrapper.get('input[placeholder="请输入营业执照上的企业名称"]').setValue(fullTenant.legalName)
|
||||
await wrapper.get('input[placeholder="请输入法定代表人姓名"]').setValue(fullTenant.legalRepresentative)
|
||||
await wrapper.get('input[placeholder="请输入主要联系人姓名"]').setValue(fullTenant.contactName)
|
||||
await wrapper.get('input[placeholder="请输入公司联系电话"]').setValue(fullTenant.contactPhone)
|
||||
await wrapper.get('input[placeholder="请输入18位大写字母或数字"]').setValue(fullTenant.unifiedSocialCreditCode)
|
||||
const licenseFile = new File(['license'], 'license.png', { type: 'image/png' })
|
||||
selectQualificationFile(0, licenseFile)
|
||||
await flushPromises()
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.find((button) => button.textContent?.includes('保存资料和资质'))?.click()
|
||||
await flushPromises()
|
||||
|
||||
expect(createPlatformTenant).toHaveBeenCalledWith(expect.objectContaining({
|
||||
tenantName: fullTenant.tenantName,
|
||||
contactPhone: fullTenant.contactPhone,
|
||||
}))
|
||||
expect(createPlatformTenant).toHaveBeenCalledWith(expect.not.objectContaining({ unifiedSocialCreditCode: expect.anything() }))
|
||||
expect(uploadPlatformTenantRequiredQualificationPackage).toHaveBeenCalledWith(
|
||||
fullTenant.id, licenseFile, null, fullTenant.unifiedSocialCreditCode, false, false,
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps the company contact phone editable without changing a member login phone', async () => {
|
||||
const activeTenant = { ...fullTenant, status: 'ACTIVE' as const }
|
||||
vi.mocked(listPlatformTenants).mockResolvedValue({ records: [activeTenant], page: 1, size: 10, total: 1, pages: 1 })
|
||||
vi.mocked(getPlatformTenant).mockResolvedValue(activeTenant)
|
||||
vi.mocked(updatePlatformTenant).mockResolvedValue(activeTenant)
|
||||
vi.mocked(listTenantQualifications).mockResolvedValue([{
|
||||
id: 'qualification-license', tenantId: fullTenant.id, qualificationType: 'BUSINESS_LICENSE',
|
||||
certificateNo: fullTenant.unifiedSocialCreditCode, documentUrl: '/files/license.png', status: 'APPROVED', reviewComment: null, validTo: null,
|
||||
}])
|
||||
const wrapper = mount(CustomersView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
|
||||
await flushPromises()
|
||||
tableButtonByText('编辑')?.click()
|
||||
await flushPromises()
|
||||
|
||||
const phone = wrapper.get('input[placeholder="请输入公司联系电话"]')
|
||||
expect(phone.attributes('disabled')).toBeUndefined()
|
||||
await phone.setValue('0755-12345678')
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.find((button) => button.textContent?.includes('保存资料和资质'))?.click()
|
||||
await flushPromises()
|
||||
|
||||
expect(updatePlatformTenant).toHaveBeenCalledWith(fullTenant.id, expect.objectContaining({ contactPhone: '0755-12345678' }))
|
||||
expect(document.body.textContent).toContain('不会修改企业管理员登录手机号')
|
||||
expect(customersSource).not.toContain('联系电话(登录用户名)')
|
||||
})
|
||||
|
||||
it('does not count rejected or superseded history as a current qualification when resubmitting', async () => {
|
||||
@@ -272,11 +348,11 @@ describe('CustomersView', () => {
|
||||
await flushPromises()
|
||||
|
||||
expect(getPlatformTenant).toHaveBeenCalledWith(fullTenant.id)
|
||||
expect(document.querySelector<HTMLInputElement>('input[placeholder="请输入手机号或固定电话"]')?.value).toBe(fullTenant.contactPhone)
|
||||
expect(document.querySelector<HTMLInputElement>('input[placeholder="请输入公司联系电话"]')?.value).toBe(fullTenant.contactPhone)
|
||||
expect(routerReplace).toHaveBeenCalledWith({ name: 'customers' })
|
||||
})
|
||||
|
||||
it('saves both required files and the credit code through one package action', async () => {
|
||||
it('saves the business license, credit code and optional legal-person ID through one package action', async () => {
|
||||
vi.mocked(listTenantQualifications).mockResolvedValue([{
|
||||
id: 'qualification-legal', tenantId: fullTenant.id, qualificationType: 'LEGAL_PERSON_ID',
|
||||
certificateNo: null, documentUrl: '/files/legal-person.png', status: 'PENDING', reviewComment: null, validTo: null,
|
||||
@@ -288,13 +364,8 @@ describe('CustomersView', () => {
|
||||
|
||||
const licenseFile = new File(['license'], 'license.png', { type: 'image/png' })
|
||||
const legalPersonFile = new File(['legal-person'], 'legal-person.png', { type: 'image/png' })
|
||||
const qualificationInputs = document.querySelectorAll<HTMLInputElement>('.qualification-file input[type="file"]')
|
||||
const licenseInput = qualificationInputs[0]
|
||||
const legalPersonInput = qualificationInputs[1]
|
||||
Object.defineProperty(licenseInput, 'files', { configurable: true, value: [licenseFile] })
|
||||
licenseInput?.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
Object.defineProperty(legalPersonInput, 'files', { configurable: true, value: [legalPersonFile] })
|
||||
legalPersonInput?.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
selectQualificationFile(0, licenseFile)
|
||||
selectQualificationFile(1, legalPersonFile)
|
||||
await flushPromises()
|
||||
|
||||
const buttons = [...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
@@ -314,6 +385,101 @@ describe('CustomersView', () => {
|
||||
expect(uploadPlatformTenantQualification).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('replaces only the business license while preserving an existing legal-person ID', async () => {
|
||||
vi.mocked(listTenantQualifications).mockResolvedValue([
|
||||
{ id: 'qualification-license', tenantId: fullTenant.id, qualificationType: 'BUSINESS_LICENSE', certificateNo: fullTenant.unifiedSocialCreditCode, documentUrl: '/files/license.png', status: 'PENDING', reviewComment: null, validTo: null },
|
||||
{ id: 'qualification-legal', tenantId: fullTenant.id, qualificationType: 'LEGAL_PERSON_ID', certificateNo: null, documentUrl: '/files/legal-person.png', status: 'PENDING', reviewComment: null, validTo: null },
|
||||
])
|
||||
vi.mocked(uploadPlatformTenantRequiredQualificationPackage).mockResolvedValueOnce({
|
||||
businessLicenseQualificationId: 'qualification-business-new', legalPersonIdQualificationId: null,
|
||||
tenantStatus: 'PROFILE_REQUIRED', businessLicenseDocumentUrl: '/files/license-new.png', legalPersonIdDocumentUrl: null,
|
||||
})
|
||||
mount(CustomersView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
|
||||
await flushPromises()
|
||||
tableButtonByText('编辑')?.click()
|
||||
await flushPromises()
|
||||
|
||||
const licenseFile = new File(['license-new'], 'license-new.png', { type: 'image/png' })
|
||||
selectQualificationFile(0, licenseFile)
|
||||
await flushPromises()
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.find((button) => button.textContent?.includes('保存资料和资质'))?.click()
|
||||
await flushPromises()
|
||||
|
||||
expect(uploadPlatformTenantRequiredQualificationPackage).toHaveBeenCalledWith(
|
||||
fullTenant.id, licenseFile, null, fullTenant.unifiedSocialCreditCode, true, false,
|
||||
)
|
||||
expect(uploadPlatformTenantQualification).not.toHaveBeenCalled()
|
||||
expect(document.body.textContent).toContain('法人身份证可按需补充')
|
||||
})
|
||||
|
||||
it('uploads an optional legal-person ID independently when the required business license already exists', async () => {
|
||||
vi.mocked(listTenantQualifications).mockResolvedValue([{
|
||||
id: 'qualification-license', tenantId: fullTenant.id, qualificationType: 'BUSINESS_LICENSE',
|
||||
certificateNo: fullTenant.unifiedSocialCreditCode, documentUrl: '/files/license.png', status: 'PENDING', reviewComment: null, validTo: null,
|
||||
}])
|
||||
mount(CustomersView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
|
||||
await flushPromises()
|
||||
tableButtonByText('编辑')?.click()
|
||||
await flushPromises()
|
||||
|
||||
const legalPersonFile = new File(['legal-person'], 'legal-person.png', { type: 'image/png' })
|
||||
selectQualificationFile(1, legalPersonFile)
|
||||
await flushPromises()
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.find((button) => button.textContent?.includes('保存资料和资质'))?.click()
|
||||
await flushPromises()
|
||||
|
||||
expect(uploadPlatformTenantRequiredQualificationPackage).not.toHaveBeenCalled()
|
||||
expect(uploadPlatformTenantQualification).toHaveBeenCalledWith(
|
||||
fullTenant.id, 'LEGAL_PERSON_ID', legalPersonFile, undefined, undefined, false,
|
||||
)
|
||||
})
|
||||
|
||||
it('retains a selected business license after an upload failure so the operator can retry', async () => {
|
||||
vi.mocked(uploadPlatformTenantRequiredQualificationPackage)
|
||||
.mockRejectedValueOnce(new Error('upload unavailable'))
|
||||
.mockResolvedValueOnce({
|
||||
businessLicenseQualificationId: 'qualification-business', legalPersonIdQualificationId: null,
|
||||
tenantStatus: 'PROFILE_REQUIRED', businessLicenseDocumentUrl: '/files/license-new.png', legalPersonIdDocumentUrl: null,
|
||||
})
|
||||
mount(CustomersView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
|
||||
await flushPromises()
|
||||
tableButtonByText('编辑')?.click()
|
||||
await flushPromises()
|
||||
|
||||
const licenseFile = new File(['license-new'], 'retry-license.png', { type: 'image/png' })
|
||||
selectQualificationFile(0, licenseFile)
|
||||
await flushPromises()
|
||||
const saveButton = [...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.find((button) => button.textContent?.includes('保存资料和资质'))
|
||||
saveButton?.click()
|
||||
await flushPromises()
|
||||
expect(document.body.textContent).toContain('retry-license.png')
|
||||
|
||||
saveButton?.click()
|
||||
await flushPromises()
|
||||
expect(uploadPlatformTenantRequiredQualificationPackage).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('submits a customer for review with a business license and no legal-person ID', async () => {
|
||||
vi.mocked(listTenantQualifications).mockResolvedValue([{
|
||||
id: 'qualification-license', tenantId: fullTenant.id, qualificationType: 'BUSINESS_LICENSE',
|
||||
certificateNo: fullTenant.unifiedSocialCreditCode, documentUrl: '/files/license.png', status: 'PENDING', reviewComment: null, validTo: null,
|
||||
}])
|
||||
vi.spyOn(ElMessageBox, 'confirm').mockResolvedValue('confirm' as never)
|
||||
mount(CustomersView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
|
||||
await flushPromises()
|
||||
tableButtonByText('编辑')?.click()
|
||||
await flushPromises()
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.find((button) => button.textContent?.trim() === '提交审核')?.click()
|
||||
await flushPromises()
|
||||
|
||||
expect(ElMessageBox.confirm).toHaveBeenCalled()
|
||||
expect(submitPlatformTenantReview).toHaveBeenCalledWith(fullTenant.id)
|
||||
})
|
||||
|
||||
it('submits the current filters to the backend export service without loading every customer page', async () => {
|
||||
const wrapper = mount(CustomersView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
|
||||
await flushPromises()
|
||||
@@ -398,7 +564,7 @@ describe('CustomersView', () => {
|
||||
.find((button) => button.textContent?.trim() === '审核通过')?.click()
|
||||
await flushPromises()
|
||||
|
||||
expect(approveEnterpriseReview).toHaveBeenCalledWith(pendingReview.id, '企业资料与必填资质完整、清晰,审核通过')
|
||||
expect(approveEnterpriseReview).toHaveBeenCalledWith(pendingReview.id, '企业资料、营业执照和统一社会信用代码完整、清晰,审核通过(法人身份证为选填项)')
|
||||
expect(rejectEnterpriseReview).not.toHaveBeenCalled()
|
||||
expect(document.body.textContent).toContain('请立即保存以下一次性凭证')
|
||||
expect(document.body.textContent).toContain('merchant_8_owner')
|
||||
@@ -459,7 +625,7 @@ describe('CustomersView', () => {
|
||||
.find((button) => button.textContent?.trim() === '审核通过')?.click()
|
||||
await flushPromises()
|
||||
|
||||
expect(approveEnterpriseReview).toHaveBeenCalledWith(pendingReview.id, '企业资料与必填资质完整、清晰,审核通过')
|
||||
expect(approveEnterpriseReview).toHaveBeenCalledWith(pendingReview.id, '企业资料、营业执照和统一社会信用代码完整、清晰,审核通过(法人身份证为选填项)')
|
||||
expect(rejectEnterpriseReview).not.toHaveBeenCalled()
|
||||
expect(document.body.textContent).toContain('注册账号继续作为企业管理员')
|
||||
expect(document.body.textContent).not.toContain('请立即保存以下一次性凭证')
|
||||
|
||||
@@ -377,7 +377,7 @@ describe('platform IAM roles', () => {
|
||||
expect(http.post).toHaveBeenNthCalledWith(2, '/platform/v1/iam/tenants/8/submit-review', {})
|
||||
})
|
||||
|
||||
it('uploads both required files and the credit code in one package request', async () => {
|
||||
it('uploads the business license, credit code and optional legal-person ID in one package request', async () => {
|
||||
const result = {
|
||||
businessLicenseQualificationId: '91', legalPersonIdQualificationId: '92',
|
||||
tenantStatus: 'PROFILE_REQUIRED', businessLicenseDocumentUrl: '/license.pdf',
|
||||
@@ -400,6 +400,26 @@ describe('platform IAM roles', () => {
|
||||
expect(form.get('replaceLegalPersonId')).toBe('true')
|
||||
})
|
||||
|
||||
it('omits the optional legal-person ID parts when only the business license is uploaded', async () => {
|
||||
const result = {
|
||||
businessLicenseQualificationId: '91', legalPersonIdQualificationId: null,
|
||||
tenantStatus: 'PROFILE_REQUIRED', businessLicenseDocumentUrl: '/license.pdf',
|
||||
legalPersonIdDocumentUrl: null,
|
||||
}
|
||||
vi.mocked(http.post).mockResolvedValue({ code: '200', message: 'success', data: result })
|
||||
const businessLicense = new File(['%PDF-1.4'], '营业执照.pdf', { type: 'application/pdf' })
|
||||
|
||||
await expect(uploadPlatformTenantRequiredQualificationPackage(
|
||||
'8', businessLicense, null, '91440300MA5ABCDE12', true, false,
|
||||
)).resolves.toEqual(result)
|
||||
|
||||
const form = vi.mocked(http.post).mock.calls[0]?.[1] as FormData
|
||||
expect(form.get('businessLicense')).toBe(businessLicense)
|
||||
expect(form.get('legalPersonId')).toBeNull()
|
||||
expect(form.get('replaceBusinessLicense')).toBe('true')
|
||||
expect(form.get('replaceLegalPersonId')).toBeNull()
|
||||
})
|
||||
|
||||
it('preserves long review identifiers and returns one-time administrator credentials', async () => {
|
||||
const reviewId = '190000000000000001'
|
||||
const approval = {
|
||||
|
||||
Reference in New Issue
Block a user