@vellumai/assistant 0.4.37 → 0.4.41

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.
Files changed (169) hide show
  1. package/ARCHITECTURE.md +3 -3
  2. package/README.md +13 -13
  3. package/bun.lock +80 -24
  4. package/docs/architecture/integrations.md +126 -128
  5. package/docs/runbook-trusted-contacts.md +1 -1
  6. package/docs/trusted-contact-access.md +12 -12
  7. package/package.json +3 -1
  8. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -14
  9. package/src/__tests__/app-bundler.test.ts +209 -0
  10. package/src/__tests__/app-compiler.test.ts +279 -0
  11. package/src/__tests__/app-executors.test.ts +293 -483
  12. package/src/__tests__/app-migration.test.ts +148 -0
  13. package/src/__tests__/app-routes-csp.test.ts +202 -0
  14. package/src/__tests__/avatar-e2e.test.ts +452 -0
  15. package/src/__tests__/avatar-generator.test.ts +193 -0
  16. package/src/__tests__/avatar-router.test.ts +186 -0
  17. package/src/__tests__/browser-download-timeout.test.ts +28 -0
  18. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +9 -9
  19. package/src/__tests__/call-domain.test.ts +3 -7
  20. package/src/__tests__/credential-security-e2e.test.ts +19 -12
  21. package/src/__tests__/credentials-cli.test.ts +30 -4
  22. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +1 -1
  23. package/src/__tests__/handlers-slack-config.test.ts +0 -72
  24. package/src/__tests__/handlers-telegram-config.test.ts +19 -12
  25. package/src/__tests__/handlers-twitter-config.test.ts +105 -48
  26. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  27. package/src/__tests__/integration-status.test.ts +15 -5
  28. package/src/__tests__/integrations-cli.test.ts +1 -1
  29. package/src/__tests__/invite-redemption-service.test.ts +62 -7
  30. package/src/__tests__/ipc-snapshot.test.ts +0 -8
  31. package/src/__tests__/managed-avatar-client.test.ts +280 -0
  32. package/src/__tests__/mcp-cli.test.ts +3 -3
  33. package/src/__tests__/oauth-cli.test.ts +203 -0
  34. package/src/__tests__/relay-server.test.ts +3 -3
  35. package/src/__tests__/secret-onetime-send.test.ts +19 -12
  36. package/src/__tests__/secure-keys.test.ts +78 -0
  37. package/src/__tests__/session-messaging-secret-redirect.test.ts +3 -0
  38. package/src/__tests__/slack-channel-config.test.ts +23 -16
  39. package/src/__tests__/slack-share-routes.test.ts +263 -0
  40. package/src/__tests__/sms-messaging-provider.test.ts +3 -1
  41. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +7 -7
  42. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  43. package/src/__tests__/trusted-contact-verification.test.ts +10 -10
  44. package/src/__tests__/twilio-config.test.ts +15 -36
  45. package/src/__tests__/twilio-provider.test.ts +4 -0
  46. package/src/__tests__/twitter-auth-handler.test.ts +27 -14
  47. package/src/__tests__/twitter-cli-error-shaping.test.ts +1 -1
  48. package/src/__tests__/twitter-cli-routing.test.ts +38 -53
  49. package/src/__tests__/twitter-oauth-client.test.ts +18 -47
  50. package/src/__tests__/voice-invite-redemption.test.ts +27 -3
  51. package/src/amazon/cart.ts +1 -1
  52. package/src/amazon/client.ts +89 -7
  53. package/src/approvals/guardian-request-resolvers.ts +2 -2
  54. package/src/bundler/app-bundler.ts +77 -32
  55. package/src/bundler/app-compiler.ts +195 -0
  56. package/src/bundler/manifest.ts +1 -1
  57. package/src/bundler/package-resolver.ts +185 -0
  58. package/src/calls/call-domain.ts +4 -14
  59. package/src/calls/relay-server.ts +2 -2
  60. package/src/calls/twilio-config.ts +5 -24
  61. package/src/calls/twilio-rest.ts +19 -5
  62. package/src/cli/amazon.ts +74 -249
  63. package/src/cli/audit.ts +2 -2
  64. package/src/cli/autonomy.ts +9 -9
  65. package/src/cli/channels.ts +5 -5
  66. package/src/cli/completions.ts +27 -27
  67. package/src/cli/config.ts +14 -14
  68. package/src/cli/contacts.ts +27 -27
  69. package/src/cli/credentials.ts +28 -28
  70. package/src/cli/dev.ts +2 -2
  71. package/src/cli/doctor.ts +2 -2
  72. package/src/cli/email.ts +82 -82
  73. package/src/cli/influencer.ts +13 -13
  74. package/src/cli/integrations.ts +19 -144
  75. package/src/cli/keys.ts +10 -10
  76. package/src/cli/map.ts +4 -4
  77. package/src/cli/mcp.ts +17 -17
  78. package/src/cli/memory.ts +18 -18
  79. package/src/cli/notifications.ts +13 -13
  80. package/src/cli/oauth.ts +77 -0
  81. package/src/cli/program.ts +2 -0
  82. package/src/cli/sequence.ts +27 -27
  83. package/src/cli/sessions.ts +12 -12
  84. package/src/cli/trust.ts +8 -8
  85. package/src/cli/twitter.ts +124 -70
  86. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  87. package/src/config/bundled-skills/agentmail/SKILL.md +34 -34
  88. package/src/config/bundled-skills/amazon/SKILL.md +54 -54
  89. package/src/config/bundled-skills/app-builder/SKILL.md +137 -3
  90. package/src/config/bundled-skills/app-builder/tools/app-create.ts +10 -4
  91. package/src/config/bundled-skills/configure-settings/SKILL.md +18 -18
  92. package/src/config/bundled-skills/contacts/SKILL.md +12 -12
  93. package/src/config/bundled-skills/doordash/lib/client.ts +7 -9
  94. package/src/config/bundled-skills/email-setup/SKILL.md +4 -4
  95. package/src/config/bundled-skills/frontend-design/icon.svg +16 -0
  96. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +143 -162
  97. package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +4 -4
  98. package/src/config/bundled-skills/influencer/SKILL.md +13 -13
  99. package/src/config/bundled-skills/mcp-setup/SKILL.md +11 -11
  100. package/src/config/bundled-skills/phone-calls/SKILL.md +48 -54
  101. package/src/config/bundled-skills/public-ingress/SKILL.md +6 -6
  102. package/src/config/bundled-skills/slack-app-setup/SKILL.md +1 -1
  103. package/src/config/bundled-skills/sms-setup/SKILL.md +3 -3
  104. package/src/config/bundled-skills/telegram-setup/SKILL.md +2 -2
  105. package/src/config/bundled-skills/twilio-setup/SKILL.md +136 -225
  106. package/src/config/bundled-skills/twitter/SKILL.md +68 -44
  107. package/src/config/bundled-skills/voice-setup/SKILL.md +2 -2
  108. package/src/config/core-schema.ts +26 -0
  109. package/src/config/env.ts +4 -0
  110. package/src/config/feature-flag-registry.json +9 -1
  111. package/src/config/schema.ts +8 -0
  112. package/src/config/system-prompt.ts +6 -3
  113. package/src/config/templates/BOOTSTRAP.md +7 -5
  114. package/src/contacts/contacts-write.ts +5 -1
  115. package/src/daemon/handlers/apps.ts +31 -4
  116. package/src/daemon/handlers/config-ingress.ts +3 -3
  117. package/src/daemon/handlers/config-integrations.ts +120 -49
  118. package/src/daemon/handlers/config-slack-channel.ts +26 -7
  119. package/src/daemon/handlers/config-slack.ts +1 -54
  120. package/src/daemon/handlers/config-telegram.ts +28 -10
  121. package/src/daemon/handlers/config.ts +1 -4
  122. package/src/daemon/handlers/twitter-auth.ts +11 -4
  123. package/src/daemon/ipc-contract/apps.ts +0 -13
  124. package/src/daemon/ipc-contract-inventory.json +0 -2
  125. package/src/daemon/lifecycle.ts +8 -1
  126. package/src/daemon/session-messaging.ts +2 -2
  127. package/src/daemon/tool-side-effects.ts +30 -0
  128. package/src/email/providers/agentmail.ts +1 -1
  129. package/src/email/providers/index.ts +1 -1
  130. package/src/email/service.ts +1 -1
  131. package/src/gallery/default-gallery.ts +538 -0
  132. package/src/gallery/gallery-manifest.ts +5 -1
  133. package/src/influencer/client.ts +8 -6
  134. package/src/mcp/client.ts +1 -1
  135. package/src/media/avatar-router.ts +99 -0
  136. package/src/media/avatar-types.ts +60 -0
  137. package/src/media/managed-avatar-client.ts +189 -0
  138. package/src/memory/app-migration.ts +114 -0
  139. package/src/memory/app-store.ts +11 -0
  140. package/src/memory/qdrant-client.ts +1 -1
  141. package/src/messaging/providers/slack/client.ts +12 -2
  142. package/src/messaging/providers/sms/adapter.ts +6 -10
  143. package/src/migrations/data-layout.ts +8 -1
  144. package/src/oauth/token-persistence.ts +9 -6
  145. package/src/runtime/assistant-scope.ts +5 -0
  146. package/src/runtime/auth/route-policy.ts +4 -0
  147. package/src/runtime/channel-readiness-service.ts +9 -4
  148. package/src/runtime/gateway-internal-client.ts +11 -3
  149. package/src/runtime/http-server.ts +2 -0
  150. package/src/runtime/invite-redemption-service.ts +23 -13
  151. package/src/runtime/middleware/twilio-validation.ts +2 -2
  152. package/src/runtime/routes/app-routes.ts +131 -3
  153. package/src/runtime/routes/inbound-stages/verification-intercept.ts +3 -3
  154. package/src/runtime/routes/integration-routes.ts +2 -2
  155. package/src/runtime/routes/slack-share-routes.ts +235 -0
  156. package/src/runtime/routes/twilio-routes.ts +47 -34
  157. package/src/schedule/integration-status.ts +2 -3
  158. package/src/security/token-manager.ts +11 -3
  159. package/src/tools/apps/executors.ts +116 -8
  160. package/src/tools/browser/browser-manager.ts +30 -2
  161. package/src/tools/browser/chrome-cdp.ts +31 -3
  162. package/src/tools/credentials/vault.ts +9 -7
  163. package/src/tools/executor.ts +4 -0
  164. package/src/tools/system/avatar-generator.ts +55 -34
  165. package/src/twitter/client.ts +1 -1
  166. package/src/twitter/oauth-client.ts +31 -43
  167. package/src/twitter/router.ts +25 -23
  168. package/src/util/platform.ts +5 -0
  169. package/src/slack/slack-webhook.ts +0 -66
