import assert from 'node:assert/strict'; import { buildApp } from '../dist/app.js'; import { signAccessToken } from '../dist/auth/jwt.js'; const secret = 'test-only-franchise-management-secret-32'; const token = signAccessToken({ sub: '21', sid: '5c4d3af8-c63c-4edb-bf95-b84127bb3f6e', tid: '7', aid: '9', rv: 1 }, secret, 900); let submitted; let listInput; let assignment; let followUp; const repository = { async submitApplication(input) { submitted = input; return { applicationId: '101', applicationNo: 'FR101', idempotent: false }; }, async listApplications(input) { listInput = input; return { items: [], total: 0, page: input.page, pageSize: input.pageSize }; }, async getApplication(tenantId, id) { return { application: { id, tenantId }, followUps: [] }; }, async assignApplication(_actor, tenantId, id, assigneeUserId) { assignment = { tenantId, id, assigneeUserId }; return { applicationId: id, assigneeUserId }; }, async addFollowUp(_actor, tenantId, id, input) { followUp = { tenantId, id, input }; return { applicationId: id, followUpId: '301', status: input.status || 'NEW' }; } }; const app = await buildApp({ franchise: { repository, platformConfig: { async resolveBootstrap(appId, tenantId) { return appId === 'wx-franchise' ? { tenantId: tenantId || '7' } : null; } }, authRepository: { async validateSession() { return { id: '5c4d3af8-c63c-4edb-bf95-b84127bb3f6e', tenantId: '7', platformAppId: '9', expiresAt: new Date(Date.now() + 60000), user: { id: '21', tenantId: '7', userType: 'STAFF', status: 'ACTIVE', roleVersion: 1, nickname: '', avatarUrl: '', phone: '' } }; } }, accessControl: { async getAccessProfile() { return { roles: ['TENANT_ADMIN'], capabilities: ['tenant.manage'], storeIds: [] }; } }, jwtSecret: secret } }); const created = await app.inject({ method: 'POST', url: '/app-api/franchise-applications', headers: { 'x-wechat-appid': 'wx-franchise' }, payload: { city: '上海市', contactName: '张先生', contactPhone: '13800138000', message: '计划开店', clientRequestId: 'franchise-request-001' } }); assert.equal(created.statusCode, 201); assert.equal(submitted.tenantId, '7'); assert.equal(submitted.contactPhone, '13800138000'); assert.equal('contactPhone' in (submitted.audit || {}), false); const auth = { authorization: `Bearer ${token}` }; const listed = await app.inject({ method: 'GET', url: '/admin-api/franchise-applications?status=NEW&page=2&pageSize=10', headers: auth }); assert.equal(listed.statusCode, 200); assert.deepEqual({ tenantId: listInput.tenantId, status: listInput.status, page: listInput.page }, { tenantId: '7', status: 'NEW', page: 2 }); const crossTenant = await app.inject({ method: 'GET', url: '/admin-api/franchise-applications?tenantId=8', headers: auth }); assert.equal(crossTenant.statusCode, 403); const assigned = await app.inject({ method: 'PATCH', url: '/admin-api/franchise-applications/101/assignee', headers: auth, payload: { assigneeUserId: '22' } }); assert.equal(assigned.statusCode, 200); assert.deepEqual(assignment, { tenantId: '7', id: '101', assigneeUserId: '22' }); const followed = await app.inject({ method: 'POST', url: '/admin-api/franchise-applications/101/follow-ups', headers: auth, payload: { followUpType: 'CALL', note: '已电话沟通', status: 'CONTACTED' } }); assert.equal(followed.statusCode, 201); assert.equal(followUp.input.status, 'CONTACTED'); await app.close(); console.log('PASS: M08-D franchise submission, tenant scope, assignment and follow-up routes are present.');