import test, { after } from 'node:test';
import assert from 'node:assert/strict';
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { createServer } from 'vite';
import { readFile } from 'node:fs/promises';
const server = await createServer({ server: { middlewareMode: true, hmr: false }, appType: 'custom' });
after(() => server.close());
const { makeSeed: makeMcmSeed, upgradeDemoState } = await server.ssrLoadModule('/src/seed.js');
function makeSeed() { const state = makeMcmSeed(); state.session = { team: 'RMS', role: 'user', name: 'RMS User', authenticated: true }; state.activeWorkspaceId = 'w1042'; return state; }
const { resolveSelection, readiness, eligibleDocuments, bumpVersion, contextKey, csv, parseCsv, StoreProvider } = await server.ssrLoadModule('/src/store.jsx');
const { makeDemoSuite } = await server.ssrLoadModule('/src/generation.js');
const { default: App } = await server.ssrLoadModule('/src/AppShell.jsx');
const { authenticateTeam, hashPassword, addTeam, documentInputIssues, baselineInputIssues, saveClarification } = await server.ssrLoadModule('/src/access.js');
const { canApprove, visibleWorkspaces, roleLabels } = await server.ssrLoadModule('/src/store.jsx');
const { approvalBlockers } = await server.ssrLoadModule('/src/pages/Approvals.jsx');
const { stageFile } = await server.ssrLoadModule('/src/files.js');
const { requestBaseline, requestBaselineMerge, decideBaseline } = await server.ssrLoadModule('/src/baseline.js');
const { makeFreshState } = await server.ssrLoadModule('/src/store.jsx');
const { readTemplate, readZip } = await server.ssrLoadModule('/src/excel.js');
const { buildChatRequest, interpretChatRequest, requestWorkspaceAssistant, saveChatDraft, assertChatContext } = await server.ssrLoadModule('/src/chatAssistant.js');
const { suiteExport } = await server.ssrLoadModule('/src/suiteExport.js');
function chatFixture() {
const state = makeMcmSeed(); state.session = { team: 'MCM', role: 'user', name: 'MCM User', authenticated: true };
const workspace = state.workspaces.find(item => item.id === state.activeWorkspaceId);
state.threads[contextKey(workspace)] = { workspaceId: workspace.id, team: workspace.team, version: workspace.version, messages: [] };
return { state, workspace, messages: state.threads[contextKey(workspace)].messages };
}
async function chatReply(fixture, prompt, options = {}) {
const { state, workspace, messages } = fixture;
const request = buildChatRequest(state, workspace, { prompt, templateId: 'mcm-t1', messages, ...options });
const response = await requestWorkspaceAssistant(request, { state });
messages.push({ id: `user-${messages.length}`, role: 'user', actor: 'MCM User', content: prompt, at: new Date().toISOString() });
const message = { ...response, id: `assistant-${messages.length}`, role: 'assistant', actor: 'Workspace assistant', at: new Date().toISOString() };
messages.push(message); return message;
}
test('chat separates questions, source requests, typed generation and follow-up refinements', () => {
assert.equal(interpretChatRequest('What does the member identifier requirement say?').intent, 'question');
assert.equal(interpretChatRequest('Generate technical test cases', { kind: 'Business' }).kind, 'Technical');
assert.equal(interpretChatRequest('Generate test cases', { kind: 'Business' }).kind, 'Business');
const previousDraft = { kind: 'Technical' };
assert.deepEqual([interpretChatRequest('Only negative scenarios', { previousDraft }).intent, interpretChatRequest('Only negative scenarios', { previousDraft }).kind], ['refine', 'Technical']);
assert.equal(interpretChatRequest('Show sources for these test cases', { previousDraft }).intent, 'sources');
assert.equal(interpretChatRequest('Generate 1000 technical tests').count, 1000);
});
test('technical chat cases use technical sources and retain exact approved expected behavior', async () => {
const f = chatFixture(), response = await chatReply(f, 'Generate technical test cases');
assert.equal(response.engine, 'local-evidence'); assert.equal(response.draft.kind, 'Technical');
assert.deepEqual(response.draft.tests.map(test => test.requirement), ['VAL-001', 'VAL-003']);
assert.ok(response.draft.tests.every(test => test.sourceIds.every(id => f.state.documents.find(doc => doc.id === id)?.category === 0)));
for (const test of response.draft.tests) assert.equal(test.expected, f.state.baselines.find(b => b.id === f.workspace.baselineId).rules.find(rule => rule.id === test.requirement).text);
assert.equal(response.citations[0].revision, 2); assert.match(response.citations[0].locator, /Lines/);
assert.ok(!response.citations.some(citation => ['d1', 'd2', 'mcm-d2'].includes(citation.id)));
const large = await chatReply(f, 'Generate 1000 technical test cases');
assert.equal(large.draft.tests.length, 2); assert.match(large.content, /not filled the requested count/);
const missing = await chatReply(f, 'Generate negative technical test cases');
assert.equal(missing.draft, undefined); assert.match(missing.content, /does not support negative technical/);
});
test('source follow-ups cite the preceding answer and support opening its pinned revision', async () => {
const f = chatFixture(), answer = await chatReply(f, 'What is the required member identifier behavior?');
assert.match(answer.content, /missing required member identifier/);
const sources = await chatReply(f, 'Show sources for this answer');
assert.equal(sources.intent, 'sources');
assert.deepEqual(sources.citations.map(item => item.id).sort(), answer.citations.map(item => item.id).sort());
assert.ok(sources.citations.every(citation => citation.excerpt && citation.name && citation.revision));
const sourceList = await chatReply(chatFixture(), 'Show sources for the selected knowledge');
assert.ok(sourceList.citations.length > 0);
const unknown = await chatReply(f, 'What is the approved quantum banana timeout?');
assert.ok(!unknown.draft); assert.ok(!unknown.content.includes('30 seconds'));
});
test('chat refinements preserve previous drafts, filter to documented scenarios, and respect selected CR scope', async () => {
const f = chatFixture(), original = await chatReply(f, 'Generate functional test cases');
const originalCopy = structuredClone(original.draft);
const refined = await chatReply(f, 'Only negative scenarios');
assert.equal(refined.draft.tests.length, 1); assert.equal(refined.draft.tests[0].requirement, 'VAL-002');
assert.equal(refined.draft.tests[0].expected, 'Reject a request with a missing required member identifier.');
assert.deepEqual(original.draft, originalCopy);
const scoped = await chatReply(f, 'Generate functional test cases for CR-1042');
assert.deepEqual(scoped.draft.tests.map(test => test.requirement), ['VAL-002']);
const unavailable = await chatReply(f, 'Generate technical test cases for CR-9999');
assert.equal(unavailable.draft, undefined); assert.match(unavailable.content, /not in the selected workspace/);
});
test('chat drafts save once as independent reviewable suites and export the selected template columns', async () => {
const f = chatFixture();
f.state.templates.find(t => t.id === 'mcm-t1').fields = ['Test ID', 'Type', 'Title', 'Expected result', 'Evidence', 'Review batch'];
const reply = await chatReply(f, 'Generate technical test cases'), before = f.state.suites.length;
const id = saveChatDraft(f.state, f.workspace, reply.id);
assert.equal(saveChatDraft(f.state, f.workspace, reply.id), id); assert.equal(f.state.suites.length, before + 1);
const saved = f.state.suites.find(suite => suite.id === id);
saved.tests[0].notes = 'Reviewed'; assert.equal(reply.draft.tests[0].notes, '');
saved.tests[0].customFields['Review batch'] = 'Office review';
f.state.templates.find(t => t.id === 'mcm-t1').fields.push('Later header');
f.state.documents.find(d => d.id === 'mcm-d1').revision = 9;
const rows = parseCsv(suiteExport(f.state, f.workspace, saved).content);
assert.deepEqual(rows[0], ['Test ID', 'Type', 'Title', 'Expected result', 'Evidence', 'Review batch']);
assert.equal(rows[1][1], 'Technical'); assert.match(rows[1][4], /revision 2/); assert.ok(!rows[1][4].includes('revision 9'));
assert.equal(rows[1][5], 'Office review');
assert.throws(() => saveChatDraft(f.state, f.workspace, reply.id), /knowledge changed/);
});
test('knowledge changes, revoked access and history prevent stale chat drafts being saved or refined', async () => {
const f = chatFixture(), response = await chatReply(f, 'Generate technical test cases');
const request = buildChatRequest(f.state, f.workspace, { prompt: 'What is the identifier format?', templateId: 'mcm-t1', messages: f.messages });
f.state.documents.find(d => d.id === 'mcm-d1').excerpt += ' Changed extraction.';
assert.throws(() => assertChatContext(f.state, request), /knowledge changed/);
assert.throws(() => saveChatDraft(f.state, f.workspace, response.id), /knowledge changed/);
assert.throws(() => buildChatRequest(f.state, f.workspace, { prompt: 'Add negative scenarios', messages: f.messages }), /Generate a new draft/);
assert.throws(() => saveChatDraft(f.state, f.workspace, response.id, 'historic:1'), /read-only/);
const fresh = chatFixture(), pending = buildChatRequest(fresh.state, fresh.workspace, { prompt: 'Show sources', templateId: 'mcm-t1', messages: [] });
fresh.state.shares.find(share => share.consumer === 'MCM').status = 'Revoked';
await assert.rejects(requestWorkspaceAssistant(pending, { state: fresh.state }), /knowledge changed/);
});
test('chat transport honors cancellation and rejects fabricated citations and malformed cases', async () => {
const f = chatFixture(), request = buildChatRequest(f.state, f.workspace, { prompt: 'Generate technical test cases', templateId: 'mcm-t1' });
const abort = new AbortController(); abort.abort();
await assert.rejects(requestWorkspaceAssistant(request, { state: f.state, signal: abort.signal }), { name: 'AbortError' });
const during = new AbortController();
await assert.rejects(requestWorkspaceAssistant(request, { state: f.state, signal: during.signal, transport: async () => { during.abort(); return {}; } }), { name: 'AbortError' });
await assert.rejects(requestWorkspaceAssistant(request, { state: f.state, transport: async () => ({ content: 'Answer', citations: [{ id: 'd1', revision: 2 }] }) }), /invalid evidence/);
await assert.rejects(requestWorkspaceAssistant(request, { state: f.state, transport: async () => ({ content: 'Answer', citations: [{ id: 'mcm-d1', revision: 99 }] }) }), /invalid evidence/);
const valid = await requestWorkspaceAssistant(request, { state: f.state });
valid.draft.tests[0].steps = [42];
await assert.rejects(requestWorkspaceAssistant(request, { state: f.state, transport: async () => valid }), /invalid test-case draft/);
});
test('chat draft controls and suite deep links render with the existing application shell', async () => {
const f = chatFixture(); await chatReply(f, 'Generate technical test cases');
globalThis.location = { hash: '#chat' };
globalThis.localStorage = { getItem: key => key === 'testcraft-studio-v2' ? JSON.stringify(f.state) : null };
const html = renderToStaticMarkup(React.createElement(StoreProvider, null, React.createElement(App)));
assert.ok(html.includes('Technical test cases')); assert.ok(html.includes('Save to test suite')); assert.ok(html.includes('Chat test-case template')); assert.ok(html.includes('Upload Excel template'));
const message = f.messages.at(-1), id = saveChatDraft(f.state, f.workspace, message.id);
globalThis.location = { hash: `#suites?suite=${id}` };
const suite = renderToStaticMarkup(React.createElement(StoreProvider, null, React.createElement(App)));
assert.ok(suite.includes('Verify identifier format')); assert.ok(suite.includes('Technical tests'));
});
test('document uploads retain readable content, checksum, category, and original file', async () => {
const saved = [];
globalThis.indexedDB = { open() { const request = {}; queueMicrotask(() => {
request.result = { objectStoreNames: { contains: () => true }, close() {}, transaction() {
const transaction = { objectStore: () => ({ put(file, id) { saved.push({ file, id }); queueMicrotask(() => transaction.oncomplete()); } }) };
return transaction;
} }; request.onsuccess();
}); return request; } };
const original = new File(['abc'], 'MCM_context.txt', { type: 'text/plain' });
const document = await stageFile(original, { team: 'MCM', category: 2 });
assert.equal(document.excerpt, 'abc');
assert.equal(document.checksum, 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad');
assert.equal(document.category, 2);
assert.equal(saved[0].file, original); assert.equal(saved[0].id, document.id);
await assert.rejects(stageFile(new File([], 'empty.txt'), {}), /empty/);
await assert.rejects(stageFile(new File(['abc'], 'program.exe'), {}), /use PDF/);
delete globalThis.indexedDB;
});
test('blocked browser file storage produces a retryable error instead of an endless upload', async () => {
globalThis.indexedDB = { open() { const request = {}; queueMicrotask(() => request.onblocked()); return request; } };
await assert.rejects(stageFile(new File(['abc'], 'context.txt'), {}), /storage is busy/);
delete globalThis.indexedDB;
});
test('baseline approval checks knowledge selection without blocking on optional questions', () => {
const state = makeSeed(), candidate = state.candidates[0];
assert.deepEqual(approvalBlockers(state, candidate), []);
candidate.crIds.push('cr1068');
assert.deepEqual(approvalBlockers(state, candidate), []);
});
test('shared team passwords select only User or Administrator access', async () => {
const state = makeMcmSeed();
assert.deepEqual(Object.keys(roleLabels), ['user', 'administrator']);
const normal = await authenticateTeam(state, 'MCM', 'team-demo');
const admin = await authenticateTeam(state, 'MCM', 'admin-demo');
assert.equal(normal.role, 'user'); assert.equal(admin.role, 'administrator');
assert.equal(canApprove({ ...state, session: normal }), false);
assert.equal(canApprove({ ...state, session: admin }), true);
await assert.rejects(authenticateTeam(state, 'MCM', 'incorrect'), /password does not match/);
await assert.rejects(authenticateTeam(state, 'Unknown', 'team-demo'), /Choose a team/);
});
test('only Administrators add teams, with separate shared and admin passwords', async () => {
const state = makeMcmSeed();
const userHash = await hashPassword('new-team-password'), adminHash = await hashPassword('new-admin-password');
state.session = await authenticateTeam(state, 'MCM', 'team-demo');
assert.throws(() => addTeam(state, 'New Team', userHash, adminHash), /Only an Administrator/);
state.session = await authenticateTeam(state, 'MCM', 'admin-demo');
addTeam(state, 'New Team', userHash, adminHash);
assert.equal((await authenticateTeam(state, 'New Team', 'new-team-password')).role, 'user');
assert.equal((await authenticateTeam(state, 'New Team', 'new-admin-password')).role, 'administrator');
await assert.rejects(authenticateTeam(state, 'New Team', 'team-demo'), /password does not match/);
});
test('baseline and CR uploads need one document in any category, never all three', () => {
const state = makeSeed();
assert.equal(baselineInputIssues(state, 'New baseline', []).length, 1);
assert.equal(documentInputIssues([]).length, 1);
for (const category of [0, 1, 2]) {
const docs = [{ id: `single-${category}`, category }];
assert.deepEqual(baselineInputIssues(state, 'New baseline', docs), []);
assert.deepEqual(documentInputIssues(docs), []);
}
});
test('Users resolve source-backed questions without a reviewer approval step', () => {
const state = makeSeed(), workspace = state.workspaces[0];
const docs = eligibleDocuments(state, workspace);
for (const question of state.questions.filter(q => q.workspaceId === workspace.id)) saveClarification(state, workspace, question.id, 'Synthetic supported behavior', 'dshared', 'Sample contract v2.1, interface section', docs);
assert.equal(readiness(state, workspace).ready, true);
assert.ok(state.questions.filter(q => q.workspaceId === workspace.id).every(q => q.status === 'resolved'));
assert.equal(visibleWorkspaces(state).length, 4);
});
test('MCM is the default team with complete isolated demo data', () => {
const state = makeMcmSeed();
assert.equal(state.session.team, 'MCM');
assert.equal(state.teams[0], 'MCM');
const workspace = state.workspaces.find(w => w.id === state.activeWorkspaceId);
assert.equal(workspace.team, 'MCM');
assert.ok(eligibleDocuments(state, workspace).some(d => d.name === 'MCM_Message_Processor.sql'));
assert.ok(eligibleDocuments(state, workspace).every(d => d.team === 'MCM' || d.id === 'dshared'));
assert.deepEqual(resolveSelection(state, workspace.baselineId, workspace.crIds).issues, []);
const reviewWorkspace = state.workspaces.find(w => w.id === 'mcm-w1051');
assert.equal(readiness(state, reviewWorkspace).ready, true);
assert.ok(makeDemoSuite(state, reviewWorkspace, 'mcm-t1', false).tests.length > 0);
});
test('MCM migration preserves existing data and later team choices', () => {
const state = makeSeed();
state.demoTeamVersion = undefined;
state.documents.find(d => d.id === 'd1').excerpt = 'Existing local review';
state.threads['w1042:1'] = [{ content: 'Existing conversation' }];
const counts = Object.fromEntries(['workspaces', 'documents', 'suites'].map(key => [key, state[key].length]));
upgradeDemoState(state);
assert.equal(state.session.team, 'MCM');
assert.equal(state.documents.find(d => d.id === 'd1').excerpt, 'Existing local review');
assert.equal(state.threads['w1042:1'][0].content, 'Existing conversation');
for (const key of Object.keys(counts)) assert.equal(state[key].length, counts[key]);
state.session.team = 'RMS';
state.session.name = 'RMS Normal User';
state.session.authenticated = true;
state.audit.push({ id: 'legacy-user-label', actor: 'RMS Normal User', action: 'Signed in to RMS as Normal User' });
upgradeDemoState(state);
assert.equal(state.session.team, 'RMS');
assert.equal(state.session.name, 'RMS User');
assert.equal(state.session.authenticated, true);
assert.equal(state.audit.at(-1).actor, 'RMS User');
assert.equal(state.audit.at(-1).action, 'Signed in to RMS as User');
});
test('Membership is an alias of MCM and existing records retain their references', () => {
const state = makeMcmSeed();
state.teams.push('Membership');
state.workspaces.push({ ...state.workspaces[0], id: 'membership-workspace', team: 'Membership', members: ['Membership Reviewer'] });
state.documents.push({ ...state.documents[0], id: 'membership-source', team: 'Membership', sharedWith: ['Membership', 'MCM'] });
state.shares.push({ id: 'membership-share', sourceId: 'membership-source', owner: 'Membership', consumer: 'RMS', status: 'Active' });
state.session = { team: 'Membership', role: 'reviewer', name: 'Membership Reviewer' };
delete state.accessModelVersion;
state.activeWorkspaceId = 'membership-workspace';
upgradeDemoState(state);
assert.equal(state.teams.filter(t => t === 'MCM').length, 1);
assert.ok(!state.teams.includes('Membership'));
assert.equal(state.session.name, 'MCM User');
assert.equal(state.activeWorkspaceId, 'membership-workspace');
assert.equal(state.workspaces.find(w => w.id === 'membership-workspace').team, 'MCM');
assert.deepEqual(state.documents.find(d => d.id === 'membership-source').sharedWith, ['MCM']);
assert.equal(state.shares.find(g => g.id === 'membership-share').owner, 'MCM');
});
test('all application pages render their intended content', () => {
const cases = [
['overview', 'Workspace overview'], ['workspaces', 'Your workspaces'], ['baselines', 'System Baselines'],
['workspace', 'Functional / Bug Information'], ['versions', 'Knowledge diff'], ['chat', 'Workspace chat'],
['readiness', 'Generate test cases'], ['suites', 'Accept scenario'], ['templates', 'Template library'],
['approvals', 'Exact manifest SHA-256'], ['graph', 'Interactive source-backed dependency graph'],
['admin', 'Configured teams'], ['login', 'Welcome back'],
];
for (const [page, expected] of cases) {
const state = makeSeed();
if (page === 'admin') state.session.role = 'administrator';
if (page === 'suites') state.activeWorkspaceId = 'w1051';
globalThis.location = { hash: `#${page}` };
globalThis.localStorage = { getItem: key => key === 'ccil-studio-demo-v1' ? JSON.stringify(state) : null };
const html = renderToStaticMarkup(React.createElement(StoreProvider, null, React.createElement(App)));
assert.ok(html.includes(expected), `${page} must render ${expected}`);
assert.ok(!/Team Baseline|Demo role|Baseline Approver|Reviewer|Contributor|Frontend demo|Local frontend demo|One shared login for your team/.test(html), `${page} must use the clean shared team access model`);
assert.ok(!html.includes('Planned for the complete frontend'), `${page} must not contain prototype placeholders`);
}
});
test('application pages require the shared team sign-in form', () => {
const state = makeMcmSeed();
globalThis.location = { hash: '#overview' };
globalThis.localStorage = { getItem: key => key === 'ccil-studio-demo-v1' ? JSON.stringify(state) : null };
const html = renderToStaticMarkup(React.createElement(StoreProvider, null, React.createElement(App)));
assert.ok(html.includes('System password'));
assert.ok(!html.includes('Workspace overview'));
assert.ok(!html.includes('Demo role'));
assert.ok(!html.includes('Sample team passwords'));
assert.ok(!html.includes('team-demo'));
assert.ok(!html.includes('admin-demo'));
});
test('a newly configured team sees useful empty states on every page', () => {
const state = makeSeed(); state.teams.push('New Team'); state.session = { team: 'New Team', role: 'user', name: 'New Team User', authenticated: true }; state.activeWorkspaceId = '';
globalThis.localStorage = { getItem: key => key === 'ccil-studio-demo-v1' ? JSON.stringify(state) : null };
for (const page of ['overview', 'workspaces', 'baselines', 'workspace', 'versions', 'chat', 'readiness', 'suites', 'templates', 'approvals', 'graph', 'admin']) {
globalThis.location = { hash: `#${page}` };
assert.doesNotThrow(() => renderToStaticMarkup(React.createElement(StoreProvider, null, React.createElement(App))), page);
}
});
test('knowledge resolution rejects prerequisites, conflicts, and incorporated CRs', () => {
const s = makeSeed();
assert.ok(resolveSelection(s, 'b1', ['cr1068']).issues.some(i => i.includes('requires CR-1051')));
assert.ok(resolveSelection(s, 'b1', ['cr1042', 'cr1073']).issues.some(i => i.includes('conflicts')));
assert.ok(resolveSelection(s, 'b2', ['cr1042']).issues.some(i => i.includes('already incorporated')));
assert.equal(resolveSelection(s, 'b1', ['cr1051', 'cr1068']).issues.length, 0);
});
test('partial replacement preserves unchanged knowledge and suppresses obsolete source text', () => {
const s = makeSeed(), w = s.workspaces[0];
s.questions.filter(q => q.workspaceId === w.id).forEach(q => { q.status = 'resolved'; q.answer = 'Approved sample behavior'; q.sourceReference = 'Sample contract revision 2.1'; });
const suite = makeDemoSuite(s, w, 't1', false);
assert.equal(suite.tests.find(t => t.requirement === 'VAL-002').expected, 'Reject a request with a missing required member identifier.');
assert.ok(suite.tests.some(t => t.requirement === 'VAL-001'));
assert.ok(suite.tests.some(t => t.requirement === 'VAL-003'));
assert.ok(!eligibleDocuments(s, w).some(d => d.id === 'd2'));
});
test('REMOVE excludes deleted rules and their obsolete evidence from current generation', () => {
const s = makeSeed(), w = s.workspaces[0]; w.crIds = ['cr1073'];
s.questions = s.questions.filter(q => q.workspaceId !== w.id);
const suite = makeDemoSuite(s, w, 't1', false);
assert.ok(!suite.tests.some(t => t.requirement === 'VAL-002'));
assert.ok(!eligibleDocuments(s, w).some(d => d.id === 'd2' || d.id === 'd3'));
});
test('open clarifications are optional and do not produce invented expected outcomes', () => {
const s = makeSeed(), w = s.workspaces[0];
const question = s.questions.find(q => q.workspaceId === w.id);
for (let index = 0; index < 1000; index++) s.questions.push({ ...question, id: `optional-${index}` });
assert.equal(readiness(s, w).ready, true);
const suite = makeDemoSuite(s, w, 't1');
assert.equal(suite.provisional, false); assert.equal(suite.status, 'Ready for review');
assert.ok(suite.gaps.length >= 1000);
assert.ok(suite.tests.every(test => test.expected !== 'Expected result unresolved' && test.sourceIds.length));
w.baselineId = '';
assert.throws(() => makeDemoSuite(s, w, 't1'), /active System Baseline/);
});
test('new installations start with no workspaces or baselines and retain default templates', () => {
const state = makeFreshState();
assert.equal(state.session.authenticated, false);
assert.equal(state.workspaces.length, 0); assert.equal(state.baselines.length, 0);
assert.ok(state.templates.some(template => template.team === 'MCM'));
});
test('baseline creation needs approval and only an Administrator can make it usable', async () => {
const state = makeFreshState(); state.session = await authenticateTeam(state, 'MCM', 'team-demo');
const workspace = { id: 'fresh-workspace', team: 'MCM', version: 1, baselineId: '', crIds: [], customRules: [], requirementCount: 0 };
state.workspaces.push(workspace);
const doc = { id: 'fresh-document', team: 'MCM', category: 0, name: 'source.md', revision: 1, checksum: 'abc' };
const request = requestBaseline(state, { ref: 'Membership foundation' }, [doc], workspace.id);
assert.equal(workspace.baselineId, ''); assert.equal(workspace.pendingBaselineId, request.baselineId);
assert.ok(resolveSelection(state, request.baselineId, []).issues.length);
assert.throws(() => decideBaseline(state, request.id, 'Approved'), /Only an Administrator/);
state.session = await authenticateTeam(state, 'MCM', 'admin-demo');
decideBaseline(state, request.id, 'Approved', 'Reviewed');
assert.equal(workspace.baselineId, request.baselineId);
assert.equal(state.baselines[0].status, 'Approved');
assert.deepEqual(resolveSelection(state, request.baselineId, []).issues, []);
});
test('baseline edits add and remove documents only after approval, preserving the old version', async () => {
const state = makeSeed(); state.session = await authenticateTeam(state, 'RMS', 'team-demo');
const parent = state.baselines.find(item => item.id === 'b1'), workspace = state.workspaces[0], originalSources = [...parent.sourceIds];
const addition = { id: 'new-source', team: 'RMS', name: 'revised.md', category: 1, revision: 1, checksum: 'new' };
assert.throws(() => requestBaseline(state, { ref: parent.ref }, [], workspace.id, parent.id), /at least one/);
const request = requestBaseline(state, { ref: parent.ref, description: 'Updated knowledge' }, [addition], workspace.id, parent.id);
assert.equal(workspace.baselineId, parent.id); assert.deepEqual(parent.sourceIds, originalSources);
state.session = await authenticateTeam(state, 'RMS', 'admin-demo');
decideBaseline(state, request.id, 'Approved');
assert.equal(workspace.baselineId, request.baselineId); assert.ok(workspace.version > 1);
assert.deepEqual(parent.sourceIds, originalSources);
assert.deepEqual(state.baselines.find(item => item.id === request.baselineId).sourceIds, ['new-source']);
assert.throws(() => decideBaseline(state, request.id, 'Approved'), /already been decided/);
});
test('changed source revisions invalidate a pending baseline approval', async () => {
const state = makeFreshState(); state.session = await authenticateTeam(state, 'MCM', 'team-demo');
const request = requestBaseline(state, { ref: 'New baseline' }, [{ id: 'source', team: 'MCM', revision: 1, checksum: 'initial' }]);
state.documents[0].checksum = 'changed'; state.session = await authenticateTeam(state, 'MCM', 'admin-demo');
assert.throws(() => decideBaseline(state, request.id, 'Approved'), /changed or is unavailable/);
});
test('CR merges create a new baseline only after Administrator approval and retain the complete source set', async () => {
const state = makeMcmSeed(); state.session = await authenticateTeam(state, 'MCM', 'team-demo');
const workspace = state.workspaces.find(item => item.id === state.activeWorkspaceId);
const parent = state.baselines.find(item => item.id === workspace.baselineId), original = structuredClone(parent), count = state.baselines.length;
const contextDoc = { id: 'context-only', team: 'MCM', name: 'Business_context.md', revision: 1, status: 'Ready', checksum: 'context' };
state.documents.push(contextDoc); parent.sourceIds.push(contextDoc.id); original.sourceIds.push(contextDoc.id);
const request = requestBaselineMerge(state, workspace.id, 'MCM merged knowledge');
assert.equal(state.baselines.length, count); assert.equal(workspace.baselineId, parent.id);
assert.equal(request.status, 'Pending review'); assert.equal(request.type, 'changes');
assert.throws(() => requestBaselineMerge(state, workspace.id, 'Duplicate request'), /pending baseline request/);
assert.throws(() => decideBaseline(state, request.id, 'Approved'), /Only an Administrator/);
state.session = await authenticateTeam(state, 'MCM', 'admin-demo');
const created = decideBaseline(state, request.id, 'Approved', 'Reviewed CR sources', 'manifest-hash');
assert.equal(state.baselines.length, count + 1); assert.equal(created.version, '2.0');
assert.equal(created.parentId, parent.id); assert.equal(workspace.baselineId, created.id);
assert.ok(created.sourceIds.includes(contextDoc.id));
assert.ok(created.incorporatedCRs.includes('mcm-cr1042')); assert.deepEqual(workspace.crIds, []);
assert.equal(created.rules.find(rule => rule.id === 'VAL-002').text, 'Reject a request with a missing required member identifier.');
assert.equal(created.rules.find(rule => rule.id === 'VAL-001').text, original.rules.find(rule => rule.id === 'VAL-001').text);
assert.deepEqual(parent, original);
});
test('pending CR merges reject source or CR revision changes and require reviewed selected evidence', async () => {
const state = makeMcmSeed(); state.session = await authenticateTeam(state, 'MCM', 'team-demo');
const workspace = state.workspaces.find(item => item.id === state.activeWorkspaceId);
const source = state.documents.find(item => item.id === 'mcm-d3');
source.status = 'Pending review';
assert.throws(() => requestBaselineMerge(state, workspace.id, 'MCM merged knowledge'), /Review every selected/);
source.status = 'Ready'; const request = requestBaselineMerge(state, workspace.id, 'MCM merged knowledge');
source.revision += 1; state.session = await authenticateTeam(state, 'MCM', 'admin-demo');
assert.throws(() => decideBaseline(state, request.id, 'Approved'), /document changed/);
source.revision -= 1; state.crs.find(item => item.id === workspace.crIds[0]).revision += 1;
assert.throws(() => decideBaseline(state, request.id, 'Approved'), /CR changed/);
});
test('template input validates headers, duplicates, invalid workbooks and file size', async () => {
const imported = await readTemplate(new File(['Test ID,Title,Expected result\nTC-1,Accept member,Accepted'], 'format.csv'));
assert.deepEqual(imported.fields, ['Test ID', 'Title', 'Expected result']);
await assert.rejects(readTemplate(new File(['Title,title\nA,B'], 'duplicate.csv')), /unique name/);
await assert.rejects(readTemplate(new File(['invalid'], 'invalid.xlsx')), /valid Excel/);
await assert.rejects(readTemplate({ name: 'large.xlsx', size: 3 * 1024 * 1024 }), /smaller than 2 MB/);
});
test('Excel ZIP reader reads a real compressed workbook fixture', async () => {
const workbook = await readFile(new URL('./fixtures/Membership_Test_Template.xlsx', import.meta.url));
const zip = await readZip(workbook.buffer.slice(workbook.byteOffset, workbook.byteOffset + workbook.byteLength));
assert.ok((await zip.get('xl/workbook.xml')()).includes('name="Test cases"'));
const sheet = await zip.get('xl/worksheets/sheet1.xml')();
assert.ok(sheet.includes('Expected result')); assert.ok(sheet.includes('Validate active member'));
});
test('version changes reset clarification readiness and preserve historic chat context', () => {
const s = makeSeed(), w = s.workspaces[0], oldKey = contextKey(w);
s.threads[oldKey] = { workspaceId: w.id, version: 1, messages: [{ content: 'Historic evidence' }] };
s.questions.filter(q => q.workspaceId === w.id).forEach(q => { q.status = 'resolved'; q.answer = 'Old answer'; });
bumpVersion(s, w);
assert.notEqual(contextKey(w), oldKey);
assert.equal(s.threads[oldKey].messages[0].content, 'Historic evidence');
assert.equal(s.threads[contextKey(w)], undefined);
const current = s.questions.filter(q => q.workspaceId === w.id && q.version === w.version);
assert.equal(current.length, 2); assert.ok(current.every(q => q.status === 'open' && q.answer === ''));
});
test('revoked shared contracts leave scope and block generation', () => {
const s = makeSeed(), w = s.workspaces[0];
s.questions.filter(q => q.workspaceId === w.id).forEach(q => { q.status = 'resolved'; q.answer = 'Reviewed'; });
assert.ok(eligibleDocuments(s, w).some(d => d.id === 'dshared'));
s.shares[0].status = 'Revoked';
assert.ok(!eligibleDocuments(s, w).some(d => d.id === 'dshared'));
assert.equal(readiness(s, w).ready, false);
});
test('CSV round-trips quotes and multiline cells and neutralizes spreadsheet formulas', () => {
const text = csv([['Title', 'Expected result'], ['A "quoted" title', 'Two\nlines'], ['=SUM(A1:A2)', 'literal']]);
const rows = parseCsv(text);
assert.equal(rows[1][0], 'A "quoted" title'); assert.equal(rows[1][1], 'Two\nlines');
assert.equal(rows[2][0], "'=SUM(A1:A2)");
assert.throws(() => parseCsv('"Unclosed'), /unclosed/);
});
test('selected templates retain immutable field snapshots and custom fields', () => {
const s = makeSeed(), w = s.workspaces.find(w => w.id === 'w1051');
s.templates[0].fields.push('Review batch');
const suite = makeDemoSuite(s, w, 't1', false);
assert.equal(suite.tests[0].customFields['Review batch'], '');
s.templates[0].fields.push('Later field');
assert.ok(!suite.templateSnapshot.fields.includes('Later field'));
s.templates[0].visibility = 'workspace'; s.templates[0].workspaceId = 'w1042';
assert.throws(() => makeDemoSuite(s, w, 't1', false), /accessible team template/);
});
Top comments (0)