Upload files to "tests/providers/gcp"
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user