@@ -0,0 +1,148 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
4
+
5
+ import { migrateAppToMultifile } from "../memory/app-migration.js";
6
+ import { createApp, getApp, getAppsDir } from "../memory/app-store.js";
7
+
8
+ // Redirect app storage to a temp directory for test isolation
9
+ const tmpDir = join(import.meta.dir, ".tmp-app-migration-test");
10
+
11
+ mock.module("../util/platform.js", () => ({
12
+ getDataDir: () => tmpDir,
13
+ isContainerized: () => false,
14
+ }));
15
+
16
+ describe("migrateAppToMultifile", () => {
17
+ beforeEach(() => {
18
+ mkdirSync(tmpDir, { recursive: true });
19
+ });
20
+
21
+ afterEach(() => {
22
+ rmSync(tmpDir, { recursive: true, force: true });
23
+ });
24
+
25
+ test("migrates a simple HTML app to src/ layout", () => {
26
+ const app = createApp({
27
+ name: "Test App",
28
+ schemaJson: "{}",
29
+ htmlDefinition: "<html><body>Hello</body></html>",
30
+ });
31
+
32
+ const result = migrateAppToMultifile(app.id);
33
+ expect(result.ok).toBe(true);
34
+ expect(result.error).toBeUndefined();
35
+
36
+ const appDir = join(getAppsDir(), app.id);
37
+
38
+ // Root index.html is preserved
39
+ expect(existsSync(join(appDir, "index.html"))).toBe(true);
40
+ expect(readFileSync(join(appDir, "index.html"), "utf-8")).toBe(
41
+ "<html><body>Hello</body></html>",
42
+ );
43
+
44
+ // src/index.html created
45
+ expect(existsSync(join(appDir, "src", "index.html"))).toBe(true);
46
+
47
+ // src/main.tsx created
48
+ const mainTsx = readFileSync(join(appDir, "src", "main.tsx"), "utf-8");
49
+ expect(mainTsx).toContain("Entry point");
50
+ // No inline styles in source HTML → no styles.css import
51
+ expect(mainTsx).not.toContain("import './styles.css'");
52
+ });
53
+
54
+ test("sets formatVersion to 2 after migration", () => {
55
+ const app = createApp({
56
+ name: "Version Test",
57
+ schemaJson: "{}",
58
+ htmlDefinition: "<html><body>Hi</body></html>",
59
+ });
60
+
61
+ expect(app.formatVersion).toBeUndefined();
62
+
63
+ migrateAppToMultifile(app.id);
64
+
65
+ const updated = getApp(app.id);
66
+ expect(updated).not.toBeNull();
67
+ expect(updated!.formatVersion).toBe(2);
68
+ });
69
+
70
+ test("extracts inline styles to CSS file", () => {
71
+ const htmlWithStyle = `<html>
72
+ <head><style>body { color: red; }</style></head>
73
+ <body>Styled</body>
74
+ </html>`;
75
+
76
+ const app = createApp({
77
+ name: "Styled App",
78
+ schemaJson: "{}",
79
+ htmlDefinition: htmlWithStyle,
80
+ });
81
+
82
+ const result = migrateAppToMultifile(app.id);
83
+ expect(result.ok).toBe(true);
84
+
85
+ const appDir = join(getAppsDir(), app.id);
86
+
87
+ // styles.css should contain the extracted CSS
88
+ const css = readFileSync(join(appDir, "src", "styles.css"), "utf-8");
89
+ expect(css).toContain("body { color: red; }");
90
+
91
+ // src/index.html should have a <link> tag instead of <style>
92
+ const srcHtml = readFileSync(join(appDir, "src", "index.html"), "utf-8");
93
+ expect(srcHtml).toContain('<link rel="stylesheet" href="styles.css">');
94
+ expect(srcHtml).not.toContain("<style>");
95
+ });
96
+
97
+ test("extracts multiple inline style blocks", () => {
98
+ const html = `<html>
99
+ <head><style>.a { margin: 0; }</style></head>
100
+ <body><style>.b { padding: 0; }</style></body>
101
+ </html>`;
102
+
103
+ const app = createApp({
104
+ name: "Multi Style",
105
+ schemaJson: "{}",
106
+ htmlDefinition: html,
107
+ });
108
+
109
+ migrateAppToMultifile(app.id);
110
+
111
+ const appDir = join(getAppsDir(), app.id);
112
+ const css = readFileSync(join(appDir, "src", "styles.css"), "utf-8");
113
+ expect(css).toContain(".a { margin: 0; }");
114
+ expect(css).toContain(".b { padding: 0; }");
115
+ });
116
+
117
+ test("migration of already-migrated app is a no-op", () => {
118
+ const app = createApp({
119
+ name: "Already Migrated",
120
+ schemaJson: "{}",
121
+ htmlDefinition: "<html><body>Done</body></html>",
122
+ });
123
+
124
+ const first = migrateAppToMultifile(app.id);
125
+ expect(first.ok).toBe(true);
126
+
127
+ // Grab the updatedAt after first migration
128
+ const afterFirst = getApp(app.id);
129
+ const firstUpdatedAt = afterFirst!.updatedAt;
130
+
131
+ // Small delay so updatedAt would differ if re-written
132
+ const second = migrateAppToMultifile(app.id);
133
+ expect(second.ok).toBe(true);
134
+
135
+ // formatVersion should still be 2
136
+ const afterSecond = getApp(app.id);
137
+ expect(afterSecond!.formatVersion).toBe(2);
138
+
139
+ // updatedAt should NOT have changed (no-op)
140
+ expect(afterSecond!.updatedAt).toBe(firstUpdatedAt);
141
+ });
142
+
143
+ test("returns error when app not found", () => {
144
+ const result = migrateAppToMultifile("nonexistent-id-12345");
145
+ expect(result.ok).toBe(false);
146
+ expect(result.error).toContain("App not found");
147
+ });
148
+ });
@@ -0,0 +1,202 @@
1
+ import { describe, expect, mock, test } from "bun:test";
2
+
3
+ // Mock the logger before importing the module under test
4
+ mock.module("../util/logger.js", () => ({
5
+ getLogger: () =>
6
+ new Proxy({} as Record<string, unknown>, {
7
+ get: () => () => {},
8
+ }),
9
+ }));
10
+
11
+ // Fake app records keyed by ID
12
+ const legacyApp = {
13
+ id: "legacy-1",
14
+ name: "Legacy App",
15
+ htmlDefinition: "<div>Hello</div>",
16
+ schemaJson: "{}",
17
+ createdAt: 0,
18
+ updatedAt: 0,
19
+ // No formatVersion → legacy
20
+ };
21
+
22
+ const multifileApp = {
23
+ id: "multi-1",
24
+ name: "Multifile App",
25
+ htmlDefinition: "",
26
+ schemaJson: "{}",
27
+ createdAt: 0,
28
+ updatedAt: 0,
29
+ formatVersion: 2,
30
+ };
31
+
32
+ const apps = new Map<string, typeof legacyApp | typeof multifileApp>([
33
+ ["legacy-1", legacyApp],
34
+ ["multi-1", multifileApp],
35
+ ]);
36
+
37
+ mock.module("../memory/app-store.js", () => ({
38
+ getApp: (id: string) => apps.get(id) ?? null,
39
+ getAppsDir: () => "/fake/apps",
40
+ isMultifileApp: (app: Record<string, unknown>) => app.formatVersion === 2,
41
+ }));
42
+
43
+ // Mock shared-app-links-store (imported by app-routes but unused here)
44
+ mock.module("../memory/shared-app-links-store.js", () => ({
45
+ createSharedAppLink: () => ({ shareToken: "tok" }),
46
+ getSharedAppLink: () => null,
47
+ incrementDownloadCount: () => {},
48
+ deleteSharedAppLinkByToken: () => false,
49
+ }));
50
+
51
+ // Stub fs so the multifile path finds a fake dist/index.html
52
+ mock.module("node:fs", () => ({
53
+ existsSync: (p: string) => p === "/fake/apps/multi-1/dist/index.html",
54
+ readFileSync: (p: string, _enc?: string) => {
55
+ if (p === "/fake/apps/multi-1/dist/index.html") {
56
+ return '<!DOCTYPE html><html><head></head><body><script src="main.js"></script></body></html>';
57
+ }
58
+ // Design system CSS — return empty string
59
+ return "";
60
+ },
61
+ }));
62
+
63
+ import {
64
+ handleServeDistFile,
65
+ handleServePage,
66
+ } from "../runtime/routes/app-routes.js";
67
+
68
+ /** Parse CSP header into a directive map. */
69
+ function parseCsp(header: string): Record<string, string> {
70
+ const directives: Record<string, string> = {};
71
+ for (const part of header.split(";")) {
72
+ const trimmed = part.trim();
73
+ const spaceIdx = trimmed.indexOf(" ");
74
+ if (spaceIdx === -1) continue;
75
+ directives[trimmed.slice(0, spaceIdx)] = trimmed.slice(spaceIdx + 1);
76
+ }
77
+ return directives;
78
+ }
79
+
80
+ describe("app-routes CSP headers", () => {
81
+ describe("legacy apps", () => {
82
+ test("includes 'unsafe-inline' in script-src", () => {
83
+ const res = handleServePage("legacy-1");
84
+ const csp = res.headers.get("Content-Security-Policy")!;
85
+ const directives = parseCsp(csp);
86
+ expect(directives["script-src"]).toContain("'unsafe-inline'");
87
+ });
88
+
89
+ test("includes 'unsafe-inline' in style-src", () => {
90
+ const res = handleServePage("legacy-1");
91
+ const csp = res.headers.get("Content-Security-Policy")!;
92
+ const directives = parseCsp(csp);
93
+ expect(directives["style-src"]).toContain("'unsafe-inline'");
94
+ });
95
+
96
+ test("has img-src with self, data, and https", () => {
97
+ const res = handleServePage("legacy-1");
98
+ const csp = res.headers.get("Content-Security-Policy")!;
99
+ const directives = parseCsp(csp);
100
+ expect(directives["img-src"]).toContain("'self'");
101
+ expect(directives["img-src"]).toContain("data:");
102
+ expect(directives["img-src"]).toContain("https:");
103
+ });
104
+ });
105
+
106
+ describe("multifile apps", () => {
107
+ test("does NOT include 'unsafe-inline' in script-src", () => {
108
+ const res = handleServePage("multi-1");
109
+ const csp = res.headers.get("Content-Security-Policy")!;
110
+ const directives = parseCsp(csp);
111
+ expect(directives["script-src"]).not.toContain("'unsafe-inline'");
112
+ });
113
+
114
+ test("includes 'self' in script-src for external main.js", () => {
115
+ const res = handleServePage("multi-1");
116
+ const csp = res.headers.get("Content-Security-Policy")!;
117
+ const directives = parseCsp(csp);
118
+ expect(directives["script-src"]).toContain("'self'");
119
+ });
120
+
121
+ test("includes 'unsafe-inline' in style-src", () => {
122
+ const res = handleServePage("multi-1");
123
+ const csp = res.headers.get("Content-Security-Policy")!;
124
+ const directives = parseCsp(csp);
125
+ expect(directives["style-src"]).toContain("'unsafe-inline'");
126
+ });
127
+
128
+ test("has img-src with self, data, and https", () => {
129
+ const res = handleServePage("multi-1");
130
+ const csp = res.headers.get("Content-Security-Policy")!;
131
+ const directives = parseCsp(csp);
132
+ expect(directives["img-src"]).toContain("'self'");
133
+ expect(directives["img-src"]).toContain("data:");
134
+ expect(directives["img-src"]).toContain("https:");
135
+ });
136
+ });
137
+
138
+ describe("handleServeDistFile appId validation", () => {
139
+ test("rejects appId with encoded path traversal (..)", () => {
140
+ const res = handleServeDistFile("..", "main.js");
141
+ expect(res.status).toBe(400);
142
+ });
143
+
144
+ test("rejects appId with forward slash", () => {
145
+ const res = handleServeDistFile("../../etc", "main.js");
146
+ expect(res.status).toBe(400);
147
+ });
148
+
149
+ test("rejects appId with backslash", () => {
150
+ const res = handleServeDistFile("foo\\bar", "main.js");
151
+ expect(res.status).toBe(400);
152
+ });
153
+
154
+ test("rejects empty appId", () => {
155
+ const res = handleServeDistFile("", "main.js");
156
+ expect(res.status).toBe(400);
157
+ });
158
+
159
+ test("rejects appId with leading whitespace", () => {
160
+ const res = handleServeDistFile(" multi-1", "main.js");
161
+ expect(res.status).toBe(400);
162
+ });
163
+
164
+ test("rejects appId with trailing whitespace", () => {
165
+ const res = handleServeDistFile("multi-1 ", "main.js");
166
+ expect(res.status).toBe(400);
167
+ });
168
+
169
+ test("rejects appId containing .. in the middle", () => {
170
+ const res = handleServeDistFile("foo..bar", "main.js");
171
+ expect(res.status).toBe(400);
172
+ });
173
+
174
+ test("allows valid appId and filename (file not found is 404)", () => {
175
+ const res = handleServeDistFile("multi-1", "main.js");
176
+ // File doesn't exist in our mock fs, so 404
177
+ expect(res.status).toBe(404);
178
+ });
179
+ });
180
+
181
+ describe("consistent directives across formats", () => {
182
+ test("both formats share the same style-src policy", () => {
183
+ const legacy = handleServePage("legacy-1");
184
+ const multi = handleServePage("multi-1");
185
+ const legacyCsp = parseCsp(
186
+ legacy.headers.get("Content-Security-Policy")!,
187
+ );
188
+ const multiCsp = parseCsp(multi.headers.get("Content-Security-Policy")!);
189
+ expect(legacyCsp["style-src"]).toBe(multiCsp["style-src"]);
190
+ });
191
+
192
+ test("both formats share the same img-src policy", () => {
193
+ const legacy = handleServePage("legacy-1");
194
+ const multi = handleServePage("multi-1");
195
+ const legacyCsp = parseCsp(
196
+ legacy.headers.get("Content-Security-Policy")!,
197
+ );
198
+ const multiCsp = parseCsp(multi.headers.get("Content-Security-Policy")!);
199
+ expect(legacyCsp["img-src"]).toBe(multiCsp["img-src"]);
200
+ });
201
+ });
202
+ });