Files
Miasma/tests/providers/gcp/secrets.test.ts
T

206 lines
7.8 KiB
TypeScript

import { beforeEach, describe, expect, it, mock } from "bun:test";
// ── Mock the auth module ────────────────────────────────────────────────────
const mockResolveToken = mock();
const mockResolveAllIdentities = mock();
mock.module("../../../src/providers/gcp/auth", () => ({
resolveToken: mockResolveToken,
resolveAllIdentities: mockResolveAllIdentities,
}));
// ── Mock the client module ──────────────────────────────────────────────────
const mockListSecrets = mock();
const mockAccessSecretVersion = mock();
const mockListProjects = mock();
const mockExtractSecretShortName = mock();
const mockBuildSecretName = mock();
mock.module("../../../src/providers/gcp/client", () => ({
listSecrets: mockListSecrets,
accessSecretVersion: mockAccessSecretVersion,
listProjects: mockListProjects,
extractSecretShortName: mockExtractSecretShortName,
buildSecretName: mockBuildSecretName,
}));
import { GcpSecretsService } from "../../../src/providers/gcp/secrets";
// ── Helpers ─────────────────────────────────────────────────────────────────
function setupAuth() {
mockResolveToken.mockResolvedValue({
token: "fake-gcp-token",
expiresOn: Date.now() / 1000 + 3600,
projectId: "test-project",
});
}
function makeSecretItem(name: string) {
return { name, createTime: "2024-01-01T00:00:00Z" };
}
// ── Tests ───────────────────────────────────────────────────────────────────
describe("GcpSecretsService", () => {
let service: GcpSecretsService;
beforeEach(() => {
mockResolveToken.mockReset();
mockResolveAllIdentities.mockReset();
mockListSecrets.mockReset();
mockAccessSecretVersion.mockReset();
mockListProjects.mockReset();
mockExtractSecretShortName.mockReset();
mockBuildSecretName.mockReset();
// Default: short name extraction passes through the name
mockExtractSecretShortName.mockImplementation(
(name: string) => name.split("/").pop() ?? name,
);
delete process.env.GCP_PROJECT;
delete process.env.GOOGLE_CLOUD_PROJECT;
service = new GcpSecretsService();
});
// ── Auth failure ──────────────────────────────────────────────────────
it("returns failure when authentication fails", async () => {
mockResolveToken.mockRejectedValue(new Error("No GCP credentials"));
const result = await service.execute();
expect(result.success).toBe(true);
const data = result.data as any;
expect(data.ok).toBe(false);
expect(data.authError).toBeArray();
expect(data.authError[0]).toContain("GCP auth failed");
});
// ── No projects found ─────────────────────────────────────────────────
it("returns failure when no projects found", async () => {
setupAuth();
mockListProjects.mockResolvedValue([]);
const result = await service.execute();
expect(result.success).toBe(true);
const data = result.data as any;
expect(data.ok).toBe(false);
expect(data.projectsFound).toBe(0);
expect(data.dumped).toBe(0);
});
// ── Env var project discovery ─────────────────────────────────────────
it("discovers project from GCP_PROJECT env var", async () => {
setupAuth();
process.env.GCP_PROJECT = "my-project";
mockListSecrets.mockResolvedValue([
makeSecretItem("projects/my-project/secrets/db-pass"),
]);
mockAccessSecretVersion.mockResolvedValue("s3cr3t");
const result = await service.execute();
expect(result.success).toBe(true);
const data = result.data as any;
expect(data.projects).toHaveLength(1);
expect(data.projects[0].projectId).toBe("my-project");
expect(data.projects[0].secrets["db-pass"]).toBe("s3cr3t");
});
// ── Single project, single secret ─────────────────────────────────────
it("returns success with single project and secret", async () => {
setupAuth();
process.env.GCP_PROJECT = "proj-1";
mockListSecrets.mockResolvedValue([
makeSecretItem("projects/proj-1/secrets/api-key"),
]);
mockAccessSecretVersion.mockResolvedValue("sk-abc");
const result = await service.execute();
expect(result.success).toBe(true);
const data = result.data as any;
expect(data.projects).toHaveLength(1);
expect(data.projects[0].secrets["api-key"]).toBe("sk-abc");
});
// ── Multiple secrets in one project ───────────────────────────────────
it("returns success with multiple secrets", async () => {
setupAuth();
process.env.GCP_PROJECT = "proj-1";
mockListSecrets.mockResolvedValue([
makeSecretItem("projects/proj-1/secrets/s1"),
makeSecretItem("projects/proj-1/secrets/s2"),
makeSecretItem("projects/proj-1/secrets/s3"),
]);
mockAccessSecretVersion.mockImplementation((name: string) => {
if (name.includes("/s1")) return Promise.resolve("val1");
if (name.includes("/s2")) return Promise.resolve("val2");
if (name.includes("/s3")) return Promise.resolve("val3");
return Promise.resolve(undefined);
});
const result = await service.execute();
expect(result.success).toBe(true);
const secrets = (result.data as any).projects[0].secrets;
expect(Object.keys(secrets)).toHaveLength(3);
});
// ── Empty project (no secrets) ────────────────────────────────────────
it("handles project with no secrets", async () => {
setupAuth();
process.env.GCP_PROJECT = "empty-proj";
mockListSecrets.mockResolvedValue([]);
const result = await service.execute();
expect(result.success).toBe(true);
const data = result.data as any;
expect(data.projects).toHaveLength(1);
expect(data.projects[0].secrets).toEqual({});
});
// ── All projects fail ─────────────────────────────────────────────────
it("returns failure when all projects fail", async () => {
setupAuth();
process.env.GCP_PROJECT = "bad-project";
mockListSecrets.mockRejectedValue(new Error("PermissionDenied"));
const result = await service.execute();
expect(result.success).toBe(true);
const data = result.data as any;
expect(data.ok).toBe(false);
expect(data.dumped).toBe(0);
expect(data.errored).toBe(1);
});
// ── Multiple projects via ARM ─────────────────────────────────────────
it("discovers projects via Cloud Resource Manager", async () => {
setupAuth();
mockListProjects.mockResolvedValue([
{ projectId: "p1", name: "Project One", lifecycleState: "ACTIVE" },
{ projectId: "p2", name: "Project Two", lifecycleState: "ACTIVE" },
]);
mockListSecrets.mockResolvedValue([
makeSecretItem("projects/p1/secrets/s1"),
]);
mockAccessSecretVersion.mockResolvedValue("v1");
const result = await service.execute();
expect(result.success).toBe(true);
const data = result.data as any;
expect(data.projects).toHaveLength(2);
});
});