文件
fenleixinxi/uniapp/tests/request.test.mjs
linda 376a7ed793 feat(uniapp): 小程序端(用户端 uni-app)源码入库到 uniapp/ 目录
需求:小程序端代码要同时同步到服务器与 git 远程仓库,此前前端仅存在于本地目录(无 remote,从未推送),
服务器与 gitcode 上只有后端。现并入本仓库,复用现有「push 即部署」链路,一次推送双端落地。

- 新增 uniapp/:小程序端源码全量快照(150 文件 / 1.2M)
  pages(13 页面) · components(fc-navbar/fc-tabbar/fc-post-card/fc-empty) ·
  common(api·utils·mock·assets·store·config) · static(74 图标 + 3 轮播插画 + LOGO) ·
  docs(01-04 交付文档) · tests(零依赖全链路用例) · tools(图标/主题/轮播配图生成脚本)
- 明确排除:AGENTS.md(含后台密码、服务器 root 密码、小程序 AppSecret,切勿入库)、
  unpackage/(构建产物,由 uniapp/.gitignore 忽略)、node_modules、.DS_Store、*.log
- .gitignore 新增 /uniapp/AGENTS.md 兜底(与根 /AGENTS.md、/fluniapp/ 同一约定)
- README.md 增补「本项目结构 + 部署链路」章节,说明 uniapp/ 为源码快照、
  小程序真正上线仍需微信开发者工具 cli upload

说明:本次为源码快照,未携带前端本地仓库历史——其历史提交中含 AGENTS.md 凭据,
若做 subtree 合并会把密码永久写入本仓库历史,故采用快照方式。
2026-10-17 14:19:03 +08:00

135 行
4.5 KiB
JavaScript

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

/**
* 请求层(全链路统一网关)测试
* 覆盖:URL 拼接 / 鉴权头 / 业务码解包 / 401 / 网络异常 / mock 降级
*/
import { describe, it, expect, expectReject } from './harness.mjs'
import config from '../common/config/index.js'
import storage from '../common/utils/storage.js'
import request, { get, post, buildUrl, buildHeaders, getRuntime } from '../common/utils/request.js'
/** 构造一个可控的 uni 运行时 */
function installUni(handler) {
const calls = { toasts: [], navigations: [], requests: [] }
globalThis.uni = {
request(opts) {
calls.requests.push(opts)
handler(opts, calls)
},
showToast(o) {
calls.toasts.push(o)
},
navigateTo(o) {
calls.navigations.push(o)
},
getStorageSync: () => '',
setStorageSync: () => {},
removeStorageSync: () => {},
}
return calls
}
function clearUni() {
delete globalThis.uni
}
describe('请求层 · 基础能力', () => {
it('buildUrl 拼接版本号与网关地址', () => {
expect(buildUrl('/home/feed')).toBe(`${config.baseURL}/${config.apiVersion}/home/feed`)
expect(buildUrl('post/create')).toContain('/v1/post/create')
expect(buildUrl('https://a.com/x')).toBe('https://a.com/x')
})
it('buildHeaders 注入 token 与客户端标识', () => {
storage.clear()
storage.set(config.storageKeys.token, 'T-123')
const h = buildHeaders()
expect(h.Authorization).toBe('Bearer T-123')
expect(h['X-Client']).toBe('fenleixinxi')
storage.remove(config.storageKeys.token)
expect(buildHeaders().Authorization).toBeFalsy()
})
it('无 uni.request 时自动走 mock(离线可运行)', async () => {
clearUni()
const data = await get('/home/categories')
expect(data).toBeArray()
expect(data.length).toBeGreaterThan(0)
})
})
describe('请求层 · 真实 HTTP 通道', () => {
it('业务码 0 时返回 data 字段', async () => {
const calls = installUni((opts) => {
opts.success({ statusCode: 200, data: { code: 0, message: 'ok', data: { total: 7 } } })
})
const res = await get('/home/feed', { page: 1 })
expect(res.total).toBe(7)
expect(calls.requests[0].url).toContain('/v1/home/feed')
clearUni()
})
it('业务码非 0 时 reject 并 toast 提示', async () => {
const calls = installUni((opts) => {
opts.success({ statusCode: 200, data: { code: 1001, message: '参数错误' } })
})
await expectReject(() => post('/post/create', { title: '' }), '参数错误')
expect(calls.toasts[0].title).toBe('参数错误')
clearUni()
})
it('HTTP 401 清空登录态并跳转登录页', async () => {
storage.set(config.storageKeys.token, 'EXPIRED')
const calls = installUni((opts) => opts.success({ statusCode: 401, data: {} }))
await expectReject(() => get('/auth/profile'), '登录已过期')
expect(storage.get(config.storageKeys.token, '')).toBe('')
expect(calls.navigations[0].url).toBe('/pages/auth/login')
clearUni()
})
it('显式开启 enableMockFallback 时,网络异常且存在 mock 路由则自动降级', async () => {
const saved = config.enableMockFallback
config.enableMockFallback = true
const calls = installUni((opts) => opts.fail({ errMsg: 'request:fail timeout' }))
const res = await get('/member/plans')
expect(res).toBeArray()
expect(res[0].planId).toBeDefined()
clearUni()
config.enableMockFallback = saved
})
// 生产配置下必须关闭降级:否则真实接口失败会静默返回 mock 假数据,
// 表现为「后台改了但前端不变」,且真实问题被完全掩盖。
it('生产配置(enableMockFallback=false)网络异常时不静默返回 mock 数据', async () => {
expect(config.forceMock).toBe(false)
expect(config.enableMockFallback).toBe(false)
installUni((opts) => opts.fail({ errMsg: 'request:fail timeout' }))
await expectReject(() => get('/member/plans'), 'request:fail')
clearUni()
})
it('网络异常且无 mock 路由时抛出异常', async () => {
installUni((opts) => opts.fail({ errMsg: 'request:fail' }))
await expectReject(() => get('/not/exist/endpoint'), 'request:fail')
clearUni()
})
it('getRuntime 在无 uni 环境返回安全降级函数', () => {
clearUni()
const rt = getRuntime()
expect(rt.request).toBe(null)
rt.showToast({ title: 'x' }) // 不应抛错
})
})
describe('请求层 · mock 强制模式', () => {
it('mock:true 时绕过网络直接返回 mock 数据', async () => {
const calls = installUni(() => {
throw new Error('不应发起真实请求')
})
const res = await request({ url: '/home/banners', mock: true })
expect(res).toBeArray()
expect(calls.requests).toHaveLength(0)
clearUni()
})
})