@synth-deploy/server 0.1.0
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/dist/agent/debrief-retention.d.ts +12 -0
- package/dist/agent/debrief-retention.d.ts.map +1 -0
- package/dist/agent/debrief-retention.js +27 -0
- package/dist/agent/debrief-retention.js.map +1 -0
- package/dist/agent/envoy-client.d.ts +216 -0
- package/dist/agent/envoy-client.d.ts.map +1 -0
- package/dist/agent/envoy-client.js +266 -0
- package/dist/agent/envoy-client.js.map +1 -0
- package/dist/agent/envoy-registry.d.ts +102 -0
- package/dist/agent/envoy-registry.d.ts.map +1 -0
- package/dist/agent/envoy-registry.js +319 -0
- package/dist/agent/envoy-registry.js.map +1 -0
- package/dist/agent/health-checker.d.ts +39 -0
- package/dist/agent/health-checker.d.ts.map +1 -0
- package/dist/agent/health-checker.js +49 -0
- package/dist/agent/health-checker.js.map +1 -0
- package/dist/agent/mcp-client-manager.d.ts +36 -0
- package/dist/agent/mcp-client-manager.d.ts.map +1 -0
- package/dist/agent/mcp-client-manager.js +106 -0
- package/dist/agent/mcp-client-manager.js.map +1 -0
- package/dist/agent/stale-deployment-detector.d.ts +15 -0
- package/dist/agent/stale-deployment-detector.d.ts.map +1 -0
- package/dist/agent/stale-deployment-detector.js +50 -0
- package/dist/agent/stale-deployment-detector.js.map +1 -0
- package/dist/agent/step-runner.d.ts +31 -0
- package/dist/agent/step-runner.d.ts.map +1 -0
- package/dist/agent/step-runner.js +80 -0
- package/dist/agent/step-runner.js.map +1 -0
- package/dist/agent/synth-agent.d.ts +168 -0
- package/dist/agent/synth-agent.d.ts.map +1 -0
- package/dist/agent/synth-agent.js +1195 -0
- package/dist/agent/synth-agent.js.map +1 -0
- package/dist/api/agent.d.ts +36 -0
- package/dist/api/agent.d.ts.map +1 -0
- package/dist/api/agent.js +867 -0
- package/dist/api/agent.js.map +1 -0
- package/dist/api/api-keys.d.ts +4 -0
- package/dist/api/api-keys.d.ts.map +1 -0
- package/dist/api/api-keys.js +118 -0
- package/dist/api/api-keys.js.map +1 -0
- package/dist/api/artifacts.d.ts +5 -0
- package/dist/api/artifacts.d.ts.map +1 -0
- package/dist/api/artifacts.js +142 -0
- package/dist/api/artifacts.js.map +1 -0
- package/dist/api/auth.d.ts +4 -0
- package/dist/api/auth.d.ts.map +1 -0
- package/dist/api/auth.js +280 -0
- package/dist/api/auth.js.map +1 -0
- package/dist/api/deployments.d.ts +11 -0
- package/dist/api/deployments.d.ts.map +1 -0
- package/dist/api/deployments.js +1098 -0
- package/dist/api/deployments.js.map +1 -0
- package/dist/api/environments.d.ts +5 -0
- package/dist/api/environments.d.ts.map +1 -0
- package/dist/api/environments.js +69 -0
- package/dist/api/environments.js.map +1 -0
- package/dist/api/envoy-reports.d.ts +17 -0
- package/dist/api/envoy-reports.d.ts.map +1 -0
- package/dist/api/envoy-reports.js +138 -0
- package/dist/api/envoy-reports.js.map +1 -0
- package/dist/api/envoys.d.ts +5 -0
- package/dist/api/envoys.d.ts.map +1 -0
- package/dist/api/envoys.js +192 -0
- package/dist/api/envoys.js.map +1 -0
- package/dist/api/fleet.d.ts +11 -0
- package/dist/api/fleet.d.ts.map +1 -0
- package/dist/api/fleet.js +394 -0
- package/dist/api/fleet.js.map +1 -0
- package/dist/api/graph.d.ts +8 -0
- package/dist/api/graph.d.ts.map +1 -0
- package/dist/api/graph.js +355 -0
- package/dist/api/graph.js.map +1 -0
- package/dist/api/health.d.ts +20 -0
- package/dist/api/health.d.ts.map +1 -0
- package/dist/api/health.js +248 -0
- package/dist/api/health.js.map +1 -0
- package/dist/api/idp-schemas.d.ts +41 -0
- package/dist/api/idp-schemas.d.ts.map +1 -0
- package/dist/api/idp-schemas.js +17 -0
- package/dist/api/idp-schemas.js.map +1 -0
- package/dist/api/idp.d.ts +6 -0
- package/dist/api/idp.d.ts.map +1 -0
- package/dist/api/idp.js +620 -0
- package/dist/api/idp.js.map +1 -0
- package/dist/api/intake.d.ts +10 -0
- package/dist/api/intake.d.ts.map +1 -0
- package/dist/api/intake.js +418 -0
- package/dist/api/intake.js.map +1 -0
- package/dist/api/partitions.d.ts +5 -0
- package/dist/api/partitions.d.ts.map +1 -0
- package/dist/api/partitions.js +113 -0
- package/dist/api/partitions.js.map +1 -0
- package/dist/api/progress-event-store.d.ts +62 -0
- package/dist/api/progress-event-store.d.ts.map +1 -0
- package/dist/api/progress-event-store.js +118 -0
- package/dist/api/progress-event-store.js.map +1 -0
- package/dist/api/schemas.d.ts +1000 -0
- package/dist/api/schemas.d.ts.map +1 -0
- package/dist/api/schemas.js +328 -0
- package/dist/api/schemas.js.map +1 -0
- package/dist/api/security-boundaries.d.ts +4 -0
- package/dist/api/security-boundaries.d.ts.map +1 -0
- package/dist/api/security-boundaries.js +32 -0
- package/dist/api/security-boundaries.js.map +1 -0
- package/dist/api/settings.d.ts +4 -0
- package/dist/api/settings.d.ts.map +1 -0
- package/dist/api/settings.js +99 -0
- package/dist/api/settings.js.map +1 -0
- package/dist/api/system.d.ts +75 -0
- package/dist/api/system.d.ts.map +1 -0
- package/dist/api/system.js +558 -0
- package/dist/api/system.js.map +1 -0
- package/dist/api/telemetry.d.ts +4 -0
- package/dist/api/telemetry.d.ts.map +1 -0
- package/dist/api/telemetry.js +24 -0
- package/dist/api/telemetry.js.map +1 -0
- package/dist/api/users.d.ts +4 -0
- package/dist/api/users.d.ts.map +1 -0
- package/dist/api/users.js +173 -0
- package/dist/api/users.js.map +1 -0
- package/dist/archive-unpacker.d.ts +24 -0
- package/dist/archive-unpacker.d.ts.map +1 -0
- package/dist/archive-unpacker.js +239 -0
- package/dist/archive-unpacker.js.map +1 -0
- package/dist/artifact-analyzer.d.ts +59 -0
- package/dist/artifact-analyzer.d.ts.map +1 -0
- package/dist/artifact-analyzer.js +334 -0
- package/dist/artifact-analyzer.js.map +1 -0
- package/dist/auth/idp/index.d.ts +9 -0
- package/dist/auth/idp/index.d.ts.map +1 -0
- package/dist/auth/idp/index.js +5 -0
- package/dist/auth/idp/index.js.map +1 -0
- package/dist/auth/idp/ldap.d.ts +56 -0
- package/dist/auth/idp/ldap.d.ts.map +1 -0
- package/dist/auth/idp/ldap.js +276 -0
- package/dist/auth/idp/ldap.js.map +1 -0
- package/dist/auth/idp/oidc.d.ts +27 -0
- package/dist/auth/idp/oidc.d.ts.map +1 -0
- package/dist/auth/idp/oidc.js +97 -0
- package/dist/auth/idp/oidc.js.map +1 -0
- package/dist/auth/idp/role-mapping.d.ts +9 -0
- package/dist/auth/idp/role-mapping.d.ts.map +1 -0
- package/dist/auth/idp/role-mapping.js +16 -0
- package/dist/auth/idp/role-mapping.js.map +1 -0
- package/dist/auth/idp/saml.d.ts +40 -0
- package/dist/auth/idp/saml.d.ts.map +1 -0
- package/dist/auth/idp/saml.js +117 -0
- package/dist/auth/idp/saml.js.map +1 -0
- package/dist/auth/idp/types.d.ts +23 -0
- package/dist/auth/idp/types.d.ts.map +1 -0
- package/dist/auth/idp/types.js +2 -0
- package/dist/auth/idp/types.js.map +1 -0
- package/dist/fleet/fleet-executor.d.ts +35 -0
- package/dist/fleet/fleet-executor.d.ts.map +1 -0
- package/dist/fleet/fleet-executor.js +228 -0
- package/dist/fleet/fleet-executor.js.map +1 -0
- package/dist/fleet/fleet-store.d.ts +13 -0
- package/dist/fleet/fleet-store.d.ts.map +1 -0
- package/dist/fleet/fleet-store.js +13 -0
- package/dist/fleet/fleet-store.js.map +1 -0
- package/dist/fleet/index.d.ts +5 -0
- package/dist/fleet/index.d.ts.map +1 -0
- package/dist/fleet/index.js +4 -0
- package/dist/fleet/index.js.map +1 -0
- package/dist/fleet/representative-selector.d.ts +15 -0
- package/dist/fleet/representative-selector.d.ts.map +1 -0
- package/dist/fleet/representative-selector.js +71 -0
- package/dist/fleet/representative-selector.js.map +1 -0
- package/dist/graph/graph-executor.d.ts +36 -0
- package/dist/graph/graph-executor.d.ts.map +1 -0
- package/dist/graph/graph-executor.js +348 -0
- package/dist/graph/graph-executor.js.map +1 -0
- package/dist/graph/graph-inference.d.ts +22 -0
- package/dist/graph/graph-inference.d.ts.map +1 -0
- package/dist/graph/graph-inference.js +149 -0
- package/dist/graph/graph-inference.js.map +1 -0
- package/dist/graph/graph-store.d.ts +12 -0
- package/dist/graph/graph-store.d.ts.map +1 -0
- package/dist/graph/graph-store.js +61 -0
- package/dist/graph/graph-store.js.map +1 -0
- package/dist/graph/index.d.ts +5 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +4 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +837 -0
- package/dist/index.js.map +1 -0
- package/dist/intake/index.d.ts +6 -0
- package/dist/intake/index.d.ts.map +1 -0
- package/dist/intake/index.js +5 -0
- package/dist/intake/index.js.map +1 -0
- package/dist/intake/intake-processor.d.ts +17 -0
- package/dist/intake/intake-processor.d.ts.map +1 -0
- package/dist/intake/intake-processor.js +99 -0
- package/dist/intake/intake-processor.js.map +1 -0
- package/dist/intake/intake-store.d.ts +7 -0
- package/dist/intake/intake-store.d.ts.map +1 -0
- package/dist/intake/intake-store.js +7 -0
- package/dist/intake/intake-store.js.map +1 -0
- package/dist/intake/registry-poller.d.ts +41 -0
- package/dist/intake/registry-poller.d.ts.map +1 -0
- package/dist/intake/registry-poller.js +202 -0
- package/dist/intake/registry-poller.js.map +1 -0
- package/dist/intake/webhook-handlers.d.ts +37 -0
- package/dist/intake/webhook-handlers.d.ts.map +1 -0
- package/dist/intake/webhook-handlers.js +268 -0
- package/dist/intake/webhook-handlers.js.map +1 -0
- package/dist/logger.d.ts +5 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +15 -0
- package/dist/logger.js.map +1 -0
- package/dist/mcp/resources.d.ts +9 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +72 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/server.d.ts +15 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +20 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +9 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +88 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/middleware/auth.d.ts +29 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +76 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/permissions.d.ts +13 -0
- package/dist/middleware/permissions.d.ts.map +1 -0
- package/dist/middleware/permissions.js +32 -0
- package/dist/middleware/permissions.js.map +1 -0
- package/dist/pattern-store.d.ts +104 -0
- package/dist/pattern-store.d.ts.map +1 -0
- package/dist/pattern-store.js +299 -0
- package/dist/pattern-store.js.map +1 -0
- package/package.json +54 -0
- package/src/agent/debrief-retention.ts +44 -0
- package/src/agent/envoy-client.ts +474 -0
- package/src/agent/envoy-registry.ts +384 -0
- package/src/agent/health-checker.ts +70 -0
- package/src/agent/mcp-client-manager.ts +131 -0
- package/src/agent/stale-deployment-detector.ts +79 -0
- package/src/agent/step-runner.ts +124 -0
- package/src/agent/synth-agent.ts +1567 -0
- package/src/api/agent.ts +1075 -0
- package/src/api/api-keys.ts +129 -0
- package/src/api/artifacts.ts +194 -0
- package/src/api/auth.ts +320 -0
- package/src/api/deployments.ts +1347 -0
- package/src/api/environments.ts +97 -0
- package/src/api/envoy-reports.ts +159 -0
- package/src/api/envoys.ts +237 -0
- package/src/api/fleet.ts +510 -0
- package/src/api/graph.ts +516 -0
- package/src/api/health.ts +311 -0
- package/src/api/idp-schemas.ts +19 -0
- package/src/api/idp.ts +735 -0
- package/src/api/intake.ts +537 -0
- package/src/api/partitions.ts +147 -0
- package/src/api/progress-event-store.ts +153 -0
- package/src/api/schemas.ts +376 -0
- package/src/api/security-boundaries.ts +54 -0
- package/src/api/settings.ts +118 -0
- package/src/api/system.ts +704 -0
- package/src/api/telemetry.ts +32 -0
- package/src/api/users.ts +210 -0
- package/src/archive-unpacker.ts +271 -0
- package/src/artifact-analyzer.ts +438 -0
- package/src/auth/idp/index.ts +8 -0
- package/src/auth/idp/ldap.ts +340 -0
- package/src/auth/idp/oidc.ts +117 -0
- package/src/auth/idp/role-mapping.ts +22 -0
- package/src/auth/idp/saml.ts +148 -0
- package/src/auth/idp/types.ts +22 -0
- package/src/fleet/fleet-executor.ts +309 -0
- package/src/fleet/fleet-store.ts +13 -0
- package/src/fleet/index.ts +4 -0
- package/src/fleet/representative-selector.ts +83 -0
- package/src/graph/graph-executor.ts +446 -0
- package/src/graph/graph-inference.ts +184 -0
- package/src/graph/graph-store.ts +75 -0
- package/src/graph/index.ts +4 -0
- package/src/index.ts +916 -0
- package/src/intake/index.ts +5 -0
- package/src/intake/intake-processor.ts +111 -0
- package/src/intake/intake-store.ts +7 -0
- package/src/intake/registry-poller.ts +230 -0
- package/src/intake/webhook-handlers.ts +328 -0
- package/src/logger.ts +19 -0
- package/src/mcp/resources.ts +98 -0
- package/src/mcp/server.ts +34 -0
- package/src/mcp/tools.ts +117 -0
- package/src/middleware/auth.ts +103 -0
- package/src/middleware/permissions.ts +35 -0
- package/src/pattern-store.ts +409 -0
- package/tests/agent-mode.test.ts +536 -0
- package/tests/api-handlers.test.ts +1245 -0
- package/tests/archive-unpacker.test.ts +179 -0
- package/tests/artifact-analyzer.test.ts +240 -0
- package/tests/auth-middleware.test.ts +189 -0
- package/tests/decision-diary.test.ts +957 -0
- package/tests/diary-reader.test.ts +782 -0
- package/tests/envoy-client.test.ts +342 -0
- package/tests/envoy-reports.test.ts +156 -0
- package/tests/mcp-tools.test.ts +213 -0
- package/tests/orchestration.test.ts +536 -0
- package/tests/partition-deletion.test.ts +143 -0
- package/tests/partition-isolation.test.ts +830 -0
- package/tests/pattern-store.test.ts +371 -0
- package/tests/rbac-enforcement.test.ts +409 -0
- package/tests/ssrf-validation.test.ts +56 -0
- package/tests/stale-deployment.test.ts +85 -0
- package/tests/step-runner.test.ts +308 -0
- package/tests/ui-journey.test.ts +330 -0
- package/tsconfig.json +11 -0
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import AdmZip from "adm-zip";
|
|
3
|
+
import {
|
|
4
|
+
unpackArchive,
|
|
5
|
+
archiveFormat,
|
|
6
|
+
formatExtractedFiles,
|
|
7
|
+
} from "../src/archive-unpacker.js";
|
|
8
|
+
import type { ArtifactInput } from "../src/artifact-analyzer.js";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Helpers
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
function makeZipBuffer(entries: Record<string, string>): Buffer {
|
|
15
|
+
const zip = new AdmZip();
|
|
16
|
+
for (const [path, content] of Object.entries(entries)) {
|
|
17
|
+
zip.addFile(path, Buffer.from(content, "utf-8"));
|
|
18
|
+
}
|
|
19
|
+
return zip.toBuffer();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function makeArtifact(name: string): ArtifactInput {
|
|
23
|
+
return { name, source: "test" };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// archiveFormat
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
describe("archiveFormat", () => {
|
|
31
|
+
it("maps zip to zip", () => {
|
|
32
|
+
expect(archiveFormat("zip", "bundle.zip")).toBe("zip");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("maps nupkg to zip", () => {
|
|
36
|
+
expect(archiveFormat("nupkg", "MyService.1.0.0.nupkg")).toBe("zip");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("maps java-archive to zip", () => {
|
|
40
|
+
expect(archiveFormat("java-archive", "app.jar")).toBe("zip");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("maps python-package to zip", () => {
|
|
44
|
+
expect(archiveFormat("python-package", "app-1.0.0-py3-none-any.whl")).toBe("zip");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("maps .tar.gz tarball to tar-gz", () => {
|
|
48
|
+
expect(archiveFormat("tarball", "release.tar.gz")).toBe("tar-gz");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("maps .tgz tarball to tar-gz", () => {
|
|
52
|
+
expect(archiveFormat("tarball", "release.tgz")).toBe("tar-gz");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("maps bare .tar to tar", () => {
|
|
56
|
+
expect(archiveFormat("tarball", "image.tar")).toBe("tar");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("returns null for non-archive types", () => {
|
|
60
|
+
expect(archiveFormat("dockerfile", "Dockerfile")).toBeNull();
|
|
61
|
+
expect(archiveFormat("node-package", "package.json")).toBeNull();
|
|
62
|
+
expect(archiveFormat("unknown", "mystery.dat")).toBeNull();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// ZIP unpacking
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
describe("unpackArchive — zip", () => {
|
|
71
|
+
it("extracts markdown and yaml files", async () => {
|
|
72
|
+
const buf = makeZipBuffer({
|
|
73
|
+
"README.md": "# My Service\nDeploys to Kubernetes.",
|
|
74
|
+
"helm/values.yaml": "replicaCount: 3\nimage: myapp:latest",
|
|
75
|
+
"app.bin": "\x00\x01\x02\x03binary data",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const result = await unpackArchive(buf, "zip");
|
|
79
|
+
const paths = result.files.map((f) => f.path);
|
|
80
|
+
|
|
81
|
+
expect(paths).toContain("README.md");
|
|
82
|
+
expect(paths).toContain("helm/values.yaml");
|
|
83
|
+
expect(paths).not.toContain("app.bin");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("extracts Dockerfile and shell scripts", async () => {
|
|
87
|
+
const buf = makeZipBuffer({
|
|
88
|
+
"Dockerfile": "FROM node:20-alpine\nEXPOSE 3000",
|
|
89
|
+
"deploy.sh": "#!/bin/bash\ndocker build .",
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const result = await unpackArchive(buf, "zip");
|
|
93
|
+
const paths = result.files.map((f) => f.path);
|
|
94
|
+
|
|
95
|
+
expect(paths).toContain("Dockerfile");
|
|
96
|
+
expect(paths).toContain("deploy.sh");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("skips node_modules and .git paths", async () => {
|
|
100
|
+
const buf = makeZipBuffer({
|
|
101
|
+
"node_modules/express/README.md": "Express docs",
|
|
102
|
+
".git/config": "git config",
|
|
103
|
+
"SYNTH.md": "deployment context",
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const result = await unpackArchive(buf, "zip");
|
|
107
|
+
const paths = result.files.map((f) => f.path);
|
|
108
|
+
|
|
109
|
+
expect(paths).not.toContain("node_modules/express/README.md");
|
|
110
|
+
expect(paths).not.toContain(".git/config");
|
|
111
|
+
expect(paths).toContain("SYNTH.md");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("counts skipped binary and filtered files", async () => {
|
|
115
|
+
const buf = makeZipBuffer({
|
|
116
|
+
"app.exe": "\x00\x00\x4D\x5A binary",
|
|
117
|
+
"logo.png": "\x89PNG binary",
|
|
118
|
+
"config.yaml": "key: value",
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const result = await unpackArchive(buf, "zip");
|
|
122
|
+
expect(result.files.length).toBe(1);
|
|
123
|
+
expect(result.skipped).toBeGreaterThan(0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("returns empty result for corrupt buffer", async () => {
|
|
127
|
+
const result = await unpackArchive(Buffer.from("not a zip"), "zip");
|
|
128
|
+
expect(result.files).toHaveLength(0);
|
|
129
|
+
expect(result.skipped).toBe(0);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("preserves file content accurately", async () => {
|
|
133
|
+
const content = "# Deploy Notes\n\nRun after database migration.\n";
|
|
134
|
+
const buf = makeZipBuffer({ "NOTES.md": content });
|
|
135
|
+
|
|
136
|
+
const result = await unpackArchive(buf, "zip");
|
|
137
|
+
expect(result.files[0].content).toBe(content);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// formatExtractedFiles
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
describe("formatExtractedFiles", () => {
|
|
146
|
+
it("formats multiple files with separators", () => {
|
|
147
|
+
const result = formatExtractedFiles({
|
|
148
|
+
files: [
|
|
149
|
+
{ path: "README.md", content: "# Hello" },
|
|
150
|
+
{ path: "values.yaml", content: "replicas: 2" },
|
|
151
|
+
],
|
|
152
|
+
skipped: 0,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(result).toContain("=== README.md ===");
|
|
156
|
+
expect(result).toContain("# Hello");
|
|
157
|
+
expect(result).toContain("=== values.yaml ===");
|
|
158
|
+
expect(result).toContain("replicas: 2");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("includes skipped file count when non-zero", () => {
|
|
162
|
+
const result = formatExtractedFiles({ files: [], skipped: 3 });
|
|
163
|
+
expect(result).toContain("(no readable text files found in archive)");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("appends skip count when files also present", () => {
|
|
167
|
+
const result = formatExtractedFiles({
|
|
168
|
+
files: [{ path: "README.md", content: "hi" }],
|
|
169
|
+
skipped: 5,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
expect(result).toContain("5 binary or oversized files skipped");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("returns no-files message when empty", () => {
|
|
176
|
+
const result = formatExtractedFiles({ files: [], skipped: 0 });
|
|
177
|
+
expect(result).toContain("no readable text files found");
|
|
178
|
+
});
|
|
179
|
+
});
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { DecisionDebrief } from "@synth-deploy/core";
|
|
3
|
+
import { ArtifactAnalyzer, createArtifactAnalyzer, detectArtifactType } from "../src/artifact-analyzer.js";
|
|
4
|
+
import type { ArtifactInput } from "../src/artifact-analyzer.js";
|
|
5
|
+
import type { LlmClient } from "@synth-deploy/core";
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Mock LLM client
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
function createMockLlm(opts: { available?: boolean; response?: string } = {}): LlmClient {
|
|
12
|
+
const available = opts.available ?? true;
|
|
13
|
+
const response = opts.response ?? "{}";
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
isAvailable: () => available,
|
|
17
|
+
reason: vi.fn().mockResolvedValue(
|
|
18
|
+
available
|
|
19
|
+
? { ok: true, text: response, model: "test-model", responseTimeMs: 100 }
|
|
20
|
+
: { ok: false, fallback: true, reason: "LLM not configured" },
|
|
21
|
+
),
|
|
22
|
+
classify: vi.fn().mockResolvedValue({ ok: false, fallback: true, reason: "not used" }),
|
|
23
|
+
healthCheck: vi.fn().mockResolvedValue({ configured: false, healthy: false }),
|
|
24
|
+
getLastHealthStatus: vi.fn().mockReturnValue(null),
|
|
25
|
+
} as unknown as LlmClient;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function makeArtifact(overrides: Partial<ArtifactInput> = {}): ArtifactInput {
|
|
29
|
+
return {
|
|
30
|
+
name: "test-artifact",
|
|
31
|
+
source: "test-registry",
|
|
32
|
+
...overrides,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function bufferFrom(text: string): Buffer {
|
|
37
|
+
return Buffer.from(text, "utf-8");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Type detection
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
describe("detectArtifactType", () => {
|
|
45
|
+
it("detects Dockerfile by name", () => {
|
|
46
|
+
expect(detectArtifactType(makeArtifact({ name: "Dockerfile" }))).toBe("dockerfile");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("detects Chart.yaml", () => {
|
|
50
|
+
expect(detectArtifactType(makeArtifact({ name: "Chart.yaml" }))).toBe("helm-chart");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("detects values.yaml", () => {
|
|
54
|
+
expect(detectArtifactType(makeArtifact({ name: "values.yaml" }))).toBe("helm-values");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("detects package.json", () => {
|
|
58
|
+
expect(detectArtifactType(makeArtifact({ name: "package.json" }))).toBe("node-package");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("detects Makefile", () => {
|
|
62
|
+
expect(detectArtifactType(makeArtifact({ name: "Makefile" }))).toBe("makefile");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("detects .tgz", () => {
|
|
66
|
+
expect(detectArtifactType(makeArtifact({ name: "app-1.0.0.tgz" }))).toBe("tarball");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("detects .tar", () => {
|
|
70
|
+
expect(detectArtifactType(makeArtifact({ name: "image.tar" }))).toBe("tarball");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("detects .zip", () => {
|
|
74
|
+
expect(detectArtifactType(makeArtifact({ name: "deploy.zip" }))).toBe("zip");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("detects .nupkg", () => {
|
|
78
|
+
expect(detectArtifactType(makeArtifact({ name: "MyService.1.0.0.nupkg" }))).toBe("nupkg");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("detects .jar", () => {
|
|
82
|
+
expect(detectArtifactType(makeArtifact({ name: "app.jar" }))).toBe("java-archive");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("respects explicit type override", () => {
|
|
86
|
+
expect(detectArtifactType(makeArtifact({ name: "build-config", type: "dockerfile" }))).toBe("dockerfile");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("returns unknown for unrecognized files", () => {
|
|
90
|
+
expect(detectArtifactType(makeArtifact({ name: "mystery.dat" }))).toBe("unknown");
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// LLM analysis
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
describe("ArtifactAnalyzer — LLM analysis", () => {
|
|
99
|
+
it("returns llm method when LLM produces valid analysis", async () => {
|
|
100
|
+
const llmResponse = JSON.stringify({
|
|
101
|
+
summary: "A Node.js API server with PostgreSQL dependency",
|
|
102
|
+
dependencies: ["npm:express", "system:postgresql-15"],
|
|
103
|
+
configurationExpectations: { DATABASE_URL: "PostgreSQL connection string", PORT: "HTTP port" },
|
|
104
|
+
deploymentIntent: "Containerized Node.js API deployment",
|
|
105
|
+
confidence: 0.92,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const analyzer = createArtifactAnalyzer({
|
|
109
|
+
llm: createMockLlm({ available: true, response: llmResponse }),
|
|
110
|
+
debrief: new DecisionDebrief(),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const result = await analyzer.analyze(
|
|
114
|
+
makeArtifact({ name: "package.json", content: bufferFrom('{"name":"api","version":"1.0.0"}') }),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
expect(result.method).toBe("llm");
|
|
118
|
+
expect(result.analysis.summary).toContain("Node.js API server");
|
|
119
|
+
expect(result.analysis.dependencies).toContain("npm:express");
|
|
120
|
+
expect(result.analysis.dependencies).toContain("system:postgresql-15");
|
|
121
|
+
expect(result.analysis.configurationExpectations["DATABASE_URL"]).toBeDefined();
|
|
122
|
+
expect(result.analysis.confidence).toBe(0.92);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("handles LLM response wrapped in markdown code blocks", async () => {
|
|
126
|
+
const wrappedResponse = `Here is the analysis:\n\n\`\`\`json\n{\n "summary": "A Python web service",\n "dependencies": ["pip:flask"],\n "configurationExpectations": {},\n "deploymentIntent": "Python WSGI deployment",\n "confidence": 0.8\n}\n\`\`\``;
|
|
127
|
+
|
|
128
|
+
const analyzer = createArtifactAnalyzer({
|
|
129
|
+
llm: createMockLlm({ available: true, response: wrappedResponse }),
|
|
130
|
+
debrief: new DecisionDebrief(),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const result = await analyzer.analyze(makeArtifact({ name: "app.tar.gz" }));
|
|
134
|
+
|
|
135
|
+
expect(result.method).toBe("llm");
|
|
136
|
+
expect(result.analysis.summary).toContain("Python web service");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("passes artifact content and type to the LLM", async () => {
|
|
140
|
+
const llmResponse = JSON.stringify({
|
|
141
|
+
summary: "Container image",
|
|
142
|
+
dependencies: [],
|
|
143
|
+
configurationExpectations: {},
|
|
144
|
+
deploymentIntent: "Container deployment",
|
|
145
|
+
confidence: 0.9,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const mockLlm = createMockLlm({ available: true, response: llmResponse });
|
|
149
|
+
const analyzer = createArtifactAnalyzer({ llm: mockLlm, debrief: new DecisionDebrief() });
|
|
150
|
+
|
|
151
|
+
const content = "FROM node:20-alpine\nEXPOSE 3000\n";
|
|
152
|
+
await analyzer.analyze(makeArtifact({ name: "Dockerfile", content: bufferFrom(content) }));
|
|
153
|
+
|
|
154
|
+
const reasonCall = (mockLlm.reason as ReturnType<typeof vi.fn>).mock.calls[0][0];
|
|
155
|
+
expect(reasonCall.prompt).toContain("Dockerfile");
|
|
156
|
+
expect(reasonCall.prompt).toContain("dockerfile");
|
|
157
|
+
expect(reasonCall.prompt).toContain("FROM node:20-alpine");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("records a debrief entry for every analysis", async () => {
|
|
161
|
+
const debrief = new DecisionDebrief();
|
|
162
|
+
const llmResponse = JSON.stringify({
|
|
163
|
+
summary: "A service",
|
|
164
|
+
dependencies: [],
|
|
165
|
+
configurationExpectations: {},
|
|
166
|
+
confidence: 0.7,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const analyzer = createArtifactAnalyzer({
|
|
170
|
+
llm: createMockLlm({ available: true, response: llmResponse }),
|
|
171
|
+
debrief,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
await analyzer.analyze(makeArtifact({ name: "Dockerfile", content: bufferFrom("FROM alpine") }));
|
|
175
|
+
|
|
176
|
+
const entries = debrief.getByType("artifact-analysis");
|
|
177
|
+
expect(entries.length).toBe(1);
|
|
178
|
+
expect(entries[0].decision).toContain("Dockerfile");
|
|
179
|
+
expect(entries[0].context).toHaveProperty("method", "llm");
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// LLM unavailable
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
describe("ArtifactAnalyzer — LLM unavailable", () => {
|
|
188
|
+
it("returns unavailable method when LLM is not configured", async () => {
|
|
189
|
+
const analyzer = createArtifactAnalyzer({
|
|
190
|
+
llm: createMockLlm({ available: false }),
|
|
191
|
+
debrief: new DecisionDebrief(),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const result = await analyzer.analyze(makeArtifact({ name: "Dockerfile" }));
|
|
195
|
+
|
|
196
|
+
expect(result.method).toBe("unavailable");
|
|
197
|
+
expect(result.analysis.confidence).toBe(0);
|
|
198
|
+
expect(result.analysis.summary).toContain("LLM is required");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("returns unavailable when LLM returns invalid JSON", async () => {
|
|
202
|
+
const analyzer = createArtifactAnalyzer({
|
|
203
|
+
llm: createMockLlm({ available: true, response: "I cannot produce valid JSON right now." }),
|
|
204
|
+
debrief: new DecisionDebrief(),
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const result = await analyzer.analyze(makeArtifact({ name: "Dockerfile" }));
|
|
208
|
+
|
|
209
|
+
expect(result.method).toBe("unavailable");
|
|
210
|
+
expect(result.analysis.confidence).toBe(0);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("records a debrief entry even when unavailable", async () => {
|
|
214
|
+
const debrief = new DecisionDebrief();
|
|
215
|
+
const analyzer = createArtifactAnalyzer({
|
|
216
|
+
llm: createMockLlm({ available: false }),
|
|
217
|
+
debrief,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
await analyzer.analyze(makeArtifact({ name: "app.zip" }));
|
|
221
|
+
|
|
222
|
+
const entries = debrief.getByType("artifact-analysis");
|
|
223
|
+
expect(entries.length).toBe(1);
|
|
224
|
+
expect(entries[0].context).toHaveProperty("method", "unavailable");
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
// Factory
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
describe("createArtifactAnalyzer", () => {
|
|
233
|
+
it("returns an ArtifactAnalyzer instance", () => {
|
|
234
|
+
const analyzer = createArtifactAnalyzer({
|
|
235
|
+
llm: createMockLlm(),
|
|
236
|
+
debrief: new DecisionDebrief(),
|
|
237
|
+
});
|
|
238
|
+
expect(analyzer).toBeInstanceOf(ArtifactAnalyzer);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import Fastify from "fastify";
|
|
3
|
+
import type { FastifyInstance } from "fastify";
|
|
4
|
+
import { SignJWT } from "jose";
|
|
5
|
+
import {
|
|
6
|
+
UserStore,
|
|
7
|
+
RoleStore,
|
|
8
|
+
UserRoleStore,
|
|
9
|
+
SessionStore,
|
|
10
|
+
} from "@synth-deploy/core";
|
|
11
|
+
import type { UserId, RoleId } from "@synth-deploy/core";
|
|
12
|
+
import { registerAuthMiddleware, generateTokens } from "../src/middleware/auth.js";
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Constants
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
const JWT_SECRET = new TextEncoder().encode("test-secret");
|
|
19
|
+
const TEST_USER_ID = "user-1" as UserId;
|
|
20
|
+
const TEST_ROLE_ID = "role-admin" as RoleId;
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Helpers
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
function createStores() {
|
|
27
|
+
const userStore = new UserStore();
|
|
28
|
+
const roleStore = new RoleStore();
|
|
29
|
+
const userRoleStore = new UserRoleStore(roleStore);
|
|
30
|
+
const sessionStore = new SessionStore();
|
|
31
|
+
return { userStore, roleStore, userRoleStore, sessionStore };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function seedTestUser(stores: ReturnType<typeof createStores>) {
|
|
35
|
+
stores.userStore.create({
|
|
36
|
+
id: TEST_USER_ID,
|
|
37
|
+
email: "test@example.com",
|
|
38
|
+
name: "Test User",
|
|
39
|
+
passwordHash: "hashed",
|
|
40
|
+
createdAt: new Date(),
|
|
41
|
+
updatedAt: new Date(),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
stores.roleStore.create({
|
|
45
|
+
id: TEST_ROLE_ID,
|
|
46
|
+
name: "admin",
|
|
47
|
+
permissions: ["deployment.view"],
|
|
48
|
+
isBuiltIn: true,
|
|
49
|
+
createdAt: new Date(),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
stores.userRoleStore.assign(TEST_USER_ID, TEST_ROLE_ID, TEST_USER_ID);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function buildApp(): Promise<{
|
|
56
|
+
app: FastifyInstance;
|
|
57
|
+
stores: ReturnType<typeof createStores>;
|
|
58
|
+
token: string;
|
|
59
|
+
}> {
|
|
60
|
+
const stores = createStores();
|
|
61
|
+
seedTestUser(stores);
|
|
62
|
+
|
|
63
|
+
const app = Fastify({ logger: false });
|
|
64
|
+
registerAuthMiddleware(app, stores.userStore, stores.userRoleStore, stores.sessionStore, JWT_SECRET);
|
|
65
|
+
|
|
66
|
+
// Health endpoint — exempt from auth
|
|
67
|
+
app.get("/health", async () => ({ status: "ok" }));
|
|
68
|
+
|
|
69
|
+
// Protected endpoint
|
|
70
|
+
app.get("/api/partitions", async () => ({ partitions: [] }));
|
|
71
|
+
|
|
72
|
+
await app.ready();
|
|
73
|
+
|
|
74
|
+
// Generate a valid token and persist the session
|
|
75
|
+
const { token, refreshToken, expiresAt } = await generateTokens(TEST_USER_ID, JWT_SECRET);
|
|
76
|
+
stores.sessionStore.create({
|
|
77
|
+
id: "session-1",
|
|
78
|
+
userId: TEST_USER_ID,
|
|
79
|
+
token,
|
|
80
|
+
refreshToken,
|
|
81
|
+
expiresAt,
|
|
82
|
+
createdAt: new Date(),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return { app, stores, token };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Tests — auth middleware (JWT-based, always enabled)
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
describe("auth middleware — enabled", () => {
|
|
93
|
+
let app: FastifyInstance;
|
|
94
|
+
let token: string;
|
|
95
|
+
|
|
96
|
+
beforeEach(async () => {
|
|
97
|
+
const built = await buildApp();
|
|
98
|
+
app = built.app;
|
|
99
|
+
token = built.token;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
afterEach(async () => {
|
|
103
|
+
await app.close();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("returns 401 when Authorization header is missing", async () => {
|
|
107
|
+
const res = await app.inject({
|
|
108
|
+
method: "GET",
|
|
109
|
+
url: "/api/partitions",
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(res.statusCode).toBe(401);
|
|
113
|
+
expect(JSON.parse(res.payload)).toEqual({ error: "Authentication required" });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("returns 401 when Authorization header is malformed (no Bearer prefix)", async () => {
|
|
117
|
+
const res = await app.inject({
|
|
118
|
+
method: "GET",
|
|
119
|
+
url: "/api/partitions",
|
|
120
|
+
headers: { authorization: "Basic some-credentials" },
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(res.statusCode).toBe(401);
|
|
124
|
+
expect(JSON.parse(res.payload)).toEqual({ error: "Authentication required" });
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("returns 401 when Bearer token is invalid", async () => {
|
|
128
|
+
const res = await app.inject({
|
|
129
|
+
method: "GET",
|
|
130
|
+
url: "/api/partitions",
|
|
131
|
+
headers: { authorization: "Bearer not-a-valid-jwt" },
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(res.statusCode).toBe(401);
|
|
135
|
+
expect(JSON.parse(res.payload)).toEqual({ error: "Invalid token" });
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("passes request through with a valid JWT token", async () => {
|
|
139
|
+
const res = await app.inject({
|
|
140
|
+
method: "GET",
|
|
141
|
+
url: "/api/partitions",
|
|
142
|
+
headers: { authorization: `Bearer ${token}` },
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(res.statusCode).toBe(200);
|
|
146
|
+
expect(JSON.parse(res.payload)).toEqual({ partitions: [] });
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("allows /health without any authorization", async () => {
|
|
150
|
+
const res = await app.inject({
|
|
151
|
+
method: "GET",
|
|
152
|
+
url: "/health",
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(res.statusCode).toBe(200);
|
|
156
|
+
expect(JSON.parse(res.payload)).toEqual({ status: "ok" });
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("allows /health even with an invalid token", async () => {
|
|
160
|
+
const res = await app.inject({
|
|
161
|
+
method: "GET",
|
|
162
|
+
url: "/health",
|
|
163
|
+
headers: { authorization: "Bearer totally-wrong" },
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// /health bypasses auth entirely — should still succeed
|
|
167
|
+
expect(res.statusCode).toBe(200);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// Tests — registerAuthMiddleware return value
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
describe("registerAuthMiddleware return value", () => {
|
|
176
|
+
it("always returns { enabled: true }", () => {
|
|
177
|
+
const stores = createStores();
|
|
178
|
+
const app = Fastify({ logger: false });
|
|
179
|
+
const result = registerAuthMiddleware(
|
|
180
|
+
app,
|
|
181
|
+
stores.userStore,
|
|
182
|
+
stores.userRoleStore,
|
|
183
|
+
stores.sessionStore,
|
|
184
|
+
JWT_SECRET,
|
|
185
|
+
);
|
|
186
|
+
expect(result.enabled).toBe(true);
|
|
187
|
+
app.close();
|
|
188
|
+
});
|
|
189
|
+
});
|