import { beforeEach, describe, expect, it, vi } from 'vitest'; import { useAuthStore } from '@/stores/auth-store'; import { setUnauthorizedHandler } from './client'; import { applyCustomerField, applyIndustryPack, approveCustomerField, createCustomer, createCustomerGroup, createIndustry, deleteCustomerGroup, deleteIndustry, fetchCustomerDetail, fetchCustomerFieldList, fetchCustomerFields, fetchCustomerGroupDetail, fetchCustomerGroupList, fetchCustomerList, fetchIndustryDetail, fetchIndustryList, setCustomerGroups, setCustomerStatus, setIndustryStatus, transferCustomer, updateCustomer, updateCustomerGroup, updateCustomerGroupMembers, updateIndustry, } from './customers'; const ID = '01HZZZZZZZZZZZZZZZZZZZZZZ1'; const ID2 = '01HZZZZZZZZZZZZZZZZZZZZZZ2'; const NOW = '2026-11-03T10:00:00.000Z'; const jsonResponse = (body: unknown, status = 200): Response => new Response(status === 204 ? null : JSON.stringify(body), { status, headers: status === 204 ? undefined : { 'Content-Type': 'application/json' }, }); const industryNode = { id: ID, parentId: null, level: 1, name: '电商', sortOrder: 10, status: 'active', remark: '', customerCount: 1, assetCount: 0, updatedAt: NOW, }; const industryTree = { ...industryNode, children: [] }; const industryDetail = { ...industryNode, parentName: null, references: { childCount: 0, activeChildCount: 0, customerCount: 1, customers: [], assetCount: 0, }, }; const customerSummary = { id: ID, name: '花间集', shortName: 'HJ', industryId: ID, industryName: '服饰鞋包', industryPath: '电商 / 服饰鞋包', status: 'active', ownerUserId: ID2, ownerName: '陈默', groups: [], baseFieldCount: 1, genFieldCount: 1, updatedAt: NOW, }; const customerDetail = { ...customerSummary, contactName: '林晓', contactPhone: '138****2210', preferences: { naming: '', directory: '', specs: [], taboo: [] }, baseFields: [{ key: '门店', value: '沪-001' }], fields: [], remark: null, createdAt: NOW, }; const groupSummary = { id: ID, name: '电商核心组', description: '', memberCount: 0, updatedAt: NOW, }; const groupDetail = { ...groupSummary, members: [] }; const field = { id: ID, customerId: ID2, name: '活动编码', code: 'campaign_code', type: 'text', options: [], purpose: '检索', entersGeneration: true, status: 'pending', applicantName: '陈默', approverName: null, appliedAt: NOW, approvedAt: null, rejectReason: null, }; const fieldListItem = { ...field, customerName: '花间集', customerShortName: 'HJ', industryPath: '电商 / 服饰鞋包', }; function lastCall(): { url: string; init: RequestInit | undefined } { const fetchMock = vi.mocked(globalThis.fetch); const call = fetchMock.mock.calls.at(-1); const input = call?.[0]; const url = typeof input === 'string' ? input : input instanceof URL ? input.href : (input?.url ?? ''); return { url, init: call?.[1] }; } function lastBody(): unknown { const body = lastCall().init?.body; return typeof body === 'string' ? (JSON.parse(body) as unknown) : undefined; } beforeEach(() => { vi.restoreAllMocks(); window.localStorage.clear(); useAuthStore.getState().clearSession(); setUnauthorizedHandler(null); useAuthStore.getState().setSession({ accessToken: 'token', user: { id: ID2, name: '陈默', account: 'chen.mo' }, permissions: ['customer:read'], }); }); describe('客户与行业接口封装', () => { it('行业:列表 / 详情 / 新建 / 更新 / 状态 / 删除', async () => { vi.spyOn(globalThis, 'fetch') .mockResolvedValueOnce(jsonResponse({ items: [industryTree], next_cursor: null })) .mockResolvedValueOnce(jsonResponse(industryDetail)) .mockResolvedValueOnce(jsonResponse(industryDetail, 201)) .mockResolvedValueOnce(jsonResponse(industryDetail)) .mockResolvedValueOnce(jsonResponse(industryDetail)) .mockResolvedValueOnce(jsonResponse(undefined, 204)); await fetchIndustryList({ keyword: '电商', limit: 100 }); expect(lastCall().url).toContain('/industries?keyword='); await fetchIndustryDetail(ID); await createIndustry({ name: '电商', sortOrder: 10, remark: '' }); expect(lastBody()).toEqual({ name: '电商', sortOrder: 10, remark: '' }); await updateIndustry(ID, { name: '电商2' }); await setIndustryStatus(ID, { status: 'disabled' }); await deleteIndustry(ID); expect(lastCall().url).toContain(`/industries/${ID}`); }); it('客户:列表 / 详情 / 新建 / 更新 / 状态 / 转交 / 组 / 资产包', async () => { vi.spyOn(globalThis, 'fetch') .mockResolvedValueOnce(jsonResponse({ items: [customerSummary], next_cursor: null })) .mockResolvedValueOnce(jsonResponse(customerDetail)) .mockResolvedValueOnce(jsonResponse(customerDetail, 201)) .mockResolvedValueOnce(jsonResponse(customerDetail)) .mockResolvedValueOnce(jsonResponse(customerDetail)) .mockResolvedValueOnce(jsonResponse(customerDetail)) .mockResolvedValueOnce(jsonResponse(customerDetail)) .mockResolvedValueOnce(jsonResponse({}, 200)); await fetchCustomerList({ status: 'active', limit: 100 }); expect(lastCall().url).toContain('/customers?status=active'); await fetchCustomerDetail(ID); await createCustomer({ industryId: ID, name: '花间集', shortName: 'HJ', ownerUserId: ID2, }); await updateCustomer(ID, { shortName: 'HJ2' }); await setCustomerStatus(ID, { status: 'paused' }); await transferCustomer(ID, { ownerUserId: ID2 }); await setCustomerGroups(ID, { groupIds: [ID] }); await applyIndustryPack(ID); expect(lastCall().url).toContain(`/customers/${ID}/apply-industry-pack`); }); it('客户组:列表 / 详情 / 新建 / 更新 / 成员 / 删除', async () => { vi.spyOn(globalThis, 'fetch') .mockResolvedValueOnce(jsonResponse({ items: [groupSummary], next_cursor: null })) .mockResolvedValueOnce(jsonResponse(groupDetail)) .mockResolvedValueOnce(jsonResponse(groupDetail, 201)) .mockResolvedValueOnce(jsonResponse(groupDetail)) .mockResolvedValueOnce(jsonResponse(groupDetail)) .mockResolvedValueOnce(jsonResponse(undefined, 204)); await fetchCustomerGroupList(); await fetchCustomerGroupDetail(ID); await createCustomerGroup({ name: '电商核心组', description: '' }); await updateCustomerGroup(ID, { description: '说明' }); await updateCustomerGroupMembers(ID, { addCustomerIds: [ID2], removeCustomerIds: [] }); await deleteCustomerGroup(ID); }); it('客户字段:审批队列 / 客户字段 / 申请 / 审批', async () => { vi.spyOn(globalThis, 'fetch') .mockResolvedValueOnce(jsonResponse({ items: [fieldListItem], next_cursor: null })) .mockResolvedValueOnce(jsonResponse([field])) .mockResolvedValueOnce(jsonResponse(field, 201)) .mockResolvedValueOnce(jsonResponse(field)); await fetchCustomerFieldList({ status: 'pending', limit: 100 }); await fetchCustomerFields(ID); await applyCustomerField(ID, { name: '活动编码', code: 'campaign_code', type: 'text', options: [], purpose: '检索', entersGeneration: true, }); await approveCustomerField(ID, { decision: 'approve' }); expect(lastCall().url).toContain(`/customer-fields/${ID}/approve`); }); });