import { describe, expect, it } from 'vitest'; import { ErrorCodes } from '@photo/contracts/errors'; import { errorCodeI18nKeys } from '../api/errors'; import en from '../i18n/locales/en'; import zhCN from '../i18n/locales/zh-CN'; // T15 全端语言验收(P4b Task 6):错误码→文案映射的机器守护。 // 1. 契约 ErrorCodes 枚举全集在本表无缺漏(A12「主要提示无漏译」); // 2. 表内每个文案键在 zh-CN / en 两份词条都真实存在(防键注册了但一侧 // 没写文案的真漏译;点路径解析); // 3. zh-CN / en 两份词条键集合递归相等(模板级 zh/en 一致性,镜像 admin // 侧 locale 守护与 P3 mobile arb 完整性测试)。 // 服务端不做文案本地化(错误体 message 仅英文诊断兜底,客户端按稳定 code // 映射——见 openapi.yaml info.description),故本表即观众侧错误文案全集。 type Messages = Record; /** 递归收集词条文件的叶子键点路径集合(errors.albumNotFound 等)。 */ function leafPaths(messages: Messages, prefix = ''): Set { const paths = new Set(); for (const [key, value] of Object.entries(messages)) { const path = prefix === '' ? key : `${prefix}.${key}`; if (value !== null && typeof value === 'object') { for (const child of leafPaths(value as Messages, path)) paths.add(child); } else { paths.add(path); } } return paths; } /** 按点路径取词条值(不存在返回 undefined)。 */ function resolvePath(messages: Messages, path: string): unknown { let current: unknown = messages; for (const segment of path.split('.')) { if (current === null || typeof current !== 'object') return undefined; current = (current as Messages)[segment]; } return current; } describe('错误码文案覆盖(T15/A12)', () => { const zhPaths = leafPaths(zhCN as Messages); const enPaths = leafPaths(en as Messages); it('契约 ErrorCodes 枚举全集在 errorCodeI18nKeys 无缺漏', () => { const codes = Object.values(ErrorCodes); expect(codes.length).toBeGreaterThanOrEqual(18); // 契约现存 18 码,只增不减 for (const code of codes) { expect(errorCodeI18nKeys[code], `缺少错误码映射: ${code}`).toBeDefined(); } }); it('映射表内每个文案键在 zh-CN 与 en 词条均存在且非空', () => { for (const [code, key] of Object.entries(errorCodeI18nKeys)) { for (const [locale, paths] of [ ['zh-CN', zhPaths], ['en', enPaths], ] as const) { expect(paths.has(key), `${code} 的文案键 ${key} 未登记于 ${locale} 词条`).toBe(true); const value = resolvePath((locale === 'zh-CN' ? zhCN : en) as Messages, key); expect(typeof value === 'string' && value.length > 0, `${key} 在 ${locale} 非非空字符串`).toBe(true); } } }); it('zh-CN 与 en 词条键集合递归相等(无单侧漏译)', () => { expect([...zhPaths].filter((path) => !enPaths.has(path))).toEqual([]); expect([...enPaths].filter((path) => !zhPaths.has(path))).toEqual([]); }); });