path-list-stress.test.mjs
107 lines 3.4 KB
Raw
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 10 days ago
1 /**
2 * Tier 4 — STRESS: 200+ truncate, concurrent last-updated, 20-step cap (KN-WORK-PATH-LIST-b).
3 *
4 * @see docs/KN-WORK-PATH-LIST-FREEZE.md §7
5 */
6 import { describe, it, beforeEach, afterEach } from 'node:test';
7 import assert from 'node:assert/strict';
8 import fs from 'node:fs';
9 import path from 'node:path';
10 import { fileURLToPath } from 'node:url';
11 import { saveFlowStore } from '../lib/flow/flow-store.mjs';
12 import {
13 listLearningPaths,
14 upsertLearningPath,
15 getLearningPath,
16 MAX_LEARNING_PATH_SUMMARIES,
17 validateSteps,
18 } from '../lib/path/path-store.mjs';
19
20 const __dirname = path.dirname(fileURLToPath(import.meta.url));
21 const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-path-list-stress');
22
23 function makePath(i, overrides = {}) {
24 return {
25 schema: 'knowtation.learning_path/v0',
26 path_id: `path_${String(i).padStart(16, '0')}`,
27 scope: 'personal',
28 status: 'active',
29 title: `Path ${i}`,
30 summary: `Summary ${i}`,
31 goal: `Goal ${i}`,
32 steps: [{ title: 'S', objective: 'O', source_document_ids: [] }],
33 current_step_index: 0,
34 step_count: 1,
35 next_step_title: 'S',
36 active_decisions: '',
37 workspace_id: 'ws-personal',
38 note_path: null,
39 created: '2026-08-18T00:00:00Z',
40 updated: overrides.updated ?? `2026-08-18T00:00:${String(i % 60).padStart(2, '0')}Z`,
41 ...overrides,
42 };
43 }
44
45 beforeEach(() => {
46 fs.rmSync(tmpRoot, { recursive: true, force: true });
47 fs.mkdirSync(tmpRoot, { recursive: true });
48 });
49
50 afterEach(() => {
51 fs.rmSync(tmpRoot, { recursive: true, force: true });
52 });
53
54 describe('path-list stress', () => {
55 it('200+ rows truncate at MAX_LEARNING_PATH_SUMMARIES', () => {
56 const dataDir = path.join(tmpRoot, 'cap');
57 fs.mkdirSync(dataDir, { recursive: true });
58 const rows = [];
59 for (let i = 0; i < MAX_LEARNING_PATH_SUMMARIES + 40; i += 1) {
60 rows.push(makePath(i));
61 }
62 saveFlowStore(dataDir, {
63 vaults: {
64 v: {
65 flows: [],
66 steps: [],
67 runs: [],
68 candidates: [],
69 projections: [],
70 tasks: [],
71 task_loops: [],
72 orchestrator_graphs: [],
73 learning_paths: rows,
74 },
75 },
76 });
77 const listed = listLearningPaths(dataDir, 'v', {
78 visibleScopes: new Set(['personal']),
79 filterScopes: new Set(['personal']),
80 effectiveScope: 'personal',
81 });
82 assert.equal(listed.paths.length, MAX_LEARNING_PATH_SUMMARIES);
83 assert.equal(listed.truncated, true);
84 });
85
86 it('concurrent upsert same path_id last-updated wins', () => {
87 const dataDir = path.join(tmpRoot, 'race');
88 fs.mkdirSync(dataDir, { recursive: true });
89 const id = 'path_concurrent000001';
90 upsertLearningPath(dataDir, 'v', makePath(1, { path_id: id, title: 'Older', updated: '2026-08-18T01:00:00Z' }));
91 upsertLearningPath(dataDir, 'v', makePath(1, { path_id: id, title: 'Newer', updated: '2026-08-18T02:00:00Z' }));
92 const got = getLearningPath(dataDir, 'v', id, { visibleScopes: new Set(['personal']) });
93 assert.equal(got.title, 'Newer');
94 assert.equal(got.updated, '2026-08-18T02:00:00Z');
95 });
96
97 it('20-step cap', () => {
98 const twenty = Array.from({ length: 20 }, (_, i) => ({
99 title: `T${i}`,
100 objective: `O${i}`,
101 source_document_ids: [],
102 }));
103 const twentyOne = [...twenty, { title: 'T20', objective: 'O20', source_document_ids: [] }];
104 assert.equal(validateSteps(twenty).ok, true);
105 assert.equal(validateSteps(twentyOne).ok, false);
106 });
107 });
File History 1 commit
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 10 days ago