Row 153 round 1, Rocko's candidate on 686fc1cb6. Filbert approved
(comment 28727). projectRepo lives in packages/project/src/paths.mjs, so
business.mjs is unchanged (sage-0469). Sage's landing gate ran on this
commit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
92 lines
4.8 KiB
JavaScript
92 lines
4.8 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { chmodSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readdirSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { checkProject, initProject, projectRepo, writeRecord } from '../src/index.mjs';
|
|
|
|
function fixture(t) {
|
|
const root = mkdtempSync(join(tmpdir(), 'project-init-failure-'));
|
|
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
return { root, dataRoot: join(root, 'data'), project: 'stack', business: {
|
|
id: 'acme', human: 'jason', projects: { stack: { root: join(root, 'code'), repo: join(root, 'records') } },
|
|
} };
|
|
}
|
|
async function failingCommit(c, operation, sideEffect = ':') {
|
|
const realGit = execFileSync('sh', ['-c', 'command -v git'], { encoding: 'utf8' }).trim();
|
|
const bin = join(c.root, 'bin');
|
|
mkdirSync(bin);
|
|
writeFileSync(join(bin, 'git'), `#!/bin/sh\nfor arg do if [ "$arg" = commit ]; then ${sideEffect}; exit 71; fi; done\nexec '${realGit}' "$@"\n`);
|
|
chmodSync(join(bin, 'git'), 0o755);
|
|
const saved = process.env.PATH;
|
|
try { process.env.PATH = bin + ':' + saved; await operation(); }
|
|
finally { process.env.PATH = saved; }
|
|
}
|
|
|
|
for (const existing of [false, true]) test(`failed init cleans its layout and permits retry (pre-existing=${existing})`, async t => {
|
|
const c = fixture(t), repo = projectRepo(c);
|
|
if (existing) mkdirSync(repo);
|
|
const inode = existing ? lstatSync(repo).ino : null;
|
|
await failingCommit(c, () => assert.rejects(initProject(c), { code: 'project-git' }));
|
|
if (existing) {
|
|
assert.equal(lstatSync(repo).ino, inode);
|
|
assert.deepEqual(readdirSync(repo), []);
|
|
} else assert.equal(existsSync(repo), false);
|
|
const result = await initProject(c);
|
|
assert.match(result.commit, /^[0-9a-f]{40}$/);
|
|
assert.equal((await checkProject(c)).ok, true);
|
|
});
|
|
|
|
test('project check distinguishes a complete but uncommitted layout', async t => {
|
|
const c = fixture(t), { repo } = await initProject(c);
|
|
execFileSync('git', ['-C', repo, 'update-ref', '-d', 'refs/heads/main']);
|
|
await assert.rejects(checkProject(c), { code: 'project-no-commit' });
|
|
});
|
|
|
|
test('record repo refuses every declared code root including a sibling project', async t => {
|
|
const c = fixture(t);
|
|
c.business.projects.other = { root: join(c.root, 'other-code') };
|
|
c.business.projects.stack.repo = join(c.business.projects.other.root, 'records');
|
|
assert.throws(() => projectRepo(c), { code: 'project-path' });
|
|
await assert.rejects(initProject(c), { code: 'project-path' });
|
|
assert.equal(existsSync(c.business.projects.other.root), false);
|
|
c.business.projects.stack.repo = c.business.projects.other.root;
|
|
assert.throws(() => projectRepo(c), { code: 'project-path' });
|
|
c.business.projects.stack.repo = c.root;
|
|
assert.throws(() => projectRepo(c), { code: 'project-path' });
|
|
});
|
|
|
|
test('agent record writer refuses a human definition without changing history or files', async t => {
|
|
const c = fixture(t), { repo, commit } = await initProject(c);
|
|
await assert.rejects(writeRecord({ ...c, path: 'docs/human.md', record: '# Human',
|
|
author: { definition: 'human', instance: c.business.human } }), { code: 'project-author' });
|
|
assert.equal(existsSync(join(repo, 'docs/human.md')), false);
|
|
assert.equal(execFileSync('git', ['-C', repo, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim(), commit);
|
|
assert.equal(execFileSync('git', ['-C', repo, 'status', '--porcelain'], { encoding: 'utf8' }), '');
|
|
});
|
|
|
|
test('failed init preserves an unrelated file created while its commit runs', async t => {
|
|
const c = fixture(t), repo = projectRepo(c), keep = join(repo, 'human-note.txt');
|
|
await failingCommit(c, () => assert.rejects(initProject(c), { code: 'project-git' }),
|
|
`printf keep > '${keep}'`);
|
|
assert.deepEqual(readdirSync(repo), ['human-note.txt']);
|
|
assert.equal(readFileSync(keep, 'utf8'), 'keep');
|
|
});
|
|
|
|
test('failed init preserves an unrelated file inside a newly created layout directory', async t => {
|
|
const c = fixture(t), repo = projectRepo(c), keep = join(repo, 'docs/human-note.md');
|
|
await failingCommit(c, () => assert.rejects(initProject(c), { code: 'project-git' }),
|
|
`printf keep > '${keep}'`);
|
|
assert.deepEqual(readdirSync(repo), ['docs']);
|
|
assert.deepEqual(readdirSync(join(repo, 'docs')), ['human-note.md']);
|
|
assert.equal(readFileSync(keep, 'utf8'), 'keep');
|
|
});
|
|
|
|
test('failed init refuses to delete a replacement of its owned metadata file', async t => {
|
|
const c = fixture(t), repo = projectRepo(c), file = join(repo, 'project.json');
|
|
await failingCommit(c, () => assert.rejects(initProject(c), { code: 'project-init-cleanup' }),
|
|
`mv '${file}' '${join(c.root, 'original.json')}' && printf human > '${file}'`);
|
|
assert.equal(readFileSync(file, 'utf8'), 'human');
|
|
});
|