membershipGate.test.js 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. /**
  2. * 会员有效态门控(`utils/membershipGate.js`)单元测试
  3. *
  4. * 背景:`membershipGate.js` 随 `merge(miniprogram): 聊天/消息类入口按有效会员隐藏`
  5. * (commit `3d35ab714`,2026-11-12 00:33)合入主线,**合入时零测试**。
  6. * 该函数决定 9 个页面(首页/心/身/智/行/我的/应用库/详情页)的聊天·消息入口可见性,
  7. * 属「越权即放行非会员」的强业务约束,必须有可执行锚点。
  8. *
  9. * 契约(源码注释自述,逐条断言):
  10. * - 唯一判据 = `POST /api/ai/gate` 的 `data.isActive === true`(严格等于,不看 canUse/status)
  11. * - 无 token 直接 false,**不发请求**(fail-closed)
  12. * - 接口异常 / 超时 / 字段缺失 一律 false(fail-closed)
  13. *
  14. * 锚点:TC-MG-U01 ~ TC-MG-U07
  15. */
  16. jest.mock('@/utils/api.js', () => ({
  17. aiGateStatus: jest.fn()
  18. }))
  19. import { aiGateStatus } from '@/utils/api.js'
  20. import { isActiveMember } from '@/utils/membershipGate.js'
  21. beforeEach(() => {
  22. global.uni._storage = {}
  23. aiGateStatus.mockReset()
  24. })
  25. describe('isActiveMember · 会员有效态(TC-MG-U01~U07)', () => {
  26. it('TC-MG-U01 无 token 直接 false,且不发起 /api/ai/gate 请求', async () => {
  27. const r = await isActiveMember()
  28. expect(r).toBe(false)
  29. expect(aiGateStatus).not.toHaveBeenCalled()
  30. })
  31. it('TC-MG-U02 有 token 且 data.isActive === true → true', async () => {
  32. global.uni._storage.token = 'tk-abc'
  33. aiGateStatus.mockResolvedValue({ code: 200, data: { isActive: true } })
  34. await expect(isActiveMember()).resolves.toBe(true)
  35. expect(aiGateStatus).toHaveBeenCalledTimes(1)
  36. })
  37. it('TC-MG-U03 有 token 且 data.isActive === false(过期会员)→ false', async () => {
  38. global.uni._storage.token = 'tk-abc'
  39. aiGateStatus.mockResolvedValue({ code: 200, data: { isActive: false } })
  40. await expect(isActiveMember()).resolves.toBe(false)
  41. })
  42. it('TC-MG-U04 响应缺 data 字段 → false(字段缺失 fail-closed)', async () => {
  43. global.uni._storage.token = 'tk-abc'
  44. aiGateStatus.mockResolvedValue({ code: 200 })
  45. await expect(isActiveMember()).resolves.toBe(false)
  46. })
  47. it('TC-MG-U05 isActive 为非布尔真值(后端返回 1 / "true")→ false(严格等值,不误放行)', async () => {
  48. global.uni._storage.token = 'tk-abc'
  49. aiGateStatus.mockResolvedValueOnce({ code: 200, data: { isActive: 1 } })
  50. await expect(isActiveMember()).resolves.toBe(false)
  51. aiGateStatus.mockResolvedValueOnce({ code: 200, data: { isActive: 'true' } })
  52. await expect(isActiveMember()).resolves.toBe(false)
  53. })
  54. it('TC-MG-U06 接口 reject(网络异常/超时)→ false,不抛错', async () => {
  55. global.uni._storage.token = 'tk-abc'
  56. aiGateStatus.mockRejectedValue(new Error('timeout'))
  57. await expect(isActiveMember()).resolves.toBe(false)
  58. })
  59. it('TC-MG-U07 接口 resolve 成 undefined → false(不因空响应放行)', async () => {
  60. global.uni._storage.token = 'tk-abc'
  61. aiGateStatus.mockResolvedValue(undefined)
  62. await expect(isActiveMember()).resolves.toBe(false)
  63. })
  64. })