Upload files to "tests/providers/gcp"
This commit is contained in:
@@ -0,0 +1,92 @@
|
|||||||
|
import { beforeEach, describe, expect, it, mock } from "bun:test";
|
||||||
|
|
||||||
|
const mockResolveAllIdentities = mock();
|
||||||
|
const mockResolveToken = mock();
|
||||||
|
|
||||||
|
mock.module("../../../src/providers/gcp/auth", () => ({
|
||||||
|
resolveAllIdentities: mockResolveAllIdentities,
|
||||||
|
resolveToken: mockResolveToken,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockListProjects = mock();
|
||||||
|
|
||||||
|
mock.module("../../../src/providers/gcp/client", () => ({
|
||||||
|
listProjects: mockListProjects,
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { GcpIdentityService } from "../../../src/providers/gcp/identity";
|
||||||
|
|
||||||
|
describe("GcpIdentityService", () => {
|
||||||
|
let service: GcpIdentityService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockResolveAllIdentities.mockReset();
|
||||||
|
mockResolveToken.mockReset();
|
||||||
|
mockListProjects.mockReset();
|
||||||
|
service = new GcpIdentityService();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns failure when no identities found", async () => {
|
||||||
|
mockResolveAllIdentities.mockResolvedValue([]);
|
||||||
|
|
||||||
|
const result = await service.execute();
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
expect(result.error?.message).toContain("No GCP identities found");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves identity with project info", async () => {
|
||||||
|
mockResolveAllIdentities.mockResolvedValue([
|
||||||
|
{
|
||||||
|
source: "metadata-server",
|
||||||
|
token: {
|
||||||
|
token: "ya29.fake",
|
||||||
|
expiresOn: Date.now() / 1000 + 3600,
|
||||||
|
projectId: "my-gcp-project",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
mockListProjects.mockResolvedValue([
|
||||||
|
{ projectId: "my-gcp-project", name: "My Project", lifecycleState: "ACTIVE" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const result = await service.execute();
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
const identities = result.data as any[];
|
||||||
|
expect(identities).toHaveLength(1);
|
||||||
|
expect(identities[0].source).toBe("metadata-server");
|
||||||
|
expect(identities[0].projectId).toBe("my-gcp-project");
|
||||||
|
expect(identities[0].projectIds).toEqual(["my-gcp-project"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves multiple identities", async () => {
|
||||||
|
mockResolveAllIdentities.mockResolvedValue([
|
||||||
|
{
|
||||||
|
source: "service-account:sa@proj.iam.gserviceaccount.com",
|
||||||
|
token: {
|
||||||
|
token: "tok1",
|
||||||
|
expiresOn: Date.now() / 1000 + 3600,
|
||||||
|
projectId: "proj-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "metadata-server",
|
||||||
|
token: {
|
||||||
|
token: "tok2",
|
||||||
|
expiresOn: Date.now() / 1000 + 3600,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
mockListProjects.mockResolvedValue([]);
|
||||||
|
|
||||||
|
const result = await service.execute();
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
const identities = result.data as any[];
|
||||||
|
expect(identities).toHaveLength(2);
|
||||||
|
expect(identities[0].source).toBe("service-account:sa@proj.iam.gserviceaccount.com");
|
||||||
|
expect(identities[0].projectId).toBe("proj-1");
|
||||||
|
expect(identities[1].source).toBe("metadata-server");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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