Files
stack/packages/library/tests/write.test.mjs

172 lines
11 KiB
JavaScript

// Row 288: real bare-repository plumbing and remote effects. All text and credentials are fixtures.
import test from 'node:test';
import assert from 'node:assert/strict';
import { writeFileSync, existsSync, renameSync, mkdirSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { git } from './helpers.mjs';
import { sync, push, withLibraryWriteGuard } from '../src/sync.mjs';
import { servedCommit, withLibraryLock, readSyncState, SYNC_STATE_FILE } from '../src/mirror.mjs';
import { publishLibraryWrite } from '../src/sync.mjs';
import { checkMapping } from '../src/mapping.mjs';
import { writeLibraryDoc, saveLibraryFile, revertLibraryCommit } from '../src/write.mjs';
import { world, PATH, EDITOR } from './write-fixture.mjs';
test('C2-02: write/delete each publish one parent, seat ident, fixed trailer, index and lastPush', async t => {
const f = await world(t), parent = f.remoteHead();
const saved = await writeLibraryDoc(f.args(), f.opts);
assert.deepEqual(saved, { project: 'books', path: PATH, commit: f.remoteHead() });
assert.equal(git(f.remote, 'show', `${saved.commit}:${PATH}`), 'after');
assert.equal(git(f.remote, 'show', '-s', '--format=%P', saved.commit), parent);
assert.equal(git(f.remote, 'show', '-s', '--format=%an <%ae>|%cn <%ce>', saved.commit),
'Bean Fixture <[email protected]>|Bean Fixture <[email protected]>');
assert.equal(git(f.remote, 'show', '-s', '--format=%B', saved.commit), `Mosaic: write ${PATH}\n\nMosaic-Agent: acme/bean`);
assert.equal(await servedCommit(f.mirror), saved.commit);
assert.equal((await f.state()).lastPush.commit, saved.commit);
assert.equal(existsSync(join(f.mirror, 'index')), false);
const gone = await writeLibraryDoc(f.args({ content: undefined }), { ...f.opts, op: 'delete' });
assert.equal(git(f.remote, 'show', '-s', '--format=%P', gone.commit), saved.commit);
assert.equal(git(f.remote, 'ls-tree', '-r', gone.commit, '--', PATH), '');
assert.equal(git(f.remote, 'show', '-s', '--format=%B', gone.commit), `Mosaic: delete ${PATH}\n\nMosaic-Agent: acme/bean`);
});
test('C2-02 M12: each path refusal leaves every ref and remote unchanged', async t => {
const f = await world(t);
const paths = ['Book One/Chapters/Chapter 01.md', 'Book One/Playground/What If.md', 'Inbox Note.md',
'Book One/Notes/X.canvas', 'Book One/Notes/X.base', 'Book One/Notes/X.png', 'Second World/Chapters/01.md',
'/Book One/Notes/X.md', 'Book One/Notes/../X.md', 'Book One/./Notes/X.md', 'Book One//Notes/X.md',
'Book One/Notes/X\\Y.md', 'Book One/Notes/X\nY.md', 'Book One/Notes/Link.md',
'Book One/Notes/agent.md', `Book One/Notes/${'é'.repeat(512)}.md`];
for (const path of paths) {
const before = f.refs(), head = f.remoteHead();
await assert.rejects(writeLibraryDoc(f.args({ path, base: null }), f.opts), { code: 'library-write-story-path' }, path);
assert.equal(f.refs(), before); assert.equal(f.remoteHead(), head);
}
});
test('C2-02 Q4: delete requires an existing 100644 blob; null base never deletes a missing path', async t => {
const f = await world(t);
for (const path of ['Book One/Notes/Missing.md', 'Book One/Notes/Executable.md', 'Book One/Notes/Link.md', 'Book One/Notes/Submodule.md']) {
const before = f.refs();
await assert.rejects(writeLibraryDoc(f.args({ path, base: null, content: undefined }), { ...f.opts, op: 'delete' }),
{ code: 'library-write-story-path' });
assert.equal(f.refs(), before);
}
});
test('C2-02: stale base and failed push keep remote/served/tracking refs unchanged', async t => {
const f = await world(t), refs = f.refs(), head = f.remoteHead();
await assert.rejects(writeLibraryDoc(f.args({ base: 'a'.repeat(40) }), f.opts), { code: 'stale-base' });
assert.equal(f.refs(), refs); assert.equal(f.remoteHead(), head);
f.reject();
await assert.rejects(writeLibraryDoc(f.args(), f.opts), { code: 'library-push-failed' });
assert.equal(f.refs(), refs); assert.equal(f.remoteHead(), head);
});
test('C2-02: a moved unrelated blob retries once on the new parent, changed target refuses', async t => {
const f = await world(t);
const args = f.args(); let raced;
f.race(() => { f.external('Inbox Note.md', 'another\n'); raced = f.remoteHead(); });
const saved = await writeLibraryDoc(args, f.opts);
assert.equal(git(f.remote, 'show', '-s', '--format=%P', saved.commit), raced);
assert.equal(git(f.remote, 'show', `${saved.commit}:Inbox Note.md`), 'another');
const g = await world(t), before = g.refs();
g.race(() => g.external(PATH, 'raced target\n'));
await assert.rejects(writeLibraryDoc(g.args(), g.opts), { code: 'stale-base' });
assert.equal(g.refs(), before); assert.equal(git(g.remote, 'show', `main:${PATH}`), 'raced target');
});
test('C2-02: create with null base; human revert restores parent and does not require seat credentials', async t => {
const f = await world(t);
const made = await writeLibraryDoc(f.args({ path: 'Book One/Notes/New.md', base: null, content: 'new\n' }), f.opts);
const options = { credential: { ...f.credential, kind: 'human' }, command: f.command };
const reverted = await revertLibraryCommit({ ...f.context, commit: made.commit }, options);
assert.deepEqual(reverted, { project: 'books', path: 'Book One/Notes/New.md', reverted: made.commit, commit: f.remoteHead() });
assert.equal(git(f.remote, 'ls-tree', '-r', reverted.commit, '--', reverted.path), '');
assert.equal(git(f.remote, 'show', '-s', '--format=%an <%ae>|%cn <%ce>', reverted.commit),
'Human Fixture <[email protected]>|Human Fixture <[email protected]>');
assert.equal(git(f.remote, 'show', '-s', '--format=%(trailers:key=Mosaic-Agent,valueonly)', reverted.commit), '');
});
test('C2-02: revert requires reachable business trailer and refuses a later path change', async t => {
const f = await world(t), options = { credential: { ...f.credential, kind: 'human' }, command: f.command };
await assert.rejects(revertLibraryCommit({ ...f.context, commit: f.commit }, options), { code: 'library-revert-not-agent' });
const saved = await writeLibraryDoc(f.args(), f.opts);
await saveLibraryFile({ ...f.context, path: PATH, base: git(f.mirror, 'rev-parse', `served:${PATH}`), content: 'human later\n' }, options);
await assert.rejects(revertLibraryCommit({ ...f.context, commit: saved.commit }, options), { code: 'stale-base' });
});
test('C2-02: human save reaches story, preserves UTF-8 bytes and uses editor without a trailer', async t => {
const f = await world(t), path = 'Book One/Chapters/Chapter 01.md';
const content = '# Her text\n' + 'é'.repeat(40000) + '\n\n';
const saved = await saveLibraryFile({ ...f.context, path, base: git(f.mirror, 'rev-parse', `served:${path}`), content },
{ credential: { ...f.credential, kind: 'human' }, command: f.command });
assert.equal(git(f.remote, 'show', '-s', '--format=%an <%ae>', saved.commit), 'Human Fixture <[email protected]>');
// git helper trims; compare the stored blob byte count separately.
assert.equal(Number(git(f.remote, 'cat-file', '-s', `${saved.commit}:${path}`)), Buffer.byteLength(content));
});
test('C2-02: invalid content/editor/human credential refuse before any git command', async t => {
const f = await world(t), before = f.refs();
for (const content of ['nul\0', '\ud800', 'x'.repeat(1024 * 1024 + 1)])
await assert.rejects(writeLibraryDoc(f.args({ content }), f.opts), { code: 'invalid-request' });
const args = { ...f.context, path: PATH, base: null, content: 'x' };
writeFileSync(join(f.mirror, 'unexpected'), 'any local git inspection must refuse\n');
delete f.entry.editor;
await assert.rejects(saveLibraryFile(args, { credential: { ...f.credential, kind: 'human' }, command: f.command }), { code: 'library-mapping-invalid' });
await assert.rejects(revertLibraryCommit({ ...f.context, commit: f.commit }, { credential: { ...f.credential, kind: 'human' }, command: f.command }), { code: 'library-mapping-invalid' });
f.entry.editor = EDITOR;
const n = f.commands.length;
await assert.rejects(saveLibraryFile(args, { credential: { kind: 'human', user: 'human', file: '/absent' }, command: f.command }), { code: 'library-human-missing' });
await assert.rejects(revertLibraryCommit({ ...f.context, commit: f.commit }, { credential: { kind: 'human', user: 'human', file: '/absent' }, command: f.command }), { code: 'library-human-missing' });
assert.equal(f.commands.length, n); assert.equal(f.refs(), before);
});
test('C2-02 guard: only the exact owning token permits a push race fetch; sync refuses busy', async t => {
const f = await world(t), commit = f.remoteHead();
f.external('Inbox Note.md', 'remote moved\n');
await withLibraryWriteGuard(f.mirror, async token => {
await assert.rejects(sync(f.context, { credential: f.credential, command: f.command }), { code: 'library-sync-busy' });
for (const writeToken of [null, {}, Object.freeze({})])
await assert.rejects(push(f.context, { commit, credential: f.credential, command: f.command, writeToken }), { code: 'library-sync-busy' });
const raced = await push(f.context, { commit, credential: f.credential, command: f.command, writeToken: token });
assert.equal(raced.raced, true); assert.equal(raced.tip, f.remoteHead());
});
});
test('C2-02 publication F1: failed state rename restores served to match prior state', async t => {
const f = await world(t), previous = await servedCommit(f.mirror);
const commit = git(f.mirror, 'commit-tree', git(f.mirror, 'rev-parse', `${previous}^{tree}`), '-p', previous, '-m', 'candidate');
const statePath = join(f.mirror, SYNC_STATE_FILE), backup = join(f.root, 'state-backup');
await withLibraryLock(f.mirror, async lock => {
// A filesystem failure after the locked state read, before state publication. The original
// state is saved outside the mirror and restored after the failed rename.
const read = async options => {
const state = await lock.readSyncState(options);
renameSync(statePath, backup); mkdirSync(statePath, { mode: 0o700 });
return state;
};
try {
await assert.rejects(publishLibraryWrite(f.mirror, { mapping: checkMapping(f.entry), commit, previous, readSyncState: read }),
{ code: 'outcome-unknown' });
assert.equal(git(f.mirror, 'rev-parse', 'served'), previous);
} finally { rmSync(statePath, { recursive: true, force: true }); renameSync(backup, statePath); }
});
assert.equal((await readSyncState(f.mirror, { branch: 'main' })).served, previous);
});
test('C2-02 E1: a dotted business id never matches another business trailer', async t => {
const f = await world(t, { business: 'acme.co' });
writeFileSync(join(f.repo, PATH), 'foreign seat change\n');
git(f.repo, 'add', '-A'); git(f.repo, 'commit', '-qm', 'Foreign\n\nMosaic-Agent: acme-co/bean');
const foreign = git(f.repo, 'rev-parse', 'HEAD');
git(f.repo, 'push', '-q', f.remote, 'HEAD:main');
await sync(f.context, { credential: f.credential, command: f.command });
const before = f.refs();
await assert.rejects(revertLibraryCommit({ ...f.context, commit: foreign },
{ credential: { ...f.credential, kind: 'human' }, command: f.command }), { code: 'library-revert-not-agent' });
assert.equal(f.refs(), before); assert.equal(f.remoteHead(), foreign);
});