api.test.js 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174
  1. /**
  2. * Tests for cfc-frontend utils/api.js
  3. *
  4. * Mocks uni.request to intercept and verify API calls.
  5. */
  6. import * as api from '@/utils/api'
  7. // --------------- helpers ---------------
  8. /** Helper: make uni.request call success(data) on next tick */
  9. function mockSuccess(data) {
  10. global.uni.request.mockImplementation((opts) => {
  11. process.nextTick(() => {
  12. if (opts.success) opts.success({ data })
  13. })
  14. })
  15. }
  16. /** Helper: make uni.request call fail(err) on next tick */
  17. function mockFail(err) {
  18. global.uni.request.mockImplementation((opts) => {
  19. process.nextTick(() => {
  20. if (opts.fail) opts.fail(err)
  21. })
  22. })
  23. }
  24. function mockSequence(responses) {
  25. var i = 0
  26. global.uni.request.mockImplementation((opts) => {
  27. var resp = responses[Math.min(i, responses.length - 1)]
  28. i++
  29. process.nextTick(() => {
  30. if (resp.err) {
  31. if (opts.fail) opts.fail(resp.err)
  32. } else {
  33. if (opts.success) opts.success({ data: resp.data })
  34. }
  35. })
  36. })
  37. }
  38. // --------------- global beforeEach ---------------
  39. beforeEach(() => {
  40. global.uni._storage = {}
  41. global.uni.request.mockReset()
  42. global.uni.showToast.mockReset()
  43. global.uni.reLaunch.mockReset()
  44. global.uni.login.mockReset()
  45. global.getCurrentPages.mockReset()
  46. global.getCurrentPages.mockReturnValue([{ route: 'pages/index-home/index' }])
  47. global.uni.getAccountInfoSync.mockReset()
  48. global.uni.getAccountInfoSync.mockReturnValue({
  49. miniProgram: { envVersion: 'develop' }
  50. })
  51. // api.js 的 3 秒请求去重缓存是模块级单例,跨用例残留会让后续用例拿到上一个用例缓存的
  52. // Promise(同 URL+method+body),表现为「本该 401 却是 200」「lastRequest() 为 undefined」。
  53. // 每个用例前显式清空,保证用例间隔离。
  54. api.clearApiDedup()
  55. })
  56. /** Capture the last uni.request call options */
  57. function lastRequest() {
  58. const calls = global.uni.request.mock.calls
  59. return calls[calls.length - 1][0]
  60. }
  61. // --------------- auth module ---------------
  62. describe('auth API', () => {
  63. beforeEach(() => {
  64. global.uni._storage.token = 'test-token'
  65. global.uni._storage.userId = '42'
  66. })
  67. test('wechatLogin sends POST /api/auth/wechat-login', async () => {
  68. mockSuccess({ code: 200, data: { token: 'jwt' } })
  69. const res = await api.wechatLogin('code123', { nickname: 'test' })
  70. const opts = lastRequest()
  71. expect(opts.method).toBe('POST')
  72. expect(opts.url).toMatch(/\/api\/auth\/wechat-login$/)
  73. expect(opts.data.code).toBe('code123')
  74. expect(res.data.token).toBe('jwt')
  75. })
  76. test('phoneLogin sends POST with phone and code', async () => {
  77. mockSuccess({ code: 200, data: { token: 'jwt' } })
  78. await api.phoneLogin({ phone: '13800138000', code: '1234' })
  79. const opts = lastRequest()
  80. expect(opts.method).toBe('POST')
  81. expect(opts.url).toMatch(/\/api\/auth\/phone-login$/)
  82. expect(opts.data.phone).toBe('13800138000')
  83. })
  84. test('sendCode sends POST with data', async () => {
  85. mockSuccess({ code: 200 })
  86. await api.sendCode({ phone: '13800138000' })
  87. const opts = lastRequest()
  88. expect(opts.method).toBe('POST')
  89. expect(opts.url).toMatch(/\/api\/auth\/send-code$/)
  90. })
  91. test('silentLogin sends POST with code', async () => {
  92. mockSuccess({ code: 200 })
  93. await api.silentLogin('wxcode')
  94. expect(lastRequest().data.code).toBe('wxcode')
  95. })
  96. // 已废弃:后端 /api/auth/auto-login 已改为 410 Gone(裸 openid 换 token 属越权漏洞)。
  97. // 导出仅为排查历史调用保留,不得在新代码中使用;续期请走 silentRelogin()。
  98. test('autoLogin (deprecated) still targets /api/auth/auto-login', async () => {
  99. mockSuccess({ code: 410, message: '该接口已废弃,请改用 /api/auth/silent-login' })
  100. await expect(api.autoLogin('openid_xxx')).rejects.toEqual(
  101. { code: 410, message: '该接口已废弃,请改用 /api/auth/silent-login' }
  102. )
  103. const opts = lastRequest()
  104. expect(opts.url).toMatch(/\/api\/auth\/auto-login$/)
  105. expect(opts.data.openid).toBe('openid_xxx')
  106. })
  107. test('setPassword and verifyPassword send correct endpoints', async () => {
  108. mockSuccess({ code: 200 })
  109. await api.setPassword('secret')
  110. expect(lastRequest().url).toMatch(/\/api\/auth\/set-password$/)
  111. expect(lastRequest().data.password).toBe('secret')
  112. await api.verifyPassword('secret')
  113. expect(lastRequest().url).toMatch(/\/api\/auth\/verify-password$/)
  114. })
  115. test('directRegister sends POST', async () => {
  116. mockSuccess({ code: 200 })
  117. await api.directRegister({ phone: '13800138000', nickname: 'tester' })
  118. expect(lastRequest().url).toMatch(/\/api\/auth\/direct-register$/)
  119. })
  120. test('checkPhone sends POST with phone', async () => {
  121. mockSuccess({ code: 200 })
  122. await api.checkPhone('13800138000')
  123. expect(lastRequest().data.phone).toBe('13800138000')
  124. })
  125. test('wechatPhoneLogin sends POST', async () => {
  126. mockSuccess({ code: 200 })
  127. await api.wechatPhoneLogin({ code: 'wxcode', iv: 'iv', encryptedData: 'enc' })
  128. expect(lastRequest().url).toMatch(/\/api\/auth\/wechat-phone-login$/)
  129. })
  130. test('verifyToken sends POST /api/auth/verify with bearer token', async () => {
  131. mockSuccess({ code: 200, data: { userId: 42, role: 'parent' } })
  132. const res = await api.verifyToken()
  133. const opts = lastRequest()
  134. expect(opts.method).toBe('POST')
  135. expect(opts.url).toMatch(/\/api\/auth\/verify$/)
  136. expect(opts.header.Authorization).toBe('Bearer test-token')
  137. expect(res.data.userId).toBe(42)
  138. })
  139. test('verifyToken with 401 (token expired) clears auth and redirects to login', async () => {
  140. jest.useFakeTimers()
  141. mockSuccess({ code: 401, message: 'Token无效或已过期', data: null })
  142. await expect(api.verifyToken()).rejects.toEqual({ code: 401, message: 'Token无效或已过期', data: null })
  143. expect(global.uni._storage.token).toBeUndefined()
  144. // 触发 _clearAuthAndRedirect 内 1.5s setTimeout(reLaunch 在此定时器回调中执行)
  145. jest.runAllTimers()
  146. expect(global.uni.reLaunch).toHaveBeenCalledWith({ url: '/pages/login/login' })
  147. expect(global.uni.showToast).toHaveBeenCalled()
  148. jest.useRealTimers()
  149. })
  150. test('verifyToken with 401 (user deleted) clears auth and redirects to login', async () => {
  151. jest.useFakeTimers()
  152. mockSuccess({ code: 401, message: '用户不存在', data: null })
  153. await expect(api.verifyToken()).rejects.toEqual({ code: 401, message: '用户不存在', data: null })
  154. expect(global.uni._storage.token).toBeUndefined()
  155. jest.runAllTimers()
  156. expect(global.uni.reLaunch).toHaveBeenCalledWith({ url: '/pages/login/login' })
  157. expect(global.uni.showToast).toHaveBeenCalled()
  158. jest.useRealTimers()
  159. })
  160. // 静默续期 = uni.login() 取微信签发的一次性 code → /api/auth/silent-login(服务端 code2Session)。
  161. // 本地 openid 只作为「本设备曾登录过」的触发条件,不作为凭证上送。
  162. function mockUniLoginCode(code) {
  163. global.uni.login.mockImplementation((opts) => {
  164. process.nextTick(() => {
  165. if (opts.success) opts.success({ code: code || 'wxcode' })
  166. })
  167. })
  168. }
  169. test('401 silently re-logins via wx.login + code2Session and retries original request', async () => {
  170. global.uni.reLaunch.mockReset()
  171. global.uni.showToast.mockReset()
  172. global.uni._storage.openid = 'openid_abc'
  173. global.uni._storage.token = 'expired-token'
  174. mockUniLoginCode('fresh-wxcode')
  175. mockSequence([
  176. { data: { code: 401, message: 'Token无效或已过期', data: null } },
  177. { data: { code: 200, data: { token: 'new-jwt', userId: 42, role: 'parent', familyId: 1, openid: 'openid_abc' } } },
  178. { data: { code: 200, data: { userId: 42, role: 'parent' } } }
  179. ])
  180. const res = await api.verifyToken()
  181. expect(res.data.userId).toBe(42)
  182. expect(global.uni._storage.token).toBe('new-jwt')
  183. // 续期必须走 code2Session,且不得把 openid 当凭证发送
  184. const calls = global.uni.request.mock.calls.map((c) => c[0])
  185. const renewReq = calls.find((c) => /\/api\/auth\/silent-login/.test(c.url))
  186. expect(renewReq).toBeDefined()
  187. expect(renewReq.data.code).toBe('fresh-wxcode')
  188. expect(renewReq.data.openid).toBeUndefined()
  189. // 已废弃的裸 openid 换 token 接口不得被调用
  190. expect(calls.find((c) => /\/api\/auth\/auto-login/.test(c.url))).toBeUndefined()
  191. expect(global.uni.reLaunch).not.toHaveBeenCalled()
  192. expect(global.uni.showToast).not.toHaveBeenCalled()
  193. })
  194. test('401 retry does not duplicate query params in the replayed request URL', async () => {
  195. global.uni._storage.openid = 'openid_abc'
  196. global.uni._storage.token = 'expired-token'
  197. mockUniLoginCode('fresh-wxcode')
  198. mockSequence([
  199. { data: { code: 401, message: 'Token无效或已过期', data: null } },
  200. { data: { code: 200, data: { token: 'new-jwt', userId: 42, role: 'parent', familyId: 1, openid: 'openid_abc' } } },
  201. { data: { code: 200, data: { ok: true } } }
  202. ])
  203. // publishPackage 通过 options 传 query(?publish=true),是最容易暴露重复拼接的调用形态
  204. await api.publishPackage(7, true)
  205. const calls = global.uni.request.mock.calls.map((c) => c[0])
  206. const hits = calls.filter((c) => /\/api\/packages\/7\/publish/.test(c.url))
  207. expect(hits.length).toBe(2) // 原始请求 1 次 + 401 续期后重放 1 次
  208. // 修复前:_handle401 收到的是已拼过 query 的 url,重放时 options 又拼一次 → ?publish=true&publish=true
  209. hits.forEach((c) => {
  210. expect(c.url).toBe(c.url.split('?')[0] + '?publish=true')
  211. expect((c.url.match(/publish=/g) || []).length).toBe(1)
  212. })
  213. })
  214. test('401 during app launch window clears auth but does NOT navigate to login subpackage', async () => {
  215. jest.useFakeTimers()
  216. global.uni._storage.openid = 'openid_abc'
  217. global.uni._storage.token = 'expired-token'
  218. // 仍停在 splash:pages/login 是分包,此刻尚未下载,
  219. // reLaunch 过去会报 'Page "pages/login/login" has not been registered yet'
  220. global.getCurrentPages.mockReturnValue([{ route: 'pages/splash/index' }])
  221. mockUniLoginCode()
  222. mockSequence([
  223. { data: { code: 401, message: 'Token无效或已过期', data: null } },
  224. { data: { code: 500, message: '用户不存在,请先登录', data: null } }
  225. ])
  226. await expect(api.verifyToken()).rejects.toEqual({ code: 401, message: 'Token无效或已过期', data: null })
  227. jest.runAllTimers()
  228. // 登录态必须清掉
  229. expect(global.uni._storage.token).toBeUndefined()
  230. // 但启动窗口内绝不跳登录页,交由 splash 落地首页(主包)
  231. expect(global.uni.reLaunch).not.toHaveBeenCalled()
  232. jest.useRealTimers()
  233. })
  234. test('401 during app launch window (no page registered yet) also skips navigation', async () => {
  235. jest.useFakeTimers()
  236. global.uni._storage.openid = 'openid_abc'
  237. global.uni._storage.token = 'expired-token'
  238. // 首屏尚未注册,getCurrentPages() 为空
  239. global.getCurrentPages.mockReturnValue([])
  240. mockUniLoginCode()
  241. mockSequence([
  242. { data: { code: 401, message: 'Token无效或已过期', data: null } },
  243. { data: { code: 500, message: '用户不存在,请先登录', data: null } }
  244. ])
  245. await expect(api.verifyToken()).rejects.toEqual({ code: 401, message: 'Token无效或已过期', data: null })
  246. jest.runAllTimers()
  247. expect(global.uni._storage.token).toBeUndefined()
  248. expect(global.uni.reLaunch).not.toHaveBeenCalled()
  249. jest.useRealTimers()
  250. })
  251. test('401 falls back to login page when silent re-login is refused', async () => {
  252. jest.useFakeTimers()
  253. global.uni._storage.openid = 'openid_abc'
  254. global.uni._storage.token = 'expired-token'
  255. mockUniLoginCode()
  256. mockSequence([
  257. { data: { code: 401, message: 'Token无效或已过期', data: null } },
  258. { data: { code: 500, message: '用户不存在,请先登录', data: null } }
  259. ])
  260. await expect(api.verifyToken()).rejects.toEqual({ code: 401, message: 'Token无效或已过期', data: null })
  261. jest.runAllTimers()
  262. expect(global.uni.reLaunch).toHaveBeenCalledWith({ url: '/pages/login/login' })
  263. expect(global.uni.showToast).toHaveBeenCalled()
  264. jest.useRealTimers()
  265. })
  266. test('401 with no cached openid (never logged in on this device) goes straight to login', async () => {
  267. jest.useFakeTimers()
  268. global.uni._storage.token = 'expired-token'
  269. mockSequence([
  270. { data: { code: 401, message: 'Token无效或已过期', data: null } }
  271. ])
  272. await expect(api.verifyToken()).rejects.toEqual({ code: 401, message: 'Token无效或已过期', data: null })
  273. // 本设备从未登录过 → 不应调用 uni.login 浪费一次 code2Session
  274. expect(global.uni.login).not.toHaveBeenCalled()
  275. jest.runAllTimers()
  276. expect(global.uni.reLaunch).toHaveBeenCalledWith({ url: '/pages/login/login' })
  277. jest.useRealTimers()
  278. })
  279. test('silentRelogin returns null when wx.login yields no code', async () => {
  280. global.uni._storage.openid = 'openid_abc'
  281. global.uni.login.mockImplementation((opts) => {
  282. process.nextTick(() => { if (opts.success) opts.success({}) })
  283. })
  284. await expect(api.silentRelogin()).resolves.toBeNull()
  285. expect(global.uni.request).not.toHaveBeenCalled()
  286. })
  287. test('silentRelogin returns null when wx.login fails', async () => {
  288. global.uni._storage.openid = 'openid_abc'
  289. global.uni.login.mockImplementation((opts) => {
  290. process.nextTick(() => { if (opts.fail) opts.fail({ errMsg: 'login:fail' }) })
  291. })
  292. await expect(api.silentRelogin()).resolves.toBeNull()
  293. expect(global.uni.request).not.toHaveBeenCalled()
  294. })
  295. test('silentRelogin does not issue a renew request on 401 (no recursive renewal)', async () => {
  296. global.uni._storage.openid = 'openid_abc'
  297. global.uni._storage.token = 'expired-token'
  298. mockUniLoginCode()
  299. // 续期接口自身返回 401:必须直接失败,不能再触发一次 silentRelogin,否则会递归续期/误清登录态
  300. mockSuccess({ code: 401, message: 'Token无效或已过期', data: null })
  301. await expect(api.silentRelogin()).resolves.toBeNull()
  302. expect(global.uni.request.mock.calls.length).toBe(1)
  303. // 续期失败不得清空登录态(那是「主动登出」才做的事)
  304. expect(global.uni._storage.token).toBe('expired-token')
  305. expect(global.uni.reLaunch).not.toHaveBeenCalled()
  306. })
  307. test('Authorization header includes Bearer token', async () => {
  308. mockSuccess({ code: 200 })
  309. global.uni._storage.token = 'my-jwt-token'
  310. await api.getUserInfo()
  311. const opts = lastRequest()
  312. expect(opts.header.Authorization).toBe('Bearer my-jwt-token')
  313. })
  314. test('X-User-Id header is NOT sent (removed for security)', async () => {
  315. mockSuccess({ code: 200 })
  316. global.uni._storage.userId = '99'
  317. await api.getUserInfo()
  318. expect(lastRequest().header['X-User-Id']).toBeUndefined()
  319. })
  320. test('rejects on non-200 code', async () => {
  321. mockSuccess({ code: 400, message: 'Bad Request' })
  322. await expect(api.getUserInfo()).rejects.toEqual({ code: 400, message: 'Bad Request' })
  323. })
  324. test('rejects on network failure', async () => {
  325. mockFail(new Error('Network error'))
  326. await expect(api.getUserInfo()).rejects.toThrow('Network error')
  327. })
  328. })
  329. // --------------- streets module ---------------
  330. describe('street address API', () => {
  331. test('getProvinces sends POST to /api/streets/provinces', async () => {
  332. mockSuccess({ code: 200, data: [] })
  333. await api.getProvinces()
  334. expect(lastRequest().url).toMatch(/\/api\/streets\/provinces$/)
  335. })
  336. test('getStreetChildren sends POST with parentId', async () => {
  337. mockSuccess({ code: 200 })
  338. await api.getStreetChildren(110000)
  339. expect(lastRequest().url).toMatch(/\/api\/streets\/children\/110000$/)
  340. })
  341. test('getStreetById sends POST to correct URL', async () => {
  342. mockSuccess({ code: 200 })
  343. await api.getStreetById(110101)
  344. expect(lastRequest().url).toMatch(/\/api\/streets\/110101$/)
  345. })
  346. test('getStreetPath sends POST', async () => {
  347. mockSuccess({ code: 200 })
  348. await api.getStreetPath(110101)
  349. expect(lastRequest().url).toMatch(/\/api\/streets\/110101\/path$/)
  350. })
  351. test('searchStreet sends POST with keyword', async () => {
  352. mockSuccess({ code: 200 })
  353. await api.searchStreet('北京')
  354. expect(lastRequest().data.keyword).toBe('北京')
  355. })
  356. test('matchByStreet sends POST with streetId', async () => {
  357. mockSuccess({ code: 200 })
  358. await api.matchByStreet(110101)
  359. expect(lastRequest().url).toMatch(/\/110101\/match$/)
  360. })
  361. })
  362. // --------------- user / family module ---------------
  363. describe('user & family API', () => {
  364. test('getUserInfo sends POST', async () => {
  365. mockSuccess({ code: 200 })
  366. await api.getUserInfo()
  367. expect(lastRequest().url).toMatch(/\/api\/user\/info$/)
  368. })
  369. test('updateUserInfo sends POST with data', async () => {
  370. mockSuccess({ code: 200 })
  371. await api.updateUserInfo({ nickname: 'new-name' })
  372. expect(lastRequest().data.nickname).toBe('new-name')
  373. })
  374. test('joinFamily sends POST with inviteCode', async () => {
  375. mockSuccess({ code: 200 })
  376. await api.joinFamily('ABC123')
  377. expect(lastRequest().data.inviteCode).toBe('ABC123')
  378. })
  379. test('createFamily sends POST with name', async () => {
  380. mockSuccess({ code: 200 })
  381. await api.createFamily('幸福之家')
  382. expect(lastRequest().data.name).toBe('幸福之家')
  383. })
  384. test('getFamilyMemberList sends POST to unified endpoint', async () => {
  385. mockSuccess({ code: 200 })
  386. await api.getFamilyMemberList()
  387. expect(lastRequest().url).toMatch(/\/api\/family\/member\/list$/)
  388. })
  389. test('getFamilyMemberList sends visibleOnly param', async () => {
  390. mockSuccess({ code: 200 })
  391. await api.getFamilyMemberList({ visibleOnly: true })
  392. expect(lastRequest().url).toMatch(/\/api\/family\/member\/list$/)
  393. expect(lastRequest().data.visibleOnly).toBe(true)
  394. })
  395. test('getFamilyMemberList sends includeFamily param', async () => {
  396. mockSuccess({ code: 200 })
  397. await api.getFamilyMemberList({ includeFamily: true })
  398. expect(lastRequest().url).toMatch(/\/api\/family\/member\/list$/)
  399. expect(lastRequest().data.includeFamily).toBe(true)
  400. })
  401. test('updateMemberVisibility sends POST with params', async () => {
  402. mockSuccess({ code: 200 })
  403. await api.updateMemberVisibility(1, 'child', true)
  404. expect(lastRequest().data.memberId).toBe(1)
  405. expect(lastRequest().data.memberType).toBe('child')
  406. expect(lastRequest().data.showToFamily).toBe(true)
  407. })
  408. test('switchMode sends POST with mode', async () => {
  409. mockSuccess({ code: 200 })
  410. await api.switchMode('child')
  411. expect(lastRequest().data.mode).toBe('child')
  412. })
  413. test('switchToChild sends POST with memberId', async () => {
  414. mockSuccess({ code: 200 })
  415. await api.switchToChild(5)
  416. expect(lastRequest().data.memberId).toBe(5)
  417. })
  418. test('switchBackToParent sends POST', async () => {
  419. mockSuccess({ code: 200 })
  420. await api.switchBackToParent()
  421. expect(lastRequest().url).toMatch(/\/api\/user\/switch-back-to-parent$/)
  422. })
  423. test('getChildren sends POST to unified member list', async () => {
  424. mockSuccess({ code: 200 })
  425. await api.getChildren()
  426. expect(lastRequest().url).toMatch(/\/api\/family\/member\/list$/)
  427. })
  428. test('createChild sends POST with data', async () => {
  429. mockSuccess({ code: 200 })
  430. await api.createChild({ name: '小宝', gender: 'male' })
  431. expect(lastRequest().url).toMatch(/\/api\/family\/user\/children$/)
  432. expect(lastRequest().data.name).toBe('小宝')
  433. })
  434. test('updateChild sends POST merged with memberId', async () => {
  435. mockSuccess({ code: 200 })
  436. await api.updateChild(5, { nickname: '大宝' })
  437. expect(lastRequest().data.memberId).toBe(5)
  438. expect(lastRequest().data.nickname).toBe('大宝')
  439. })
  440. test('kickFamilyMember sends POST with targetUserId', async () => {
  441. mockSuccess({ code: 200 })
  442. await api.kickFamilyMember(10)
  443. expect(lastRequest().data.targetUserId).toBe(10)
  444. })
  445. })
  446. // --------------- membership module ---------------
  447. describe('membership API', () => {
  448. test('getMembershipLevels sends POST', async () => {
  449. mockSuccess({ code: 200 })
  450. await api.getMembershipLevels()
  451. expect(lastRequest().url).toMatch(/\/api\/membership\/levels$/)
  452. })
  453. test('getMyMembership sends POST', async () => {
  454. mockSuccess({ code: 200 })
  455. await api.getMyMembership()
  456. expect(lastRequest().url).toMatch(/\/api\/membership\/my$/)
  457. })
  458. test('createOrder sends POST with levelCode and paymentType', async () => {
  459. mockSuccess({ code: 200 })
  460. await api.createOrder('vip', 'wechat')
  461. expect(lastRequest().data.levelCode).toBe('vip')
  462. expect(lastRequest().data.paymentType).toBe('wechat')
  463. })
  464. })
  465. // --------------- tasks module ---------------
  466. describe('tasks API', () => {
  467. test('createTask sends POST with data', async () => {
  468. mockSuccess({ code: 200 })
  469. await api.createTask({ title: '晨读', memberId: 5 })
  470. expect(lastRequest().url).toMatch(/\/api\/tasks$/)
  471. expect(lastRequest().data.title).toBe('晨读')
  472. })
  473. test('getTodayTasks sends POST with memberId', async () => {
  474. mockSuccess({ code: 200 })
  475. await api.getTodayTasks(5)
  476. expect(lastRequest().data.memberId).toBe(5)
  477. })
  478. test('getPendingReviewTasks sends POST', async () => {
  479. mockSuccess({ code: 200 })
  480. await api.getPendingReviewTasks()
  481. expect(lastRequest().url).toMatch(/\/api\/tasks\/pending-review$/)
  482. })
  483. test('approveTask sends POST with approved:true', async () => {
  484. mockSuccess({ code: 200 })
  485. await api.approveTask(10)
  486. expect(lastRequest().url).toMatch(/\/api\/tasks\/10\/review$/)
  487. expect(lastRequest().data.approved).toBe(true)
  488. })
  489. test('rejectTask sends POST with approved:false', async () => {
  490. mockSuccess({ code: 200 })
  491. await api.rejectTask(10)
  492. expect(lastRequest().data.approved).toBe(false)
  493. })
  494. test('completeTask sends POST with memberId and photoUrl', async () => {
  495. mockSuccess({ code: 200 })
  496. await api.completeTask(10, 5, 'http://photo.url')
  497. expect(lastRequest().data.memberId).toBe(5)
  498. expect(lastRequest().data.photoUrl).toBe('http://photo.url')
  499. })
  500. test('reviewTask sends POST with data', async () => {
  501. mockSuccess({ code: 200 })
  502. await api.reviewTask(10, { approved: true, comment: '很好' })
  503. expect(lastRequest().url).toMatch(/\/api\/tasks\/10\/review$/)
  504. expect(lastRequest().data.comment).toBe('很好')
  505. })
  506. test('getTaskHistory sends POST with pagination', async () => {
  507. mockSuccess({ code: 200 })
  508. await api.getTaskHistory(5, 1, 20)
  509. expect(lastRequest().data.memberId).toBe(5)
  510. expect(lastRequest().data.page).toBe(1)
  511. expect(lastRequest().data.size).toBe(20)
  512. })
  513. test('deleteTask sends POST with _method DELETE', async () => {
  514. mockSuccess({ code: 200 })
  515. await api.deleteTask(10)
  516. expect(lastRequest().method).toBe('POST')
  517. expect(lastRequest().url).toMatch(/\/api\/tasks\/10$/)
  518. expect(lastRequest().data._method).toBe('DELETE')
  519. })
  520. test('batchCompleteTasks sends POST with array', async () => {
  521. mockSuccess({ code: 200 })
  522. await api.batchCompleteTasks([1, 2, 3])
  523. expect(lastRequest().data.taskIds).toEqual([1, 2, 3])
  524. })
  525. test('getTodayParentTasks sends POST', async () => {
  526. mockSuccess({ code: 200 })
  527. await api.getTodayParentTasks()
  528. expect(lastRequest().url).toMatch(/\/api\/tasks\/today-parent$/)
  529. })
  530. })
  531. // --------------- points module ---------------
  532. describe('points API', () => {
  533. test('getPointsBalance sends POST with memberId', async () => {
  534. mockSuccess({ code: 200 })
  535. await api.getPointsBalance(5)
  536. expect(lastRequest().data.memberId).toBe(5)
  537. })
  538. test('getPointsLogs sends POST with pagination', async () => {
  539. mockSuccess({ code: 200 })
  540. await api.getPointsLogs(5, 2, 20)
  541. expect(lastRequest().data.memberId).toBe(5)
  542. expect(lastRequest().data.page).toBe(2)
  543. expect(lastRequest().data.size).toBe(20)
  544. })
  545. })
  546. // --------------- rewards / wishes module ---------------
  547. describe('rewards & wishes API', () => {
  548. test('createReward sends POST', async () => {
  549. mockSuccess({ code: 200 })
  550. await api.createReward({ name: '玩具', points: 100 })
  551. expect(lastRequest().data.name).toBe('玩具')
  552. })
  553. test('getWishlist sends POST with memberId', async () => {
  554. mockSuccess({ code: 200 })
  555. await api.getWishlist(5)
  556. expect(lastRequest().data.memberId).toBe(5)
  557. })
  558. test('exchangeReward sends POST', async () => {
  559. mockSuccess({ code: 200 })
  560. await api.exchangeReward(1, 5)
  561. expect(lastRequest().data.memberId).toBe(5)
  562. })
  563. test('getPendingWishes sends POST with empty body', async () => {
  564. mockSuccess({ code: 200 })
  565. await api.getPendingWishes()
  566. expect(lastRequest().data).toEqual({})
  567. })
  568. test('getWishes sends POST with params', async () => {
  569. mockSuccess({ code: 200 })
  570. await api.getWishes({ memberId: 5, status: 'pending' })
  571. expect(lastRequest().data.memberId).toBe(5)
  572. })
  573. test('setWishPrice sends POST with pointsRequired', async () => {
  574. mockSuccess({ code: 200 })
  575. await api.setWishPrice(1, 200)
  576. expect(lastRequest().data.pointsRequired).toBe(200)
  577. })
  578. test('approveWishExchange sends POST', async () => {
  579. mockSuccess({ code: 200 })
  580. await api.approveWishExchange(1, true)
  581. expect(lastRequest().data.approved).toBe(true)
  582. })
  583. test('createWish sends POST with data', async () => {
  584. mockSuccess({ code: 200 })
  585. await api.createWish({ title: '新书包', memberId: 5 })
  586. expect(lastRequest().data.title).toBe('新书包')
  587. })
  588. })
  589. // --------------- identity parameter hardening ---------------
  590. // 前端不再向后端发送可伪造的 userId / familyId / childId(仅 memberId),
  591. // 身份一律由后端从 JWT 推导。以下断言锁定这一不变量。
  592. describe('identity parameter hardening', () => {
  593. beforeEach(() => {
  594. global.uni._storage.token = 'test-token'
  595. })
  596. test('request() does NOT send X-User-Id header', async () => {
  597. mockSuccess({ code: 200 })
  598. await api.getPendingWishes()
  599. const opts = lastRequest()
  600. expect(opts.header['X-User-Id']).toBeUndefined()
  601. })
  602. test('getMyAssessmentResults sends empty body, no userId', async () => {
  603. mockSuccess({ code: 200 })
  604. await api.getMyAssessmentResults()
  605. const opts = lastRequest()
  606. expect(opts.data).toEqual({})
  607. expect(opts.data.userId).toBeUndefined()
  608. })
  609. test('getChildAssessmentResults sends empty body, no userId', async () => {
  610. mockSuccess({ code: 200 })
  611. await api.getChildAssessmentResults()
  612. const opts = lastRequest()
  613. expect(opts.data).toEqual({})
  614. expect(opts.data.userId).toBeUndefined()
  615. })
  616. test('getTodayTasksByCategory sends only memberId and category', async () => {
  617. mockSuccess({ code: 200 })
  618. await api.getTodayTasksByCategory(7, 'wisdom')
  619. const opts = lastRequest()
  620. expect(opts.data.memberId).toBe(7)
  621. expect(opts.data.category).toBe('wisdom')
  622. expect(opts.data.childId).toBeUndefined()
  623. expect(opts.data.userId).toBeUndefined()
  624. })
  625. })
  626. // --------------- guide / packages module ---------------
  627. describe('guide & packages API', () => {
  628. test('getGuidePackages sends POST to my list', async () => {
  629. mockSuccess({ code: 200 })
  630. await api.getGuidePackages(1)
  631. expect(lastRequest().url).toMatch(/\/api\/guide\/packages\/my$/)
  632. expect(lastRequest().data.guideId).toBe(1)
  633. })
  634. test('registerGuide sends POST', async () => {
  635. mockSuccess({ code: 200 })
  636. await api.registerGuide({ name: '张老师', level: 'senior' })
  637. expect(lastRequest().url).toMatch(/\/api\/guide\/register$/)
  638. })
  639. test('generateBindInvite sends POST', async () => {
  640. mockSuccess({ code: 200 })
  641. await api.generateBindInvite()
  642. expect(lastRequest().url).toMatch(/\/api\/guide\/bind\/invite$/)
  643. })
  644. test('getBoundFamilies sends POST', async () => {
  645. mockSuccess({ code: 200 })
  646. await api.getBoundFamilies()
  647. expect(lastRequest().url).toMatch(/\/api\/guide\/families$/)
  648. })
  649. test('getTeamMembers sends POST', async () => {
  650. mockSuccess({ code: 200 })
  651. await api.getTeamMembers()
  652. expect(lastRequest().url).toMatch(/\/api\/guide\/team\/members$/)
  653. })
  654. test('getGuideOrders sends POST', async () => {
  655. mockSuccess({ code: 200 })
  656. await api.getGuideOrders()
  657. expect(lastRequest().url).toMatch(/\/api\/guide\/orders\/list$/)
  658. })
  659. test('getGuideMessages sends POST', async () => {
  660. mockSuccess({ code: 200 })
  661. await api.getGuideMessages()
  662. expect(lastRequest().url).toMatch(/\/api\/guide\/messages\/list$/)
  663. })
  664. })
  665. // --------------- payment module ---------------
  666. describe('payment API', () => {
  667. test('createWechatPayOrder sends POST', async () => {
  668. mockSuccess({ code: 200 })
  669. await api.createWechatPayOrder('NO001', '测试订单', 100)
  670. expect(lastRequest().data.orderNo).toBe('NO001')
  671. expect(lastRequest().data.amount).toBe(100)
  672. })
  673. })
  674. // --------------- assessment module ---------------
  675. describe('assessment API', () => {
  676. test('createAssessmentAppointment sends POST', async () => {
  677. mockSuccess({ code: 200 })
  678. await api.createAssessmentAppointment({ memberId: 5, date: '2026-06-20' })
  679. expect(lastRequest().url).toMatch(/\/api\/dan-assessment\/appointment\/create$/)
  680. })
  681. test('payAssessmentOrder sends POST', async () => {
  682. mockSuccess({ code: 200 })
  683. await api.payAssessmentOrder('ORDER001', 'wechat')
  684. expect(lastRequest().data.orderNo).toBe('ORDER001')
  685. })
  686. test('recordAssessmentResult sends POST', async () => {
  687. mockSuccess({ code: 200 })
  688. await api.recordAssessmentResult({ orderId: 1, score: 85 })
  689. expect(lastRequest().url).toMatch(/\/api\/dan-assessment\/result\/record$/)
  690. })
  691. })
  692. // --------------- mini-games module ---------------
  693. describe('mini-games API', () => {
  694. test('getMiniGameList sends POST', async () => {
  695. mockSuccess({ code: 200 })
  696. await api.getMiniGameList()
  697. expect(lastRequest().url).toMatch(/\/api\/mini-game\/list$/)
  698. })
  699. test('getMiniGameByCode sends POST', async () => {
  700. mockSuccess({ code: 200 })
  701. await api.getMiniGameByCode('sudoku')
  702. expect(lastRequest().url).toMatch(/\/api\/mini-game\/sudoku$/)
  703. })
  704. test('completeMiniGame sends POST with game data', async () => {
  705. mockSuccess({ code: 200 })
  706. await api.completeMiniGame(5, 'sudoku', 120, 85)
  707. expect(lastRequest().data.memberId).toBe(5)
  708. expect(lastRequest().data.gameCode).toBe('sudoku')
  709. expect(lastRequest().data.completionTime).toBe(120)
  710. expect(lastRequest().data.score).toBe(85)
  711. })
  712. test('getGameHistory sends POST', async () => {
  713. mockSuccess({ code: 200 })
  714. await api.getGameHistory({ memberId: 5 })
  715. expect(lastRequest().url).toMatch(/\/api\/game\/history$/)
  716. })
  717. test('getGameLeaderboard sends POST', async () => {
  718. mockSuccess({ code: 200 })
  719. await api.getGameLeaderboard({ gameCode: 'sudoku' })
  720. expect(lastRequest().url).toMatch(/\/api\/game\/leaderboard$/)
  721. })
  722. })
  723. // --------------- growth module ---------------
  724. describe('growth records API', () => {
  725. test('createGrowthRecord sends POST', async () => {
  726. mockSuccess({ code: 200 })
  727. await api.createGrowthRecord({ memberId: 5, content: '长高了2cm' })
  728. expect(lastRequest().url).toMatch(/\/api\/growth\/record\/create$/)
  729. })
  730. test('getGrowthRecordList sends POST', async () => {
  731. mockSuccess({ code: 200 })
  732. await api.getGrowthRecordList()
  733. expect(lastRequest().url).toMatch(/\/api\/growth\/record\/list$/)
  734. })
  735. test('createGrowthRecord with alsoPurchaseAssessment uses merge endpoint', async () => {
  736. mockSuccess({ code: 200 })
  737. await api.createGrowthRecord({ memberId: 5, alsoPurchaseAssessment: true })
  738. expect(lastRequest().url).toMatch(/\/api\/growth\/record\/create-with-order$/)
  739. })
  740. })
  741. // --------------- energy module ---------------
  742. describe('energy API', () => {
  743. test('getEnergyOverview sends POST with memberId', async () => {
  744. mockSuccess({ code: 200 })
  745. await api.getEnergyOverview(5)
  746. expect(lastRequest().data.memberId).toBe(5)
  747. })
  748. test('getEnergyLogs sends POST', async () => {
  749. mockSuccess({ code: 200 })
  750. await api.getEnergyLogs(5, 'body', 1, 20)
  751. expect(lastRequest().data.dimensionCode).toBe('body')
  752. })
  753. test('getFamilyEnergySandbox sends POST', async () => {
  754. mockSuccess({ code: 200 })
  755. await api.getFamilyEnergySandbox()
  756. expect(lastRequest().url).toMatch(/\/api\/energy\/sandbox$/)
  757. })
  758. })
  759. // --------------- badges module ---------------
  760. describe('badges API', () => {
  761. test('getActiveBadges sends POST', async () => {
  762. mockSuccess({ code: 200 })
  763. await api.getActiveBadges()
  764. expect(lastRequest().url).toMatch(/\/api\/badges\/list$/)
  765. })
  766. test('getChildBadges sends POST with memberId', async () => {
  767. mockSuccess({ code: 200 })
  768. await api.getChildBadges(5)
  769. expect(lastRequest().data.memberId).toBe(5)
  770. })
  771. test('toggleBadgeFavorite sends POST', async () => {
  772. mockSuccess({ code: 200 })
  773. await api.toggleBadgeFavorite(5, 3)
  774. expect(lastRequest().data.memberId).toBe(5)
  775. expect(lastRequest().data.badgeId).toBe(3)
  776. })
  777. })
  778. // --------------- articles module ---------------
  779. describe('articles API', () => {
  780. test('getArticleCategories sends POST', async () => {
  781. mockSuccess({ code: 200 })
  782. await api.getArticleCategories()
  783. expect(lastRequest().url).toMatch(/\/api\/articles\/categories$/)
  784. })
  785. test('getArticleList sends POST with data', async () => {
  786. mockSuccess({ code: 200 })
  787. await api.getArticleList({ categoryId: 1, page: 1 })
  788. expect(lastRequest().url).toMatch(/\/api\/articles\/list$/)
  789. })
  790. test('getArticleDetail sends POST', async () => {
  791. mockSuccess({ code: 200 })
  792. await api.getArticleDetail({ id: 42 })
  793. expect(lastRequest().url).toMatch(/\/api\/articles\/detail$/)
  794. })
  795. test('getDailyTip sends POST', async () => {
  796. mockSuccess({ code: 200 })
  797. await api.getDailyTip()
  798. expect(lastRequest().url).toMatch(/\/api\/articles\/daily-tip$/)
  799. })
  800. })
  801. // --------------- contact module ---------------
  802. describe('contact API', () => {
  803. test('getContactList sends POST', async () => {
  804. mockSuccess({ code: 200 })
  805. await api.getContactList({ memberId: 5 })
  806. expect(lastRequest().url).toMatch(/\/api\/contact\/list$/)
  807. })
  808. test('createContact sends POST with data', async () => {
  809. mockSuccess({ code: 200 })
  810. await api.createContact({ name: '爷爷', relation: 'grandfather' })
  811. expect(lastRequest().url).toMatch(/\/api\/contact\/create$/)
  812. })
  813. test('deleteContact sends POST with id', async () => {
  814. mockSuccess({ code: 200 })
  815. await api.deleteContact(3)
  816. expect(lastRequest().url).toMatch(/\/api\/contact\/delete$/)
  817. expect(lastRequest().data.id).toBe(3)
  818. })
  819. test('importPhoneContact sends POST', async () => {
  820. mockSuccess({ code: 200 })
  821. await api.importPhoneContact({ contacts: [{ name: '张三', phone: '13800138000' }] })
  822. expect(lastRequest().url).toMatch(/\/api\/contact\/import-phone$/)
  823. })
  824. })
  825. // --------------- vendor module ---------------
  826. describe('vendor API', () => {
  827. test('vendorApply sends POST with data', async () => {
  828. mockSuccess({ code: 200 })
  829. await api.vendorApply({ companyName: '某某公司', type: 'planner' })
  830. expect(lastRequest().url).toMatch(/\/api\/vendor\/apply$/)
  831. })
  832. test('vendorStatus sends POST', async () => {
  833. mockSuccess({ code: 200 })
  834. await api.vendorStatus()
  835. expect(lastRequest().url).toMatch(/\/api\/vendor\/status$/)
  836. })
  837. test('vendorInfo sends POST', async () => {
  838. mockSuccess({ code: 200 })
  839. await api.vendorInfo()
  840. expect(lastRequest().url).toMatch(/\/api\/vendor\/info$/)
  841. })
  842. })
  843. // --------------- product module ---------------
  844. describe('product API', () => {
  845. test('productList sends POST', () => {
  846. // productList uses new Promise without success/fail callbacks,
  847. // so we check the request synchronously.
  848. api.productList({ page: 1, size: 10 })
  849. const opts = lastRequest()
  850. expect(opts.method).toBe('POST')
  851. expect(opts.url).toMatch(/\/api\/product\/list$/)
  852. })
  853. test('productDetail sends POST', async () => {
  854. mockSuccess({ code: 200 })
  855. await api.productDetail({ id: 1 })
  856. expect(lastRequest().url).toMatch(/\/api\/product\/detail$/)
  857. })
  858. test('productCreate sends POST', async () => {
  859. mockSuccess({ code: 200 })
  860. await api.productCreate({ name: '测试商品', price: 100 })
  861. expect(lastRequest().url).toMatch(/\/api\/product\/create$/)
  862. })
  863. test('productOrderCreate sends POST', async () => {
  864. mockSuccess({ code: 200 })
  865. await api.productOrderCreate({ productId: 1, quantity: 2 })
  866. expect(lastRequest().url).toMatch(/\/api\/product\/order\/create$/)
  867. })
  868. })
  869. // --------------- commission / invite ---------------
  870. describe('commission & invite API', () => {
  871. test('getReferralCode sends POST', async () => {
  872. mockSuccess({ code: 200 })
  873. await api.getReferralCode()
  874. expect(lastRequest().url).toMatch(/\/api\/invite\/code$/)
  875. })
  876. test('bindReferral sends POST with code', async () => {
  877. mockSuccess({ code: 200 })
  878. await api.bindReferral('ABC123')
  879. expect(lastRequest().data.referralCode).toBe('ABC123')
  880. })
  881. test('getCommissionSummary sends POST', async () => {
  882. mockSuccess({ code: 200 })
  883. await api.getCommissionSummary()
  884. expect(lastRequest().url).toMatch(/\/api\/commission\/summary$/)
  885. })
  886. test('applyWithdrawal sends POST', async () => {
  887. mockSuccess({ code: 200 })
  888. await api.applyWithdrawal(500, { bank: 'ICBC', account: '6222****' })
  889. expect(lastRequest().data.amount).toBe(500)
  890. })
  891. })
  892. // --------------- stats module ---------------
  893. describe('stats API', () => {
  894. test('getChildCompletionStats sends POST', async () => {
  895. mockSuccess({ code: 200 })
  896. await api.getChildCompletionStats({ memberId: 5 })
  897. expect(lastRequest().url).toMatch(/\/api\/stats\/child-completion$/)
  898. })
  899. test('getChildOverview sends POST with memberId', async () => {
  900. mockSuccess({ code: 200 })
  901. await api.getChildOverview(5)
  902. expect(lastRequest().data.memberId).toBe(5)
  903. })
  904. })
  905. // --------------- AI chat module ---------------
  906. describe('AI chat API', () => {
  907. test('aiSendMessage sends POST', async () => {
  908. mockSuccess({ code: 200 })
  909. await api.aiSendMessage({ conversationId: 1, content: '你好' })
  910. expect(lastRequest().url).toMatch(/\/api\/ai\/chat\/send$/)
  911. })
  912. test('aiGetConversations sends POST', async () => {
  913. mockSuccess({ code: 200 })
  914. await api.aiGetConversations()
  915. expect(lastRequest().url).toMatch(/\/api\/ai\/chat\/conversations$/)
  916. })
  917. })
  918. // --------------- health / wealth module ---------------
  919. // --------------- health report module (new) ---------------
  920. describe('health report API', () => {
  921. test('createHealthReport sends POST', async () => {
  922. mockSuccess({ code: 200 })
  923. await api.createHealthReport({ memberId: 5, reportData: { height: 120, weight: 22 } })
  924. expect(lastRequest().url).toMatch(/\/api\/health\/report\/create$/)
  925. expect(lastRequest().data.memberId).toBe(5)
  926. })
  927. test('getReportList sends POST with memberId', async () => {
  928. mockSuccess({ code: 200 })
  929. await api.getReportList(5)
  930. expect(lastRequest().url).toMatch(/\/api\/health\/report\/list$/)
  931. expect(lastRequest().data.memberId).toBe(5)
  932. })
  933. test('getReportDetail sends POST with reportId', async () => {
  934. mockSuccess({ code: 200 })
  935. await api.getReportDetail(42)
  936. expect(lastRequest().url).toMatch(/\/api\/health\/report\/detail$/)
  937. expect(lastRequest().data.reportId).toBe(42)
  938. })
  939. test('getLatestHealthReport sends POST with memberId', async () => {
  940. mockSuccess({ code: 200 })
  941. await api.getLatestHealthReport(5)
  942. expect(lastRequest().url).toMatch(/\/api\/health\/report\/latest$/)
  943. expect(lastRequest().data.memberId).toBe(5)
  944. })
  945. test('getHealthIndicatorList sends POST with memberId', async () => {
  946. mockSuccess({ code: 200 })
  947. await api.getHealthIndicatorList(5)
  948. expect(lastRequest().url).toMatch(/\/api\/health\/indicator\/list$/)
  949. expect(lastRequest().data.memberId).toBe(5)
  950. })
  951. test('createReportSurvey sends POST with reportId and memberId', async () => {
  952. mockSuccess({ code: 200 })
  953. await api.createReportSurvey(10, 5)
  954. expect(lastRequest().url).toMatch(/\/api\/health\/survey\/create$/)
  955. expect(lastRequest().data.reportId).toBe(10)
  956. expect(lastRequest().data.memberId).toBe(5)
  957. })
  958. test('submitReportSurvey sends POST with surveyId and data', async () => {
  959. mockSuccess({ code: 200 })
  960. await api.submitReportSurvey(1, { q1: 'yes', q2: 'no' })
  961. expect(lastRequest().url).toMatch(/\/api\/health\/survey\/submit$/)
  962. expect(lastRequest().data.surveyId).toBe(1)
  963. expect(lastRequest().data.surveyData.q1).toBe('yes')
  964. })
  965. test('getSurveyStatus sends POST with reportId', async () => {
  966. mockSuccess({ code: 200 })
  967. await api.getSurveyStatus({ reportId: 10 })
  968. expect(lastRequest().url).toMatch(/\/api\/survey\/status$/)
  969. expect(lastRequest().data.reportId).toBe(10)
  970. })
  971. })
  972. describe('health & wealth API', () => {
  973. test('healthCheckinList sends POST', async () => {
  974. mockSuccess({ code: 200 })
  975. await api.healthCheckinList({ memberId: 5 })
  976. expect(lastRequest().url).toMatch(/\/api\/health\/checkin\/list$/)
  977. })
  978. test('getCheckinList sends POST', async () => {
  979. mockSuccess({ code: 200 })
  980. await api.getCheckinList({ memberId: 5 })
  981. expect(lastRequest().url).toMatch(/\/api\/wealth\/checkin\/list$/)
  982. })
  983. test('getInsuranceList sends POST', async () => {
  984. mockSuccess({ code: 200 })
  985. await api.getInsuranceList({ memberId: 5 })
  986. expect(lastRequest().url).toMatch(/\/api\/wealth\/insurance\/list$/)
  987. })
  988. })
  989. // --------------- invite card module ---------------
  990. describe('invite card API', () => {
  991. test('generateInviteCard sends POST', async () => {
  992. mockSuccess({ code: 200 })
  993. await api.generateInviteCard('guide', 1)
  994. expect(lastRequest().data.type).toBe('guide')
  995. })
  996. test('verifyInviteCard sends POST with code', async () => {
  997. mockSuccess({ code: 200 })
  998. await api.verifyInviteCard('CARD123')
  999. expect(lastRequest().data.code).toBe('CARD123')
  1000. })
  1001. })