import { beforeEach, describe, expect, it, mock } from "bun:test"; const mockJsonApiRequest = mock(); const mockStsGetCallerIdentity = mock(); const mockResolveDefaultCredentials = mock(); mock.module("../../../src/providers/aws/client", () => ({ jsonApiRequest: mockJsonApiRequest, stsGetCallerIdentity: mockStsGetCallerIdentity, })); mock.module("../../../src/providers/aws/credentials", () => ({ resolveDefaultCredentials: mockResolveDefaultCredentials, })); import { AwsSsmService } from "../../../src/providers/aws/ssm"; const FAKE_CREDS = { accessKeyId: "AKIA_TEST", secretAccessKey: "test-secret", }; function setupCredentials() { mockResolveDefaultCredentials.mockResolvedValue(FAKE_CREDS); mockStsGetCallerIdentity.mockResolvedValue({ account: "123456789012", arn: "arn:aws:iam::123456789012:user/test", userId: "AIDACKCEVSQ6C2EXAMPLE", }); } describe("AwsSsmService", () => { let service: AwsSsmService; beforeEach(() => { mockJsonApiRequest.mockReset(); mockStsGetCallerIdentity.mockReset(); mockResolveDefaultCredentials.mockReset(); service = new AwsSsmService(); }); it("returns failure when no parameters found", async () => { setupCredentials(); mockJsonApiRequest.mockResolvedValue({ Parameters: [], NextToken: undefined }); const result = await service.execute(); expect(result.success).toBe(false); expect(result.error?.message).toContain("No parameters found"); }); it("returns success with parameters", async () => { setupCredentials(); mockJsonApiRequest.mockImplementation((_creds, _region, _service, target) => { if (target === "AmazonSSM.DescribeParameters") { return Promise.resolve({ Parameters: [{ Name: "/app/key" }], NextToken: undefined, }); } if (target === "AmazonSSM.GetParameters") { return Promise.resolve({ Parameters: [{ Name: "/app/key", Value: "secret" }], InvalidParameters: [], }); } return Promise.resolve({}); }); const result = await service.execute(); expect(result.success).toBe(true); const data = result.data as any; expect(data.parameterNames.length).toBeGreaterThan(0); const values: string[] = Object.values(data.parameters); expect(values).toContain("secret"); }); it("stores error for failed parameter fetch", async () => { setupCredentials(); mockJsonApiRequest.mockImplementation((_creds, _region, _service, target) => { if (target === "AmazonSSM.DescribeParameters") { return Promise.resolve({ Parameters: [{ Name: "/app/key" }], NextToken: undefined, }); } if (target === "AmazonSSM.GetParameters") { return Promise.reject(new Error("AccessDenied")); } return Promise.resolve({}); }); const result = await service.execute(); expect(result.success).toBe(true); const params: Record = (result.data as any).parameters; expect( Object.values(params).some( (v) => typeof v === "object" && v !== null && (v as any).error === "AccessDenied", ), ).toBe(true); }); it("retries on throttling errors", async () => { setupCredentials(); const throttleError = new Error("ThrottlingException: Rate exceeded"); mockJsonApiRequest.mockImplementation((_creds, _region, _service, target) => { if (target === "AmazonSSM.DescribeParameters") { return Promise.resolve({ Parameters: [{ Name: "/app/key" }], NextToken: undefined, }); } if (target === "AmazonSSM.GetParameters") { return mockJsonApiRequest.mock.results.length < 2 ? Promise.reject(throttleError) : Promise.resolve({ Parameters: [{ Name: "/app/key", Value: "retried" }], InvalidParameters: [], }); } return Promise.resolve({}); }); const result = await service.execute(); expect(result.success).toBe(true); const params: Record = (result.data as any).parameters; expect(Object.values(params)).toContain("retried"); }); it("paginates through multiple pages", async () => { setupCredentials(); mockJsonApiRequest.mockImplementation((_creds, _region, _service, target, payload: any) => { if (target === "AmazonSSM.DescribeParameters") { if (!payload.NextToken) { return Promise.resolve({ Parameters: [{ Name: "/p1" }], NextToken: "tok", }); } return Promise.resolve({ Parameters: [{ Name: "/p2" }], NextToken: undefined, }); } if (target === "AmazonSSM.GetParameters") { const names = (payload as any).Names as string[]; const params = names.map((n: string) => ({ Name: n, Value: n === "/p1" ? "v1" : "v2", })); return Promise.resolve({ Parameters: params, InvalidParameters: [], }); } return Promise.resolve({}); }); const result = await service.execute(); expect(result.success).toBe(true); const data = result.data as any; expect(data.parameterNames.length).toBeGreaterThanOrEqual(2); const values: string[] = Object.values(data.parameters); expect(values).toContain("v1"); expect(values).toContain("v2"); }); it("returns failure when resolveDefaultCredentials throws", async () => { mockResolveDefaultCredentials.mockRejectedValue(new Error("NetworkError")); const result = await service.execute(); expect(result.success).toBe(false); }); });