/** * Tests for cfc-frontend utils/api.js * * Mocks uni.request to intercept and verify API calls. */ import * as api from '@/utils/api' // --------------- helpers --------------- /** Helper: make uni.request call success(data) on next tick */ function mockSuccess(data) { global.uni.request.mockImplementation((opts) => { process.nextTick(() => { if (opts.success) opts.success({ data }) }) }) } /** Helper: make uni.request call fail(err) on next tick */ function mockFail(err) { global.uni.request.mockImplementation((opts) => { process.nextTick(() => { if (opts.fail) opts.fail(err) }) }) } /** Helper: make uni.request return a sequence of responses (one per call) */ function mockSequence(responses) { var i = 0 global.uni.request.mockImplementation((opts) => { var resp = responses[Math.min(i, responses.length - 1)] i++ process.nextTick(() => { if (resp.err) { if (opts.fail) opts.fail(resp.err) } else { if (opts.success) opts.success({ data: resp.data }) } }) }) } // --------------- global beforeEach --------------- beforeEach(() => { global.uni._storage = {} global.uni.request.mockReset() global.uni.showToast.mockReset() global.uni.getAccountInfoSync.mockReset() global.uni.getAccountInfoSync.mockReturnValue({ miniProgram: { envVersion: 'develop' } }) }) /** Capture the last uni.request call options */ function lastRequest() { const calls = global.uni.request.mock.calls return calls[calls.length - 1][0] } // --------------- auth module --------------- describe('auth API', () => { beforeEach(() => { global.uni._storage.token = 'test-token' global.uni._storage.userId = '42' }) test('wechatLogin sends POST /api/auth/wechat-login', async () => { mockSuccess({ code: 200, data: { token: 'jwt' } }) const res = await api.wechatLogin('code123', { nickname: 'test' }) const opts = lastRequest() expect(opts.method).toBe('POST') expect(opts.url).toMatch(/\/api\/auth\/wechat-login$/) expect(opts.data.code).toBe('code123') expect(res.data.token).toBe('jwt') }) test('phoneLogin sends POST with phone and code', async () => { mockSuccess({ code: 200, data: { token: 'jwt' } }) await api.phoneLogin({ phone: '13800138000', code: '1234' }) const opts = lastRequest() expect(opts.method).toBe('POST') expect(opts.url).toMatch(/\/api\/auth\/phone-login$/) expect(opts.data.phone).toBe('13800138000') }) test('sendCode sends POST with data', async () => { mockSuccess({ code: 200 }) await api.sendCode({ phone: '13800138000' }) const opts = lastRequest() expect(opts.method).toBe('POST') expect(opts.url).toMatch(/\/api\/auth\/send-code$/) }) test('silentLogin sends POST with code', async () => { mockSuccess({ code: 200 }) await api.silentLogin('wxcode') expect(lastRequest().data.code).toBe('wxcode') }) test('autoLogin sends POST with openid', async () => { mockSuccess({ code: 200 }) await api.autoLogin('openid_xxx') expect(lastRequest().data.openid).toBe('openid_xxx') }) test('setPassword and verifyPassword send correct endpoints', async () => { mockSuccess({ code: 200 }) await api.setPassword('secret') expect(lastRequest().url).toMatch(/\/api\/auth\/set-password$/) expect(lastRequest().data.password).toBe('secret') await api.verifyPassword('secret') expect(lastRequest().url).toMatch(/\/api\/auth\/verify-password$/) }) test('directRegister sends POST', async () => { mockSuccess({ code: 200 }) await api.directRegister({ phone: '13800138000', nickname: 'tester' }) expect(lastRequest().url).toMatch(/\/api\/auth\/direct-register$/) }) test('checkPhone sends POST with phone', async () => { mockSuccess({ code: 200 }) await api.checkPhone('13800138000') expect(lastRequest().data.phone).toBe('13800138000') }) test('wechatPhoneLogin sends POST', async () => { mockSuccess({ code: 200 }) await api.wechatPhoneLogin({ code: 'wxcode', iv: 'iv', encryptedData: 'enc' }) expect(lastRequest().url).toMatch(/\/api\/auth\/wechat-phone-login$/) }) test('verifyToken sends POST /api/auth/verify with bearer token', async () => { mockSuccess({ code: 200, data: { userId: 42, role: 'parent' } }) const res = await api.verifyToken() const opts = lastRequest() expect(opts.method).toBe('POST') expect(opts.url).toMatch(/\/api\/auth\/verify$/) expect(opts.header.Authorization).toBe('Bearer test-token') expect(res.data.userId).toBe(42) }) test('verifyToken with 401 (token expired) clears auth and redirects to login', async () => { jest.useFakeTimers() mockSuccess({ code: 401, message: 'Token无效或已过期', data: null }) await expect(api.verifyToken()).rejects.toEqual({ code: 401, message: 'Token无效或已过期', data: null }) expect(global.uni._storage.token).toBeUndefined() // 触发 _clearAuthAndRedirect 内 1.5s setTimeout(reLaunch 在此定时器回调中执行) jest.runAllTimers() expect(global.uni.reLaunch).toHaveBeenCalledWith({ url: '/pages/login/login' }) expect(global.uni.showToast).toHaveBeenCalled() jest.useRealTimers() }) test('verifyToken with 401 (user deleted) clears auth and redirects to login', async () => { jest.useFakeTimers() mockSuccess({ code: 401, message: '用户不存在', data: null }) await expect(api.verifyToken()).rejects.toEqual({ code: 401, message: '用户不存在', data: null }) expect(global.uni._storage.token).toBeUndefined() jest.runAllTimers() expect(global.uni.reLaunch).toHaveBeenCalledWith({ url: '/pages/login/login' }) expect(global.uni.showToast).toHaveBeenCalled() jest.useRealTimers() }) test('Authorization header includes Bearer token', async () => { mockSuccess({ code: 200 }) global.uni._storage.token = 'my-jwt-token' await api.getUserInfo() const opts = lastRequest() expect(opts.header.Authorization).toBe('Bearer my-jwt-token') }) test('X-User-Id header is NOT sent (removed for security)', async () => { mockSuccess({ code: 200 }) global.uni._storage.userId = '99' await api.getUserInfo() expect(lastRequest().header['X-User-Id']).toBeUndefined() }) test('rejects on non-200 code', async () => { mockSuccess({ code: 400, message: 'Bad Request' }) await expect(api.getUserInfo()).rejects.toEqual({ code: 400, message: 'Bad Request' }) }) test('rejects on network failure', async () => { mockFail(new Error('Network error')) await expect(api.getUserInfo()).rejects.toThrow('Network error') }) }) // --------------- streets module --------------- describe('street address API', () => { test('getProvinces sends POST to /api/streets/provinces', async () => { mockSuccess({ code: 200, data: [] }) await api.getProvinces() expect(lastRequest().url).toMatch(/\/api\/streets\/provinces$/) }) test('getStreetChildren sends POST with parentId', async () => { mockSuccess({ code: 200 }) await api.getStreetChildren(110000) expect(lastRequest().url).toMatch(/\/api\/streets\/children\/110000$/) }) test('getStreetById sends POST to correct URL', async () => { mockSuccess({ code: 200 }) await api.getStreetById(110101) expect(lastRequest().url).toMatch(/\/api\/streets\/110101$/) }) test('getStreetPath sends POST', async () => { mockSuccess({ code: 200 }) await api.getStreetPath(110101) expect(lastRequest().url).toMatch(/\/api\/streets\/110101\/path$/) }) test('searchStreet sends POST with keyword', async () => { mockSuccess({ code: 200 }) await api.searchStreet('北京') expect(lastRequest().data.keyword).toBe('北京') }) test('matchByStreet sends POST with streetId', async () => { mockSuccess({ code: 200 }) await api.matchByStreet(110101) expect(lastRequest().url).toMatch(/\/110101\/match$/) }) }) // --------------- user / family module --------------- describe('user & family API', () => { test('getUserInfo sends POST', async () => { mockSuccess({ code: 200 }) await api.getUserInfo() expect(lastRequest().url).toMatch(/\/api\/user\/info$/) }) test('updateUserInfo sends POST with data', async () => { mockSuccess({ code: 200 }) await api.updateUserInfo({ nickname: 'new-name' }) expect(lastRequest().data.nickname).toBe('new-name') }) test('joinFamily sends POST with inviteCode', async () => { mockSuccess({ code: 200 }) await api.joinFamily('ABC123') expect(lastRequest().data.inviteCode).toBe('ABC123') }) test('createFamily sends POST with name', async () => { mockSuccess({ code: 200 }) await api.createFamily('幸福之家') expect(lastRequest().data.name).toBe('幸福之家') }) test('getFamilyMemberList sends POST to unified endpoint', async () => { mockSuccess({ code: 200 }) await api.getFamilyMemberList() expect(lastRequest().url).toMatch(/\/api\/family\/member\/list$/) }) test('getFamilyMemberList sends visibleOnly param', async () => { mockSuccess({ code: 200 }) await api.getFamilyMemberList({ visibleOnly: true }) expect(lastRequest().url).toMatch(/\/api\/family\/member\/list$/) expect(lastRequest().data.visibleOnly).toBe(true) }) test('getFamilyMemberList sends includeFamily param', async () => { mockSuccess({ code: 200 }) await api.getFamilyMemberList({ includeFamily: true }) expect(lastRequest().url).toMatch(/\/api\/family\/member\/list$/) expect(lastRequest().data.includeFamily).toBe(true) }) test('updateMemberVisibility sends POST with params', async () => { mockSuccess({ code: 200 }) await api.updateMemberVisibility(1, 'child', true) expect(lastRequest().data.memberId).toBe(1) expect(lastRequest().data.memberType).toBe('child') expect(lastRequest().data.showToFamily).toBe(true) }) test('switchMode sends POST with mode', async () => { mockSuccess({ code: 200 }) await api.switchMode('child') expect(lastRequest().data.mode).toBe('child') }) test('switchToChild sends POST with memberId', async () => { mockSuccess({ code: 200 }) await api.switchToChild(5) expect(lastRequest().data.memberId).toBe(5) }) test('switchBackToParent sends POST', async () => { mockSuccess({ code: 200 }) await api.switchBackToParent() expect(lastRequest().url).toMatch(/\/api\/user\/switch-back-to-parent$/) }) test('getChildren sends POST', async () => { mockSuccess({ code: 200 }) await api.getChildren() expect(lastRequest().url).toMatch(/\/api\/family\/user\/children\/list$/) }) test('createChild sends POST with data', async () => { mockSuccess({ code: 200 }) await api.createChild({ name: '小宝', gender: 'male' }) expect(lastRequest().url).toMatch(/\/api\/family\/user\/children$/) expect(lastRequest().data.name).toBe('小宝') }) test('updateChild sends POST merged with memberId', async () => { mockSuccess({ code: 200 }) await api.updateChild(5, { nickname: '大宝' }) expect(lastRequest().data.memberId).toBe(5) expect(lastRequest().data.nickname).toBe('大宝') }) test('kickFamilyMember sends POST with targetUserId', async () => { mockSuccess({ code: 200 }) await api.kickFamilyMember(10) expect(lastRequest().data.targetUserId).toBe(10) }) }) // --------------- membership module --------------- describe('membership API', () => { test('getMembershipLevels sends POST', async () => { mockSuccess({ code: 200 }) await api.getMembershipLevels() expect(lastRequest().url).toMatch(/\/api\/membership\/levels$/) }) test('getMyMembership sends POST', async () => { mockSuccess({ code: 200 }) await api.getMyMembership() expect(lastRequest().url).toMatch(/\/api\/membership\/my$/) }) test('createOrder sends POST with levelCode and paymentType', async () => { mockSuccess({ code: 200 }) await api.createOrder('vip', 'wechat') expect(lastRequest().data.levelCode).toBe('vip') expect(lastRequest().data.paymentType).toBe('wechat') }) }) // --------------- tasks module --------------- describe('tasks API', () => { test('createTask sends POST with data', async () => { mockSuccess({ code: 200 }) await api.createTask({ title: '晨读', memberId: 5 }) expect(lastRequest().url).toMatch(/\/api\/tasks$/) expect(lastRequest().data.title).toBe('晨读') }) test('getTodayTasks sends POST with memberId', async () => { mockSuccess({ code: 200 }) await api.getTodayTasks(5) expect(lastRequest().data.memberId).toBe(5) }) test('getPendingReviewTasks sends POST', async () => { mockSuccess({ code: 200 }) await api.getPendingReviewTasks() expect(lastRequest().url).toMatch(/\/api\/tasks\/pending-review$/) }) test('approveTask sends POST with approved:true', async () => { mockSuccess({ code: 200 }) await api.approveTask(10) expect(lastRequest().url).toMatch(/\/api\/tasks\/10\/review$/) expect(lastRequest().data.approved).toBe(true) }) test('rejectTask sends POST with approved:false', async () => { mockSuccess({ code: 200 }) await api.rejectTask(10) expect(lastRequest().data.approved).toBe(false) }) test('completeTask sends POST with memberId and photoUrl', async () => { mockSuccess({ code: 200 }) await api.completeTask(10, 5, 'http://photo.url') expect(lastRequest().data.memberId).toBe(5) expect(lastRequest().data.photoUrl).toBe('http://photo.url') }) test('reviewTask sends POST with data', async () => { mockSuccess({ code: 200 }) await api.reviewTask(10, { approved: true, comment: '很好' }) expect(lastRequest().url).toMatch(/\/api\/tasks\/10\/review$/) expect(lastRequest().data.comment).toBe('很好') }) test('getTaskHistory sends POST with pagination', async () => { mockSuccess({ code: 200 }) await api.getTaskHistory(5, 1, 20) expect(lastRequest().data.memberId).toBe(5) expect(lastRequest().data.page).toBe(1) expect(lastRequest().data.size).toBe(20) }) test('deleteTask sends DELETE', async () => { mockSuccess({ code: 200 }) await api.deleteTask(10) expect(lastRequest().method).toBe('DELETE') expect(lastRequest().url).toMatch(/\/api\/tasks\/10$/) }) test('batchCompleteTasks sends POST with array', async () => { mockSuccess({ code: 200 }) await api.batchCompleteTasks([1, 2, 3]) expect(lastRequest().data.taskIds).toEqual([1, 2, 3]) }) test('getTodayParentTasks sends POST', async () => { mockSuccess({ code: 200 }) await api.getTodayParentTasks() expect(lastRequest().url).toMatch(/\/api\/tasks\/today-parent$/) }) }) // --------------- points module --------------- describe('points API', () => { test('getPointsBalance sends POST with memberId', async () => { mockSuccess({ code: 200 }) await api.getPointsBalance(5) expect(lastRequest().data.memberId).toBe(5) }) test('getPointsLogs sends POST with pagination', async () => { mockSuccess({ code: 200 }) await api.getPointsLogs(5, 2, 20) expect(lastRequest().data.memberId).toBe(5) expect(lastRequest().data.page).toBe(2) expect(lastRequest().data.size).toBe(20) }) }) // --------------- rewards / wishes module --------------- describe('rewards & wishes API', () => { test('createReward sends POST', async () => { mockSuccess({ code: 200 }) await api.createReward({ name: '玩具', points: 100 }) expect(lastRequest().data.name).toBe('玩具') }) test('getWishlist sends POST with memberId', async () => { mockSuccess({ code: 200 }) await api.getWishlist(5) expect(lastRequest().data.memberId).toBe(5) }) test('exchangeReward sends POST', async () => { mockSuccess({ code: 200 }) await api.exchangeReward(1, 5) expect(lastRequest().data.memberId).toBe(5) }) test('getPendingWishes sends POST with empty body', async () => { mockSuccess({ code: 200 }) await api.getPendingWishes() expect(lastRequest().data).toEqual({}) }) test('getWishes sends POST with params', async () => { mockSuccess({ code: 200 }) await api.getWishes({ memberId: 5, status: 'pending' }) expect(lastRequest().data.memberId).toBe(5) }) test('setWishPrice sends POST with pointsRequired', async () => { mockSuccess({ code: 200 }) await api.setWishPrice(1, 200) expect(lastRequest().data.pointsRequired).toBe(200) }) test('approveWishExchange sends POST', async () => { mockSuccess({ code: 200 }) await api.approveWishExchange(1, true) expect(lastRequest().data.approved).toBe(true) }) test('createWish sends POST with data', async () => { mockSuccess({ code: 200 }) await api.createWish({ title: '新书包', memberId: 5 }) expect(lastRequest().data.title).toBe('新书包') }) }) // --------------- identity parameter hardening --------------- // 前端不再向后端发送可伪造的 userId / familyId / childId(仅 memberId), // 身份一律由后端从 JWT 推导。以下断言锁定这一不变量。 describe('identity parameter hardening', () => { beforeEach(() => { global.uni._storage.token = 'test-token' }) test('request() does NOT send X-User-Id header', async () => { mockSuccess({ code: 200 }) await api.getPendingWishes() const opts = lastRequest() expect(opts.header['X-User-Id']).toBeUndefined() }) test('getMyAssessmentResults sends empty body, no userId', async () => { mockSuccess({ code: 200 }) await api.getMyAssessmentResults() const opts = lastRequest() expect(opts.data).toEqual({}) expect(opts.data.userId).toBeUndefined() }) test('getChildAssessmentResults sends empty body, no userId', async () => { mockSuccess({ code: 200 }) await api.getChildAssessmentResults() const opts = lastRequest() expect(opts.data).toEqual({}) expect(opts.data.userId).toBeUndefined() }) test('getTodayTasksByCategory sends only memberId and category', async () => { mockSuccess({ code: 200 }) await api.getTodayTasksByCategory(7, 'wisdom') const opts = lastRequest() expect(opts.data.memberId).toBe(7) expect(opts.data.category).toBe('wisdom') expect(opts.data.childId).toBeUndefined() expect(opts.data.userId).toBeUndefined() }) }) // --------------- guide / packages module --------------- describe('guide & packages API', () => { test('getGuidePackages sends POST to list', async () => { mockSuccess({ code: 200 }) await api.getGuidePackages(1) expect(lastRequest().url).toMatch(/\/api\/guide\/packages$/) }) test('registerGuide sends POST', async () => { mockSuccess({ code: 200 }) await api.registerGuide({ name: '张老师', level: 'senior' }) expect(lastRequest().url).toMatch(/\/api\/guide\/register$/) }) test('generateBindInvite sends POST', async () => { mockSuccess({ code: 200 }) await api.generateBindInvite() expect(lastRequest().url).toMatch(/\/api\/guide\/bind\/invite$/) }) test('getBoundFamilies sends POST', async () => { mockSuccess({ code: 200 }) await api.getBoundFamilies() expect(lastRequest().url).toMatch(/\/api\/guide\/families$/) }) test('getTeamMembers sends POST', async () => { mockSuccess({ code: 200 }) await api.getTeamMembers() expect(lastRequest().url).toMatch(/\/api\/guide\/team\/members$/) }) test('getGuideOrders sends POST', async () => { mockSuccess({ code: 200 }) await api.getGuideOrders() expect(lastRequest().url).toMatch(/\/api\/guide\/orders\/list$/) }) test('getGuideMessages sends POST', async () => { mockSuccess({ code: 200 }) await api.getGuideMessages() expect(lastRequest().url).toMatch(/\/api\/guide\/messages\/list$/) }) }) // --------------- payment module --------------- describe('payment API', () => { test('createPackageOrder sends POST', async () => { mockSuccess({ code: 200 }) await api.createPackageOrder({ packageId: 1, userId: 5 }) expect(lastRequest().url).toMatch(/\/api\/package\/order\/create$/) }) test('createWechatPayOrder sends POST', async () => { mockSuccess({ code: 200 }) await api.createWechatPayOrder('NO001', '测试订单', 100) expect(lastRequest().data.orderNo).toBe('NO001') expect(lastRequest().data.amount).toBe(100) }) }) // --------------- assessment module --------------- describe('assessment API', () => { test('createAssessmentAppointment sends POST', async () => { mockSuccess({ code: 200 }) await api.createAssessmentAppointment({ memberId: 5, date: '2026-06-20' }) expect(lastRequest().url).toMatch(/\/api\/dan-assessment\/appointment\/create$/) }) test('payAssessmentOrder sends POST', async () => { mockSuccess({ code: 200 }) await api.payAssessmentOrder('ORDER001', 'wechat') expect(lastRequest().data.orderNo).toBe('ORDER001') }) test('recordAssessmentResult sends POST', async () => { mockSuccess({ code: 200 }) await api.recordAssessmentResult({ orderId: 1, score: 85 }) expect(lastRequest().url).toMatch(/\/api\/dan-assessment\/result\/record$/) }) }) // --------------- mini-games module --------------- describe('mini-games API', () => { test('getMiniGameList sends POST', async () => { mockSuccess({ code: 200 }) await api.getMiniGameList() expect(lastRequest().url).toMatch(/\/api\/mini-game\/list$/) }) test('getMiniGameByCode sends POST', async () => { mockSuccess({ code: 200 }) await api.getMiniGameByCode('sudoku') expect(lastRequest().url).toMatch(/\/api\/mini-game\/sudoku$/) }) test('completeMiniGame sends POST with game data', async () => { mockSuccess({ code: 200 }) await api.completeMiniGame(5, 'sudoku', 120, 85) expect(lastRequest().data.memberId).toBe(5) expect(lastRequest().data.gameCode).toBe('sudoku') expect(lastRequest().data.completionTime).toBe(120) expect(lastRequest().data.score).toBe(85) }) test('getGameHistory sends POST', async () => { mockSuccess({ code: 200 }) await api.getGameHistory({ memberId: 5 }) expect(lastRequest().url).toMatch(/\/api\/game\/history$/) }) test('getGameLeaderboard sends POST', async () => { mockSuccess({ code: 200 }) await api.getGameLeaderboard({ gameCode: 'sudoku' }) expect(lastRequest().url).toMatch(/\/api\/game\/leaderboard$/) }) }) // --------------- growth module --------------- describe('growth records API', () => { test('createGrowthRecord sends POST', async () => { mockSuccess({ code: 200 }) await api.createGrowthRecord({ memberId: 5, content: '长高了2cm' }) expect(lastRequest().url).toMatch(/\/api\/growth\/record\/create$/) }) test('getGrowthRecordList sends POST', async () => { mockSuccess({ code: 200 }) await api.getGrowthRecordList() expect(lastRequest().url).toMatch(/\/api\/growth\/record\/list$/) }) test('createGrowthRecord with alsoPurchaseAssessment uses merge endpoint', async () => { mockSuccess({ code: 200 }) await api.createGrowthRecord({ memberId: 5, alsoPurchaseAssessment: true }) expect(lastRequest().url).toMatch(/\/api\/growth\/record\/create-with-order$/) }) }) // --------------- energy module --------------- describe('energy API', () => { test('getEnergyOverview sends POST with memberId', async () => { mockSuccess({ code: 200 }) await api.getEnergyOverview(5) expect(lastRequest().data.memberId).toBe(5) }) test('getEnergyLogs sends POST', async () => { mockSuccess({ code: 200 }) await api.getEnergyLogs(5, 'body', 1, 20) expect(lastRequest().data.dimensionCode).toBe('body') }) test('getFamilyEnergySandbox sends POST', async () => { mockSuccess({ code: 200 }) await api.getFamilyEnergySandbox() expect(lastRequest().url).toMatch(/\/api\/energy\/sandbox$/) }) }) // --------------- badges module --------------- describe('badges API', () => { test('getActiveBadges sends POST', async () => { mockSuccess({ code: 200 }) await api.getActiveBadges() expect(lastRequest().url).toMatch(/\/api\/badges\/list$/) }) test('getChildBadges sends POST with memberId', async () => { mockSuccess({ code: 200 }) await api.getChildBadges(5) expect(lastRequest().data.memberId).toBe(5) }) test('toggleBadgeFavorite sends POST', async () => { mockSuccess({ code: 200 }) await api.toggleBadgeFavorite(5, 3) expect(lastRequest().data.memberId).toBe(5) expect(lastRequest().data.badgeId).toBe(3) }) }) // --------------- articles module --------------- describe('articles API', () => { test('getArticleCategories sends POST', async () => { mockSuccess({ code: 200 }) await api.getArticleCategories() expect(lastRequest().url).toMatch(/\/api\/articles\/categories$/) }) test('getArticleList sends POST with data', async () => { mockSuccess({ code: 200 }) await api.getArticleList({ categoryId: 1, page: 1 }) expect(lastRequest().url).toMatch(/\/api\/articles\/list$/) }) test('getArticleDetail sends POST', async () => { mockSuccess({ code: 200 }) await api.getArticleDetail({ id: 42 }) expect(lastRequest().url).toMatch(/\/api\/articles\/detail$/) }) test('getDailyTip sends POST', async () => { mockSuccess({ code: 200 }) await api.getDailyTip() expect(lastRequest().url).toMatch(/\/api\/articles\/daily-tip$/) }) }) // --------------- contact module --------------- describe('contact API', () => { test('getContactList sends POST', async () => { mockSuccess({ code: 200 }) await api.getContactList({ memberId: 5 }) expect(lastRequest().url).toMatch(/\/api\/contact\/list$/) }) test('createContact sends POST with data', async () => { mockSuccess({ code: 200 }) await api.createContact({ name: '爷爷', relation: 'grandfather' }) expect(lastRequest().url).toMatch(/\/api\/contact\/create$/) }) test('deleteContact sends POST with id', async () => { mockSuccess({ code: 200 }) await api.deleteContact(3) expect(lastRequest().url).toMatch(/\/api\/contact\/delete$/) expect(lastRequest().data.id).toBe(3) }) test('importPhoneContact sends POST', async () => { mockSuccess({ code: 200 }) await api.importPhoneContact({ contacts: [{ name: '张三', phone: '13800138000' }] }) expect(lastRequest().url).toMatch(/\/api\/contact\/import-phone$/) }) }) // --------------- vendor module --------------- describe('vendor API', () => { test('vendorApply sends POST with data', async () => { mockSuccess({ code: 200 }) await api.vendorApply({ companyName: '某某公司', type: 'planner' }) expect(lastRequest().url).toMatch(/\/api\/vendor\/apply$/) }) test('vendorStatus sends POST', async () => { mockSuccess({ code: 200 }) await api.vendorStatus() expect(lastRequest().url).toMatch(/\/api\/vendor\/status$/) }) test('vendorInfo sends POST', async () => { mockSuccess({ code: 200 }) await api.vendorInfo() expect(lastRequest().url).toMatch(/\/api\/vendor\/info$/) }) }) // --------------- product module --------------- describe('product API', () => { test('productList sends POST', () => { // productList uses new Promise without success/fail callbacks, // so we check the request synchronously. api.productList({ page: 1, size: 10 }) const opts = lastRequest() expect(opts.method).toBe('POST') expect(opts.url).toMatch(/\/api\/product\/list$/) }) test('productDetail sends POST', async () => { mockSuccess({ code: 200 }) await api.productDetail({ id: 1 }) expect(lastRequest().url).toMatch(/\/api\/product\/detail$/) }) test('productCreate sends POST', async () => { mockSuccess({ code: 200 }) await api.productCreate({ name: '测试商品', price: 100 }) expect(lastRequest().url).toMatch(/\/api\/product\/create$/) }) test('productOrderCreate sends POST', async () => { mockSuccess({ code: 200 }) await api.productOrderCreate({ productId: 1, quantity: 2 }) expect(lastRequest().url).toMatch(/\/api\/product\/order\/create$/) }) }) // --------------- commission / invite --------------- describe('commission & invite API', () => { test('getReferralCode sends POST', async () => { mockSuccess({ code: 200 }) await api.getReferralCode() expect(lastRequest().url).toMatch(/\/api\/invite\/code$/) }) test('bindReferral sends POST with code', async () => { mockSuccess({ code: 200 }) await api.bindReferral('ABC123') expect(lastRequest().data.referralCode).toBe('ABC123') }) test('getCommissionSummary sends POST', async () => { mockSuccess({ code: 200 }) await api.getCommissionSummary() expect(lastRequest().url).toMatch(/\/api\/commission\/summary$/) }) test('applyWithdrawal sends POST', async () => { mockSuccess({ code: 200 }) await api.applyWithdrawal(500, { bank: 'ICBC', account: '6222****' }) expect(lastRequest().data.amount).toBe(500) }) }) // --------------- stats module --------------- describe('stats API', () => { test('getChildCompletionStats sends POST', async () => { mockSuccess({ code: 200 }) await api.getChildCompletionStats({ memberId: 5 }) expect(lastRequest().url).toMatch(/\/api\/stats\/child-completion$/) }) test('getChildOverview sends POST with memberId', async () => { mockSuccess({ code: 200 }) await api.getChildOverview(5) expect(lastRequest().data.memberId).toBe(5) }) }) // --------------- AI chat module --------------- describe('AI chat API', () => { test('aiSendMessage sends POST', async () => { mockSuccess({ code: 200 }) await api.aiSendMessage({ conversationId: 1, content: '你好' }) expect(lastRequest().url).toMatch(/\/api\/ai\/chat\/send$/) }) test('aiGetConversations sends POST', async () => { mockSuccess({ code: 200 }) await api.aiGetConversations() expect(lastRequest().url).toMatch(/\/api\/ai\/chat\/conversations$/) }) }) // --------------- health / wealth module --------------- // --------------- health report module (new) --------------- describe('health report API', () => { test('createHealthReport sends POST', async () => { mockSuccess({ code: 200 }) await api.createHealthReport({ memberId: 5, reportData: { height: 120, weight: 22 } }) expect(lastRequest().url).toMatch(/\/api\/health\/report\/create$/) expect(lastRequest().data.memberId).toBe(5) }) test('getReportList sends POST with memberId', async () => { mockSuccess({ code: 200 }) await api.getReportList(5) expect(lastRequest().url).toMatch(/\/api\/health\/report\/list$/) expect(lastRequest().data.memberId).toBe(5) }) test('getReportDetail sends POST with reportId', async () => { mockSuccess({ code: 200 }) await api.getReportDetail(42) expect(lastRequest().url).toMatch(/\/api\/health\/report\/detail$/) expect(lastRequest().data.reportId).toBe(42) }) test('getLatestHealthReport sends POST with memberId', async () => { mockSuccess({ code: 200 }) await api.getLatestHealthReport(5) expect(lastRequest().url).toMatch(/\/api\/health\/report\/latest$/) expect(lastRequest().data.memberId).toBe(5) }) test('getHealthIndicatorList sends POST with memberId', async () => { mockSuccess({ code: 200 }) await api.getHealthIndicatorList(5) expect(lastRequest().url).toMatch(/\/api\/health\/indicator\/list$/) expect(lastRequest().data.memberId).toBe(5) }) test('createReportSurvey sends POST with reportId and memberId', async () => { mockSuccess({ code: 200 }) await api.createReportSurvey(10, 5) expect(lastRequest().url).toMatch(/\/api\/health\/survey\/create$/) expect(lastRequest().data.reportId).toBe(10) expect(lastRequest().data.memberId).toBe(5) }) test('submitReportSurvey sends POST with surveyId and data', async () => { mockSuccess({ code: 200 }) await api.submitReportSurvey(1, { q1: 'yes', q2: 'no' }) expect(lastRequest().url).toMatch(/\/api\/health\/survey\/submit$/) expect(lastRequest().data.surveyId).toBe(1) expect(lastRequest().data.surveyData.q1).toBe('yes') }) test('getSurveyStatus sends POST with reportId', async () => { mockSuccess({ code: 200 }) await api.getSurveyStatus(10) expect(lastRequest().url).toMatch(/\/api\/health\/survey\/status$/) expect(lastRequest().data.reportId).toBe(10) }) }) describe('health & wealth API', () => { test('healthCheckinList sends POST', async () => { mockSuccess({ code: 200 }) await api.healthCheckinList({ memberId: 5 }) expect(lastRequest().url).toMatch(/\/api\/health\/checkin\/list$/) }) test('getCheckinList sends POST', async () => { mockSuccess({ code: 200 }) await api.getCheckinList({ memberId: 5 }) expect(lastRequest().url).toMatch(/\/api\/wealth\/checkin\/list$/) }) test('getInsuranceList sends POST', async () => { mockSuccess({ code: 200 }) await api.getInsuranceList({ memberId: 5 }) expect(lastRequest().url).toMatch(/\/api\/wealth\/insurance\/list$/) }) }) // --------------- invite card module --------------- describe('invite card API', () => { test('generateInviteCard sends POST', async () => { mockSuccess({ code: 200 }) await api.generateInviteCard('guide', 1) expect(lastRequest().data.type).toBe('guide') }) test('verifyInviteCard sends POST with code', async () => { mockSuccess({ code: 200 }) await api.verifyInviteCard('CARD123') expect(lastRequest().data.code).toBe('CARD123') }) })