| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008 |
- /**
- * 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')
- })
- })
|