Skip to content

Commit

Permalink
More command tests
Browse files Browse the repository at this point in the history
This adds testing for all commands except SSH.
  • Loading branch information
nbrahms committed Feb 21, 2024
1 parent a529f98 commit 5841f22
Show file tree
Hide file tree
Showing 18 changed files with 366 additions and 29 deletions.
13 changes: 13 additions & 0 deletions __mocks__/@firebase/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const getAuth = jest.fn().mockReturnValue({});

export const signInWithCredential = jest.fn();

export const SignInMethod = {
GOOGLE: "google.com",
};

export class OAuthProvider {
credential() {
return "test-credential";
}
}
30 changes: 27 additions & 3 deletions __mocks__/firebase/firestore.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
import { noop } from "lodash";

export const doc = jest.fn();

export const getCollection = jest.fn();

export const getDoc = jest.fn();

export const getFirestore = jest.fn().mockReturnValue({});

let snapshotCallbacks: ((snapshot: object) => void)[] = [];

/** Triggerable mock onSnapshot
*
* Usage:
* ```
* import { onSnapshot } from "firebase/firestore";
*
* beforeEach(() => {
* (onSnapshot as any).clear();
* })
*
* test(..., () => {
* // call code under test here
* (onSnapshot as any).trigger(data)
* })
* ```
*
* Note that only one `onSnapshot` may be tested at a time.
*/
export const onSnapshot = Object.assign(
jest.fn().mockImplementation((_doc, cb) => {
snapshotCallbacks.push(cb);
return noop;
return () => {
snapshotCallbacks = [];
};
}),
{
clear: (snapshotCallbacks = []),
Expand All @@ -21,4 +43,6 @@ export const onSnapshot = Object.assign(
}
);

export const query = jest.fn();

export const terminate = jest.fn();
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
testRegex: ".*\\.test\\.ts$",
prettierPath: null,
preset: "ts-jest",
testEnvironment: "node",
modulePathIgnorePatterns: ["/build"],
Expand Down
40 changes: 40 additions & 0 deletions src/commands/__tests__/__snapshots__/login.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`login organization exists should write the user's identity to the file system 1`] = `
[
[
"/path/to/home/.p0",
"{
"credential": {
"access_token": "test-access-token",
"id_token": "test-id-token",
"token_type": "oidc",
"scope": "oidc",
"expires_in": 3600,
"refresh_token": "test-refresh-token",
"device_secret": "test-device-secret",
"expires_at": 1600003599
},
"org": {
"slug": "test-org",
"tenantId": "test-tenant",
"ssoProvider": "google"
}
}",
{
"mode": "600",
},
],
]
`;

exports[`login organization exists validates authentication 1`] = `
[
[
{
"tenantId": "test-tenant",
},
"test-credential",
],
]
`;
66 changes: 66 additions & 0 deletions src/commands/__tests__/login.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { pluginLoginMap } from "../../plugins/login";
import { mockGetDoc } from "../../testing/firestore";
import { login } from "../login";
import { signInWithCredential } from "firebase/auth";
import { readFile, writeFile } from "fs/promises";

jest.spyOn(Date, "now").mockReturnValue(1.6e12);
jest.mock("fs/promises");
jest.mock("../../drivers/auth", () => ({
...jest.requireActual("../../drivers/auth"),
IDENTITY_FILE_PATH: "/path/to/home/.p0",
}));
jest.mock("../../drivers/stdio");
jest.mock("../../plugins/login");

const mockReadFile = readFile as jest.Mock;
const mockWriteFile = writeFile as jest.Mock;

describe("login", () => {
it("prints a friendly error if the org is not found", async () => {
mockGetDoc(undefined);
await expect(login({ org: "test-org" })).rejects.toMatchInlineSnapshot(
`"Could not find organization"`
);
});
it("should print a friendly error if unsupported login", async () => {
mockGetDoc({
slug: "test-org",
tenantId: "test-tenant",
ssoProvider: "microsoft",
});
await expect(login({ org: "test-org" })).rejects.toMatchInlineSnapshot(
`"Unsupported login for your organization"`
);
});
describe("organization exists", () => {
let credentialData: string = "";
mockReadFile.mockImplementation(async () =>
Buffer.from(credentialData, "utf-8")
);
mockWriteFile.mockImplementation(async (_path, data) => {
credentialData = data;
});
beforeEach(() => {
credentialData = "";
jest.clearAllMocks();
mockGetDoc({
slug: "test-org",
tenantId: "test-tenant",
ssoProvider: "google",
});
});
it("should call the provider's login function", async () => {
await login({ org: "test-org" });
expect(pluginLoginMap.google).toHaveBeenCalled();
});
it("should write the user's identity to the file system", async () => {
await login({ org: "test-org" });
expect(mockWriteFile.mock.calls).toMatchSnapshot();
});
it("validates authentication", async () => {
await login({ org: "test-org" });
expect((signInWithCredential as jest.Mock).mock.calls).toMatchSnapshot();
});
});
});
10 changes: 2 additions & 8 deletions src/commands/__tests__/ls.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { fetchCommand } from "../../drivers/api";
import { print1, print2 } from "../../drivers/stdio";
import { failure } from "../../testing/yargs";
import { lsCommand } from "../ls";
import yargs from "yargs";

