@warpgogol/werkstatt-shared 0.13.0 → 0.14.1
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/README.md +26 -16
- package/README.uk.md +24 -14
- package/package.json +27 -69
- package/src/checks/result-helpers.ts +21 -4
- package/src/checks/tests/remediation-auto-populate.test.ts +83 -0
- package/src/integration/funnel.ts +78 -1
- package/src/integration/index.ts +19 -2
- package/src/integration/lagebild-ingress.ts +169 -0
- package/src/integration/orchestration.ts +7 -13
- package/src/integration/port-barrel.ts +9 -28
- package/src/integration/tests/funnel-bridge.test.ts +7 -7
- package/src/integration/tests/lagebild-ingress.test.ts +118 -0
- package/src/integration/vitest.config.ts +7 -0
- package/src/observability/typed-refs.ts +1 -1
- package/src/ontology/analytics/matomo-fleet.registry.yaml +1 -1
- package/src/ontology/archetypes/index.json +12 -9
- package/src/ontology/archetypes/index.yaml +26 -10
- package/src/ontology/blueprints/website-local.yaml +11 -11
- package/src/ontology/blueprints/website-service.yaml +20 -20
- package/src/ontology/capabilities/lead.submit.yaml +2 -2
- package/src/ontology/enums.ts +2 -2
- package/src/ontology/external-surfaces/jsonld-types.yaml +14 -14
- package/src/ontology/external-surfaces/url-schema.yaml +1 -1
- package/src/ontology/index.ts +2 -0
- package/src/ontology/manifest.ts +19 -0
- package/src/ontology/schemas/index.ts +7 -2
- package/src/ontology/schemas/system/integrations.ts +1 -1
- package/src/ontology/schemas/system/manifest.ts +38 -1
- package/src/ontology/schemas/system/tests/growth.test.ts +22 -0
- package/src/ontology/schemas/system/tests/integrations.test.ts +12 -0
- package/src/ontology/schemas/system/tests/manifest.test.ts +22 -0
- package/src/ontology/schemas/system/tests/page-output.test.ts +13 -0
- package/src/ontology/schemas/system/tests/release.test.ts +22 -0
- package/src/ontology/schemas/system/tests/text.test.ts +18 -0
- package/src/ontology/schemas/system/tests/verification.test.ts +12 -0
- package/src/ontology/schemas/system.ts +7 -2
- package/src/ontology/schemas/tests/biome.test.ts +19 -0
- package/src/ontology/schemas/tests/capability.test.ts +22 -0
- package/src/ontology/schemas/tests/constellation.test.ts +22 -0
- package/src/ontology/schemas/tests/dns-records.test.ts +13 -0
- package/src/ontology/schemas/tests/manifest-resolver.test.ts +13 -0
- package/src/ontology/schemas/tests/page-entry.test.ts +22 -0
- package/src/ontology/schemas/tests/section-archetype.test.ts +12 -0
- package/src/ontology/schemas/tests/site-family.test.ts +18 -0
- package/src/ontology/schemas/tests/system.test.ts +20 -0
- package/src/ontology/shared-section-props/common.ts +11 -0
- package/src/ontology/shared-section-props/visual-header.ts +1 -1
- package/src/share/agent/api-catalog.ts +5 -0
- package/src/share/agent/ard-catalog.ts +14 -0
- package/src/share/agent/index.ts +2 -0
- package/src/share/agent/manifest.ts +25 -2
- package/src/share/agent/mcp-card.ts +4 -0
- package/src/share/agent/openapi.ts +78 -0
- package/src/share/agent/search.test.ts +188 -0
- package/src/share/agent/search.ts +164 -0
- package/src/share/agent/tests/api-catalog.test.ts +8 -0
- package/src/share/agent/tests/ard-catalog.test.ts +8 -0
- package/src/share/agent/tests/capability.test.ts +14 -0
- package/src/share/agent/tests/fleet-catalog.test.ts +15 -0
- package/src/share/agent/tests/knowledge.test.ts +20 -0
- package/src/share/agent/tests/manifest.test.ts +9 -0
- package/src/share/agent/tests/mcp-card.test.ts +8 -0
- package/src/share/agent/tests/omit-empty.test.ts +33 -0
- package/src/share/agent/tests/openapi.test.ts +8 -0
- package/src/share/agent/tests/proof.test.ts +15 -0
- package/src/share/env.d.ts +0 -4
- package/src/share/knowledge/tests/claim.test.ts +11 -0
- package/src/share/knowledge/tests/derivation.test.ts +30 -0
- package/src/share/knowledge/tests/freshness.test.ts +18 -0
- package/src/share/knowledge/tests/ledger.test.ts +35 -0
- package/src/share/knowledge/tests/plan.test.ts +19 -0
- package/src/share/knowledge/tests/source.test.ts +19 -0
- package/src/share/remediation/remediation-catalog.ts +196 -0
- package/src/share/schemas/effects.ts +16 -0
- package/src/share/schemas/media.ts +4 -0
- package/src/share/schemas/section-shell.ts +2 -1
- package/src/share/schemas/tests/claim-records.test.ts +52 -0
- package/src/share/schemas/tests/claims.test.ts +23 -0
- package/src/share/schemas/tests/effects.test.ts +116 -0
- package/src/share/schemas/tests/features.test.ts +61 -0
- package/src/share/schemas/tests/horizontal-align.test.ts +16 -0
- package/src/share/schemas/tests/icon-color.test.ts +16 -0
- package/src/share/schemas/tests/live-photo.test.ts +40 -0
- package/src/share/schemas/tests/material-credit.test.ts +51 -0
- package/src/share/schemas/tests/media.test.ts +26 -0
- package/src/share/schemas/tests/navigation.test.ts +58 -0
- package/src/share/schemas/tests/page-base.test.ts +19 -0
- package/src/share/schemas/tests/participant.test.ts +26 -0
- package/src/share/schemas/tests/person.test.ts +12 -0
- package/src/share/schemas/tests/print.test.ts +30 -0
- package/src/share/schemas/tests/section-background.test.ts +31 -0
- package/src/share/schemas/tests/section-body.test.ts +16 -0
- package/src/share/schemas/tests/section-cards.test.ts +17 -0
- package/src/share/schemas/tests/section-cta.test.ts +41 -0
- package/src/share/schemas/tests/section-header.test.ts +31 -0
- package/src/share/schemas/tests/section-image.test.ts +24 -0
- package/src/share/schemas/tests/section-motion.test.ts +29 -0
- package/src/share/schemas/tests/section-shell.test.ts +42 -0
- package/src/share/schemas/tests/section-stats.test.ts +17 -0
- package/src/share/schemas/tests/site-background.test.ts +33 -0
- package/src/share/schemas/tests/standard-list-item.test.ts +16 -0
- package/src/share/scripts/external-link-qr.ts +9 -1
- package/src/share/scripts/gsap-counter.ts +2 -0
- package/src/share/scripts/inline-number-animation.ts +2 -0
- package/src/share/scripts/live-photos.ts +2 -0
- package/src/share/scripts/tests/external-link-qr.test.ts +8 -0
- package/src/share/scripts/tests/external-links.test.ts +25 -0
- package/src/share/scripts/tests/gsap-counter.test.ts +8 -0
- package/src/share/scripts/tests/gsap-mountain-journey.test.ts +8 -0
- package/src/share/scripts/tests/gsap-parallax.test.ts +8 -0
- package/src/share/scripts/tests/gsap-reveal.test.ts +8 -0
- package/src/share/scripts/tests/gsap-stagger.test.ts +8 -0
- package/src/share/scripts/tests/inline-number-animation.test.ts +8 -0
- package/src/share/scripts/tests/lenis.test.ts +8 -0
- package/src/share/scripts/tests/live-photos.test.ts +8 -0
- package/src/share/scripts/tests/lordicon.test.ts +8 -0
- package/src/share/scripts/tests/orchestrator.test.ts +8 -0
- package/src/share/scripts/tests/scheduler.test.ts +23 -0
- package/src/share/scripts/video-player.test.ts +210 -0
- package/src/share/scripts/video-player.ts +73 -13
- package/src/share/semantic/extract.ts +25 -1
- package/src/share/semantic/jsonld/tests/article.test.ts +89 -0
- package/src/share/semantic/jsonld/tests/breadcrumb.test.ts +62 -0
- package/src/share/semantic/jsonld/tests/collection-list.test.ts +39 -0
- package/src/share/semantic/jsonld/tests/faq.test.ts +60 -0
- package/src/share/semantic/jsonld/tests/initiative.test.ts +56 -0
- package/src/share/semantic/jsonld/tests/organization.test.ts +123 -0
- package/src/share/semantic/jsonld/tests/person.test.ts +89 -0
- package/src/share/semantic/jsonld/tests/service.test.ts +65 -0
- package/src/share/semantic/jsonld/tests/shared.test.ts +41 -0
- package/src/share/semantic/jsonld/tests/video.test.ts +84 -0
- package/src/share/semantic/jsonld/tests/webpage.test.ts +93 -0
- package/src/share/semantic/jsonld/tests/website.test.ts +27 -0
- package/src/share/semantic/jsonld/types.ts +1 -0
- package/src/share/semantic/models.ts +1 -0
- package/src/share/semantic/tests/ai.test.ts +55 -0
- package/src/share/semantic/tests/block-extraction.test.ts +34 -0
- package/src/share/semantic/tests/breadcrumbs.test.ts +127 -0
- package/src/share/semantic/tests/build-page.test.ts +122 -0
- package/src/share/semantic/tests/business-projection.test.ts +188 -0
- package/src/share/semantic/tests/derived-prices-loader.test.ts +46 -0
- package/src/share/semantic/tests/extract.test.ts +126 -0
- package/src/share/semantic/tests/feed.test.ts +113 -0
- package/src/share/semantic/tests/helpers.ts +32 -0
- package/src/share/semantic/tests/ids.test.ts +118 -0
- package/src/share/semantic/tests/image-sitemap.test.ts +109 -0
- package/src/share/semantic/tests/jsonld.test.ts +61 -0
- package/src/share/semantic/tests/llms-policy.test.ts +60 -0
- package/src/share/semantic/tests/llms.test.ts +118 -0
- package/src/share/semantic/tests/markdown-hygiene.test.ts +72 -0
- package/src/share/semantic/tests/markdown-page.test.ts +97 -0
- package/src/share/semantic/tests/markdown-twin-provenance.test.ts +152 -0
- package/src/share/semantic/tests/organization-profile.test.ts +107 -0
- package/src/share/semantic/tests/output-projection.test.ts +77 -0
- package/src/share/semantic/tests/page-markdown.test.ts +98 -0
- package/src/share/semantic/tests/page-utils.test.ts +88 -0
- package/src/share/semantic/tests/price-marker-resolver.test.ts +88 -0
- package/src/share/semantic/tests/robots.test.ts +70 -0
- package/src/share/semantic/tests/split-sentences.test.ts +57 -0
- package/src/share/semantic/tests/update-stamp.test.ts +102 -0
- package/src/share/tests/manifest.test.ts +51 -0
- package/src/share/tests/openapi.test.ts +30 -0
- package/src/share/types/qrcode-browser.d.ts +18 -0
- package/src/share/utility-registry.yaml +16 -2
- package/src/integration/crm-buffer.ts +0 -491
- package/src/integration-adapter-supabase-crm/adapter.ts +0 -437
- package/src/integration-adapter-supabase-crm/client.ts +0 -605
- package/src/integration-adapter-supabase-crm/index.ts +0 -28
- package/src/integration-adapter-supabase-crm/pipedrive-sync-target.ts +0 -482
- package/src/integration-adapter-supabase-crm/supabase-rest.ts +0 -89
- package/src/integration-adapter-supabase-crm/tenant-registry.ts +0 -334
- package/src/integration-adapter-supabase-crm/tests/change-balance.pbt.test.ts +0 -83
- package/src/integration-adapter-supabase-crm/tests/funnel-persistence.test.ts +0 -409
- package/src/integration-adapter-supabase-crm/tests/lifecycle-sync.test.ts +0 -288
- package/src/integration-adapter-supabase-crm/tests/stage-map.test.ts +0 -80
- package/src/integration-adapter-supabase-crm/vitest.config.ts +0 -8
- package/src/integration-adapter-supabase-crm/worker.ts +0 -223
- package/src/ontology/archetypes/components/currency-selector.yaml +0 -19
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
generateImageSitemapXml,
|
|
4
|
+
harvestContentImage,
|
|
5
|
+
isHtmlRedirectPage,
|
|
6
|
+
isSyntheticPreviewUrl,
|
|
7
|
+
} from "../image-sitemap.ts";
|
|
8
|
+
|
|
9
|
+
describe("isHtmlRedirectPage", () => {
|
|
10
|
+
it("detects meta-refresh redirect", () => {
|
|
11
|
+
expect(isHtmlRedirectPage('<meta http-equiv="refresh" content="0;url=/de/">')).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("returns false for normal pages", () => {
|
|
15
|
+
expect(isHtmlRedirectPage("<html><body>Hello</body></html>")).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("isSyntheticPreviewUrl", () => {
|
|
20
|
+
it("detects preview screenshots", () => {
|
|
21
|
+
expect(isSyntheticPreviewUrl("https://example.com/preview/page.png")).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("detects og-image fallback", () => {
|
|
25
|
+
expect(isSyntheticPreviewUrl("https://example.com/og-image.png")).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("returns false for content images", () => {
|
|
29
|
+
expect(isSyntheticPreviewUrl("https://example.com/_astro/img.jpg")).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("harvestContentImage", () => {
|
|
34
|
+
it("extracts canonical URL from link tag", () => {
|
|
35
|
+
const html = '<link rel="canonical" href="https://example.com/de/page" />';
|
|
36
|
+
const result = harvestContentImage(html, "https://example.com");
|
|
37
|
+
expect(result.loc).toBe("https://example.com/de/page");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("returns null loc when no canonical", () => {
|
|
41
|
+
const result = harvestContentImage("<html></html>", "https://example.com");
|
|
42
|
+
expect(result.loc).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("harvests img with data-content-image attribute", () => {
|
|
46
|
+
const html = '<img src="/_astro/hero.jpg" data-content-image alt="Hero" />';
|
|
47
|
+
const result = harvestContentImage(html, "https://example.com");
|
|
48
|
+
expect(result.imageUrls).toContain("https://example.com/_astro/hero.jpg");
|
|
49
|
+
expect(result.title).toBe("Hero");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("harvests meta x-content-image", () => {
|
|
53
|
+
const html = '<meta name="x-content-image" content="https://example.com/img.jpg" />';
|
|
54
|
+
const result = harvestContentImage(html, "https://example.com");
|
|
55
|
+
expect(result.imageUrls).toContain("https://example.com/img.jpg");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("excludes synthetic preview URLs", () => {
|
|
59
|
+
const html = '<img src="/og-image.png" data-content-image alt="OG" />';
|
|
60
|
+
const result = harvestContentImage(html, "https://example.com");
|
|
61
|
+
expect(result.imageUrls).toHaveLength(0);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("deduplicates identical URLs", () => {
|
|
65
|
+
const html = `
|
|
66
|
+
<img src="/img.jpg" data-content-image alt="A" />
|
|
67
|
+
<meta name="x-content-image" content="/img.jpg" />
|
|
68
|
+
`;
|
|
69
|
+
const result = harvestContentImage(html, "https://example.com");
|
|
70
|
+
expect(result.imageUrls).toHaveLength(1);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("falls back to og:image:alt for title", () => {
|
|
74
|
+
const html = `
|
|
75
|
+
<img src="/img.jpg" data-content-image />
|
|
76
|
+
<meta property="og:image:alt" content="OG Alt" />
|
|
77
|
+
`;
|
|
78
|
+
const result = harvestContentImage(html, "https://example.com");
|
|
79
|
+
expect(result.title).toBe("OG Alt");
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("generateImageSitemapXml", () => {
|
|
84
|
+
it("produces valid image sitemap XML", () => {
|
|
85
|
+
const result = generateImageSitemapXml([
|
|
86
|
+
{ loc: "https://example.com/page", imageUrl: "https://example.com/img.jpg", title: "Test" },
|
|
87
|
+
]);
|
|
88
|
+
expect(result).toContain('<?xml version="1.0"');
|
|
89
|
+
expect(result).toContain("<urlset");
|
|
90
|
+
expect(result).toContain("xmlns:image=");
|
|
91
|
+
expect(result).toContain("<loc>https://example.com/page</loc>");
|
|
92
|
+
expect(result).toContain("<image:loc>https://example.com/img.jpg</image:loc>");
|
|
93
|
+
expect(result).toContain("<image:title>Test</image:title>");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("produces empty urlset for no entries", () => {
|
|
97
|
+
const result = generateImageSitemapXml([]);
|
|
98
|
+
expect(result).toContain("<urlset");
|
|
99
|
+
expect(result).not.toContain("<url>");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("escapes XML in URLs", () => {
|
|
103
|
+
const result = generateImageSitemapXml([
|
|
104
|
+
{ loc: "https://example.com/a&b", imageUrl: "https://example.com/<img>" },
|
|
105
|
+
]);
|
|
106
|
+
expect(result).toContain("a&b");
|
|
107
|
+
expect(result).toContain("<img>");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildJsonLd } from "../jsonld.ts";
|
|
3
|
+
import { makePage } from "./helpers.ts";
|
|
4
|
+
|
|
5
|
+
describe("buildJsonLd", () => {
|
|
6
|
+
it("produces a valid JSON-LD document with @context and @graph", () => {
|
|
7
|
+
const page = makePage();
|
|
8
|
+
const doc = buildJsonLd(page);
|
|
9
|
+
expect(doc["@context"]).toBe("https://schema.org");
|
|
10
|
+
expect(Array.isArray(doc["@graph"])).toBe(true);
|
|
11
|
+
expect(doc["@graph"].length).toBeGreaterThan(0);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("includes Organization and WebSite nodes in graph", () => {
|
|
15
|
+
const page = makePage();
|
|
16
|
+
const doc = buildJsonLd(page);
|
|
17
|
+
const types = doc["@graph"].map((n) => n["@type"]);
|
|
18
|
+
expect(types.some((t) => Array.isArray(t) && t.includes("Organization"))).toBe(true);
|
|
19
|
+
expect(types.some((t) => t === "WebSite" || (Array.isArray(t) && t.includes("WebSite")))).toBe(
|
|
20
|
+
true,
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("includes WebPage node in graph", () => {
|
|
25
|
+
const page = makePage();
|
|
26
|
+
const doc = buildJsonLd(page);
|
|
27
|
+
const types = doc["@graph"].map((n) => n["@type"]);
|
|
28
|
+
expect(types.some((t) => Array.isArray(t) && t.includes("WebPage"))).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("deduplicates graph nodes by @id", () => {
|
|
32
|
+
const page = makePage();
|
|
33
|
+
const doc = buildJsonLd(page);
|
|
34
|
+
const ids = doc["@graph"].map((n) => n["@id"]).filter(Boolean);
|
|
35
|
+
const uniqueIds = new Set(ids);
|
|
36
|
+
expect(ids.length).toBe(uniqueIds.size);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("includes BreadcrumbList when breadcrumbs > 1", () => {
|
|
40
|
+
const page = makePage({
|
|
41
|
+
breadcrumbs: [
|
|
42
|
+
{ name: "Home", url: "https://example.com/de/" },
|
|
43
|
+
{ name: "About", url: "https://example.com/de/about/" },
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
const doc = buildJsonLd(page);
|
|
47
|
+
const types = doc["@graph"].map((n) => n["@type"]);
|
|
48
|
+
expect(types).toContain("BreadcrumbList");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("includes extraGraphNodes when provided", () => {
|
|
52
|
+
const page = makePage({
|
|
53
|
+
extraGraphNodes: [
|
|
54
|
+
{ "@type": "SoftwareApplication", "@id": "https://example.com/#/schema/app", name: "App" },
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
const doc = buildJsonLd(page);
|
|
58
|
+
const ids = doc["@graph"].map((n) => n["@id"]);
|
|
59
|
+
expect(ids).toContain("https://example.com/#/schema/app");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { LLMS_DEPTH_BY_SEMANTIC_TYPE, resolveLlmsPolicy } from "../llms-policy.ts";
|
|
3
|
+
|
|
4
|
+
describe("resolveLlmsPolicy", () => {
|
|
5
|
+
it("returns full depth by default", () => {
|
|
6
|
+
expect(resolveLlmsPolicy(undefined, "content")).toEqual({ depth: "full" });
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("uses type default for openSource", () => {
|
|
10
|
+
expect(resolveLlmsPolicy(undefined, "openSource")).toEqual({ depth: "index-only" });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("uses type default for legal", () => {
|
|
14
|
+
expect(resolveLlmsPolicy(undefined, "legal")).toEqual({ depth: "exclude" });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("accepts string shorthand", () => {
|
|
18
|
+
expect(resolveLlmsPolicy("summary", "content")).toEqual({ depth: "summary" });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("falls back to type default for invalid string depth", () => {
|
|
22
|
+
expect(resolveLlmsPolicy("invalid" as never, "openSource")).toEqual({ depth: "index-only" });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("accepts object form with valid depth", () => {
|
|
26
|
+
expect(resolveLlmsPolicy({ depth: "exclude" }, "content")).toEqual({ depth: "exclude" });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("includes section excludes when provided", () => {
|
|
30
|
+
const result = resolveLlmsPolicy(
|
|
31
|
+
{ depth: "summary", sections: { exclude: ["s1", "s2"] } },
|
|
32
|
+
"content",
|
|
33
|
+
);
|
|
34
|
+
expect(result.depth).toBe("summary");
|
|
35
|
+
expect(result.sections?.exclude).toEqual(["s1", "s2"]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("filters empty section exclude ids", () => {
|
|
39
|
+
const result = resolveLlmsPolicy(
|
|
40
|
+
{ depth: "summary", sections: { exclude: ["s1", ""] } },
|
|
41
|
+
"content",
|
|
42
|
+
);
|
|
43
|
+
expect(result.sections?.exclude).toEqual(["s1"]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("omits sections when no valid excludes", () => {
|
|
47
|
+
const result = resolveLlmsPolicy({ depth: "summary", sections: { exclude: [] } }, "content");
|
|
48
|
+
expect(result.sections).toBeUndefined();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("LLMS_DEPTH_BY_SEMANTIC_TYPE", () => {
|
|
53
|
+
it("maps openSource to index-only", () => {
|
|
54
|
+
expect(LLMS_DEPTH_BY_SEMANTIC_TYPE.openSource).toBe("index-only");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("maps legal to exclude", () => {
|
|
58
|
+
expect(LLMS_DEPTH_BY_SEMANTIC_TYPE.legal).toBe("exclude");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildLlmsFull, buildLlmsIndex } from "../llms.ts";
|
|
3
|
+
import { makePage } from "./helpers.ts";
|
|
4
|
+
import type { SemanticSiteModel } from "../models.ts";
|
|
5
|
+
|
|
6
|
+
function makeSite(overrides: Partial<SemanticSiteModel> = {}): SemanticSiteModel {
|
|
7
|
+
const page = makePage();
|
|
8
|
+
return {
|
|
9
|
+
organization: page.organization,
|
|
10
|
+
baseUrl: "https://example.com",
|
|
11
|
+
defaultLanguage: "de",
|
|
12
|
+
lang: "de",
|
|
13
|
+
pages: [page],
|
|
14
|
+
...overrides,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("buildLlmsIndex", () => {
|
|
19
|
+
it("produces llms.txt with site name and description", () => {
|
|
20
|
+
const site = makeSite();
|
|
21
|
+
const result = buildLlmsIndex(site);
|
|
22
|
+
expect(result).toContain("# Warpgogol");
|
|
23
|
+
expect(result).toContain("Digital infrastructure for nonprofits");
|
|
24
|
+
expect(result).toContain("## Primary sources");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("includes llms-full.txt reference", () => {
|
|
28
|
+
const site = makeSite();
|
|
29
|
+
const result = buildLlmsIndex(site);
|
|
30
|
+
expect(result).toContain("llms-full.txt");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("includes page links with absolute URLs", () => {
|
|
34
|
+
const site = makeSite();
|
|
35
|
+
const result = buildLlmsIndex(site);
|
|
36
|
+
expect(result).toContain("https://example.com");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("filters out excluded pages from index", () => {
|
|
40
|
+
const page = makePage({
|
|
41
|
+
output: {
|
|
42
|
+
sitemap: { include: true, category: "content", includeLastmod: true },
|
|
43
|
+
llms: { depth: "exclude" },
|
|
44
|
+
robots: { index: true, follow: true },
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
const site = makeSite({ pages: [page] });
|
|
48
|
+
const result = buildLlmsIndex(site);
|
|
49
|
+
expect(result).not.toContain("Test Page");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("includes agent discovery links when agent.enabled is not false", () => {
|
|
53
|
+
const site = makeSite();
|
|
54
|
+
const result = buildLlmsIndex(site);
|
|
55
|
+
expect(result).toContain("agent.json");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("omits agent discovery links when agent.enabled is false", () => {
|
|
59
|
+
const site = makeSite({ agent: { enabled: false } } as never);
|
|
60
|
+
const result = buildLlmsIndex(site);
|
|
61
|
+
expect(result).not.toContain("agent.json");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("includes organization facts section", () => {
|
|
65
|
+
const site = makeSite();
|
|
66
|
+
const result = buildLlmsIndex(site);
|
|
67
|
+
expect(result).toContain("## Organization");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("buildLlmsFull", () => {
|
|
72
|
+
it("produces llms-full.txt with site name and description", () => {
|
|
73
|
+
const site = makeSite();
|
|
74
|
+
const result = buildLlmsFull(site);
|
|
75
|
+
expect(result).toContain("# Warpgogol");
|
|
76
|
+
expect(result).toContain("Digital infrastructure for nonprofits");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("includes page sections for full depth pages", () => {
|
|
80
|
+
const site = makeSite();
|
|
81
|
+
const result = buildLlmsFull(site);
|
|
82
|
+
expect(result).toContain("## Test Heading");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("includes page header but not body for summary depth", () => {
|
|
86
|
+
const page = makePage({
|
|
87
|
+
output: {
|
|
88
|
+
sitemap: { include: true, category: "content", includeLastmod: true },
|
|
89
|
+
llms: { depth: "summary" },
|
|
90
|
+
robots: { index: true, follow: true },
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
const site = makeSite({ pages: [page] });
|
|
94
|
+
const result = buildLlmsFull(site);
|
|
95
|
+
expect(result).toContain("## Test Heading");
|
|
96
|
+
expect(result).toContain("URL:");
|
|
97
|
+
expect(result).toContain("Description:");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("filters out excluded pages", () => {
|
|
101
|
+
const page = makePage({
|
|
102
|
+
output: {
|
|
103
|
+
sitemap: { include: true, category: "content", includeLastmod: true },
|
|
104
|
+
llms: { depth: "exclude" },
|
|
105
|
+
robots: { index: true, follow: true },
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
const site = makeSite({ pages: [page] });
|
|
109
|
+
const result = buildLlmsFull(site);
|
|
110
|
+
expect(result).not.toContain("## Test Page");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("includes organization facts section", () => {
|
|
114
|
+
const site = makeSite();
|
|
115
|
+
const result = buildLlmsFull(site);
|
|
116
|
+
expect(result).toContain("## Organization facts");
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
canonicalizeGeneratedMarkdownText,
|
|
4
|
+
formatGeneratedMarkdownListItem,
|
|
5
|
+
normalizeGeneratedMarkdownText,
|
|
6
|
+
} from "../markdown-hygiene.ts";
|
|
7
|
+
|
|
8
|
+
describe("normalizeGeneratedMarkdownText", () => {
|
|
9
|
+
it("returns empty string for undefined", () => {
|
|
10
|
+
expect(normalizeGeneratedMarkdownText(undefined)).toBe("");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("normalizes CRLF to LF", () => {
|
|
14
|
+
expect(normalizeGeneratedMarkdownText("a\r\nb")).toBe("a\nb");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("converts <br> tags to newlines", () => {
|
|
18
|
+
expect(normalizeGeneratedMarkdownText("a<br>b")).toBe("a\nb");
|
|
19
|
+
expect(normalizeGeneratedMarkdownText("a<br/>b")).toBe("a\nb");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("converts date slashes to ISO format", () => {
|
|
23
|
+
expect(normalizeGeneratedMarkdownText("2024/1/5")).toBe("2024-01-05");
|
|
24
|
+
expect(normalizeGeneratedMarkdownText("2024/12/15")).toBe("2024-12-15");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("fixes - --- to ---", () => {
|
|
28
|
+
expect(normalizeGeneratedMarkdownText("- ---\n")).toBe("---");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("fixes - - to - ", () => {
|
|
32
|
+
expect(normalizeGeneratedMarkdownText("- - item")).toBe("- item");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("fixes - # to #", () => {
|
|
36
|
+
expect(normalizeGeneratedMarkdownText("- ## Heading")).toBe("## Heading");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("trims trailing whitespace", () => {
|
|
40
|
+
expect(normalizeGeneratedMarkdownText("text \n")).toBe("text");
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("canonicalizeGeneratedMarkdownText", () => {
|
|
45
|
+
it("strips default language prefix from URLs", () => {
|
|
46
|
+
const result = canonicalizeGeneratedMarkdownText("Link to [page](https://example.com/de/page)", {
|
|
47
|
+
baseUrl: "https://example.com",
|
|
48
|
+
defaultLanguage: "de",
|
|
49
|
+
});
|
|
50
|
+
expect(result).toBe("Link to [page](https://example.com/page)");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("does not strip when no baseUrl/defaultLanguage", () => {
|
|
54
|
+
const result = canonicalizeGeneratedMarkdownText("https://example.com/de/page");
|
|
55
|
+
expect(result).toBe("https://example.com/de/page");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("formatGeneratedMarkdownListItem", () => {
|
|
60
|
+
it("wraps plain text as list item", () => {
|
|
61
|
+
expect(formatGeneratedMarkdownListItem("Simple text")).toEqual(["- Simple text"]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("returns empty array for empty text", () => {
|
|
65
|
+
expect(formatGeneratedMarkdownListItem("")).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("splits markdown blocks into lines", () => {
|
|
69
|
+
const result = formatGeneratedMarkdownListItem("## Heading\n\nParagraph");
|
|
70
|
+
expect(result).toEqual(["## Heading", "", "Paragraph"]);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildMarkdownPageSemantic } from "../page-builders/markdown-page.ts";
|
|
3
|
+
|
|
4
|
+
const baseInput = {
|
|
5
|
+
type: "content" as const,
|
|
6
|
+
lang: "de",
|
|
7
|
+
url: "https://example.com/de/page/",
|
|
8
|
+
title: "Test Page",
|
|
9
|
+
description: "A test page",
|
|
10
|
+
heading: "Test Heading",
|
|
11
|
+
blocks: [],
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe("buildMarkdownPageSemantic", () => {
|
|
15
|
+
it("builds a SemanticPageModel with canonical URL", () => {
|
|
16
|
+
const result = buildMarkdownPageSemantic(baseInput);
|
|
17
|
+
expect(result.type).toBe("content");
|
|
18
|
+
expect(result.lang).toBe("de");
|
|
19
|
+
expect(result.url).toBe("https://example.com/de/page/");
|
|
20
|
+
expect(result.title).toBe("Test Page");
|
|
21
|
+
expect(result.description).toBe("A test page");
|
|
22
|
+
expect(result.heading).toBe("Test Heading");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("builds flat breadcrumb fallback when no breadcrumbs provided", () => {
|
|
26
|
+
const result = buildMarkdownPageSemantic({
|
|
27
|
+
...baseInput,
|
|
28
|
+
breadcrumbsContent: { homeLabel: "Start" },
|
|
29
|
+
});
|
|
30
|
+
expect(result.breadcrumbs).toHaveLength(2);
|
|
31
|
+
expect(result.breadcrumbs[0]).toEqual({ name: "Start", url: "https://example.com/de/" });
|
|
32
|
+
expect(result.breadcrumbs[1]).toEqual({ name: "Test Heading", url: "https://example.com/de/page/" });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("uses provided breadcrumbs when available", () => {
|
|
36
|
+
const result = buildMarkdownPageSemantic({
|
|
37
|
+
...baseInput,
|
|
38
|
+
breadcrumbs: [
|
|
39
|
+
{ name: "Home", url: "https://example.com/de/" },
|
|
40
|
+
{ name: "Section", url: "https://example.com/de/section/" },
|
|
41
|
+
{ name: "Page", url: "https://example.com/de/section/page/" },
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
expect(result.breadcrumbs).toHaveLength(3);
|
|
45
|
+
expect(result.breadcrumbs[1]).toEqual({ name: "Section", url: "https://example.com/de/section/" });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("passes through blocks", () => {
|
|
49
|
+
const blocks = [{ id: "b1", blockType: "prose", heading: "Block 1" }];
|
|
50
|
+
const result = buildMarkdownPageSemantic({ ...baseInput, blocks });
|
|
51
|
+
expect(result.blocks).toBe(blocks);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("passes through people and initiatives", () => {
|
|
55
|
+
const result = buildMarkdownPageSemantic({
|
|
56
|
+
...baseInput,
|
|
57
|
+
people: [{ name: "Jane", role: "CEO" }],
|
|
58
|
+
initiatives: [{ id: "i1", name: "Project", summary: "A project" }],
|
|
59
|
+
});
|
|
60
|
+
expect(result.people).toEqual([{ name: "Jane", role: "CEO" }]);
|
|
61
|
+
expect(result.initiatives).toEqual([{ id: "i1", name: "Project", summary: "A project" }]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("passes through faqEntries when provided", () => {
|
|
65
|
+
const result = buildMarkdownPageSemantic({
|
|
66
|
+
...baseInput,
|
|
67
|
+
faqEntries: [{ id: "q1", question: "Q?", answer: "A." }],
|
|
68
|
+
});
|
|
69
|
+
expect(result.faqEntries).toEqual([{ id: "q1", question: "Q?", answer: "A." }]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("sets faqEntries to undefined when empty", () => {
|
|
73
|
+
const result = buildMarkdownPageSemantic({
|
|
74
|
+
...baseInput,
|
|
75
|
+
faqEntries: [],
|
|
76
|
+
});
|
|
77
|
+
expect(result.faqEntries).toBeUndefined();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("includes audience when provided", () => {
|
|
81
|
+
const result = buildMarkdownPageSemantic({
|
|
82
|
+
...baseInput,
|
|
83
|
+
audience: "developer",
|
|
84
|
+
});
|
|
85
|
+
expect(result.audience).toBe("developer");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("omits audience when not provided", () => {
|
|
89
|
+
const result = buildMarkdownPageSemantic(baseInput);
|
|
90
|
+
expect(result.audience).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("uses default Home label when no breadcrumbsContent", () => {
|
|
94
|
+
const result = buildMarkdownPageSemantic(baseInput);
|
|
95
|
+
expect(result.breadcrumbs[0].name).toBe("Home");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
AUDIENCE_BY_PAGE_TYPE,
|
|
4
|
+
buildMarkdownTwin,
|
|
5
|
+
buildMarkdownTwinFrontmatter,
|
|
6
|
+
computeContentHash,
|
|
7
|
+
parseMarkdownTwinFrontmatter,
|
|
8
|
+
verifyMarkdownTwinHash,
|
|
9
|
+
type MarkdownTwinProvenance,
|
|
10
|
+
} from "../markdown-twin-provenance.ts";
|
|
11
|
+
|
|
12
|
+
const baseProvenance: MarkdownTwinProvenance = {
|
|
13
|
+
canonical: "https://example.com/de/page",
|
|
14
|
+
language: "de",
|
|
15
|
+
lastModified: "2024-01-15",
|
|
16
|
+
license: "CC-BY-4.0",
|
|
17
|
+
generator: "test",
|
|
18
|
+
sourceKind: "authored",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe("computeContentHash", () => {
|
|
22
|
+
it("produces sha256: prefix", () => {
|
|
23
|
+
expect(computeContentHash("body text").startsWith("sha256:")).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("is deterministic for same input", () => {
|
|
27
|
+
expect(computeContentHash("body")).toBe(computeContentHash("body"));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("normalizes trailing whitespace", () => {
|
|
31
|
+
expect(computeContentHash("body\n\n")).toBe(computeContentHash("body"));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("normalizes CRLF to LF", () => {
|
|
35
|
+
expect(computeContentHash("a\r\nb")).toBe(computeContentHash("a\nb"));
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("buildMarkdownTwinFrontmatter", () => {
|
|
40
|
+
it("includes all core provenance fields", () => {
|
|
41
|
+
const fm = buildMarkdownTwinFrontmatter(baseProvenance, "sha256:abc");
|
|
42
|
+
expect(fm).toContain('canonical: "https://example.com/de/page"');
|
|
43
|
+
expect(fm).toContain('language: "de"');
|
|
44
|
+
expect(fm).toContain('lastModified: "2024-01-15"');
|
|
45
|
+
expect(fm).toContain('contentHash: "sha256:abc"');
|
|
46
|
+
expect(fm).toContain('license: "CC-BY-4.0"');
|
|
47
|
+
expect(fm).toContain('generator: "test"');
|
|
48
|
+
expect(fm).toContain('sourceKind: "authored"');
|
|
49
|
+
expect(fm).toContain('schema: "gogol.markdown-twin@2"');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("emits null for lastModified when null", () => {
|
|
53
|
+
const fm = buildMarkdownTwinFrontmatter(
|
|
54
|
+
{ ...baseProvenance, lastModified: null },
|
|
55
|
+
"sha256:abc",
|
|
56
|
+
);
|
|
57
|
+
expect(fm).toContain("lastModified: null");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("includes pageId when provided", () => {
|
|
61
|
+
const fm = buildMarkdownTwinFrontmatter(
|
|
62
|
+
{ ...baseProvenance, pageId: "about" },
|
|
63
|
+
"sha256:abc",
|
|
64
|
+
);
|
|
65
|
+
expect(fm).toContain('pageId: "about"');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("includes sourceInputs when provided", () => {
|
|
69
|
+
const fm = buildMarkdownTwinFrontmatter(
|
|
70
|
+
{ ...baseProvenance, sourceInputs: ["file1.md", "file2.md"] },
|
|
71
|
+
"sha256:abc",
|
|
72
|
+
);
|
|
73
|
+
expect(fm).toContain('sourceInputs:');
|
|
74
|
+
expect(fm).toContain('"file1.md"');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("includes semantic meta when provided", () => {
|
|
78
|
+
const fm = buildMarkdownTwinFrontmatter(
|
|
79
|
+
{
|
|
80
|
+
...baseProvenance,
|
|
81
|
+
semantic: {
|
|
82
|
+
id: "page-1",
|
|
83
|
+
route: "/de/page",
|
|
84
|
+
title: "Page",
|
|
85
|
+
type: "content",
|
|
86
|
+
domain: "content",
|
|
87
|
+
audience: "general",
|
|
88
|
+
lang: "de",
|
|
89
|
+
metaDescription: "A page",
|
|
90
|
+
priority: 0.7,
|
|
91
|
+
tags: ["tag1"],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
"sha256:abc",
|
|
95
|
+
);
|
|
96
|
+
expect(fm).toContain('id: "page-1"');
|
|
97
|
+
expect(fm).toContain('type: "content"');
|
|
98
|
+
expect(fm).toContain("priority: 0.7");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("buildMarkdownTwin", () => {
|
|
103
|
+
it("produces frontmatter + body", () => {
|
|
104
|
+
const twin = buildMarkdownTwin("# Hello\n\nWorld", baseProvenance);
|
|
105
|
+
expect(twin.startsWith("---\n")).toBe(true);
|
|
106
|
+
expect(twin).toContain("# Hello");
|
|
107
|
+
expect(twin).toContain("World");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("parseMarkdownTwinFrontmatter", () => {
|
|
112
|
+
it("round-trips buildMarkdownTwin output", () => {
|
|
113
|
+
const body = "# Hello\n\nWorld";
|
|
114
|
+
const twin = buildMarkdownTwin(body, baseProvenance);
|
|
115
|
+
const parsed = parseMarkdownTwinFrontmatter(twin);
|
|
116
|
+
expect(parsed).not.toBeNull();
|
|
117
|
+
expect(parsed!.frontmatter.canonical).toBe("https://example.com/de/page");
|
|
118
|
+
expect(parsed!.frontmatter.language).toBe("de");
|
|
119
|
+
expect(parsed!.body).toBe("# Hello\n\nWorld\n");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("returns null for no frontmatter", () => {
|
|
123
|
+
expect(parseMarkdownTwinFrontmatter("just text")).toBeNull();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("verifyMarkdownTwinHash", () => {
|
|
128
|
+
it("verifies correct hash", () => {
|
|
129
|
+
const twin = buildMarkdownTwin("# Hello", baseProvenance);
|
|
130
|
+
expect(verifyMarkdownTwinHash(twin)).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("fails for tampered body", () => {
|
|
134
|
+
const twin = buildMarkdownTwin("# Hello", baseProvenance);
|
|
135
|
+
const tampered = twin.replace("# Hello", "# Tampered");
|
|
136
|
+
expect(verifyMarkdownTwinHash(tampered)).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe("AUDIENCE_BY_PAGE_TYPE", () => {
|
|
141
|
+
it("maps home to general", () => {
|
|
142
|
+
expect(AUDIENCE_BY_PAGE_TYPE.home).toBe("general");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("maps projects to developer", () => {
|
|
146
|
+
expect(AUDIENCE_BY_PAGE_TYPE.projects).toBe("developer");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("maps legal to business_owner", () => {
|
|
150
|
+
expect(AUDIENCE_BY_PAGE_TYPE.legal).toBe("business_owner");
|
|
151
|
+
});
|
|
152
|
+
});
|