Skip to content

Commit 85b716b

Browse files
committed
add tests
1 parent 26d12c0 commit 85b716b

File tree

3 files changed

+276
-2
lines changed

3 files changed

+276
-2
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { CredentialsProviderError } from "@smithy/property-provider";
2+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3+
4+
import { fromAwsCliV2CompatibleProviderChain } from "./fromAwsCliV2CompatibleProviderChain";
5+
6+
describe("fromAwsCliV2CompatibleProviderChain", () => {
7+
const mockLogger = {
8+
debug: vi.fn(),
9+
info: vi.fn(),
10+
warn: vi.fn(),
11+
error: vi.fn(),
12+
};
13+
14+
const mockRegion = () => Promise.resolve("us-east-1");
15+
16+
beforeEach(() => {
17+
vi.clearAllMocks();
18+
});
19+
20+
afterEach(() => {
21+
vi.resetModules();
22+
});
23+
24+
it("should return static credentials when provided", async () => {
25+
const credentials = {
26+
accessKeyId: "TEST_ACCESS_KEY",
27+
secretAccessKey: "TEST_SECRET_KEY",
28+
sessionToken: "TEST_SESSION_TOKEN",
29+
expiration: new Date(),
30+
};
31+
32+
const provider = fromAwsCliV2CompatibleProviderChain({
33+
...credentials,
34+
logger: mockLogger,
35+
});
36+
37+
const result = await provider();
38+
39+
expect(result).toEqual(credentials);
40+
expect(mockLogger.debug).toHaveBeenCalledWith(
41+
expect.stringContaining("using static credentials from initialization")
42+
);
43+
});
44+
45+
it("should use profile credentials when profile is specified", async () => {
46+
const mockFromIni = vi.fn().mockResolvedValue({
47+
accessKeyId: "PROFILE_ACCESS_KEY",
48+
secretAccessKey: "PROFILE_SECRET_KEY",
49+
});
50+
51+
vi.mock("@aws-sdk/credential-provider-ini", () => ({
52+
fromIni: () => mockFromIni,
53+
}));
54+
55+
const provider = fromAwsCliV2CompatibleProviderChain({
56+
profile: "test-profile",
57+
logger: mockLogger,
58+
});
59+
60+
const result = await provider();
61+
62+
expect(result).toEqual({
63+
accessKeyId: "PROFILE_ACCESS_KEY",
64+
secretAccessKey: "PROFILE_SECRET_KEY",
65+
});
66+
expect(mockLogger.debug).toHaveBeenCalledWith(expect.stringContaining("Using fromIni with profile:test-profile"));
67+
});
68+
69+
it("should try credential chain when no static credentials or profile", async () => {
70+
const mockEnvCredentials = {
71+
accessKeyId: "ENV_ACCESS_KEY",
72+
secretAccessKey: "ENV_SECRET_KEY",
73+
};
74+
75+
vi.mock("@aws-sdk/credential-provider-env", () => ({
76+
fromEnv: () => () => Promise.resolve(mockEnvCredentials),
77+
}));
78+
79+
const provider = fromAwsCliV2CompatibleProviderChain({
80+
logger: mockLogger,
81+
});
82+
83+
const result = await provider();
84+
85+
expect(result).toEqual(mockEnvCredentials);
86+
expect(mockLogger.debug).toHaveBeenCalledWith(expect.stringContaining("Using from custom credential chain"));
87+
});
88+
89+
it("should throw error when no credentials are found", async () => {
90+
// Mock all providers to fail
91+
vi.mock("@aws-sdk/credential-provider-env", () => ({
92+
fromEnv: () => () => Promise.reject(new Error("No env credentials")),
93+
}));
94+
vi.mock("@aws-sdk/credential-provider-web-identity", () => ({
95+
fromTokenFile: () => () => Promise.reject(new Error("No token file")),
96+
}));
97+
vi.mock("@aws-sdk/credential-provider-sso", () => ({
98+
fromSSO: () => () => Promise.reject(new Error("No SSO credentials")),
99+
}));
100+
vi.mock("@aws-sdk/credential-provider-process", () => ({
101+
fromProcess: () => () => Promise.reject(new Error("No process credentials")),
102+
}));
103+
vi.mock("@aws-sdk/credential-provider-node/src/remoteProvider", () => ({
104+
remoteProvider: () => () => Promise.reject(new Error("No remote credentials")),
105+
}));
106+
107+
const provider = fromAwsCliV2CompatibleProviderChain({
108+
logger: mockLogger,
109+
});
110+
111+
await expect(provider()).rejects.toThrow(CredentialsProviderError);
112+
await expect(provider()).rejects.toThrow("Could not load credentials from any providers");
113+
});
114+
115+
it("should merge caller client config with init options", async () => {
116+
// Initial credentials
117+
const credentials = {
118+
accessKeyId: "TEST_ACCESS_KEY",
119+
secretAccessKey: "TEST_SECRET_KEY",
120+
};
121+
122+
const provider = fromAwsCliV2CompatibleProviderChain(credentials);
123+
const callerConfig = {
124+
profile: "caller-profile",
125+
logger: mockLogger,
126+
region: mockRegion,
127+
};
128+
129+
const result = await provider({ callerClientConfig: callerConfig });
130+
131+
expect(result).toEqual(credentials);
132+
expect(mockLogger.debug).toHaveBeenCalled();
133+
});
134+
});
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { MetadataService } from "@aws-sdk/ec2-metadata-service";
2+
import { loadSharedConfigFiles } from "@smithy/shared-ini-file-loader";
3+
import { afterEach, beforeEach, describe, expect, test as it, vi } from "vitest";
4+
5+
import { getRegionFromIni, regionFromMetadataService, resolveAwsCliV2Region } from "./resolveAwsCliV2Region";
6+
7+
//Mock the dependencies
8+
vi.mock("@aws-sdk/ec2-metadata-service");
9+
vi.mock("@smithy/shared-ini-file-loader", () => ({
10+
loadSharedConfigFiles: vi.fn(),
11+
}));
12+
13+
describe("AWS Region Resolution", () => {
14+
// Store original environment variables to restore them later
15+
const originalEnv = process.env;
16+
17+
beforeEach(() => {
18+
// Reset environment variables before each test
19+
process.env = { ...originalEnv };
20+
// Clear all mock data before each test
21+
vi.clearAllMocks();
22+
});
23+
24+
afterEach(() => {
25+
// Restore environment variables after each test
26+
process.env = originalEnv;
27+
});
28+
29+
describe("resolveAwsCliV2Region", () => {
30+
it("should use AWS_REGION from environment variables", async () => {
31+
process.env.AWS_REGION = "us-west-2";
32+
const region = await resolveAwsCliV2Region({});
33+
expect(region).toBe("us-west-2");
34+
});
35+
36+
it("should fall back to AWS_DEFAULT_REGION if AWS_REGION is empty", async () => {
37+
process.env.AWS_REGION = "";
38+
process.env.AWS_DEFAULT_REGION = "eu-west-1";
39+
const region = await resolveAwsCliV2Region({});
40+
expect(region).toBe("eu-west-1");
41+
});
42+
43+
it("should use defaultRegion when no other source is available", async () => {
44+
const consoleSpy = vi.spyOn(console, "warn");
45+
const region = await resolveAwsCliV2Region({ defaultRegion: "ap-southeast-1" });
46+
expect(region).toBe("ap-southeast-1");
47+
expect(consoleSpy).toHaveBeenCalled();
48+
});
49+
50+
it("should return undefined when no region is available and no default region is provided", async () => {
51+
const consoleSpy = vi.spyOn(console, "warn");
52+
const region = await resolveAwsCliV2Region({});
53+
expect(region).toBeUndefined();
54+
expect(consoleSpy).toHaveBeenCalled();
55+
});
56+
57+
it("should use specified profile", async () => {
58+
vi.mocked(loadSharedConfigFiles).mockResolvedValue({
59+
configFile: {
60+
"custom-profile": {
61+
region: "us-east-1",
62+
},
63+
},
64+
credentialsFile: {},
65+
});
66+
67+
const region = await resolveAwsCliV2Region({ profile: "custom-profile" });
68+
expect(region).toBe("us-east-1");
69+
});
70+
});
71+
72+
describe("getRegionFromIni", () => {
73+
it("should get region from config file", async () => {
74+
vi.mocked(loadSharedConfigFiles).mockResolvedValue({
75+
configFile: {
76+
default: {
77+
region: "us-east-2",
78+
},
79+
},
80+
credentialsFile: {},
81+
});
82+
83+
const region = await getRegionFromIni("default");
84+
expect(region).toBe("us-east-2");
85+
});
86+
87+
it("should fall back to credentials file", async () => {
88+
vi.mocked(loadSharedConfigFiles).mockResolvedValue({
89+
configFile: {},
90+
credentialsFile: {
91+
default: {
92+
region: "eu-central-1",
93+
},
94+
},
95+
});
96+
97+
const region = await getRegionFromIni("default");
98+
expect(region).toBe("eu-central-1");
99+
});
100+
101+
it("should return undefined when no region is found", async () => {
102+
vi.mocked(loadSharedConfigFiles).mockResolvedValue({
103+
configFile: {},
104+
credentialsFile: {},
105+
});
106+
107+
const region = await getRegionFromIni("default");
108+
expect(region).toBeUndefined();
109+
});
110+
});
111+
112+
describe("regionFromMetadataService", () => {
113+
it("should get region from EC2 metadata service", async () => {
114+
vi.mocked(MetadataService).mockImplementation(
115+
() =>
116+
({
117+
request: vi.fn().mockResolvedValue(JSON.stringify({ region: "us-west-1" })),
118+
} as any)
119+
);
120+
121+
const region = await regionFromMetadataService();
122+
expect(region).toBe("us-west-1");
123+
});
124+
125+
it("should handle metadata service errors", async () => {
126+
vi.mocked(MetadataService).mockImplementation(
127+
() =>
128+
({
129+
request: vi.fn().mockRejectedValue(new Error("IMDS not available")),
130+
} as any)
131+
);
132+
133+
const consoleSpy = vi.spyOn(console, "warn");
134+
const region = await regionFromMetadataService();
135+
136+
expect(region).toBeUndefined();
137+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("IMDS not available"));
138+
});
139+
});
140+
});

packages/credential-providers/src/resolveAwsCliV2Region.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { MetadataService } from "@aws-sdk/ec2-metadata-service";
2-
import { fromSharedConfigFiles } from "@smithy/node-config-provider";
2+
import { loadSharedConfigFiles } from "@smithy/shared-ini-file-loader";
33

44
interface ResolveRegionOptions {
55
defaultRegion?: string;
@@ -44,7 +44,7 @@ export const resolveAwsCliV2Region = async ({
4444
* Fetches the region from the AWS shared config files.
4545
*/
4646
export async function getRegionFromIni(profile: string): Promise<string | undefined> {
47-
const sharedFiles = await fromSharedConfigFiles({ ignoreCache: true });
47+
const sharedFiles = await loadSharedConfigFiles({ ignoreCache: true });
4848
return sharedFiles.configFile?.[profile]?.region || sharedFiles.credentialsFile?.[profile]?.region;
4949
}
5050

0 commit comments

Comments
 (0)