Expand Down Expand Up @@ -51,14 +52,7 @@ Unknown argument: foo`,
});

it("should print error message", async () => {
let error: any;
try {
await lsCommand(yargs)
.fail((_, err) => (error = err))
.parse(command);
} catch (thrown: any) {
error = thrown;
}
const error = await failure(lsCommand(yargs), command);
expect(error).toMatchSnapshot();
});
});
Expand Down
10 changes: 2 additions & 8 deletions src/commands/__tests__/request.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { fetchCommand } from "../../drivers/api";
import { print1, print2 } from "../../drivers/stdio";
import { failure } from "../../testing/yargs";
import { RequestResponse } from "../../types/request";
import { sleep } from "../../util";
import { requestCommand } from "../request";
Expand Down Expand Up @@ -83,14 +84,7 @@ Unknown argument: foo`,
});

it("should print error message", async () => {
let error: any;
try {
await requestCommand(yargs)
.fail((_, err) => (error = err))
.parse(command);
} catch (thrown: any) {
error = thrown;
}
const error = await failure(requestCommand(yargs), command);
expect(error).toMatchSnapshot();
});
});
Expand Down
5 changes: 5 additions & 0 deletions src/commands/aws/__tests__/__input__/saml-response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const samlResponse = `<html>
<body>
<input name="SAMLResponse" type="hidden" value="PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KIDxzYW1sMnA6UmVzcG9uc2UgeG1sbnM6c2FtbDJwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgRGVzdGluYXRpb249Imh0dHBzOi8vc2lnbmluLmF3cy5hbWF6b24uY29tL3NhbWwiIElEPSJhYmMiIElzc3VlSW5zdGFudD0iMjAyNC0wMS0wMVQwMDowMDowMC4wMDBaIiBWZXJzaW9uPSIyLjAiPgogIDxzYW1sMjpJc3N1ZXIgeG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6ZW50aXR5Ij5odHRwOi8vd3d3Lm9rdGEuY29tL2FiY2RlZjwvc2FtbDI6SXNzdWVyPgogIDxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPgogICAgPGRzOlNpZ25lZEluZm8+CiAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+CiAgICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CiAgICAgIDxkczpSZWZlcmVuY2UgVVJJPSIjYWJjIj4KICAgICAgICA8ZHM6VHJhbnNmb3Jtcz4KICAgICAgICAgIDxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPgogICAgICAgICAgPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyI+CiAgICAgICAgICAgIDxlYzpJbmNsdXNpdmVOYW1lc3BhY2VzIHhtbG5zOmVjPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiIFByZWZpeExpc3Q9InhzIi8+CiAgICAgICAgICA8L2RzOlRyYW5zZm9ybT4KICAgICAgICA8L2RzOlRyYW5zZm9ybXM+CiAgICAgICAgPGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3NoYTI1NiIvPgogICAgICAgIDxkczpEaWdlc3RWYWx1ZT5kaWdlc3Q8L2RzOkRpZ2VzdFZhbHVlPgogICAgICA8L2RzOlJlZmVyZW5jZT4KICAgIDwvZHM6U2lnbmVkSW5mbz4KICAgIDxkczpTaWduYXR1cmVWYWx1ZT5zaWduYXR1cmU8L2RzOlNpZ25hdHVyZVZhbHVlPgogICAgPGRzOktleUluZm8+CiAgICAgIDxkczpYNTA5RGF0YT4KICAgICAgICA8ZHM6WDUwOUNlcnRpZmljYXRlPmNlcnRpZmljYXRlPC9kczpYNTA5Q2VydGlmaWNhdGU+CiAgICAgIDwvZHM6WDUwOURhdGE+CiAgICA8L2RzOktleUluZm8+CiAgPC9kczpTaWduYXR1cmU+CiAgPHNhbWwycDpTdGF0dXMgeG1sbnM6c2FtbDJwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiPgogICAgPHNhbWwycDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz4KICA8L3NhbWwycDpTdGF0dXM+CiAgPHNhbWwyOkFzc2VydGlvbiB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiBJRD0iYWJjIiBJc3N1ZUluc3RhbnQ9IjIwMjQtMDEtMDFUMDA6MDA6MDAuMDAwWiIgVmVyc2lvbj0iMi4wIj4KICAgIDxzYW1sMjpJc3N1ZXIgeG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6ZW50aXR5Ij5odHRwOi8vd3d3Lm9rdGEuY29tL2FiY2RlZjwvc2FtbDI6SXNzdWVyPgogICAgPGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgICAgIDxkczpTaWduZWRJbmZvPgogICAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+CiAgICAgICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZHNpZy1tb3JlI3JzYS1zaGEyNTYiLz4KICAgICAgICA8ZHM6UmVmZXJlbmNlIFVSST0iI2lkODQ3NzcyOTk3NzUzMjMwMTkyNzA4ODcwOCI+CiAgICAgICAgICA8ZHM6VHJhbnNmb3Jtcz4KICAgICAgICAgICAgPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+CiAgICAgICAgICAgIDxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiPgogICAgICAgICAgICAgIDxlYzpJbmNsdXNpdmVOYW1lc3BhY2VzIHhtbG5zOmVjPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiIFByZWZpeExpc3Q9InhzIi8+CiAgICAgICAgICAgIDwvZHM6VHJhbnNmb3JtPgogICAgICAgICAgPC9kczpUcmFuc2Zvcm1zPgogICAgICAgICAgPGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3NoYTI1NiIvPgogICAgICAgICAgPGRzOkRpZ2VzdFZhbHVlPmRpZ2VzdDwvZHM6RGlnZXN0VmFsdWU+CiAgICAgICAgPC9kczpSZWZlcmVuY2U+CiAgICAgIDwvZHM6U2lnbmVkSW5mbz4KICAgICAgPGRzOlNpZ25hdHVyZVZhbHVlPnNpZ25hdHVyZTwvZHM6U2lnbmF0dXJlVmFsdWU+CiAgICAgIDxkczpLZXlJbmZvPgogICAgICAgIDxkczpYNTA5RGF0YT4KICAgICAgICAgIDxkczpYNTA5Q2VydGlmaWNhdGU+Y2VydGlmaWNhdGU8L2RzOlg1MDlDZXJ0aWZpY2F0ZT4KICAgICAgICA8L2RzOlg1MDlEYXRhPgogICAgICA8L2RzOktleUluZm8+CiAgICA8L2RzOlNpZ25hdHVyZT4KICAgIDxzYW1sMjpTdWJqZWN0IHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj4KICAgICAgPHNhbWwyOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OnVuc3BlY2lmaWVkIj50ZXN0LXVzZXJAdGVzdC5jb208L3NhbWwyOk5hbWVJRD4KICAgICAgPHNhbWwyOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj4KICAgICAgICA8c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDI0LTAxLTAxVDAwOjAwOjAwLjAwMFoiIFJlY2lwaWVudD0iaHR0cHM6Ly9zaWduaW4uYXdzLmFtYXpvbi5jb20vc2FtbCIvPgogICAgICA8L3NhbWwyOlN1YmplY3RDb25maXJtYXRpb24+CiAgICA8L3NhbWwyOlN1YmplY3Q+CiAgICA8c2FtbDI6Q29uZGl0aW9ucyB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgTm90QmVmb3JlPSIyMDI0LTAxLTAxVDAwOjAwOjAwLjAwMFoiIE5vdE9uT3JBZnRlcj0iMjAyNC0wMS0wMVQwMDowMDowMC4wMDBaIj4KICAgICAgPHNhbWwyOkF1ZGllbmNlUmVzdHJpY3Rpb24+CiAgICAgICAgPHNhbWwyOkF1ZGllbmNlPnVybjphbWF6b246d2Vic2VydmljZXM8L3NhbWwyOkF1ZGllbmNlPgogICAgICA8L3NhbWwyOkF1ZGllbmNlUmVzdHJpY3Rpb24+CiAgICA8L3NhbWwyOkNvbmRpdGlvbnM+CiAgICA8c2FtbDI6QXV0aG5TdGF0ZW1lbnQgeG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIEF1dGhuSW5zdGFudD0iMjAyNC0wMS0wMVQwMDowMDowMC4wMDBaIiBTZXNzaW9uSW5kZXg9ImFiYyI+CiAgICAgIDxzYW1sMjpBdXRobkNvbnRleHQ+CiAgICAgICAgPHNhbWwyOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkUHJvdGVjdGVkVHJhbnNwb3J0PC9zYW1sMjpBdXRobkNvbnRleHRDbGFzc1JlZj4KICAgICAgPC9zYW1sMjpBdXRobkNvbnRleHQ+CiAgICA8L3NhbWwyOkF1dGhuU3RhdGVtZW50PgogICAgPHNhbWwyOkF0dHJpYnV0ZVN0YXRlbWVudCB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI+CiAgICAgIDxzYW1sMjpBdHRyaWJ1dGUgTmFtZT0iaHR0cHM6Ly9hd3MuYW1hem9uLmNvbS9TQU1ML0F0dHJpYnV0ZXMvUm9sZSIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDp1cmkiPgogICAgICAgIDxzYW1sMjpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPmFybjphd3M6aWFtOjoxOnNhbWwtcHJvdmlkZXIvdGVzdF9va3RhLGFybjphd3M6aWFtOjoxOnJvbGUvUm9sZTE8L3NhbWwyOkF0dHJpYnV0ZVZhbHVlPgogICAgICAgIDxzYW1sMjpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPmFybjphd3M6aWFtOjoxOnNhbWwtcHJvdmlkZXIvdGVzdF9va3RhLGFybjphd3M6aWFtOjoxOnJvbGUvUm9sZTI8L3NhbWwyOkF0dHJpYnV0ZVZhbHVlPgogICAgICA8L3NhbWwyOkF0dHJpYnV0ZT4KICAgICAgPHNhbWwyOkF0dHJpYnV0ZSBOYW1lPSJodHRwczovL2F3cy5hbWF6b24uY29tL1NBTUwvQXR0cmlidXRlcy9Sb2xlU2Vzc2lvbk5hbWUiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPgogICAgICAgIDxzYW1sMjpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPnRlc3QtdXNlckB0ZXN0LmNvbTwvc2FtbDI6QXR0cmlidXRlVmFsdWU+CiAgICAgIDwvc2FtbDI6QXR0cmlidXRlPgogICAgICA8c2FtbDI6QXR0cmlidXRlIE5hbWU9Imh0dHBzOi8vYXdzLmFtYXpvbi5jb20vU0FNTC9BdHRyaWJ1dGVzL1Nlc3Npb25EdXJhdGlvbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+CiAgICAgICAgPHNhbWwyOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+MzYwMDwvc2FtbDI6QXR0cmlidXRlVmFsdWU+CiAgICAgIDwvc2FtbDI6QXR0cmlidXRlPgogICAgICA8c2FtbDI6QXR0cmlidXRlIE5hbWU9Imh0dHBzOi8vYXdzLmFtYXpvbi5jb20vU0FNTC9BdHRyaWJ1dGVzL1ByaW5jaXBhbFRhZzpvcmciIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPgogICAgICAgIDxzYW1sMjpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPnRlc3Q8L3NhbWwyOkF0dHJpYnV0ZVZhbHVlPgogICAgICA8L3NhbWwyOkF0dHJpYnV0ZT4KICAgICAgPHNhbWwyOkF0dHJpYnV0ZSBOYW1lPSJodHRwczovL2F3cy5hbWF6b24uY29tL1NBTUwvQXR0cmlidXRlcy9Tb3VyY2VJZGVudGl0eSIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDp1cmkiPgogICAgICAgIDxzYW1sMjpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPnRlc3QtdXNlckB0ZXN0LmNvbTwvc2FtbDI6QXR0cmlidXRlVmFsdWU+CiAgICAgIDwvc2FtbDI6QXR0cmlidXRlPgogICAgPC9zYW1sMjpBdHRyaWJ1dGVTdGF0ZW1lbnQ+CiAgPC9zYW1sMjpBc3NlcnRpb24+Cjwvc2FtbDJwOlJlc3BvbnNlPg==" />
</body>
</html>`;
24 changes: 24 additions & 0 deletions src/commands/aws/__tests__/__input__/sts-response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export const stsResponse = `<AssumeRoleWithSAMLResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
<AssumeRoleWithSAMLResult>
<Audience>https://signin.aws.amazon.com/saml</Audience>
<AssumedRoleUser>
<AssumedRoleId>ABCDEFGHIJLMNOPQRST:test-user@test.com</AssumedRoleId>
<Arn>arn:aws:sts::1:assumed-role/Role1/test-user@test.com</Arn>
</AssumedRoleUser>
<Credentials>
<AccessKeyId>test-access-key</AccessKeyId>
<SecretAccessKey>secret-access-key</SecretAccessKey>
<SessionToken>session-token</SessionToken>
<Expiration>2024-02-22T00:18:21Z</Expiration>
</Credentials>
<Subject>test-user@test.com</Subject>
<NameQualifier>abcdefghijklmnop</NameQualifier>
<SourceIdentity>test-user@test.com</SourceIdentity>
<PackedPolicySize>2</PackedPolicySize>
<SubjectType>unspecified</SubjectType>
<Issuer>http://www.okta.com/abc</Issuer>
</AssumeRoleWithSAMLResult>
<ResponseMetadata>
<RequestId>f5b94ad4-f322-4d7b-b568-84f2ec184cd7</RequestId>
</ResponseMetadata>
</AssumeRoleWithSAMLResponse>`;
44 changes: 44 additions & 0 deletions src/commands/aws/__tests__/__snapshots__/role.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`aws role a single installed account with Okta SAML assume should assume a role 1`] = `
[
[
"Execute the following commands:
",
],
[
"
Or, populate these environment variables using BASH command substitution:
$(p0 aws role assume undefined)
",
],
]
`;

