import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import path from 'path'; import os from 'os'; describe('pipeline cache + deterministic IDs', () => { const tmpCache = () => path.join(os.tmpdir(), `cache-${Date.now()}`); beforeEach(() => { vi.resetModules(); process.env.PIPELINE_CHUNK_SOURCE = 'jsonl'; process.env.PIPELINE_CACHE_DIR = tmpCache(); }); afterEach(() => { delete process.env.PIPELINE_CHUNK_SOURCE; delete process.env.PIPELINE_CACHE_DIR; vi.restoreAllMocks(); vi.unmock('../src/retrieval/jsonl_chunks.mjs'); vi.unmock('../src/pipeline/step.mjs'); }); it('uses cached reward to skip generator/verifier/reward work', async () => { const chunk = { content: 'hello world', sourceId: 's1' }; // Deterministic IDs and cache helpers const { chunkIdFromContent, questionId, saveQuestions, saveReward, } = await import('../src/pipeline/cache.mjs'); const chunkId = chunkIdFromContent(chunk.content, chunk.sourceId); const question = 'What is said?'; const qId = questionId(chunkId, question); // Pre-populate cache with questions and an accepted reward await saveQuestions(chunkId, [question], { provider: 'mock', model: 'mock', }); await saveReward( chunkId, qId, 'gen-1', { score: 0.9, ok: true, raw: '0.9' }, { provider: 'mock', model: 'mock' }, ); // Mock chunk loader and pipeline step to observe bypass vi.doMock('../src/retrieval/jsonl_chunks.mjs', () => ({ loadRagChunks: vi.fn(async () => [{ ...chunk, id: chunkId }]), })); const runPipelineStep = vi.fn(); vi.doMock('../src/pipeline/step.mjs', () => ({ runPipelineStep })); const { runPipelineBatch } = await import('../src/pipeline/batch.mjs'); const result = await runPipelineBatch({ seedMode: 'question-first', limit: 5, verbose: false, logger: { log() {}, error() {} }, }); expect(runPipelineStep).not.toHaveBeenCalled(); expect(result.processed).toBe(1); expect(result.accepted).toBe(1); expect(result.statusCounts.cached_reward).toBe(1); }); it('stable chunk ids despite whitespace differences', async () => { const { chunkIdFromContent } = await import('../src/pipeline/ids.mjs'); const idA = chunkIdFromContent('Hello world', 'doc1'); const idB = chunkIdFromContent('Hello world', 'doc1'); const idC = chunkIdFromContent('Hello world!', 'doc1'); expect(idA).toBe(idB); expect(idA).not.toBe(idC); // punctuation changes content hash }); });