/** * Unit Tests — okr-service.js + migrate-goals-to-okr.js * * 覆盖 27-NAMING §六 正名后的两件事: * 1. OkrService 用新命名工作(okr/ 目录、O- 前缀、objective.json、Objective/KeyResult 层级) * 2. 迁移脚本的纯函数把旧 ID 正确改写(含 nodes key、children 数组) */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import fs from 'fs'; import path from 'path'; import os from 'os'; const { OkrService } = require('../../services/okr-service'); const { reId, reObjective } = require('../../../scripts/migrate-goals-to-okr'); describe('OkrService(27-NAMING §六 正名后)', () => { let tmpCompany; beforeEach(() => { // OkrService 按 companyId 落在 data/companies/{id}/okr/,用随机 id 隔离 tmpCompany = `__test-okr-${Math.random().toString(36).slice(2, 8)}`; }); afterEach(() => { const dir = path.resolve(__dirname, '../../../data/companies', tmpCompany); fs.rmSync(dir, { recursive: true, force: true }); }); it('目录用 okr/ 而不是 goals/', () => { const svc = new OkrService(tmpCompany); expect(svc.okrDir.endsWith(path.join(tmpCompany, 'okr'))).toBe(true); expect(fs.existsSync(svc.okrDir)).toBe(true); }); it('新建 Objective 用 O- 前缀,落盘为 objective.json', () => { const svc = new OkrService(tmpCompany); const obj = svc.create({ title: '测试目标', owner: 'ceo' }); expect(obj.id).toBe('O-001'); expect(fs.existsSync(path.join(svc.okrDir, 'O-001', 'objective.json'))).toBe(true); // 旧文件名不应再出现 expect(fs.existsSync(path.join(svc.okrDir, 'O-001', 'goal.json'))).toBe(false); }); it('层级名是 Objective / KeyResult / SubTask(不再有 Goal 这一层)', () => { const svc = new OkrService(tmpCompany); expect(svc.parseNodeId('O-001').levelName).toBe('Objective'); expect(svc.parseNodeId('O-001.1').levelName).toBe('KeyResult'); expect(svc.parseNodeId('O-001.1.1').levelName).toBe('SubTask'); // 返回的是 objectiveId,不是 goalId expect(svc.parseNodeId('O-001.1.1').objectiveId).toBe('O-001'); }); it('addNode / updateNode / get 走通,ID 全程 O- 前缀', () => { const svc = new OkrService(tmpCompany); const obj = svc.create({ title: '目标', owner: 'ceo' }); const { nodeId } = svc.addNode(obj.id, null, { title: 'KR1', owner: 'dev' }); expect(nodeId).toBe('O-001.1'); svc.updateNode(obj.id, nodeId, { status: 'done' }); expect(svc.get(obj.id).nodes[nodeId].status).toBe('done'); expect(svc.getProgress(obj.id).done).toBe(1); }); it('updateObjective 存在(原 updateGoal 已改名)', () => { const svc = new OkrService(tmpCompany); const obj = svc.create({ title: '旧标题', owner: 'ceo' }); expect(typeof svc.updateObjective).toBe('function'); expect(svc.updateObjective(obj.id, { title: '新标题' }).title).toBe('新标题'); }); }); describe('migrate-goals-to-okr 纯函数', () => { it('reId 只改 G- 前缀,其余原样', () => { expect(reId('G-001')).toBe('O-001'); expect(reId('G-001.1.1')).toBe('O-001.1.1'); expect(reId('O-001')).toBe('O-001'); // 幂等 expect(reId('T-abc')).toBe('T-abc'); // 不误伤 taskId expect(reId(null)).toBe(null); }); it('reObjective 改写 id、nodes 的 key、以及 children 数组', () => { const out = reObjective({ id: 'G-001', title: 'x', nodes: { 'G-001.1': { title: 'O1', children: ['G-001.1.1'] }, 'G-001.1.1': { title: 'KR1', children: [] }, }, }); expect(out.id).toBe('O-001'); expect(Object.keys(out.nodes).sort()).toEqual(['O-001.1', 'O-001.1.1']); expect(out.nodes['O-001.1'].children).toEqual(['O-001.1.1']); expect(out.title).toBe('x'); // 非 ID 字段不动 }); });