exports[`aws role a single installed account with Okta SAML assume should assume a role 2`] = `
[
[
" export AWS_ACCESS_KEY_ID=test-access-key
export AWS_SECRET_ACCESS_KEY=secret-access-key
export AWS_SESSION_TOKEN=session-token",
],
]
`;

exports[`aws role a single installed account with Okta SAML ls lists roles 1`] = `
[
[
"Your available roles for account 1:",
],
]
`;

exports[`aws role a single installed account with Okta SAML ls lists roles 2`] = `
[
[
" Role1
Role2",
],
]
`;
86 changes: 86 additions & 0 deletions src/commands/aws/__tests__/role.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { awsCommand } from "..";
import { print1, print2 } from "../../../drivers/stdio";
import { mockGetDoc } from "../../../testing/firestore";
import { failure } from "../../../testing/yargs";
import { samlResponse } from "./__input__/saml-response";
import { stsResponse } from "./__input__/sts-response";
import yargs from "yargs";

jest.mock("fs/promises");
jest.mock("../../../drivers/auth");
jest.mock("../../../drivers/stdio");
jest.mock("typescript", () => ({
...jest.requireActual("typescript"),
sys: {
writeOutputIsTTY: () => true,
},
}));

const mockFetch = jest.spyOn(global, "fetch");
const mockPrint1 = print1 as jest.Mock;
const mockPrint2 = print2 as jest.Mock;

