| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485 |
- import { test } from 'node:test';
- import assert from 'node:assert/strict';
- import { readFileSync } from 'node:fs';
- import { snapshot, projectStory, sourceUnion, validateSample } from '../src/core/showcase.js';
- const seed = () => JSON.parse(readFileSync(new URL('../data/preview-seed.json', import.meta.url), 'utf8'));
- test('sample metrics use actual records, even when declared totals are stale', () => {
- const data = seed();
- data.actualSeedCounts.projects = 999;
- const model = snapshot(data);
- assert.equal(model.total, 334);
- assert.equal(model.counts.projects, 12);
- assert.deepEqual(model.stages, { S5: 4, S4: 6, S3: 2 });
- assert.deepEqual(model.states, { completed: 2, in_progress: 7, blocked: 3 });
- assert.equal(model.opportunityStates.approved, 12);
- });
- test('a live object or a second demo namespace is rejected', () => {
- const data = seed();
- data.projects[0].dataMode = 'live';
- data.members[0].demoId = 'OTHER';
- assert.ok(validateSample(data).some((x) => x.includes('不是模拟对象')));
- assert.throws(() => snapshot(data), /同一演示空间/);
- });
- test('dangling references, duplicate records and mismatched quotes are rejected', () => {
- const data = seed();
- data.projects[0].sourceIds.push('MISSING');
- data.members.push({ ...data.members[0] });
- data.evidence[0].quote = '这不是原文';
- const errors = validateSample(data);
- assert.ok(errors.some((x) => x.includes('MISSING')));
- assert.ok(errors.some((x) => x.includes('重复')));
- assert.ok(errors.some((x) => x.includes('引文不在同源原文中')));
- });
- test('a malformed collection returns validation errors', () => {
- const data = seed();
- data.evidence = {};
- assert.ok(validateSample(data).some((x) => x.includes('缺少集合:evidence')));
- });
- test('source union counts indirect references and deduplicates direct ones', () => {
- const model = snapshot(seed());
- const source = model.seed.sources[0];
- const evidence = model.seed.evidence[0];
- assert.deepEqual(sourceUnion([
- { sourceIds: [source.id] }, { evidenceIds: [evidence.id] },
- { sourceIds: [source.id], evidenceIds: [evidence.id] },
- ], model.indexes).map((x) => x.id), [source.id]);
- });
- test('stories preserve project scope and empty later stages', () => {
- const model = snapshot(seed());
- const complete = projectStory(model, 'DEMO-P01');
- assert.deepEqual(complete.reports.map((r) => r.stageCode), ['S3', 'S4', 'S5']);
- assert.deepEqual(complete.concepts.map((c) => c.version), [1, 2]);
- assert.equal(complete.questions.length, 6);
- for (const id of ['DEMO-P01', 'DEMO-P03', 'DEMO-P10']) {
- const story = projectStory(model, id);
- for (const row of [...story.reports, ...story.questions, ...story.tasks, ...story.concepts]) assert.equal(row.projectId, id);
- for (const row of story.ideas) assert.equal(row.intendedProjectId, id);
- }
- const insufficient = projectStory(model, 'DEMO-P10');
- assert.equal(insufficient.project.stageCode, 'S3');
- assert.equal(insufficient.concepts.length, 0);
- assert.equal(insufficient.tasks.length, 0);
- assert.equal(projectStory(model, 'MISSING'), null);
- });
- test('static service retries failures, shares inflight reads and never falls back', async () => {
- const { loadShowcase } = await import('../src/services/showcase-data.js?retry-test');
- const calls = [];
- const failed = (url) => { calls.push(url.href); return Promise.resolve({ ok: false, status: 503 }); };
- await assert.rejects(loadShowcase({ transport: failed }), /503/);
- let resolve;
- const transport = (url) => { calls.push(url.href); return new Promise((done) => { resolve = done; }); };
- const first = loadShowcase({ transport });
- const second = loadShowcase({ transport });
- resolve({ ok: true, json: async () => seed() });
- assert.strictEqual(await first, await second);
- assert.equal(calls.length, 2);
- assert.ok(calls.every((url) => url.endsWith('/src/assets/showcase/preview-seed.json')));
- });
|