070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
76 lines
3.2 KiB
TypeScript
76 lines
3.2 KiB
TypeScript
// Red-spec for referencing a brand-new (never-generated) project.
|
|
//
|
|
// A managed project is only a DB row until its first file write — its
|
|
// `PROJECTS_DIR/<id>` folder does not exist yet. When such a project is picked
|
|
// via "Reference another project", the daemon resolves it to that computed
|
|
// path; the composer's existence probe (`/api/dir-exists`) then reports the
|
|
// folder missing and the reference is dropped (previously it even blocked the
|
|
// whole submission). GET /api/projects/:id?ensureDir=1 fixes this by calling
|
|
// ensureReferencedProjectDir, which materializes the managed folder so the
|
|
// reference resolves to a real directory. External / imported roots (absolute
|
|
// baseDir) are the user's own folders and must never be created here.
|
|
|
|
import { mkdtempSync, rmSync, existsSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
|
|
import { ensureReferencedProjectDir } from '../src/routes/project/index.js';
|
|
import { ensureProject } from '../src/projects.js';
|
|
|
|
const tempDirs: string[] = [];
|
|
|
|
function makeProjectsRoot(): string {
|
|
const dir = mkdtempSync(path.join(tmpdir(), 'od-ref-ensure-'));
|
|
tempDirs.push(dir);
|
|
return dir;
|
|
}
|
|
|
|
afterEach(() => {
|
|
for (const dir of tempDirs.splice(0)) {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
describe('ensureReferencedProjectDir', () => {
|
|
it('materializes a managed project folder that has never been written to', async () => {
|
|
const projectsRoot = makeProjectsRoot();
|
|
const project = { id: 'reference-managed', metadata: { kind: 'prototype' } };
|
|
const target = path.join(projectsRoot, project.id);
|
|
|
|
expect(existsSync(target)).toBe(false); // brand-new project: no folder yet
|
|
await ensureReferencedProjectDir(projectsRoot, project, ensureProject);
|
|
expect(existsSync(target)).toBe(true); // now referencing resolves to a real dir
|
|
});
|
|
|
|
it('materializes a managed project folder even with no metadata', async () => {
|
|
const projectsRoot = makeProjectsRoot();
|
|
const project = { id: 'reference-bare' };
|
|
await ensureReferencedProjectDir(projectsRoot, project, ensureProject);
|
|
expect(existsSync(path.join(projectsRoot, project.id))).toBe(true);
|
|
});
|
|
|
|
it('never creates a folder for an imported/external project (absolute baseDir)', async () => {
|
|
const projectsRoot = makeProjectsRoot();
|
|
const externalBase = makeProjectsRoot(); // stand-in for the user's own folder
|
|
const project = { id: 'reference-imported', metadata: { kind: 'prototype', baseDir: externalBase } };
|
|
|
|
await ensureReferencedProjectDir(projectsRoot, project, ensureProject);
|
|
// The managed path under projectsRoot must not be conjured up for an
|
|
// external project — its files live in the user's own baseDir.
|
|
expect(existsSync(path.join(projectsRoot, project.id))).toBe(false);
|
|
});
|
|
|
|
it('propagates managed folder materialization failures', async () => {
|
|
const projectsRoot = makeProjectsRoot();
|
|
const project = { id: 'reference-managed', metadata: { kind: 'prototype' } };
|
|
const err = new Error('disk full');
|
|
|
|
await expect(
|
|
ensureReferencedProjectDir(projectsRoot, project, async () => {
|
|
throw err;
|
|
}),
|
|
).rejects.toThrow('disk full');
|
|
});
|
|
});
|