test(FN-4945): complete Step 3 — add secrets route regression tests
Fusion-Task-Id: FN-4945 Fusion-Task-Lineage: f3049b2a-26de-4d48-be99-eb90e4780a9e
This commit is contained in:
committed by
gsxdsm
parent
bce62834f1
commit
235754a766
@@ -0,0 +1,147 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import express from "express";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createApiRoutes } from "../../routes.js";
|
||||
import { request } from "../../test-request.js";
|
||||
|
||||
const secretsStore = {
|
||||
listSecrets: vi.fn(),
|
||||
createSecret: vi.fn(),
|
||||
updateSecret: vi.fn(),
|
||||
deleteSecret: vi.fn(),
|
||||
revealSecret: vi.fn(),
|
||||
};
|
||||
|
||||
vi.mock("@fusion/core", async () => {
|
||||
const actual = await vi.importActual<typeof import("@fusion/core")>("@fusion/core");
|
||||
return {
|
||||
...actual,
|
||||
isSecretScope: actual.isSecretScope,
|
||||
isSecretAccessPolicy: actual.isSecretAccessPolicy,
|
||||
};
|
||||
});
|
||||
|
||||
function createStore() {
|
||||
return {
|
||||
getTask: vi.fn(),
|
||||
listTasks: vi.fn().mockResolvedValue([]),
|
||||
getSettings: vi.fn().mockResolvedValue({}),
|
||||
getSettingsFast: vi.fn().mockResolvedValue({}),
|
||||
getSettingsByScope: vi.fn().mockResolvedValue({ global: {}, project: {} }),
|
||||
getSettingsByScopeFast: vi.fn().mockResolvedValue({ global: {}, project: {} }),
|
||||
getGlobalSettingsStore: vi.fn(() => ({ getSettings: vi.fn().mockResolvedValue({}) })),
|
||||
getRootDir: vi.fn().mockReturnValue("/tmp"),
|
||||
getFusionDir: vi.fn().mockReturnValue("/tmp/.fusion"),
|
||||
listWorkflowSteps: vi.fn().mockResolvedValue([]),
|
||||
getMissionStore: vi.fn(),
|
||||
on: vi.fn(),
|
||||
off: vi.fn(),
|
||||
getSecretsStore: vi.fn().mockResolvedValue(secretsStore),
|
||||
} as any;
|
||||
}
|
||||
|
||||
function app() {
|
||||
const server = express();
|
||||
server.use(express.json());
|
||||
server.use("/api", createApiRoutes(createStore()));
|
||||
return server;
|
||||
}
|
||||
|
||||
describe("register-secrets-routes", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
secretsStore.createSecret.mockResolvedValue({ id: "sec_1" });
|
||||
secretsStore.updateSecret.mockResolvedValue({ id: "sec_1" });
|
||||
});
|
||||
|
||||
it("POST /api/secrets rejects non-string key", async () => {
|
||||
const res = await request(app(), "POST", "/api/secrets", JSON.stringify({ scope: "project", key: 123, value: "v" }), {
|
||||
"Content-Type": "application/json",
|
||||
});
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(String((res.body as any).error)).toContain("key must be a non-empty string");
|
||||
});
|
||||
|
||||
it("POST /api/secrets rejects non-string value", async () => {
|
||||
const res = await request(app(), "POST", "/api/secrets", JSON.stringify({ scope: "project", key: "K", value: 123 }), {
|
||||
"Content-Type": "application/json",
|
||||
});
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(String((res.body as any).error)).toContain("value must be a string");
|
||||
});
|
||||
|
||||
it("POST /api/secrets rejects invalid scope", async () => {
|
||||
const res = await request(app(), "POST", "/api/secrets", JSON.stringify({ scope: "local", key: "K", value: "v" }), {
|
||||
"Content-Type": "application/json",
|
||||
});
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(String((res.body as any).error)).toContain("scope must be 'project' or 'global'");
|
||||
});
|
||||
|
||||
it("POST /api/secrets rejects invalid accessPolicy", async () => {
|
||||
const res = await request(
|
||||
app(),
|
||||
"POST",
|
||||
"/api/secrets",
|
||||
JSON.stringify({ scope: "project", key: "K", value: "v", accessPolicy: "banana" }),
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
);
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(String((res.body as any).error)).toContain("accessPolicy must be one of: auto, prompt, deny");
|
||||
});
|
||||
|
||||
it("POST /api/secrets accepts valid payload and passes narrowed values", async () => {
|
||||
const res = await request(
|
||||
app(),
|
||||
"POST",
|
||||
"/api/secrets",
|
||||
JSON.stringify({ scope: "global", key: "MY_KEY", value: "secret", accessPolicy: "deny" }),
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
);
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
expect(secretsStore.createSecret).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
scope: "global",
|
||||
key: "MY_KEY",
|
||||
plaintextValue: "secret",
|
||||
accessPolicy: "deny",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("PATCH /api/secrets/:scope/:id rejects invalid accessPolicy", async () => {
|
||||
const res = await request(
|
||||
app(),
|
||||
"PATCH",
|
||||
"/api/secrets/project/sec_1",
|
||||
JSON.stringify({ accessPolicy: "banana" }),
|
||||
{ "Content-Type": "application/json" },
|
||||
);
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(String((res.body as any).error)).toContain("accessPolicy must be one of: auto, prompt, deny");
|
||||
});
|
||||
|
||||
it("PATCH /api/secrets/:scope/:id allows null accessPolicy passthrough", async () => {
|
||||
const res = await request(
|
||||
app(),
|
||||
"PATCH",
|
||||
"/api/secrets/project/sec_1",
|
||||
JSON.stringify({ accessPolicy: null }),
|
||||
{ "Content-Type": "application/json" },
|
||||
);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(secretsStore.updateSecret).toHaveBeenCalledWith("sec_1", "project", { accessPolicy: null });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user