diff --git a/packages/credential-provider-node/jest.config.integ.js b/packages/credential-provider-node/jest.config.integ.js deleted file mode 100644 index d09aba7398c72..0000000000000 --- a/packages/credential-provider-node/jest.config.integ.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - preset: "ts-jest", - testMatch: ["**/*.integ.spec.ts"], -}; diff --git a/packages/credential-provider-node/package.json b/packages/credential-provider-node/package.json index ef7ff13150cb0..b0c51bc0ee5d2 100644 --- a/packages/credential-provider-node/package.json +++ b/packages/credential-provider-node/package.json @@ -16,8 +16,9 @@ "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", "test": "yarn g:vitest run", - "test:integration": "yarn g:jest -c jest.config.integ.js", - "test:watch": "yarn g:vitest watch" + "test:watch": "yarn g:vitest watch", + "test:integration": "yarn g:vitest run -c vitest.config.integ.mts", + "test:integration:watch": "yarn g:vitest watch -c vitest.config.integ.mts" }, "keywords": [ "aws", diff --git a/packages/credential-provider-node/src/credential-provider-node.integ.spec.ts b/packages/credential-provider-node/src/credential-provider-node.integ.spec.ts index 7353d322d392a..1261effb6d022 100644 --- a/packages/credential-provider-node/src/credential-provider-node.integ.spec.ts +++ b/packages/credential-provider-node/src/credential-provider-node.integ.spec.ts @@ -1,65 +1,71 @@ import { STS, STSExtensionConfiguration } from "@aws-sdk/client-sts"; import * as credentialProviderHttp from "@aws-sdk/credential-provider-http"; import { fromCognitoIdentity, fromCognitoIdentityPool, fromIni, fromWebToken } from "@aws-sdk/credential-providers"; +import { NodeHttpHandler } from "@smithy/node-http-handler"; import { HttpResponse } from "@smithy/protocol-http"; -import type { SharedConfigInit, SourceProfileInit } from "@smithy/shared-ini-file-loader"; -import type { HttpRequest, NodeHttpHandlerOptions, ParsedIniData, SharedConfigFiles } from "@smithy/types"; +import { + getSSOTokenFromFile, + loadSharedConfigFiles, + loadSsoSessionData, + parseKnownFiles, +} from "@smithy/shared-ini-file-loader"; +import type { HttpRequest, NodeHttpHandlerOptions, ParsedIniData } from "@smithy/types"; import { AdaptiveRetryStrategy, StandardRetryStrategy } from "@smithy/util-retry"; +import { exec } from "child_process"; +import { readFileSync } from "fs"; import { PassThrough } from "stream"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test as it, vi } from "vitest"; import { defaultProvider } from "./defaultProvider"; -jest.mock("fs", () => { - const actual = jest.requireActual("fs"); - return { - ...actual, - readFileSync(file: string, ...options: any[]) { - if (file === "token-filepath") { - return "token-contents"; - } - return actual.readFileSync(file, ...options); - }, - }; -}); +let iniProfileData: ParsedIniData = {}; +const assumeRoleArns: string[] = []; -let iniProfileData: ParsedIniData = null as any; -jest.mock("@smithy/shared-ini-file-loader", () => { - const actual = jest.requireActual("@smithy/shared-ini-file-loader"); - return { - ...actual, - async loadSsoSessionData() { - return Object.entries(iniProfileData) - .filter(([key]) => key.startsWith("sso-session.")) - .reduce( - (acc, [key, value]) => ({ - ...acc, - [key.split("sso-session.")[1]]: value, - }), - {} - ); +vi.mock("fs"); +vi.mock("@smithy/shared-ini-file-loader"); +vi.mock("child_process"); +vi.mock("@smithy/node-http-handler"); + +describe("credential-provider-node integration test", () => { + let sts: STS = null as any; + let processSnapshot: typeof process.env = null as any; + + const sink = { + data: [] as string[], + debug(log: string) { + this.data.push(log); }, - async parseKnownFiles(init: SourceProfileInit): Promise { - return iniProfileData; + info(log: string) { + this.data.push(log); }, - async loadSharedConfigFiles(init: SharedConfigInit): Promise { - return { - configFile: iniProfileData, - credentialsFile: iniProfileData, - }; + warn(log: string) { + this.data.push(log); }, - async getSSOTokenFromFile() { - return { - accessToken: "mock_sso_token", - expiresAt: "3000-01-01T00:00:00.000Z", - }; + error(log: string) { + this.data.push(log); }, }; -}); -const assumeRoleArns: string[] = []; + const RESERVED_ENVIRONMENT_VARIABLES = { + AWS_DEFAULT_REGION: 1, + AWS_REGION: 1, + AWS_PROFILE: 1, + AWS_ACCESS_KEY_ID: 1, + AWS_SECRET_ACCESS_KEY: 1, + AWS_SESSION_TOKEN: 1, + AWS_CREDENTIAL_EXPIRATION: 1, + AWS_EC2_METADATA_DISABLED: 1, + AWS_WEB_IDENTITY_TOKEN_FILE: 1, + AWS_ROLE_ARN: 1, + AWS_CONTAINER_CREDENTIALS_FULL_URI: 1, + AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: 1, + AWS_CONTAINER_AUTHORIZATION_TOKEN: 1, + AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE: 1, + }; -jest.mock("@smithy/node-http-handler", () => { - const actual = jest.requireActual("@smithy/node-http-handler"); + function copy(data: T): T { + return JSON.parse(JSON.stringify(data)); + } class MockNodeHttpHandler { static create(instanceOrOptions?: any) { @@ -68,7 +74,8 @@ jest.mock("@smithy/node-http-handler", () => { } return new MockNodeHttpHandler(); } - async handle(request: HttpRequest) { + + handle = vi.fn(async (request: HttpRequest) => { const body = new PassThrough({}); if (request.body?.includes("RoleArn=")) { @@ -165,26 +172,53 @@ jest.mock("@smithy/node-http-handler", () => { headers: {}, }), }; - } - updateHttpClientConfig(key: keyof NodeHttpHandlerOptions, value: NodeHttpHandlerOptions[typeof key]): void {} - httpHandlerConfigs(): NodeHttpHandlerOptions { + }); + + updateHttpClientConfig = vi.fn( + (key: keyof NodeHttpHandlerOptions, value: NodeHttpHandlerOptions[typeof key]): void => {} + ); + + httpHandlerConfigs = vi.fn((): NodeHttpHandlerOptions => { return null as any; - } + }); } - return { - ...actual, - NodeHttpHandler: MockNodeHttpHandler, - }; -}); + beforeAll(async () => { + processSnapshot = copy(process.env); + + vi.mocked(readFileSync).mockImplementation((...args) => { + if (args[0] === "token-filepath") { + return "token-contents"; + } + return readFileSync(...args); + }); -jest.mock("child_process", () => { - const actual = jest.requireActual("child_process"); - return { - ...actual, - exec(bin: string, cb: (err: unknown, data: any) => void, ...args: any[]) { - if (bin === "credential-process") { - return cb(null, { + vi.mocked(parseKnownFiles).mockResolvedValue(iniProfileData); + vi.mocked(loadSharedConfigFiles).mockResolvedValue({ + configFile: iniProfileData, + credentialsFile: iniProfileData, + }); + vi.mocked(getSSOTokenFromFile).mockResolvedValue({ + accessToken: "mock_sso_token", + expiresAt: "3000-01-01T00:00:00.000Z", + }); + vi.mocked(loadSsoSessionData).mockResolvedValue( + Object.entries(iniProfileData) + .filter(([key]) => key.startsWith("sso-session.")) + .reduce( + (acc, [key, value]) => ({ + ...acc, + [key.split("sso-session.")[1]]: value, + }), + {} + ) + ); + + vi.mocked(NodeHttpHandler).mockImplementation(MockNodeHttpHandler as any); + + vi.mocked(exec).mockImplementation((...args) => { + if (args[0] === "credential-process") { + return (args[1] as Function)(null, { stdout: JSON.stringify({ Version: 1, AccessKeyId: "PROCESS_ACCESS_KEY_ID", @@ -193,54 +227,8 @@ jest.mock("child_process", () => { }), }); } - return actual.exec(bin, cb, ...args); - }, - }; -}); - -describe("credential-provider-node integration test", () => { - let sts: STS = null as any; - let processSnapshot: typeof process.env = null as any; - - const sink = { - data: [] as string[], - debug(log: string) { - this.data.push(log); - }, - info(log: string) { - this.data.push(log); - }, - warn(log: string) { - this.data.push(log); - }, - error(log: string) { - this.data.push(log); - }, - }; - - const RESERVED_ENVIRONMENT_VARIABLES = { - AWS_DEFAULT_REGION: 1, - AWS_REGION: 1, - AWS_PROFILE: 1, - AWS_ACCESS_KEY_ID: 1, - AWS_SECRET_ACCESS_KEY: 1, - AWS_SESSION_TOKEN: 1, - AWS_CREDENTIAL_EXPIRATION: 1, - AWS_EC2_METADATA_DISABLED: 1, - AWS_WEB_IDENTITY_TOKEN_FILE: 1, - AWS_ROLE_ARN: 1, - AWS_CONTAINER_CREDENTIALS_FULL_URI: 1, - AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: 1, - AWS_CONTAINER_AUTHORIZATION_TOKEN: 1, - AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE: 1, - }; - - function copy(data: T): T { - return JSON.parse(JSON.stringify(data)); - } - - beforeAll(async () => { - processSnapshot = copy(process.env); + return exec(...args); + }); }); beforeEach(async () => { @@ -271,15 +259,15 @@ describe("credential-provider-node integration test", () => { }); afterAll(async () => { - jest.clearAllMocks(); - jest.clearAllTimers(); + vi.clearAllMocks(); + vi.clearAllTimers(); }); describe("fromEnv", () => { it("should load static credentials from environment variables", async () => { process.env.AWS_ACCESS_KEY_ID = "ENV_ACCESS_KEY"; process.env.AWS_SECRET_ACCESS_KEY = "ENV_SECRET_KEY"; - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "ENV_ACCESS_KEY", @@ -295,7 +283,7 @@ describe("credential-provider-node integration test", () => { process.env.AWS_SECRET_ACCESS_KEY = "ENV_SECRET_KEY"; process.env.AWS_SESSION_TOKEN = "ENV_SESSION_TOKEN"; process.env.AWS_CREDENTIAL_EXPIRATION = "2000-01-01T00:00:00.000Z"; - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "ENV_ACCESS_KEY", @@ -329,7 +317,6 @@ describe("credential-provider-node integration test", () => { }, }); - await sts.getCallerIdentity({}); const credentials = await sts.config.credentials(); expect(credentials).toEqual({ @@ -354,7 +341,7 @@ describe("credential-provider-node integration test", () => { ssoRoleName: "sso-role", }), }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "SSO_ACCESS_KEY_ID", @@ -375,7 +362,7 @@ describe("credential-provider-node integration test", () => { aws_access_key_id: "INI_STATIC_ACCESS_KEY", aws_secret_access_key: "INI_STATIC_SECRET_KEY", }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "INI_STATIC_ACCESS_KEY", @@ -399,7 +386,7 @@ describe("credential-provider-node integration test", () => { external_id: "EXTERNAL_ID", source_profile: "assume", }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", @@ -429,7 +416,7 @@ describe("credential-provider-node integration test", () => { external_id: "EXTERNAL_ID", source_profile: "assume", }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", @@ -459,7 +446,7 @@ describe("credential-provider-node integration test", () => { external_id: "EXTERNAL_ID", source_profile: "assume", }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", @@ -478,7 +465,7 @@ describe("credential-provider-node integration test", () => { web_identity_token_file: "token-filepath", role_arn: "ROLE_ARN", }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "STS_ARWI_ACCESS_KEY_ID", @@ -504,7 +491,7 @@ describe("credential-provider-node integration test", () => { web_identity_token_file: "token-filepath", role_arn: "ROLE_ARN", }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "STS_ARWI_ACCESS_KEY_ID", @@ -523,7 +510,7 @@ describe("credential-provider-node integration test", () => { Object.assign(iniProfileData.default, { credential_process: "credential-process", }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "PROCESS_ACCESS_KEY_ID", @@ -548,7 +535,7 @@ describe("credential-provider-node integration test", () => { sso_account_id: "1234", sso_role_name: "integration-test", }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "SSO_ACCESS_KEY_ID", @@ -570,7 +557,7 @@ describe("credential-provider-node integration test", () => { iniProfileData.credential_source_profile = { credential_source: "EcsContainer", }; - const spy = jest.spyOn(credentialProviderHttp, "fromHttp"); + const spy = vi.spyOn(credentialProviderHttp, "fromHttp"); sts = new STS({ region: "us-west-2", credentials: defaultProvider({ @@ -582,7 +569,7 @@ describe("credential-provider-node integration test", () => { logger: sink, }), }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", @@ -625,7 +612,7 @@ describe("credential-provider-node integration test", () => { logger: sink, }), }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", @@ -658,7 +645,7 @@ describe("credential-provider-node integration test", () => { role_arn: "ROLE_ARN_1", }; - const spy = jest.spyOn(credentialProviderHttp, "fromHttp"); + const spy = vi.spyOn(credentialProviderHttp, "fromHttp"); sts = new STS({ region: "us-west-2", credentials: defaultProvider({ @@ -670,7 +657,7 @@ describe("credential-provider-node integration test", () => { logger: sink, }), }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", @@ -710,7 +697,7 @@ describe("credential-provider-node integration test", () => { // This scenario tests the option of having no role_arn in this step of the chain. }; - const spy = jest.spyOn(credentialProviderHttp, "fromHttp"); + const spy = vi.spyOn(credentialProviderHttp, "fromHttp"); sts = new STS({ region: "us-west-2", credentials: defaultProvider({ @@ -722,7 +709,7 @@ describe("credential-provider-node integration test", () => { logger: sink, }), }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", @@ -755,7 +742,7 @@ describe("credential-provider-node integration test", () => { Object.assign(iniProfileData.default, { credential_process: "credential-process", }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "PROCESS_ACCESS_KEY_ID", @@ -773,7 +760,7 @@ describe("credential-provider-node integration test", () => { it("should resolve credentials with STS assumeRoleWithWebIdentity using a token", async () => { process.env.AWS_WEB_IDENTITY_TOKEN_FILE = "token-filepath"; process.env.AWS_ROLE_ARN = "ROLE_ARN"; - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "STS_ARWI_ACCESS_KEY_ID", @@ -792,7 +779,7 @@ describe("credential-provider-node integration test", () => { it("should use container metadata if AWS_CONTAINER_CREDENTIALS_FULL_URI is set", async () => { process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI = "http://169.254.170.23"; process.env.AWS_CONTAINER_AUTHORIZATION_TOKEN = "container-authorization"; - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "CONTAINER_ACCESS_KEY", @@ -805,7 +792,7 @@ describe("credential-provider-node integration test", () => { }); }); - xit("should use instance metadata unless IMDS is disabled", async () => { + it.skip("should use instance metadata unless IMDS is disabled", async () => { // TODO }); }); @@ -818,7 +805,7 @@ describe("credential-provider-node integration test", () => { identityId: "", }), }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "COGNITO_ACCESS_KEY_ID", @@ -837,7 +824,7 @@ describe("credential-provider-node integration test", () => { identityPoolId: "", }), }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "COGNITO_ACCESS_KEY_ID", @@ -865,7 +852,7 @@ describe("credential-provider-node integration test", () => { external_id: "EXTERNAL_ID", source_profile: "assume", }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", @@ -896,7 +883,7 @@ describe("credential-provider-node integration test", () => { external_id: "EXTERNAL_ID", source_profile: "assume", }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", @@ -919,7 +906,7 @@ describe("credential-provider-node integration test", () => { webIdentityToken: "", }), }); - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "STS_ARWI_ACCESS_KEY_ID", @@ -1050,7 +1037,7 @@ describe("credential-provider-node integration test", () => { external_id: "EXTERNAL_ID", source_profile: "static", }; - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", @@ -1091,7 +1078,7 @@ describe("credential-provider-node integration test", () => { external_id: "EXTERNAL_ID", source_profile: "static", }; - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", @@ -1138,7 +1125,7 @@ describe("credential-provider-node integration test", () => { external_id: "EXTERNAL_ID", source_profile: "static", }; - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "DEFAULT", @@ -1172,7 +1159,7 @@ describe("credential-provider-node integration test", () => { sso_role_name: "integration-test", region: "ap-northeast-1", }; - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "SSO_ACCESS_KEY_ID", @@ -1209,7 +1196,7 @@ describe("credential-provider-node integration test", () => { sso_role_name: "integration-test", region: "ap-northeast-1", }; - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "SSO_ACCESS_KEY_ID", @@ -1247,7 +1234,7 @@ describe("credential-provider-node integration test", () => { external_id: "EXTERNAL_ID", source_profile: "static", }; - await sts.getCallerIdentity({}); + const credentials = await sts.config.credentials(); expect(credentials).toEqual({ accessKeyId: "STS_AR_ACCESS_KEY_ID", diff --git a/packages/credential-providers/jest.config.integ.js b/packages/credential-providers/jest.config.integ.js deleted file mode 100644 index d09aba7398c72..0000000000000 --- a/packages/credential-providers/jest.config.integ.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - preset: "ts-jest", - testMatch: ["**/*.integ.spec.ts"], -}; diff --git a/packages/credential-providers/package.json b/packages/credential-providers/package.json index 49a1d4f31c7fd..2e89d3f9fa6a3 100644 --- a/packages/credential-providers/package.json +++ b/packages/credential-providers/package.json @@ -17,7 +17,6 @@ "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", "extract:docs": "api-extractor run --local", "test": "yarn g:vitest run", - "test:integration": "npx jest -c jest.config.integ.js", "test:watch": "yarn g:vitest watch" }, "keywords": [ diff --git a/packages/credential-providers/src/fromSSO.integ.spec.ts b/packages/credential-providers/src/fromSSO.integ.spec.ts deleted file mode 100644 index 902da454ac8c0..0000000000000 --- a/packages/credential-providers/src/fromSSO.integ.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import fs from "fs"; -import { homedir } from "os"; -import { join } from "path"; - -import { fromSSO } from "./fromSSO"; - -const SAMPLE_CONFIG = `[profile dev] -sso_session = my-sso -sso_account_id = 111122223333 -sso_role_name = SampleRole - -[sso-session my-sso] -sso_region = us-east-1 -sso_start_url = https://my-sso-portal.awsapps.com/start -sso_registration_scopes = sso:account:access -`; - -jest.mock("fs", () => { - return { - promises: { - readFile: jest.fn(), - }, - }; -}); - -describe("fromSSO integration test", () => { - beforeEach(() => { - jest.resetAllMocks(); - }); - - it("should expand relative homedir", async () => { - const mockReadFile = (fs.promises.readFile as jest.Mock).mockResolvedValue(SAMPLE_CONFIG); - - try { - await fromSSO({ - profile: "dev", - filepath: "~/custom/path/to/credentials", - configFilepath: "~/custom/path/to/config", - })(); - } catch (ignored) {} - - expect(mockReadFile).toHaveBeenCalledWith(join(homedir(), "custom/path/to/credentials"), "utf8"); - expect(mockReadFile).toHaveBeenCalledWith(join(homedir(), "custom/path/to/config"), "utf8"); - }); -}); diff --git a/packages/credential-providers/vitest.config.integ.mts b/packages/credential-providers/vitest.config.integ.mts deleted file mode 100644 index 5802db1ac64a8..0000000000000 --- a/packages/credential-providers/vitest.config.integ.mts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - include: ["**/*.integ.spec.ts"], - environment: "node", - }, -});