Skip to content

Commit 478e5e2

Browse files
Merge worktree-agent-190: Fail loudly on a missing docs content root (#190)
2 parents 6600a48 + 2c73291 commit 478e5e2

12 files changed

Lines changed: 230 additions & 6 deletions

backend/e2e/auth-error-production-portal.spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { test, expect } = require('@playwright/test');
22
const { spawn } = require('child_process');
33
const http = require('http');
44
const path = require('path');
5+
const { createDocsCacheRoot } = require('./helpers/docs-content-root');
56

67
const PORT = 3017;
78
const BASE_URL = `http://127.0.0.1:${PORT}`;
@@ -34,6 +35,7 @@ test.describe('production portal authentication error', () => {
3435
DATAOPS_DOCS_DOMAIN: '1',
3536
WORK_ENGINE_AUTH_MODE: 'portal',
3637
DTC_OFFLINE: '1',
38+
DTC_CACHE_ROOT: createDocsCacheRoot('issue-190-docs-cache/auth-error-production-portal'),
3739
FRONTEND_ROOT: path.resolve(__dirname, '..', '..', 'frontend'),
3840
AUTH_BASE_URL: 'https://auth.example.test',
3941
AUTH_ISSUER: 'https://issuer.example.test/pool',

backend/e2e/bookkeeping-production-portal.spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { test, expect } = require("@playwright/test");
22
const { spawn } = require("child_process");
33
const http = require("http");
44
const path = require("path");
5+
const { createDocsCacheRoot } = require("./helpers/docs-content-root");
56

67
const PORT = 3014;
78
const BASE_URL = `http://127.0.0.1:${PORT}`;
@@ -37,6 +38,7 @@ test.describe("production portal bookkeeping", () => {
3738
SKIP_AUTH: "true",
3839
DATAOPS_DOCS_DOMAIN: "1",
3940
DTC_OFFLINE: "1",
41+
DTC_CACHE_ROOT: createDocsCacheRoot("issue-190-docs-cache/bookkeeping-production-portal"),
4042
FRONTEND_ROOT: path.resolve(__dirname, "..", "..", "frontend"),
4143
PORT: String(PORT),
4244
},

backend/e2e/bookkeeping-sponsors-design-production-portal.spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const { spawn } = require("child_process");
44
const fs = require("fs");
55
const http = require("http");
66
const path = require("path");
7+
const { createDocsCacheRoot } = require("./helpers/docs-content-root");
78

89
const PORT = 3318;
910
const BASE_URL = `http://127.0.0.1:${PORT}`;
@@ -203,6 +204,7 @@ test.describe("Bookkeeping and Sponsors design prototype", () => {
203204
SKIP_AUTH: "true",
204205
DATAOPS_DOCS_DOMAIN: "1",
205206
DTC_OFFLINE: "1",
207+
DTC_CACHE_ROOT: createDocsCacheRoot("issue-190-docs-cache/bookkeeping-sponsors-design-production-portal"),
206208
CONVERSATIONAL_TELEGRAM_INGRESS_ENABLED: "false",
207209
CONVERSATIONAL_EXECUTION_ENABLED: "false",
208210
CONVERSATIONAL_ENABLED_PLUGINS: "none",

backend/e2e/browser-cookie-bootstrap-production-portal.spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { test, expect } = require('@playwright/test');
22
const { spawn } = require('child_process');
33
const http = require('http');
44
const path = require('path');
5+
const { createDocsCacheRoot } = require('./helpers/docs-content-root');
56

67
const PORT = 3018;
78
const BASE_URL = `http://localhost:${PORT}`;
@@ -35,6 +36,7 @@ test.describe('production portal browser-cookie bootstrap', () => {
3536
DATAOPS_DOCS_DOMAIN: '1',
3637
WORK_ENGINE_AUTH_MODE: 'portal',
3738
DTC_OFFLINE: '1',
39+
DTC_CACHE_ROOT: createDocsCacheRoot('issue-190-docs-cache/browser-cookie-bootstrap-production-portal'),
3840
// The built artifact packages this same canonical source at
3941
// dist/frontend; source-mode E2E points to it explicitly.
4042
FRONTEND_ROOT: path.resolve(__dirname, '..', '..', 'frontend'),

backend/e2e/frontend-module-characterization.spec.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
11
const { test, expect } = require("@playwright/test");
22
const { spawn } = require("node:child_process");
3+
const fs = require("node:fs");
34
const net = require("node:net");
45
const path = require("node:path");
6+
const { createDocsCacheRoot } = require("./helpers/docs-content-root");
57

68
const ROOT = path.resolve(__dirname, "..", "..");
9+
const SCREENSHOT_DIR = path.join(ROOT, ".tmp", "screenshots", "issue-190");
10+
const FIXTURE_DOC_PATH = "content/overview/reference/schedule.md";
11+
const FIXTURE_DOC_TITLE = "Synthetic Characterization Schedule";
12+
const FIXTURE_DOC_SUMMARY = "Synthetic reference used only by browser characterization tests.";
13+
// `buildOperationsReferenceLinks` indexes this exact path, so the docs surface
14+
// only lists this document when `GET /docs` really returned the spec's corpus.
15+
const FIXTURE_DOC = [
16+
"---",
17+
"id: ref.synthetic.characterization-schedule",
18+
`title: ${FIXTURE_DOC_TITLE}`,
19+
`summary: ${FIXTURE_DOC_SUMMARY}`,
20+
"doc_type: reference",
21+
"tags: [synthetic]",
22+
"systems: [dataops]",
23+
"---",
24+
"",
25+
`# ${FIXTURE_DOC_TITLE}`,
26+
"",
27+
"Synthetic public-safe content for the characterization corpus.",
28+
"",
29+
].join("\n");
730
let server;
831
let baseURL;
932

@@ -111,6 +134,10 @@ test.describe("pre-refactor frontend module characterization", () => {
111134
test.beforeAll(async () => {
112135
const port = await freePort();
113136
baseURL = `http://127.0.0.1:${port}`;
137+
const cacheRoot = createDocsCacheRoot("issue-190-frontend-characterization", {
138+
[FIXTURE_DOC_PATH]: FIXTURE_DOC,
139+
});
140+
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
114141
server = spawn(path.join(ROOT, "node_modules", ".bin", "tsx"), ["scripts/test-server.ts"], {
115142
cwd: path.join(ROOT, "backend"),
116143
detached: true,
@@ -122,6 +149,7 @@ test.describe("pre-refactor frontend module characterization", () => {
122149
SKIP_AUTH: "true",
123150
DATAOPS_DOCS_DOMAIN: "1",
124151
DTC_OFFLINE: "1",
152+
DTC_CACHE_ROOT: cacheRoot,
125153
FRONTEND_ROOT: path.join(ROOT, "frontend"),
126154
E2E_TEMPLATE_ACTOR_ID: "00000000-0000-0000-0000-000000000001",
127155
CONVERSATIONAL_TELEGRAM_INGRESS_ENABLED: "false",
@@ -226,6 +254,15 @@ test.describe("pre-refactor frontend module characterization", () => {
226254
await expect(page).toHaveURL(`${baseURL}/#${route}`);
227255
await expect(page.locator("#library-title")).toHaveText(title);
228256
await expect(page.locator(marker)).toBeVisible();
257+
if (route === "/processes") {
258+
// The docs surface must list the spec's own corpus. Without this the
259+
// suite would pass just as happily against an empty or wrongly pointed
260+
// content root.
261+
await expect(
262+
page.locator(".ops-surface-docs .ops-reference-link", { hasText: FIXTURE_DOC_TITLE }),
263+
).toBeVisible();
264+
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "processes.png"), fullPage: true });
265+
}
229266
await expectNoHorizontalOverflow(page, route);
230267
}
231268
await page.goto(`${baseURL}/#/templates`);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Spec-owned docs content roots for offline browser tests.
3+
*
4+
* `scripts/test-server.ts` builds its docs store from the environment, and
5+
* `githubStoreConfigFromEnv()` falls back to `/tmp/dataops` when
6+
* `DTC_CACHE_ROOT` is unset. Offline (`DTC_OFFLINE=1`) nothing hydrates that
7+
* directory, so a spec without an explicit cache root inherits whatever the
8+
* machine happens to have: present and empty on a developer laptop, absent on a
9+
* clean CI runner. Every offline spec therefore owns a content root under the
10+
* project-local `.tmp/`, created fresh for each run.
11+
*/
12+
13+
const fs = require('node:fs');
14+
const path = require('node:path');
15+
16+
const REPO_ROOT = path.resolve(__dirname, '..', '..', '..');
17+
const TMP_ROOT = path.join(REPO_ROOT, '.tmp');
18+
19+
/**
20+
* Create an empty-but-present docs cache for `name` and return its root.
21+
*
22+
* The returned path is what `DTC_CACHE_ROOT` must be set to; `<root>/content`
23+
* is created so the docs collection endpoints see a readable (possibly empty)
24+
* corpus instead of a missing configured content root.
25+
*
26+
* @param {string} name spec-unique directory name, relative to `<repo>/.tmp/`
27+
* @param {Record<string, string>} documents repo-relative `content/...` path -> markdown
28+
* @returns {string} absolute cache root for `DTC_CACHE_ROOT`
29+
*/
30+
function createDocsCacheRoot(name, documents = {}) {
31+
const cacheRoot = path.join(TMP_ROOT, name);
32+
fs.rmSync(cacheRoot, { recursive: true, force: true });
33+
const contentRoot = path.join(cacheRoot, 'content');
34+
fs.mkdirSync(contentRoot, { recursive: true });
35+
for (const [repoPath, markdown] of Object.entries(documents)) {
36+
const relative = repoPath.replace(/^content\//, '');
37+
const target = path.join(contentRoot, relative);
38+
fs.mkdirSync(path.dirname(target), { recursive: true });
39+
fs.writeFileSync(target, markdown);
40+
}
41+
return cacheRoot;
42+
}
43+
44+
module.exports = { createDocsCacheRoot, REPO_ROOT, TMP_ROOT };

backend/e2e/newsletter-production-portal.spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { test, expect } = require("@playwright/test"),
22
{ spawn } = require("child_process"),
33
http = require("http"),
44
path = require("path");
5+
const { createDocsCacheRoot } = require("./helpers/docs-content-root");
56
const PORT = 3017,
67
BASE = `http://127.0.0.1:${PORT}`;
78
let child;
@@ -29,6 +30,7 @@ test.describe("production newsletter planner", () => {
2930
SKIP_AUTH: "true",
3031
DATAOPS_DOCS_DOMAIN: "1",
3132
DTC_OFFLINE: "1",
33+
DTC_CACHE_ROOT: createDocsCacheRoot("issue-190-docs-cache/newsletter-production-portal"),
3234
FRONTEND_ROOT: path.resolve(__dirname, "..", "..", "frontend"),
3335
PORT: String(PORT),
3436
},

backend/e2e/planning-surfaces-design.spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const { spawn } = require("child_process");
44
const fs = require("fs");
55
const http = require("http");
66
const path = require("path");
7+
const { createDocsCacheRoot } = require("./helpers/docs-content-root");
78

89
const screenshots = path.resolve(__dirname, "..", "..", ".tmp", "screenshots", "planning-surfaces");
910
const PORT = 3197;
@@ -102,6 +103,7 @@ test.beforeAll(async () => {
102103
SKIP_AUTH: "true",
103104
DATAOPS_DOCS_DOMAIN: "1",
104105
DTC_OFFLINE: "1",
106+
DTC_CACHE_ROOT: createDocsCacheRoot("issue-190-docs-cache/planning-surfaces-design"),
105107
CONVERSATIONAL_TELEGRAM_INGRESS_ENABLED: "false",
106108
CONVERSATIONAL_EXECUTION_ENABLED: "false",
107109
CONVERSATIONAL_ENABLED_PLUGINS: "none",

backend/e2e/sponsor-crm-communications-production-portal.spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { test, expect } = require('@playwright/test');
22
const { spawn } = require('child_process');
33
const http = require('http');
44
const path = require('path');
5+
const { createDocsCacheRoot } = require('./helpers/docs-content-root');
56

67
const PORT = 3016;
78
const BASE_URL = `http://127.0.0.1:${PORT}`;
@@ -33,6 +34,7 @@ test.describe('production sponsor CRM communications portal', () => {
3334
SKIP_AUTH: 'true',
3435
DATAOPS_DOCS_DOMAIN: '1',
3536
DTC_OFFLINE: '1',
37+
DTC_CACHE_ROOT: createDocsCacheRoot('issue-190-docs-cache/sponsor-crm-communications-production-portal'),
3638
FRONTEND_ROOT: path.resolve(__dirname, '..', '..', 'frontend'),
3739
PORT: String(PORT),
3840
},

backend/e2e/sponsor-crm-production-portal.spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { test, expect } = require("@playwright/test");
22
const { spawn } = require("child_process");
33
const http = require("http");
44
const path = require("path");
5+
const { createDocsCacheRoot } = require("./helpers/docs-content-root");
56
const PORT = 3116,
67
BASE_URL = `http://127.0.0.1:${PORT}`;
78
let child;
@@ -31,6 +32,7 @@ test.describe("production sponsor CRM portal", () => {
3132
SKIP_AUTH: "true",
3233
DATAOPS_DOCS_DOMAIN: "1",
3334
DTC_OFFLINE: "1",
35+
DTC_CACHE_ROOT: createDocsCacheRoot("issue-190-docs-cache/sponsor-crm-production-portal"),
3436
FRONTEND_ROOT: path.resolve(__dirname, "..", "..", "frontend"),
3537
PORT: String(PORT),
3638
},

0 commit comments

Comments
 (0)