showcase-sample.test.js 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { test } from 'node:test';
  2. import assert from 'node:assert/strict';
  3. import { readFileSync } from 'node:fs';
  4. import { snapshot, projectStory, sourceUnion, validateSample } from '../src/core/showcase.js';
  5. const seed = () => JSON.parse(readFileSync(new URL('../data/preview-seed.json', import.meta.url), 'utf8'));
  6. test('sample metrics use actual records, even when declared totals are stale', () => {
  7. const data = seed();
  8. data.actualSeedCounts.projects = 999;
  9. const model = snapshot(data);
  10. assert.equal(model.total, 334);
  11. assert.equal(model.counts.projects, 12);
  12. assert.deepEqual(model.stages, { S5: 4, S4: 6, S3: 2 });
  13. assert.deepEqual(model.states, { completed: 2, in_progress: 7, blocked: 3 });
  14. assert.equal(model.opportunityStates.approved, 12);
  15. });
  16. test('a live object or a second demo namespace is rejected', () => {
  17. const data = seed();
  18. data.projects[0].dataMode = 'live';
  19. data.members[0].demoId = 'OTHER';
  20. assert.ok(validateSample(data).some((x) => x.includes('不是模拟对象')));
  21. assert.throws(() => snapshot(data), /同一演示空间/);
  22. });
  23. test('dangling references, duplicate records and mismatched quotes are rejected', () => {
  24. const data = seed();
  25. data.projects[0].sourceIds.push('MISSING');
  26. data.members.push({ ...data.members[0] });
  27. data.evidence[0].quote = '这不是原文';
  28. const errors = validateSample(data);
  29. assert.ok(errors.some((x) => x.includes('MISSING')));
  30. assert.ok(errors.some((x) => x.includes('重复')));
  31. assert.ok(errors.some((x) => x.includes('引文不在同源原文中')));
  32. });
  33. test('a malformed collection returns validation errors', () => {
  34. const data = seed();
  35. data.evidence = {};
  36. assert.ok(validateSample(data).some((x) => x.includes('缺少集合:evidence')));
  37. });
  38. test('source union counts indirect references and deduplicates direct ones', () => {
  39. const model = snapshot(seed());
  40. const source = model.seed.sources[0];
  41. const evidence = model.seed.evidence[0];
  42. assert.deepEqual(sourceUnion([
  43. { sourceIds: [source.id] }, { evidenceIds: [evidence.id] },
  44. { sourceIds: [source.id], evidenceIds: [evidence.id] },
  45. ], model.indexes).map((x) => x.id), [source.id]);
  46. });
  47. test('stories preserve project scope and empty later stages', () => {
  48. const model = snapshot(seed());
  49. const complete = projectStory(model, 'DEMO-P01');
  50. assert.deepEqual(complete.reports.map((r) => r.stageCode), ['S3', 'S4', 'S5']);
  51. assert.deepEqual(complete.concepts.map((c) => c.version), [1, 2]);
  52. assert.equal(complete.questions.length, 6);
  53. for (const id of ['DEMO-P01', 'DEMO-P03', 'DEMO-P10']) {
  54. const story = projectStory(model, id);
  55. for (const row of [...story.reports, ...story.questions, ...story.tasks, ...story.concepts]) assert.equal(row.projectId, id);
  56. for (const row of story.ideas) assert.equal(row.intendedProjectId, id);
  57. }
  58. const insufficient = projectStory(model, 'DEMO-P10');
  59. assert.equal(insufficient.project.stageCode, 'S3');
  60. assert.equal(insufficient.concepts.length, 0);
  61. assert.equal(insufficient.tasks.length, 0);
  62. assert.equal(projectStory(model, 'MISSING'), null);
  63. });
  64. test('static service retries failures, shares inflight reads and never falls back', async () => {
  65. const { loadShowcase } = await import('../src/services/showcase-data.js?retry-test');
  66. const calls = [];
  67. const failed = (url) => { calls.push(url.href); return Promise.resolve({ ok: false, status: 503 }); };
  68. await assert.rejects(loadShowcase({ transport: failed }), /503/);
  69. let resolve;
  70. const transport = (url) => { calls.push(url.href); return new Promise((done) => { resolve = done; }); };
  71. const first = loadShowcase({ transport });
  72. const second = loadShowcase({ transport });
  73. resolve({ ok: true, json: async () => seed() });
  74. assert.strictEqual(await first, await second);
  75. assert.equal(calls.length, 2);
  76. assert.ok(calls.every((url) => url.endsWith('/src/assets/showcase/preview-seed.json')));
  77. });