Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions dist/commands/demo.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/commands/demo.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/commands/demo.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/commitlore.mjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion src/commands/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ const checkPlatform = (override?: string): string | null => {

export interface DemoOptions {
cwd?: string;
/**
* Directory the temporary repository is created under. Defaults to the
* process-wide temp dir, which is what the CLI uses. A caller that needs to
* assert the temporary directory was removed passes a root it owns: read
* against the shared tmpdir, that assertion answers for every process on the
* machine, not for this call (#364).
*/
tmpRoot?: string;
/** For testing: override the detected platform. */
platformOverride?: string;
/** For testing: throw mid-execution to verify cleanup. */
Expand Down Expand Up @@ -114,7 +122,7 @@ export const runDemo = async (opts: DemoOptions = {}): Promise<DemoResult> => {

try {
// Create temporary directory with a recognizable prefix for testing
tmpDir = mkdtempSync(join(tmpdir(), 'commitlore-demo-'));
tmpDir = mkdtempSync(join(opts.tmpRoot ?? tmpdir(), 'commitlore-demo-'));

// Verify we are NOT operating on the user's repository
const userCwd = resolve(opts.cwd ?? process.cwd());
Expand Down
40 changes: 25 additions & 15 deletions test/demo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,19 @@ describe('commitlore demo', () => {
/** A real git repo used as the cwd for the demo — must remain untouched. */
let userRepo: string;
let userRepoHeadBefore: string;
/**
* A temp root this suite owns, so that "the demo left nothing behind" is a
* question about this call rather than about the machine. Asked of the
* process-wide tmpdir it was neither: a concurrent worker holding its own
* `commitlore-demo-*` directory during the assertion window answered it, and
* turned the two cleanup tests red for a reason unrelated to `runDemo` (#364).
* Keeping the demo under this root also stops the suite leaking that prefix
* into the shared tmpdir, where another checkout's run would read it.
*/
let demoRoot: string;

beforeAll(() => {
demoRoot = mkdtempSync(join(tmpdir(), 'demo-tmproot-'));
userRepo = mkdtempSync(join(tmpdir(), 'demo-user-repo-'));
createTestRepo({ path: userRepo });
// Create an initial commit so HEAD exists
Expand All @@ -34,46 +45,45 @@ describe('commitlore demo', () => {

afterAll(() => {
rmSync(userRepo, { recursive: true, force: true });
rmSync(demoRoot, { recursive: true, force: true });
});

it('demo runs without error on a supported platform', async () => {
const result = await runDemo({ cwd: userRepo });
const result = await runDemo({ cwd: userRepo, tmpRoot: demoRoot });
expect(result.exitCode).toBe(0);
expect(result.output.length).toBeGreaterThan(0);
});

it('output shows lifecycle filtering — active record present, superseded excluded', async () => {
const result = await runDemo({ cwd: userRepo });
const result = await runDemo({ cwd: userRepo, tmpRoot: demoRoot });
// The active record (r-demo02, SQLite) should be visible
expect(result.output).toContain('r-demo02');
// The superseded record (r-demo01, Redis predecessor) should be filtered out
expect(result.output).not.toContain('r-demo01');
});

it('temporary directory does not exist after successful completion', async () => {
const tmpRoot = tmpdir();
const before = readdirSync(tmpRoot).filter((d) => d.startsWith('commitlore-demo'));
await runDemo({ cwd: userRepo });
const after = readdirSync(tmpRoot).filter((d) => d.startsWith('commitlore-demo'));
const leftover = after.filter((d) => !before.includes(d));
expect(leftover).toEqual([]);
// A root used by this case alone, so emptiness is the whole assertion —
// no prefix filter, and nothing anyone else wrote can satisfy or break it.
const caseRoot = mkdtempSync(join(demoRoot, 'success-'));
await runDemo({ cwd: userRepo, tmpRoot: caseRoot });
expect(readdirSync(caseRoot)).toEqual([]);
});

it('temporary directory is gone after a simulated crash (safety property)', async () => {
const caseRoot = mkdtempSync(join(demoRoot, 'crash-'));
// Inject a crash trigger — runDemo with crashTest: true throws mid-execution
try {
await runDemo({ cwd: userRepo, crashTest: true });
await runDemo({ cwd: userRepo, crashTest: true, tmpRoot: caseRoot });
} catch {
// Expected to throw
}
// But the temp directory must still be cleaned up
const tmpRoot = tmpdir();
const leftover = readdirSync(tmpRoot).filter((d) => d.startsWith('commitlore-demo'));
expect(leftover).toEqual([]);
expect(readdirSync(caseRoot)).toEqual([]);
});

it('user repository is never written to (safety property)', async () => {
await runDemo({ cwd: userRepo });
await runDemo({ cwd: userRepo, tmpRoot: demoRoot });
// HEAD must be unchanged
const headAfter = execFileSync('git', ['rev-parse', 'HEAD'], {
cwd: userRepo,
Expand All @@ -91,14 +101,14 @@ describe('commitlore demo', () => {
});

it('on an unsupported platform prints a reason and exits non-zero', async () => {
const result = await runDemo({ cwd: userRepo, platformOverride: 'win32' });
const result = await runDemo({ cwd: userRepo, tmpRoot: demoRoot, platformOverride: 'win32' });
expect(result.exitCode).toBe(1);
expect(result.output.toLowerCase()).toContain('not supported');
});

it('completes in under 30 seconds', async () => {
const start = Date.now();
await runDemo({ cwd: userRepo });
await runDemo({ cwd: userRepo, tmpRoot: demoRoot });
const elapsed = Date.now() - start;
expect(elapsed).toBeLessThan(30_000);
});
Expand Down
Loading