/** * v1.5.0: ErrorCategory (7 类) + ErrorInfo + step-phase 归属强校验 */ import { ErrorCategory, ErrorInfo, VALID_ERROR_CATEGORIES, isValidErrorCategory, normalizeErrorCategory, ERROR_STEP_BY_PHASE, isValidErrorStepForPhase, } from '../index'; describe('v1.5.0 ErrorCategory (7 类 closed enum)', () => { describe('VALID_ERROR_CATEGORIES', () => { test('包含完整 7 档', () => { expect(VALID_ERROR_CATEGORIES).toEqual([ 'TRANSPORT', 'TIMEOUT', 'RESOURCE', 'BUSINESS', 'CONFIGURATION', 'PROTOCOL', 'AUTHORIZATION', ]); }); test('保留 v1.4.14 的 4 档', () => { expect(VALID_ERROR_CATEGORIES).toContain('TRANSPORT'); expect(VALID_ERROR_CATEGORIES).toContain('TIMEOUT'); expect(VALID_ERROR_CATEGORIES).toContain('RESOURCE'); expect(VALID_ERROR_CATEGORIES).toContain('BUSINESS'); }); test('v1.5.0 新增 3 档', () => { expect(VALID_ERROR_CATEGORIES).toContain('CONFIGURATION'); expect(VALID_ERROR_CATEGORIES).toContain('PROTOCOL'); expect(VALID_ERROR_CATEGORIES).toContain('AUTHORIZATION'); }); }); describe('isValidErrorCategory', () => { test.each([ 'TRANSPORT', 'TIMEOUT', 'RESOURCE', 'BUSINESS', 'CONFIGURATION', 'PROTOCOL', 'AUTHORIZATION', ])('合法值 %s 通过', (v) => { expect(isValidErrorCategory(v)).toBe(true); }); test.each([ 'transport', // 大小写敏感 'HARDWARE', '', null, undefined, 123, {}, ])('非法值 %p 拒收', (v) => { expect(isValidErrorCategory(v)).toBe(false); }); }); describe('normalizeErrorCategory (forward-compatibility 降级)', () => { test.each([ 'TRANSPORT', 'TIMEOUT', 'RESOURCE', 'BUSINESS', 'CONFIGURATION', 'PROTOCOL', 'AUTHORIZATION', ])('合法值 %s 原样返回', (v) => { expect(normalizeErrorCategory(v)).toBe(v); }); test('未知值降级为 BUSINESS', () => { expect(normalizeErrorCategory('HARDWARE')).toBe('BUSINESS'); expect(normalizeErrorCategory('UNKNOWN_FUTURE_CATEGORY')).toBe('BUSINESS'); }); test('null / undefined / 非字符串值降级为 BUSINESS', () => { expect(normalizeErrorCategory(undefined)).toBe('BUSINESS'); expect(normalizeErrorCategory(null)).toBe('BUSINESS'); expect(normalizeErrorCategory(123)).toBe('BUSINESS'); expect(normalizeErrorCategory({})).toBe('BUSINESS'); }); }); }); describe('v1.5.0 ErrorStep ↔ ErrorPhase 归属', () => { test('PROGRAM_INIT 仅允许 4 个 step', () => { expect(ERROR_STEP_BY_PHASE.PROGRAM_INIT).toEqual([ 'ValidateParameters', 'PrepareResources', 'LoadConfig', 'CheckConcurrency', ]); }); test('PROGRAM_FETCH 允许 Download / VerifyChecksum', () => { expect(ERROR_STEP_BY_PHASE.PROGRAM_FETCH).toEqual(['Download', 'VerifyChecksum']); }); test('PROGRAM_COMPLETE 含 ProgramWrapup (v1.10.0 §1 加, 镜像 QUICK_DETECTION_COMPLETE 含 DetectionWrapup)', () => { // 反向 invariant: PROGRAM ↔ QUICK_DETECTION 对称, 任一方改动触发对方 test failure expect(ERROR_STEP_BY_PHASE.PROGRAM_COMPLETE).toEqual(['ProgramWrapup']); expect(ERROR_STEP_BY_PHASE.QUICK_DETECTION_COMPLETE).toEqual(['DetectionWrapup']); }); describe('isValidErrorStepForPhase', () => { test('Download 属于 PROGRAM_FETCH', () => { expect(isValidErrorStepForPhase('Download', 'PROGRAM_FETCH')).toBe(true); }); test('Download 不属于 PROGRAM_COMPILE', () => { expect(isValidErrorStepForPhase('Download', 'PROGRAM_COMPILE')).toBe(false); }); test('DeviceCheck 属于 PROGRAM_UPLOAD', () => { expect(isValidErrorStepForPhase('DeviceCheck', 'PROGRAM_UPLOAD')).toBe(true); }); test('未知 phase 拒收', () => { expect(isValidErrorStepForPhase('Download', 'UNKNOWN_PHASE')).toBe(false); }); test('未知 step 拒收', () => { expect(isValidErrorStepForPhase('Bogus', 'PROGRAM_FETCH')).toBe(false); }); test('非字符串值拒收', () => { expect(isValidErrorStepForPhase(null, 'PROGRAM_FETCH')).toBe(false); expect(isValidErrorStepForPhase('Download', null)).toBe(false); expect(isValidErrorStepForPhase(123, 456)).toBe(false); }); }); }); describe('v1.5.0 ErrorInfo 使用场景示例', () => { test('PROGRAM_FETCH Download 超时 → TIMEOUT', () => { const err: ErrorInfo = { phase: 'PROGRAM_FETCH', step: 'Download', category: 'TIMEOUT', detail: 'HTTP 504 from CDN after 30s, 3/3 attempts', }; expect(isValidErrorCategory(err.category)).toBe(true); expect(isValidErrorStepForPhase(err.step, err.phase)).toBe(true); }); test('PROGRAM_INIT LoadConfig 配置缺失 → CONFIGURATION', () => { const err: ErrorInfo = { phase: 'PROGRAM_INIT', step: 'LoadConfig', category: 'CONFIGURATION', detail: 'Line.config missing required field: bargraphResolution', }; expect(isValidErrorCategory(err.category)).toBe(true); expect(isValidErrorStepForPhase(err.step, err.phase)).toBe(true); }); test('PROGRAM_UPLOAD DeviceCheck 同步器离线 → TRANSPORT', () => { const err: ErrorInfo = { phase: 'PROGRAM_UPLOAD', step: 'DeviceCheck', category: 'TRANSPORT', detail: 'Synchronizer offline, switch[1,2,3] unreachable', }; expect(isValidErrorCategory(err.category)).toBe(true); expect(isValidErrorStepForPhase(err.step, err.phase)).toBe(true); }); test('PROGRAM_INIT PrepareResources DAK 失效 → AUTHORIZATION', () => { const err: ErrorInfo = { phase: 'PROGRAM_INIT', step: 'PrepareResources', category: 'AUTHORIZATION', detail: 'Device access key expired or revoked', }; expect(isValidErrorCategory(err.category)).toBe(true); expect(isValidErrorStepForPhase(err.step, err.phase)).toBe(true); }); });