beforeEach(() => {
jest.clearAllMocks();
mockFetch.mockImplementation(
async (url: RequestInfo | URL) =>
({
ok: true,
// This is the token response from fetchSsoWebToken
json: async () => ({}),
// This is the XML response from fetchSamlResponse or stsAssumeRole
text: async () =>
(url as string).match(/okta.com/) ? samlResponse : stsResponse,
}) as Response
);
});

describe("aws role", () => {
describe("a single installed account", () => {
const item = {
account: {
id: "1",
description: "1 (test)",
},
state: "installed",
};
describe("without Okta SAML", () => {
mockGetDoc({ workflows: { items: [item] } });
describe.each([
["ls", "aws role ls"],
["assume", "aws role assume Role1"],
])("%s", (_, command) => {
it("should print a friendly error message", async () => {
const error = await failure(awsCommand(yargs), command);
expect(error).toMatchInlineSnapshot(
`"Account 1 (test) is not configured for Okta SAML login."`
);
});
});
});
describe("with Okta SAML", () => {
beforeEach(() => {
mockGetDoc({
workflows: {
items: [{ ...item, uidLocation: { id: "okta_saml_sso" } }],
},
});
});
describe("assume", () => {
it("should assume a role", async () => {
await awsCommand(yargs).parse("aws role assume Role1");
expect(mockPrint2.mock.calls).toMatchSnapshot();
expect(mockPrint1.mock.calls).toMatchSnapshot();
});
});
describe("ls", () => {
const command = "aws role ls";
it("lists roles", async () => {
await awsCommand(yargs).parse(command);
expect(mockPrint2.mock.calls).toMatchSnapshot();
expect(mockPrint1.mock.calls).toMatchSnapshot();
});
});
});
});
});
Loading

0 comments on commit 5841f22

Please sign in to comment.