kitfly 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/LICENSE +21 -0
- package/README.md +136 -0
- package/VERSION +1 -0
- package/package.json +63 -0
- package/schemas/README.md +32 -0
- package/schemas/site.schema.json +5 -0
- package/schemas/theme.schema.json +5 -0
- package/schemas/v0/site.schema.json +172 -0
- package/schemas/v0/theme.schema.json +210 -0
- package/scripts/build-all.ts +121 -0
- package/scripts/build.ts +601 -0
- package/scripts/bundle.ts +781 -0
- package/scripts/dev.ts +777 -0
- package/scripts/generate-checksums.sh +78 -0
- package/scripts/release/export-release-key.sh +28 -0
- package/scripts/release/release-guard-tag-version.sh +79 -0
- package/scripts/release/sign-release-assets.sh +123 -0
- package/scripts/release/upload-release-assets.sh +76 -0
- package/scripts/release/upload-release-provenance.sh +52 -0
- package/scripts/release/verify-public-key.sh +48 -0
- package/scripts/release/verify-signatures.sh +117 -0
- package/scripts/version-sync.ts +82 -0
- package/src/__tests__/build.test.ts +240 -0
- package/src/__tests__/bundle.test.ts +786 -0
- package/src/__tests__/cli.test.ts +706 -0
- package/src/__tests__/crucible.test.ts +1043 -0
- package/src/__tests__/engine.test.ts +157 -0
- package/src/__tests__/init.test.ts +450 -0
- package/src/__tests__/pipeline.test.ts +1087 -0
- package/src/__tests__/productbook.test.ts +1206 -0
- package/src/__tests__/runbook.test.ts +974 -0
- package/src/__tests__/server-registry.test.ts +1251 -0
- package/src/__tests__/servicebook.test.ts +1248 -0
- package/src/__tests__/shared.test.ts +2005 -0
- package/src/__tests__/styles.test.ts +14 -0
- package/src/__tests__/theme-schema.test.ts +47 -0
- package/src/__tests__/theme.test.ts +554 -0
- package/src/cli.ts +582 -0
- package/src/commands/init.ts +92 -0
- package/src/commands/update.ts +444 -0
- package/src/engine.ts +20 -0
- package/src/logger.ts +15 -0
- package/src/migrations/0000_schema_versioning.ts +67 -0
- package/src/migrations/0001_server_port.ts +52 -0
- package/src/migrations/0002_brand_logo.ts +49 -0
- package/src/migrations/index.ts +26 -0
- package/src/migrations/schema.ts +24 -0
- package/src/server-registry.ts +405 -0
- package/src/shared.ts +1239 -0
- package/src/site/styles.css +931 -0
- package/src/site/template.html +193 -0
- package/src/templates/crucible.ts +1163 -0
- package/src/templates/driver.ts +876 -0
- package/src/templates/handbook.ts +339 -0
- package/src/templates/minimal.ts +139 -0
- package/src/templates/pipeline.ts +966 -0
- package/src/templates/productbook.ts +1032 -0
- package/src/templates/runbook.ts +829 -0
- package/src/templates/schema.ts +119 -0
- package/src/templates/servicebook.ts +1242 -0
- package/src/theme.ts +245 -0
|
@@ -0,0 +1,1087 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the Pipeline template definition
|
|
3
|
+
*
|
|
4
|
+
* Covers: src/templates/pipeline.ts
|
|
5
|
+
* Strategy: import the template directly, verify structure and generated content
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
import { pipeline } from "../templates/pipeline.ts";
|
|
10
|
+
import type { BrandingConfig, TemplateContext, TemplateFile } from "../templates/schema.ts";
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Helpers
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
/** Build a standard TemplateContext for testing */
|
|
17
|
+
function makeCtx(overrides?: Partial<TemplateContext>): TemplateContext {
|
|
18
|
+
const branding: BrandingConfig = {
|
|
19
|
+
siteName: "Test Pipeline",
|
|
20
|
+
brandName: "Acme Corp",
|
|
21
|
+
brandUrl: "https://acme.example.com",
|
|
22
|
+
primaryColor: "#2563eb",
|
|
23
|
+
footerText: "Footer text",
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
name: "test-pipeline",
|
|
27
|
+
branding,
|
|
28
|
+
template: pipeline,
|
|
29
|
+
year: 2026,
|
|
30
|
+
...overrides,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Resolve the content of a TemplateFile to a string */
|
|
35
|
+
function resolveContent(file: TemplateFile, ctx: TemplateContext): string {
|
|
36
|
+
return typeof file.content === "function" ? file.content(ctx) : file.content;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Find a file entry by its path */
|
|
40
|
+
function findFile(path: string): TemplateFile {
|
|
41
|
+
const f = pipeline.files.find((entry) => entry.path === path);
|
|
42
|
+
if (!f) throw new Error(`Template file not found: ${path}`);
|
|
43
|
+
return f;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Template Definition Structure
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
describe("pipeline template definition", () => {
|
|
51
|
+
it("has correct identity metadata", () => {
|
|
52
|
+
expect(pipeline.id).toBe("pipeline");
|
|
53
|
+
expect(pipeline.name).toBe("Pipeline");
|
|
54
|
+
expect(pipeline.version).toBe(1);
|
|
55
|
+
expect(pipeline.extends).toBe("minimal");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("has a non-empty description", () => {
|
|
59
|
+
expect(pipeline.description).toBeTruthy();
|
|
60
|
+
expect(pipeline.description.length).toBeGreaterThan(10);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("defines exactly six sections", () => {
|
|
64
|
+
expect(pipeline.sections).toHaveLength(6);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("defines the expected section names", () => {
|
|
68
|
+
const names = pipeline.sections.map((s) => s.name);
|
|
69
|
+
expect(names).toEqual([
|
|
70
|
+
"Pipeline",
|
|
71
|
+
"Sources",
|
|
72
|
+
"Destinations",
|
|
73
|
+
"Operations",
|
|
74
|
+
"Troubleshooting",
|
|
75
|
+
"Reference",
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("defines the expected section paths", () => {
|
|
80
|
+
const paths = pipeline.sections.map((s) => s.path);
|
|
81
|
+
expect(paths).toEqual([
|
|
82
|
+
"content/pipeline",
|
|
83
|
+
"content/sources",
|
|
84
|
+
"content/destinations",
|
|
85
|
+
"content/operations",
|
|
86
|
+
"content/troubleshooting",
|
|
87
|
+
"content/reference",
|
|
88
|
+
]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("every section has a description", () => {
|
|
92
|
+
for (const section of pipeline.sections) {
|
|
93
|
+
expect(section.description).toBeTruthy();
|
|
94
|
+
expect(typeof section.description).toBe("string");
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("defines a non-empty files array", () => {
|
|
99
|
+
expect(pipeline.files.length).toBeGreaterThan(0);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("every file has a non-empty path", () => {
|
|
103
|
+
for (const file of pipeline.files) {
|
|
104
|
+
expect(file.path).toBeTruthy();
|
|
105
|
+
expect(typeof file.path).toBe("string");
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("every file has content (string or function)", () => {
|
|
110
|
+
for (const file of pipeline.files) {
|
|
111
|
+
expect(["string", "function"]).toContain(typeof file.content);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("has no duplicate file paths", () => {
|
|
116
|
+
const paths = pipeline.files.map((f) => f.path);
|
|
117
|
+
const unique = new Set(paths);
|
|
118
|
+
expect(unique.size).toBe(paths.length);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// site.yaml Generation
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
describe("pipeline site.yaml", () => {
|
|
127
|
+
const ctx = makeCtx();
|
|
128
|
+
const file = findFile("site.yaml");
|
|
129
|
+
|
|
130
|
+
it("exists in the file list", () => {
|
|
131
|
+
expect(file).toBeDefined();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("includes the site title from context", () => {
|
|
135
|
+
const content = resolveContent(file, ctx);
|
|
136
|
+
expect(content).toContain('title: "Test Pipeline"');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("includes the brand name from context", () => {
|
|
140
|
+
const content = resolveContent(file, ctx);
|
|
141
|
+
expect(content).toContain('name: "Acme Corp"');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("includes the brand url from context", () => {
|
|
145
|
+
const content = resolveContent(file, ctx);
|
|
146
|
+
expect(content).toContain('url: "https://acme.example.com"');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("includes sections block with all six sections", () => {
|
|
150
|
+
const content = resolveContent(file, ctx);
|
|
151
|
+
expect(content).toContain("sections:");
|
|
152
|
+
expect(content).toContain('"Pipeline"');
|
|
153
|
+
expect(content).toContain('"Sources"');
|
|
154
|
+
expect(content).toContain('"Destinations"');
|
|
155
|
+
expect(content).toContain('"Operations"');
|
|
156
|
+
expect(content).toContain('"Troubleshooting"');
|
|
157
|
+
expect(content).toContain('"Reference"');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("includes section paths", () => {
|
|
161
|
+
const content = resolveContent(file, ctx);
|
|
162
|
+
expect(content).toContain('"content/pipeline"');
|
|
163
|
+
expect(content).toContain('"content/sources"');
|
|
164
|
+
expect(content).toContain('"content/destinations"');
|
|
165
|
+
expect(content).toContain('"content/operations"');
|
|
166
|
+
expect(content).toContain('"content/troubleshooting"');
|
|
167
|
+
expect(content).toContain('"content/reference"');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("sets home page to index.md", () => {
|
|
171
|
+
const content = resolveContent(file, ctx);
|
|
172
|
+
expect(content).toContain('home: "index.md"');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("includes documentation link", () => {
|
|
176
|
+
const content = resolveContent(file, ctx);
|
|
177
|
+
expect(content).toContain("https://github.com/3leaps/kitfly");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("responds to different branding values", () => {
|
|
181
|
+
const customCtx = makeCtx({
|
|
182
|
+
branding: {
|
|
183
|
+
siteName: "Widget Pipeline",
|
|
184
|
+
brandName: "Widget Inc",
|
|
185
|
+
brandUrl: "/",
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
const content = resolveContent(file, customCtx);
|
|
189
|
+
expect(content).toContain('title: "Widget Pipeline"');
|
|
190
|
+
expect(content).toContain('name: "Widget Inc"');
|
|
191
|
+
expect(content).toContain('url: "/"');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// index.md Generation
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
describe("pipeline index.md", () => {
|
|
200
|
+
const ctx = makeCtx();
|
|
201
|
+
const file = findFile("index.md");
|
|
202
|
+
|
|
203
|
+
it("exists in the file list", () => {
|
|
204
|
+
expect(file).toBeDefined();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("starts with YAML frontmatter", () => {
|
|
208
|
+
const content = resolveContent(file, ctx);
|
|
209
|
+
expect(content).toMatch(/^---\n/);
|
|
210
|
+
expect(content).toContain("title: Home");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("includes site name in frontmatter description", () => {
|
|
214
|
+
const content = resolveContent(file, ctx);
|
|
215
|
+
expect(content).toContain("description: Test Pipeline - Data Pipeline Operations");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("includes site name in the H1 heading", () => {
|
|
219
|
+
const content = resolveContent(file, ctx);
|
|
220
|
+
expect(content).toContain("# Test Pipeline");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("includes brand name in intro text", () => {
|
|
224
|
+
const content = resolveContent(file, ctx);
|
|
225
|
+
expect(content).toContain("Data pipeline operations for Acme Corp.");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("contains a pipeline status table", () => {
|
|
229
|
+
const content = resolveContent(file, ctx);
|
|
230
|
+
expect(content).toContain("## Pipeline Status");
|
|
231
|
+
expect(content).toContain("| Stage | Description | Status |");
|
|
232
|
+
expect(content).toContain("Index");
|
|
233
|
+
expect(content).toContain("Extract");
|
|
234
|
+
expect(content).toContain("Transfer");
|
|
235
|
+
expect(content).toContain("Validate");
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("contains quick links to key sections", () => {
|
|
239
|
+
const content = resolveContent(file, ctx);
|
|
240
|
+
expect(content).toContain("## Quick Links");
|
|
241
|
+
expect(content).toContain("[Pipeline Overview](/content/pipeline/overview)");
|
|
242
|
+
expect(content).toContain("[Run a Pipeline](/content/operations/run-pipeline)");
|
|
243
|
+
expect(content).toContain("[Source Systems](/content/sources/)");
|
|
244
|
+
expect(content).toContain("[Troubleshooting](/content/troubleshooting/common-issues)");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("includes a last-updated date in ISO format", () => {
|
|
248
|
+
const content = resolveContent(file, ctx);
|
|
249
|
+
expect(content).toMatch(/\*Last updated: \d{4}-\d{2}-\d{2}\*/);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// Pipeline Section Files
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
describe("pipeline pipeline section", () => {
|
|
258
|
+
const ctx = makeCtx();
|
|
259
|
+
|
|
260
|
+
describe("overview.md", () => {
|
|
261
|
+
const file = findFile("content/pipeline/overview.md");
|
|
262
|
+
|
|
263
|
+
it("exists in the file list", () => {
|
|
264
|
+
expect(file).toBeDefined();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("has YAML frontmatter with title and description", () => {
|
|
268
|
+
const content = resolveContent(file, ctx);
|
|
269
|
+
expect(content).toMatch(/^---\n/);
|
|
270
|
+
expect(content).toContain("title: Pipeline Overview");
|
|
271
|
+
expect(content).toContain("description: End-to-end dataflow for Acme Corp");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("describes the dataflow", () => {
|
|
275
|
+
const content = resolveContent(file, ctx);
|
|
276
|
+
expect(content).toContain("## Dataflow");
|
|
277
|
+
expect(content).toContain("Source");
|
|
278
|
+
expect(content).toContain("Index");
|
|
279
|
+
expect(content).toContain("Extract");
|
|
280
|
+
expect(content).toContain("Transfer");
|
|
281
|
+
expect(content).toContain("Validate");
|
|
282
|
+
expect(content).toContain("Destination");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("documents all four stages", () => {
|
|
286
|
+
const content = resolveContent(file, ctx);
|
|
287
|
+
expect(content).toContain("### 1. Index Build");
|
|
288
|
+
expect(content).toContain("### 2. Content Extraction");
|
|
289
|
+
expect(content).toContain("### 3. Transfer / Reflow");
|
|
290
|
+
expect(content).toContain("### 4. Validation");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("links to individual stage pages", () => {
|
|
294
|
+
const content = resolveContent(file, ctx);
|
|
295
|
+
expect(content).toContain("[Index Build](/content/pipeline/index-build)");
|
|
296
|
+
expect(content).toContain("[Content Extraction](/content/pipeline/extract)");
|
|
297
|
+
expect(content).toContain("[Transfer](/content/pipeline/transfer)");
|
|
298
|
+
expect(content).toContain("[Validation](/content/pipeline/validate)");
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe("index-build.md", () => {
|
|
303
|
+
const file = findFile("content/pipeline/index-build.md");
|
|
304
|
+
|
|
305
|
+
it("exists in the file list", () => {
|
|
306
|
+
expect(file).toBeDefined();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("has YAML frontmatter", () => {
|
|
310
|
+
const content = resolveContent(file, ctx);
|
|
311
|
+
expect(content).toMatch(/^---\n/);
|
|
312
|
+
expect(content).toContain("title: Index Build");
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("includes objective, prerequisites, procedure, and verification", () => {
|
|
316
|
+
const content = resolveContent(file, ctx);
|
|
317
|
+
expect(content).toContain("## Objective");
|
|
318
|
+
expect(content).toContain("## Prerequisites");
|
|
319
|
+
expect(content).toContain("## Procedure");
|
|
320
|
+
expect(content).toContain("## Verification");
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("links to manifest templates", () => {
|
|
324
|
+
const content = resolveContent(file, ctx);
|
|
325
|
+
expect(content).toContain("[Manifest Templates](/content/reference/manifests/)");
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it("is a static template (does not use context)", () => {
|
|
329
|
+
const ctx2 = makeCtx({
|
|
330
|
+
branding: { siteName: "Other", brandName: "Other" },
|
|
331
|
+
});
|
|
332
|
+
expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
describe("extract.md", () => {
|
|
337
|
+
const file = findFile("content/pipeline/extract.md");
|
|
338
|
+
|
|
339
|
+
it("exists in the file list", () => {
|
|
340
|
+
expect(file).toBeDefined();
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("has YAML frontmatter", () => {
|
|
344
|
+
const content = resolveContent(file, ctx);
|
|
345
|
+
expect(content).toMatch(/^---\n/);
|
|
346
|
+
expect(content).toContain("title: Content Extraction");
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("includes a field extraction table", () => {
|
|
350
|
+
const content = resolveContent(file, ctx);
|
|
351
|
+
expect(content).toContain("| Field | Source | Extraction Method |");
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("includes objective and procedure", () => {
|
|
355
|
+
const content = resolveContent(file, ctx);
|
|
356
|
+
expect(content).toContain("## Objective");
|
|
357
|
+
expect(content).toContain("## Procedure");
|
|
358
|
+
expect(content).toContain("## Verification");
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it("is a static template (does not use context)", () => {
|
|
362
|
+
const ctx2 = makeCtx({
|
|
363
|
+
branding: { siteName: "Other", brandName: "Other" },
|
|
364
|
+
});
|
|
365
|
+
expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe("transfer.md", () => {
|
|
370
|
+
const file = findFile("content/pipeline/transfer.md");
|
|
371
|
+
|
|
372
|
+
it("exists in the file list", () => {
|
|
373
|
+
expect(file).toBeDefined();
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("has YAML frontmatter", () => {
|
|
377
|
+
const content = resolveContent(file, ctx);
|
|
378
|
+
expect(content).toMatch(/^---\n/);
|
|
379
|
+
expect(content).toContain("title: Transfer / Reflow");
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it("documents dry run and execute phases", () => {
|
|
383
|
+
const content = resolveContent(file, ctx);
|
|
384
|
+
expect(content).toContain("## Dry Run");
|
|
385
|
+
expect(content).toContain("## Execute");
|
|
386
|
+
expect(content).toContain("## Resume from Checkpoint");
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it("includes objective and configuration", () => {
|
|
390
|
+
const content = resolveContent(file, ctx);
|
|
391
|
+
expect(content).toContain("## Objective");
|
|
392
|
+
expect(content).toContain("## Configuration");
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("is a static template (does not use context)", () => {
|
|
396
|
+
const ctx2 = makeCtx({
|
|
397
|
+
branding: { siteName: "Other", brandName: "Other" },
|
|
398
|
+
});
|
|
399
|
+
expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
describe("validate.md", () => {
|
|
404
|
+
const file = findFile("content/pipeline/validate.md");
|
|
405
|
+
|
|
406
|
+
it("exists in the file list", () => {
|
|
407
|
+
expect(file).toBeDefined();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("has YAML frontmatter", () => {
|
|
411
|
+
const content = resolveContent(file, ctx);
|
|
412
|
+
expect(content).toMatch(/^---\n/);
|
|
413
|
+
expect(content).toContain("title: Validation");
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it("includes validation checks as checkboxes", () => {
|
|
417
|
+
const content = resolveContent(file, ctx);
|
|
418
|
+
expect(content).toContain("## Checks");
|
|
419
|
+
expect(content).toContain("- [ ] Object count matches expected");
|
|
420
|
+
expect(content).toContain("- [ ] No duplicate files in destination");
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it("includes a common issues table", () => {
|
|
424
|
+
const content = resolveContent(file, ctx);
|
|
425
|
+
expect(content).toContain("## Common Issues");
|
|
426
|
+
expect(content).toContain("| Issue | Cause | Resolution |");
|
|
427
|
+
expect(content).toContain("Count mismatch");
|
|
428
|
+
expect(content).toContain("Duplicates");
|
|
429
|
+
expect(content).toContain("Wrong paths");
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it("is a static template (does not use context)", () => {
|
|
433
|
+
const ctx2 = makeCtx({
|
|
434
|
+
branding: { siteName: "Other", brandName: "Other" },
|
|
435
|
+
});
|
|
436
|
+
expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// ---------------------------------------------------------------------------
|
|
442
|
+
// Sources Section Files
|
|
443
|
+
// ---------------------------------------------------------------------------
|
|
444
|
+
|
|
445
|
+
describe("pipeline sources section", () => {
|
|
446
|
+
const ctx = makeCtx();
|
|
447
|
+
|
|
448
|
+
describe("index.md", () => {
|
|
449
|
+
const file = findFile("content/sources/index.md");
|
|
450
|
+
|
|
451
|
+
it("exists in the file list", () => {
|
|
452
|
+
expect(file).toBeDefined();
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it("has frontmatter referencing brand name", () => {
|
|
456
|
+
const content = resolveContent(file, ctx);
|
|
457
|
+
expect(content).toContain("title: Source Systems");
|
|
458
|
+
expect(content).toContain("description: Source data systems for Acme Corp");
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it("includes a source systems table", () => {
|
|
462
|
+
const content = resolveContent(file, ctx);
|
|
463
|
+
expect(content).toContain("| Source | Type | Auth | Documentation |");
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it("documents how to add a new source", () => {
|
|
467
|
+
const content = resolveContent(file, ctx);
|
|
468
|
+
expect(content).toContain("## Adding a New Source");
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// ---------------------------------------------------------------------------
|
|
474
|
+
// Destinations Section Files
|
|
475
|
+
// ---------------------------------------------------------------------------
|
|
476
|
+
|
|
477
|
+
describe("pipeline destinations section", () => {
|
|
478
|
+
const ctx = makeCtx();
|
|
479
|
+
|
|
480
|
+
describe("index.md", () => {
|
|
481
|
+
const file = findFile("content/destinations/index.md");
|
|
482
|
+
|
|
483
|
+
it("exists in the file list", () => {
|
|
484
|
+
expect(file).toBeDefined();
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it("has frontmatter referencing brand name", () => {
|
|
488
|
+
const content = resolveContent(file, ctx);
|
|
489
|
+
expect(content).toContain("title: Destinations");
|
|
490
|
+
expect(content).toContain("description: Target structures for Acme Corp");
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it("includes a destinations table", () => {
|
|
494
|
+
const content = resolveContent(file, ctx);
|
|
495
|
+
expect(content).toContain("| Destination | Type | Structure | Documentation |");
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it("documents path structure", () => {
|
|
499
|
+
const content = resolveContent(file, ctx);
|
|
500
|
+
expect(content).toContain("## Path Structure");
|
|
501
|
+
expect(content).toContain("destination-root/");
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it("documents how to add a new destination", () => {
|
|
505
|
+
const content = resolveContent(file, ctx);
|
|
506
|
+
expect(content).toContain("## Adding a New Destination");
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// ---------------------------------------------------------------------------
|
|
512
|
+
// Operations Section Files
|
|
513
|
+
// ---------------------------------------------------------------------------
|
|
514
|
+
|
|
515
|
+
describe("pipeline operations section", () => {
|
|
516
|
+
const ctx = makeCtx();
|
|
517
|
+
|
|
518
|
+
describe("run-pipeline.md", () => {
|
|
519
|
+
const file = findFile("content/operations/run-pipeline.md");
|
|
520
|
+
|
|
521
|
+
it("exists in the file list", () => {
|
|
522
|
+
expect(file).toBeDefined();
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("has YAML frontmatter", () => {
|
|
526
|
+
const content = resolveContent(file, ctx);
|
|
527
|
+
expect(content).toMatch(/^---\n/);
|
|
528
|
+
expect(content).toContain("title: Run a Pipeline");
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it("includes prerequisites as checkboxes", () => {
|
|
532
|
+
const content = resolveContent(file, ctx);
|
|
533
|
+
expect(content).toContain("## Prerequisites");
|
|
534
|
+
expect(content).toContain("- [ ] Source and destination credentials valid");
|
|
535
|
+
expect(content).toContain("- [ ] Index up to date for target scope");
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it("documents all four pipeline steps", () => {
|
|
539
|
+
const content = resolveContent(file, ctx);
|
|
540
|
+
expect(content).toContain("### Step 1: Index Build");
|
|
541
|
+
expect(content).toContain("### Step 2: Content Probe");
|
|
542
|
+
expect(content).toContain("### Step 3: Transfer Reflow");
|
|
543
|
+
expect(content).toContain("### Step 4: Validate");
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it("documents checkpoint and resume", () => {
|
|
547
|
+
const content = resolveContent(file, ctx);
|
|
548
|
+
expect(content).toContain("## Checkpoint / Resume");
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it("includes post-run checklist", () => {
|
|
552
|
+
const content = resolveContent(file, ctx);
|
|
553
|
+
expect(content).toContain("## Post-Run");
|
|
554
|
+
expect(content).toContain("- [ ] Review validation report");
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it("is a static template (does not use context)", () => {
|
|
558
|
+
const ctx2 = makeCtx({
|
|
559
|
+
branding: { siteName: "Other", brandName: "Other" },
|
|
560
|
+
});
|
|
561
|
+
expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
describe("schedules.md", () => {
|
|
566
|
+
const file = findFile("content/operations/schedules.md");
|
|
567
|
+
|
|
568
|
+
it("exists in the file list", () => {
|
|
569
|
+
expect(file).toBeDefined();
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
it("has YAML frontmatter", () => {
|
|
573
|
+
const content = resolveContent(file, ctx);
|
|
574
|
+
expect(content).toMatch(/^---\n/);
|
|
575
|
+
expect(content).toContain("title: Schedules");
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it("includes a schedules table", () => {
|
|
579
|
+
const content = resolveContent(file, ctx);
|
|
580
|
+
expect(content).toContain("| Pipeline | Frequency | Scope | Notes |");
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it("links to run-pipeline page", () => {
|
|
584
|
+
const content = resolveContent(file, ctx);
|
|
585
|
+
expect(content).toContain("[Run a Pipeline](/content/operations/run-pipeline)");
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
it("is a static template (does not use context)", () => {
|
|
589
|
+
const ctx2 = makeCtx({
|
|
590
|
+
branding: { siteName: "Other", brandName: "Other" },
|
|
591
|
+
});
|
|
592
|
+
expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
// ---------------------------------------------------------------------------
|
|
598
|
+
// Troubleshooting Section Files
|
|
599
|
+
// ---------------------------------------------------------------------------
|
|
600
|
+
|
|
601
|
+
describe("pipeline troubleshooting section", () => {
|
|
602
|
+
const ctx = makeCtx();
|
|
603
|
+
|
|
604
|
+
describe("common-issues.md", () => {
|
|
605
|
+
const file = findFile("content/troubleshooting/common-issues.md");
|
|
606
|
+
|
|
607
|
+
it("exists in the file list", () => {
|
|
608
|
+
expect(file).toBeDefined();
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
it("has YAML frontmatter", () => {
|
|
612
|
+
const content = resolveContent(file, ctx);
|
|
613
|
+
expect(content).toMatch(/^---\n/);
|
|
614
|
+
expect(content).toContain("title: Common Issues");
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it("documents authentication expiry issue", () => {
|
|
618
|
+
const content = resolveContent(file, ctx);
|
|
619
|
+
expect(content).toContain("## Authentication Expiry Mid-Pipeline");
|
|
620
|
+
expect(content).toContain("**Symptoms**:");
|
|
621
|
+
expect(content).toContain("**Possible Causes**:");
|
|
622
|
+
expect(content).toContain("**Resolution**:");
|
|
623
|
+
expect(content).toContain("**Prevention**:");
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it("documents index lock / corruption issue", () => {
|
|
627
|
+
const content = resolveContent(file, ctx);
|
|
628
|
+
expect(content).toContain("## Index Lock / Corruption");
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
it("documents duplicate detection conflicts", () => {
|
|
632
|
+
const content = resolveContent(file, ctx);
|
|
633
|
+
expect(content).toContain("## Duplicate Detection Conflicts");
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
it("documents destination collision handling", () => {
|
|
637
|
+
const content = resolveContent(file, ctx);
|
|
638
|
+
expect(content).toContain("## Destination Collision Handling");
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
it("is a static template (does not use context)", () => {
|
|
642
|
+
const ctx2 = makeCtx({
|
|
643
|
+
branding: { siteName: "Other", brandName: "Other" },
|
|
644
|
+
});
|
|
645
|
+
expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
// ---------------------------------------------------------------------------
|
|
651
|
+
// Reference Section Files
|
|
652
|
+
// ---------------------------------------------------------------------------
|
|
653
|
+
|
|
654
|
+
describe("pipeline reference section", () => {
|
|
655
|
+
const ctx = makeCtx();
|
|
656
|
+
|
|
657
|
+
describe("manifests/job-template.md", () => {
|
|
658
|
+
const file = findFile("content/reference/manifests/job-template.md");
|
|
659
|
+
|
|
660
|
+
it("exists in the file list", () => {
|
|
661
|
+
expect(file).toBeDefined();
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
it("has YAML frontmatter", () => {
|
|
665
|
+
const content = resolveContent(file, ctx);
|
|
666
|
+
expect(content).toMatch(/^---\n/);
|
|
667
|
+
expect(content).toContain("title: Job Manifest Template");
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
it("includes a YAML manifest example", () => {
|
|
671
|
+
const content = resolveContent(file, ctx);
|
|
672
|
+
expect(content).toContain("```yaml");
|
|
673
|
+
expect(content).toContain("job:");
|
|
674
|
+
expect(content).toContain("name:");
|
|
675
|
+
expect(content).toContain("pipeline:");
|
|
676
|
+
expect(content).toContain("scope:");
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
it("includes a fields table", () => {
|
|
680
|
+
const content = resolveContent(file, ctx);
|
|
681
|
+
expect(content).toContain("## Fields");
|
|
682
|
+
expect(content).toContain("| Field | Required | Description |");
|
|
683
|
+
expect(content).toContain("`job.name`");
|
|
684
|
+
expect(content).toContain("`job.pipeline`");
|
|
685
|
+
expect(content).toContain("`job.options.dry_run`");
|
|
686
|
+
expect(content).toContain("`job.options.collision`");
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
it("is a static template (does not use context)", () => {
|
|
690
|
+
const ctx2 = makeCtx({
|
|
691
|
+
branding: { siteName: "Other", brandName: "Other" },
|
|
692
|
+
});
|
|
693
|
+
expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
describe("field-mappings/mapping-template.md", () => {
|
|
698
|
+
const file = findFile("content/reference/field-mappings/mapping-template.md");
|
|
699
|
+
|
|
700
|
+
it("exists in the file list", () => {
|
|
701
|
+
expect(file).toBeDefined();
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
it("has YAML frontmatter", () => {
|
|
705
|
+
const content = resolveContent(file, ctx);
|
|
706
|
+
expect(content).toMatch(/^---\n/);
|
|
707
|
+
expect(content).toContain("title: Field Mapping Template");
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
it("includes a field mapping table", () => {
|
|
711
|
+
const content = resolveContent(file, ctx);
|
|
712
|
+
expect(content).toContain("| Source Field | Destination Field | Transform | Notes |");
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it("documents transform rules", () => {
|
|
716
|
+
const content = resolveContent(file, ctx);
|
|
717
|
+
expect(content).toContain("## Transform Rules");
|
|
718
|
+
expect(content).toContain("### Date Formatting");
|
|
719
|
+
expect(content).toContain("### Lookup Tables");
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
it("is a static template (does not use context)", () => {
|
|
723
|
+
const ctx2 = makeCtx({
|
|
724
|
+
branding: { siteName: "Other", brandName: "Other" },
|
|
725
|
+
});
|
|
726
|
+
expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
describe("metrics/pipeline-kpis.md", () => {
|
|
731
|
+
const file = findFile("content/reference/metrics/pipeline-kpis.md");
|
|
732
|
+
|
|
733
|
+
it("exists in the file list", () => {
|
|
734
|
+
expect(file).toBeDefined();
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
it("has YAML frontmatter", () => {
|
|
738
|
+
const content = resolveContent(file, ctx);
|
|
739
|
+
expect(content).toMatch(/^---\n/);
|
|
740
|
+
expect(content).toContain("title: Pipeline KPIs");
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
it("includes a key metrics table", () => {
|
|
744
|
+
const content = resolveContent(file, ctx);
|
|
745
|
+
expect(content).toContain("## Key Metrics");
|
|
746
|
+
expect(content).toContain("| Metric | Target | Dashboard |");
|
|
747
|
+
expect(content).toContain("Pipeline success rate");
|
|
748
|
+
expect(content).toContain("Error rate");
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
it("includes SLAs and alerting", () => {
|
|
752
|
+
const content = resolveContent(file, ctx);
|
|
753
|
+
expect(content).toContain("## SLAs");
|
|
754
|
+
expect(content).toContain("## Alerting");
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
it("is a static template (does not use context)", () => {
|
|
758
|
+
const ctx2 = makeCtx({
|
|
759
|
+
branding: { siteName: "Other", brandName: "Other" },
|
|
760
|
+
});
|
|
761
|
+
expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
|
|
762
|
+
});
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
describe("checklists/pre-run.md", () => {
|
|
766
|
+
const file = findFile("content/reference/checklists/pre-run.md");
|
|
767
|
+
|
|
768
|
+
it("exists in the file list", () => {
|
|
769
|
+
expect(file).toBeDefined();
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
it("has YAML frontmatter", () => {
|
|
773
|
+
const content = resolveContent(file, ctx);
|
|
774
|
+
expect(content).toMatch(/^---\n/);
|
|
775
|
+
expect(content).toContain("title: Pre-Run Checklist");
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
it("includes checklist categories", () => {
|
|
779
|
+
const content = resolveContent(file, ctx);
|
|
780
|
+
expect(content).toContain("## Credentials");
|
|
781
|
+
expect(content).toContain("## Scope");
|
|
782
|
+
expect(content).toContain("## Configuration");
|
|
783
|
+
expect(content).toContain("## Environment");
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
it("includes a Go/No-Go decision point", () => {
|
|
787
|
+
const content = resolveContent(file, ctx);
|
|
788
|
+
expect(content).toContain("## Go/No-Go");
|
|
789
|
+
expect(content).toContain("GO / ");
|
|
790
|
+
expect(content).toContain("NO-GO");
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
it("is a static template (does not use context)", () => {
|
|
794
|
+
const ctx2 = makeCtx({
|
|
795
|
+
branding: { siteName: "Other", brandName: "Other" },
|
|
796
|
+
});
|
|
797
|
+
expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
|
|
798
|
+
});
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
describe("contacts/directory.md", () => {
|
|
802
|
+
const file = findFile("content/reference/contacts/directory.md");
|
|
803
|
+
|
|
804
|
+
it("exists in the file list", () => {
|
|
805
|
+
expect(file).toBeDefined();
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
it("has frontmatter referencing brand name", () => {
|
|
809
|
+
const content = resolveContent(file, ctx);
|
|
810
|
+
expect(content).toContain("title: Contact Directory");
|
|
811
|
+
expect(content).toContain("description: Team and vendor contacts for Acme Corp");
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
it("includes pipeline team table", () => {
|
|
815
|
+
const content = resolveContent(file, ctx);
|
|
816
|
+
expect(content).toContain("## Pipeline Team");
|
|
817
|
+
expect(content).toContain("| Role | Contact | Responsibility |");
|
|
818
|
+
expect(content).toContain("Pipeline Lead");
|
|
819
|
+
expect(content).toContain("Data Engineer");
|
|
820
|
+
expect(content).toContain("On-Call");
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
it("includes vendor contacts", () => {
|
|
824
|
+
const content = resolveContent(file, ctx);
|
|
825
|
+
expect(content).toContain("## Vendor Contacts");
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
it("includes escalation info", () => {
|
|
829
|
+
const content = resolveContent(file, ctx);
|
|
830
|
+
expect(content).toContain("## Escalation");
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
// ---------------------------------------------------------------------------
|
|
836
|
+
// CUSTOMIZING.md
|
|
837
|
+
// ---------------------------------------------------------------------------
|
|
838
|
+
|
|
839
|
+
describe("pipeline CUSTOMIZING.md", () => {
|
|
840
|
+
const ctx = makeCtx();
|
|
841
|
+
const file = findFile("CUSTOMIZING.md");
|
|
842
|
+
|
|
843
|
+
it("exists in the file list", () => {
|
|
844
|
+
expect(file).toBeDefined();
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
it("has frontmatter with template metadata", () => {
|
|
848
|
+
const content = resolveContent(file, ctx);
|
|
849
|
+
expect(content).toMatch(/^---\n/);
|
|
850
|
+
expect(content).toContain("template: pipeline");
|
|
851
|
+
expect(content).toContain("template_version: 1");
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
it("includes the site name in the heading", () => {
|
|
855
|
+
const content = resolveContent(file, ctx);
|
|
856
|
+
expect(content).toContain("# Customizing Test Pipeline");
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
it("uses the project name in the directory tree", () => {
|
|
860
|
+
const content = resolveContent(file, ctx);
|
|
861
|
+
expect(content).toContain("test-pipeline/");
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
it("documents site.yaml configuration", () => {
|
|
865
|
+
const content = resolveContent(file, ctx);
|
|
866
|
+
expect(content).toContain("### site.yaml");
|
|
867
|
+
expect(content).toContain('title: "Test Pipeline"');
|
|
868
|
+
expect(content).toContain('name: "Acme Corp"');
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
it("documents theme.yaml customization", () => {
|
|
872
|
+
const content = resolveContent(file, ctx);
|
|
873
|
+
expect(content).toContain("### theme.yaml");
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
it("includes the current year in footer example", () => {
|
|
877
|
+
const content = resolveContent(file, ctx);
|
|
878
|
+
expect(content).toContain("2026 Acme Corp");
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
it("documents the site structure with all sections", () => {
|
|
882
|
+
const content = resolveContent(file, ctx);
|
|
883
|
+
expect(content).toContain("## Site Structure");
|
|
884
|
+
expect(content).toContain("pipeline/");
|
|
885
|
+
expect(content).toContain("sources/");
|
|
886
|
+
expect(content).toContain("destinations/");
|
|
887
|
+
expect(content).toContain("operations/");
|
|
888
|
+
expect(content).toContain("troubleshooting/");
|
|
889
|
+
expect(content).toContain("reference/");
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
it("documents adding content types", () => {
|
|
893
|
+
const content = resolveContent(file, ctx);
|
|
894
|
+
expect(content).toContain("### New Pipeline Stage");
|
|
895
|
+
expect(content).toContain("### New Source System");
|
|
896
|
+
expect(content).toContain("### New Destination");
|
|
897
|
+
expect(content).toContain("### Creating Manifest Templates");
|
|
898
|
+
expect(content).toContain("### New Troubleshooting Guide");
|
|
899
|
+
expect(content).toContain("### New Section");
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
it("documents document conventions", () => {
|
|
903
|
+
const content = resolveContent(file, ctx);
|
|
904
|
+
expect(content).toContain("### Pipeline Stages");
|
|
905
|
+
expect(content).toContain("### Troubleshooting");
|
|
906
|
+
expect(content).toContain("### Checklists");
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
it("includes linking references", () => {
|
|
910
|
+
const content = resolveContent(file, ctx);
|
|
911
|
+
expect(content).toContain("### Internal Links");
|
|
912
|
+
expect(content).toContain("### External Links");
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
it("includes getting help links", () => {
|
|
916
|
+
const content = resolveContent(file, ctx);
|
|
917
|
+
expect(content).toContain("## Getting Help");
|
|
918
|
+
expect(content).toContain("https://github.com/3leaps/kitfly");
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
it("responds to different context values", () => {
|
|
922
|
+
const customCtx = makeCtx({
|
|
923
|
+
name: "my-pipeline",
|
|
924
|
+
branding: {
|
|
925
|
+
siteName: "My Pipeline Ops",
|
|
926
|
+
brandName: "My Brand",
|
|
927
|
+
brandUrl: "/",
|
|
928
|
+
},
|
|
929
|
+
year: 2027,
|
|
930
|
+
});
|
|
931
|
+
const content = resolveContent(file, customCtx);
|
|
932
|
+
expect(content).toContain("# Customizing My Pipeline Ops");
|
|
933
|
+
expect(content).toContain("my-pipeline/");
|
|
934
|
+
expect(content).toContain('name: "My Brand"');
|
|
935
|
+
expect(content).toContain("2027 My Brand");
|
|
936
|
+
});
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
// ---------------------------------------------------------------------------
|
|
940
|
+
// File Coverage Completeness
|
|
941
|
+
// ---------------------------------------------------------------------------
|
|
942
|
+
|
|
943
|
+
describe("pipeline file coverage", () => {
|
|
944
|
+
const expectedFiles = [
|
|
945
|
+
"site.yaml",
|
|
946
|
+
"index.md",
|
|
947
|
+
"content/pipeline/overview.md",
|
|
948
|
+
"content/pipeline/index-build.md",
|
|
949
|
+
"content/pipeline/extract.md",
|
|
950
|
+
"content/pipeline/transfer.md",
|
|
951
|
+
"content/pipeline/validate.md",
|
|
952
|
+
"content/sources/index.md",
|
|
953
|
+
"content/destinations/index.md",
|
|
954
|
+
"content/operations/run-pipeline.md",
|
|
955
|
+
"content/operations/schedules.md",
|
|
956
|
+
"content/troubleshooting/common-issues.md",
|
|
957
|
+
"content/reference/manifests/job-template.md",
|
|
958
|
+
"content/reference/field-mappings/mapping-template.md",
|
|
959
|
+
"content/reference/metrics/pipeline-kpis.md",
|
|
960
|
+
"content/reference/checklists/pre-run.md",
|
|
961
|
+
"content/reference/contacts/directory.md",
|
|
962
|
+
"CUSTOMIZING.md",
|
|
963
|
+
];
|
|
964
|
+
|
|
965
|
+
it("defines exactly the expected set of files", () => {
|
|
966
|
+
const actualPaths = pipeline.files.map((f) => f.path).sort();
|
|
967
|
+
const expected = [...expectedFiles].sort();
|
|
968
|
+
expect(actualPaths).toEqual(expected);
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
it("all content generators produce non-empty strings", () => {
|
|
972
|
+
const ctx = makeCtx();
|
|
973
|
+
for (const file of pipeline.files) {
|
|
974
|
+
const content = resolveContent(file, ctx);
|
|
975
|
+
expect(content.length).toBeGreaterThan(0);
|
|
976
|
+
expect(typeof content).toBe("string");
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
it("all markdown files start with YAML frontmatter or a markdown heading", () => {
|
|
981
|
+
const ctx = makeCtx();
|
|
982
|
+
for (const file of pipeline.files) {
|
|
983
|
+
if (!file.path.endsWith(".md")) continue;
|
|
984
|
+
const content = resolveContent(file, ctx);
|
|
985
|
+
const startsWithFrontmatter = content.startsWith("---\n");
|
|
986
|
+
const startsWithHeading = content.startsWith("#");
|
|
987
|
+
expect(startsWithFrontmatter || startsWithHeading).toBe(true);
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
it("site.yaml starts with a YAML comment", () => {
|
|
992
|
+
const ctx = makeCtx();
|
|
993
|
+
const file = findFile("site.yaml");
|
|
994
|
+
const content = resolveContent(file, ctx);
|
|
995
|
+
expect(content.startsWith("#")).toBe(true);
|
|
996
|
+
});
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
// ---------------------------------------------------------------------------
|
|
1000
|
+
// Branding Substitution Across All Context-Dependent Files
|
|
1001
|
+
// ---------------------------------------------------------------------------
|
|
1002
|
+
|
|
1003
|
+
describe("pipeline branding substitution", () => {
|
|
1004
|
+
it("context-dependent files use brandName, not a hardcoded value", () => {
|
|
1005
|
+
const ctx1 = makeCtx({
|
|
1006
|
+
branding: {
|
|
1007
|
+
siteName: "Alpha Pipeline",
|
|
1008
|
+
brandName: "Alpha Corp",
|
|
1009
|
+
brandUrl: "/",
|
|
1010
|
+
},
|
|
1011
|
+
});
|
|
1012
|
+
const ctx2 = makeCtx({
|
|
1013
|
+
branding: {
|
|
1014
|
+
siteName: "Beta Pipeline",
|
|
1015
|
+
brandName: "Beta Corp",
|
|
1016
|
+
brandUrl: "/",
|
|
1017
|
+
},
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
// Files that use ctx.branding.brandName in their content
|
|
1021
|
+
const brandDependentFiles = [
|
|
1022
|
+
"content/pipeline/overview.md",
|
|
1023
|
+
"content/sources/index.md",
|
|
1024
|
+
"content/destinations/index.md",
|
|
1025
|
+
"content/reference/contacts/directory.md",
|
|
1026
|
+
];
|
|
1027
|
+
|
|
1028
|
+
for (const path of brandDependentFiles) {
|
|
1029
|
+
const file = findFile(path);
|
|
1030
|
+
const content1 = resolveContent(file, ctx1);
|
|
1031
|
+
const content2 = resolveContent(file, ctx2);
|
|
1032
|
+
|
|
1033
|
+
expect(content1).toContain("Alpha Corp");
|
|
1034
|
+
expect(content1).not.toContain("Beta Corp");
|
|
1035
|
+
expect(content2).toContain("Beta Corp");
|
|
1036
|
+
expect(content2).not.toContain("Alpha Corp");
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
it("context-dependent files use siteName where appropriate", () => {
|
|
1041
|
+
const ctx1 = makeCtx({
|
|
1042
|
+
branding: {
|
|
1043
|
+
siteName: "Gamma Pipeline",
|
|
1044
|
+
brandName: "Gamma Inc",
|
|
1045
|
+
brandUrl: "/",
|
|
1046
|
+
},
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
// site.yaml and index.md use siteName
|
|
1050
|
+
const siteYaml = resolveContent(findFile("site.yaml"), ctx1);
|
|
1051
|
+
expect(siteYaml).toContain("Gamma Pipeline");
|
|
1052
|
+
|
|
1053
|
+
const indexMd = resolveContent(findFile("index.md"), ctx1);
|
|
1054
|
+
expect(indexMd).toContain("Gamma Pipeline");
|
|
1055
|
+
|
|
1056
|
+
const customizing = resolveContent(findFile("CUSTOMIZING.md"), ctx1);
|
|
1057
|
+
expect(customizing).toContain("Gamma Pipeline");
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
it("static templates are not affected by branding changes", () => {
|
|
1061
|
+
const ctx1 = makeCtx({
|
|
1062
|
+
branding: { siteName: "A", brandName: "A Corp", brandUrl: "/" },
|
|
1063
|
+
});
|
|
1064
|
+
const ctx2 = makeCtx({
|
|
1065
|
+
branding: { siteName: "B", brandName: "B Corp", brandUrl: "/" },
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
const staticFiles = [
|
|
1069
|
+
"content/pipeline/index-build.md",
|
|
1070
|
+
"content/pipeline/extract.md",
|
|
1071
|
+
"content/pipeline/transfer.md",
|
|
1072
|
+
"content/pipeline/validate.md",
|
|
1073
|
+
"content/operations/run-pipeline.md",
|
|
1074
|
+
"content/operations/schedules.md",
|
|
1075
|
+
"content/troubleshooting/common-issues.md",
|
|
1076
|
+
"content/reference/manifests/job-template.md",
|
|
1077
|
+
"content/reference/field-mappings/mapping-template.md",
|
|
1078
|
+
"content/reference/metrics/pipeline-kpis.md",
|
|
1079
|
+
"content/reference/checklists/pre-run.md",
|
|
1080
|
+
];
|
|
1081
|
+
|
|
1082
|
+
for (const path of staticFiles) {
|
|
1083
|
+
const file = findFile(path);
|
|
1084
|
+
expect(resolveContent(file, ctx1)).toBe(resolveContent(file, ctx2));
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
1087
|
+
});
|