import { describe, it, expect } from 'vitest'; import { mkdtempSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { recordVersion, getVersions, diffVersions, recordObjection, getObjections, recordArg, getArgs, clearRoundMemory, buildRoundMemory, } from './round-memory-store.mjs'; const dir = () => mkdtempSync(join(tmpdir(), 'rmem-')); describe('round-memory-store', () => { it('версии: record → get round-trip', () => { const d = dir(); recordVersion('t1', 'spec', 'v1', d); recordVersion('t1', 'spec', 'v2', d); expect(getVersions('t1', 'spec', d)).toEqual(['v1', 'v2']); }); it('diffVersions двух последних', () => { const d = dir(); recordVersion('t1', 'plan', 'a\nb', d); recordVersion('t1', 'plan', 'a\nb\nc', d); expect(diffVersions('t1', 'plan', d)).toContain('+ c'); }); it('замечания по стороне дословно', () => { const d = dir(); recordObjection('t1', 'spec', 'mentor', 'правь X', d); recordObjection('t1', 'spec', 'judge', 'правь Y', d); expect(getObjections('t1', 'spec', 'mentor', d)).toEqual(['правь X']); expect(getObjections('t1', 'spec', 'judge', d)).toEqual(['правь Y']); }); it('доводы по дорожке (M/J)', () => { const d = dir(); recordArg('t1', 'spec', 'M', 'довод наставнику', d); recordArg('t1', 'spec', 'J', 'довод судье', d); expect(getArgs('t1', 'spec', 'M', d)).toEqual(['довод наставнику']); expect(getArgs('t1', 'spec', 'J', d)).toEqual(['довод судье']); }); it('изоляция по (taskId, stage)', () => { const d = dir(); recordVersion('t1', 'spec', 'x', d); expect(getVersions('t1', 'plan', d)).toEqual([]); expect(getVersions('t2', 'spec', d)).toEqual([]); }); it('clearRoundMemory стирает задачу', () => { const d = dir(); recordVersion('t1', 'spec', 'x', d); recordObjection('t1', 'spec', 'mentor', 'z', d); clearRoundMemory('t1', d); expect(getVersions('t1', 'spec', d)).toEqual([]); expect(getObjections('t1', 'spec', 'mentor', d)).toEqual([]); }); it('fail-quiet: битый baseDir не кидает', () => { expect(() => getVersions('t1', 'spec', '\0bad')).not.toThrow(); expect(getVersions('t1', 'spec', '\0bad')).toEqual([]); }); }); describe('buildRoundMemory (SP2c-2)', () => { it('круг 1 слеп: нет версий → versionDiff пуст, объекции/доводы пусты', () => { const d = dir(); const m = buildRoundMemory({ taskId: 't1', stage: 'plan', side: 'judge', currentContent: 'v1', baseDir: d }); expect(m).toEqual({ objections: [], args: [], versionDiff: '', judgeObjectionOnReturn: '' }); }); it('judge: только J-дорожка + свои judge-замечания + diff против текущей, без замечания-при-возврате', () => { const d = dir(); recordVersion('t1', 'plan', 'строка1\nстрока2', d); recordObjection('t1', 'plan', 'judge', 'замечание судьи 1', d); recordObjection('t1', 'plan', 'mentor', 'замечание наставника', d); recordArg('t1', 'plan', 'J', 'довод судье', d); recordArg('t1', 'plan', 'M', 'довод наставнику', d); const m = buildRoundMemory({ taskId: 't1', stage: 'plan', side: 'judge', currentContent: 'строка1\nстрока2-изм', baseDir: d }); expect(m.objections).toEqual(['замечание судьи 1']); expect(m.args).toEqual(['довод судье']); expect(m.versionDiff).toContain('строка2'); expect(m.judgeObjectionOnReturn).toBe(''); }); it('mentor: M-дорожка + свои mentor-замечания + ПОСЛЕДНЕЕ замечание судьи', () => { const d = dir(); recordVersion('t1', 'spec', 'тело', d); recordObjection('t1', 'spec', 'mentor', 'замечание наставника 1', d); recordObjection('t1', 'spec', 'judge', 'замечание судьи A', d); recordObjection('t1', 'spec', 'judge', 'замечание судьи B', d); recordArg('t1', 'spec', 'M', 'довод наставнику', d); const m = buildRoundMemory({ taskId: 't1', stage: 'spec', side: 'mentor', currentContent: 'тело2', baseDir: d }); expect(m.objections).toEqual(['замечание наставника 1']); expect(m.args).toEqual(['довод наставнику']); expect(m.judgeObjectionOnReturn).toBe('замечание судьи B'); }); it('fail-quiet на битом baseDir → пустая память', () => { const m = buildRoundMemory({ taskId: 't1', stage: 'plan', side: 'judge', currentContent: 'x', baseDir: '\0bad' }); expect(m).toEqual({ objections: [], args: [], versionDiff: '', judgeObjectionOnReturn: '' }); }); });