import { readFileSync } from 'node:fs'; import { join, resolve } from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; import { PACKAGE_NAME, UNKNOWN_VERSION, packageJsonCandidates, parsePackageVersion, resetAppVersionCache, resolveAppVersion, } from '../src/config/app-version'; /** * 版本号展示的**运行时唯一来源**(文档 §11.1 / §11.2)。 * * 这里守护的是一个真实发生过的缺陷:原来 `/health` 用 * `process.env.npm_package_version ?? '0.1.0'`,而正式部署方式是原生服务 * (`ExecStart=node dist/main.js`),npm 不参与 → 该环境变量为 undefined → * `/health` 在真实部署里永远报 **0.1.0**,与管理端左下角展示的 vX.Y.Z 不一致, * 直接违反「可核对:Web 端至少与 GET /health 的 version 一致」。 */ const serverPackageJson = JSON.parse( readFileSync(resolve(__dirname, '..', 'package.json'), 'utf8'), ) as { name: string; version: string }; afterEach(() => { delete process.env.APP_VERSION; resetAppVersionCache(); }); describe('parsePackageVersion(纯函数:只在包名与格式都对时才认)', () => { it('接受本项目的 package.json', () => { expect( parsePackageVersion(JSON.stringify({ name: PACKAGE_NAME, version: '1.2.3' })), ).toBe('1.2.3'); }); it('接受带预发布后缀的版本', () => { expect( parsePackageVersion(JSON.stringify({ name: PACKAGE_NAME, version: '1.2.3-rc.1' })), ).toBe('1.2.3-rc.1'); }); it('拒绝包名不匹配的文件(避免读到上层仓库的 package.json)', () => { expect(parsePackageVersion(JSON.stringify({ name: 'some-other-pkg', version: '9.9.9' }))).toBeNull(); }); it('拒绝缺少 name / version 的文件', () => { expect(parsePackageVersion(JSON.stringify({ version: '1.2.3' }))).toBeNull(); expect(parsePackageVersion(JSON.stringify({ name: PACKAGE_NAME }))).toBeNull(); expect(parsePackageVersion(JSON.stringify({ name: PACKAGE_NAME, version: 123 }))).toBeNull(); }); it('拒绝非版本串(例如 workspace:*)', () => { expect(parsePackageVersion(JSON.stringify({ name: PACKAGE_NAME, version: 'workspace:*' }))).toBeNull(); expect(parsePackageVersion(JSON.stringify({ name: PACKAGE_NAME, version: '^1.0.0' }))).toBeNull(); }); it('拒绝非法 JSON 与非对象', () => { expect(parsePackageVersion('{ 不是 JSON')).toBeNull(); expect(parsePackageVersion('"字符串"')).toBeNull(); expect(parsePackageVersion('null')).toBeNull(); }); }); describe('resolveAppVersion(运行时解析)', () => { it('解析结果等于 web-server/package.json 的 version(单一来源)', () => { expect(resolveAppVersion()).toBe(serverPackageJson.version); }); it('与 package.json 里声明的包名一致(校验逻辑没有把别的包读进来)', () => { expect(serverPackageJson.name).toBe(PACKAGE_NAME); }); it('未通过 npm 启动时也能拿到真实版本(不依赖 npm_package_version)', () => { // 模拟原生部署:systemd 直接跑 node dist/main.js,没有 npm 环境变量 const saved = process.env.npm_package_version; delete process.env.npm_package_version; delete process.env.APP_VERSION; resetAppVersionCache(); try { const version = resolveAppVersion(); expect(version).toBe(serverPackageJson.version); expect(version).not.toBe('0.1.0'); expect(version).not.toBe(UNKNOWN_VERSION); } finally { if (saved !== undefined) process.env.npm_package_version = saved; resetAppVersionCache(); } }); it('APP_VERSION 环境变量可显式覆盖(运维不重新打包也能改)', () => { process.env.APP_VERSION = '9.8.7'; resetAppVersionCache(); expect(resolveAppVersion()).toBe('9.8.7'); }); it('候选路径包含工作目录(systemd WorkingDirectory=web-server)', () => { const candidates = packageJsonCandidates(); expect(candidates).toContain(join(process.cwd(), 'package.json')); // 去重:同一路径不应出现两次 expect(new Set(candidates).size).toBe(candidates.length); }); it('结果稳定(同一进程内缓存,不会来回变)', () => { const first = resolveAppVersion(); const second = resolveAppVersion(); expect(first).toBe(second); }); });