Skip to content

Commit 2de2fc1

Browse files
committed
test(data-manager): cover bearer-token auth gate
Characterization tests for registerAuth (token/wrong-token/health-path bypass) and resolveAuthToken (configured/prod-throw/dev-ephemeral). The data-manager auth hook guards all data-plane mutation endpoints and had no test.
1 parent 90aabe6 commit 2de2fc1

1 file changed

Lines changed: 106 additions & 0 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import Fastify, { type FastifyInstance } from "fastify";
2+
import { afterEach, describe, expect, it, vi } from "vitest";
3+
import { registerAuth, resolveAuthToken } from "../src/auth.js";
4+
5+
const TOKEN = "test-secret-token";
6+
7+
async function buildApp(): Promise<FastifyInstance> {
8+
const app = Fastify();
9+
registerAuth(app, TOKEN);
10+
// A protected route plus the two health routes the hook must let through.
11+
app.get("/protected", async () => ({ ok: true }));
12+
app.get("/status", async () => ({ ok: true }));
13+
app.get("/internal/metrics", async () => ({ ok: true }));
14+
await app.ready();
15+
return app;
16+
}
17+
18+
afterEach(() => {
19+
vi.unstubAllEnvs();
20+
vi.restoreAllMocks();
21+
});
22+
23+
describe("registerAuth", () => {
24+
it("rejects a protected request with no token (401)", async () => {
25+
const app = await buildApp();
26+
const res = await app.inject({ method: "GET", url: "/protected" });
27+
expect(res.statusCode).toBe(401);
28+
await app.close();
29+
});
30+
31+
it("rejects a wrong token (401)", async () => {
32+
const app = await buildApp();
33+
const res = await app.inject({
34+
method: "GET",
35+
url: "/protected",
36+
headers: { authorization: "Bearer wrong" },
37+
});
38+
expect(res.statusCode).toBe(401);
39+
await app.close();
40+
});
41+
42+
it("accepts the correct Bearer token", async () => {
43+
const app = await buildApp();
44+
const res = await app.inject({
45+
method: "GET",
46+
url: "/protected",
47+
headers: { authorization: `Bearer ${TOKEN}` },
48+
});
49+
expect(res.statusCode).toBe(200);
50+
await app.close();
51+
});
52+
53+
it("accepts the correct x-data-manager-token header", async () => {
54+
const app = await buildApp();
55+
const res = await app.inject({
56+
method: "GET",
57+
url: "/protected",
58+
headers: { "x-data-manager-token": TOKEN },
59+
});
60+
expect(res.statusCode).toBe(200);
61+
await app.close();
62+
});
63+
64+
it("lets /status through without a token", async () => {
65+
const app = await buildApp();
66+
const res = await app.inject({ method: "GET", url: "/status" });
67+
expect(res.statusCode).toBe(200);
68+
await app.close();
69+
});
70+
71+
it("lets /internal/metrics through without a token", async () => {
72+
const app = await buildApp();
73+
const res = await app.inject({ method: "GET", url: "/internal/metrics" });
74+
expect(res.statusCode).toBe(200);
75+
await app.close();
76+
});
77+
78+
it("lets /status through even with a query string", async () => {
79+
const app = await buildApp();
80+
const res = await app.inject({ method: "GET", url: "/status?probe=1" });
81+
expect(res.statusCode).toBe(200);
82+
await app.close();
83+
});
84+
});
85+
86+
describe("resolveAuthToken", () => {
87+
const stubApp = () => ({ log: { error: vi.fn(), warn: vi.fn() } }) as unknown as FastifyInstance;
88+
89+
it("returns the configured token (trimmed)", () => {
90+
vi.stubEnv("DATA_MANAGER_AUTH_TOKEN", " configured-token ");
91+
expect(resolveAuthToken(stubApp())).toBe("configured-token");
92+
});
93+
94+
it("throws in production when unset", () => {
95+
vi.stubEnv("DATA_MANAGER_AUTH_TOKEN", "");
96+
vi.stubEnv("NODE_ENV", "production");
97+
expect(() => resolveAuthToken(stubApp())).toThrow(/required in production/i);
98+
});
99+
100+
it("generates a 64-hex ephemeral token in non-production when unset", () => {
101+
vi.stubEnv("DATA_MANAGER_AUTH_TOKEN", "");
102+
vi.stubEnv("NODE_ENV", "development");
103+
const token = resolveAuthToken(stubApp());
104+
expect(token).toMatch(/^[0-9a-f]{64}$/);
105+
});
106+
});

0 commit comments

Comments
 (0)