Skip to content

Commit 5b8a5cd

Browse files
authored
Merge branch 'main' into komal/npm-publish
2 parents 0dd66da + 89d5b2b commit 5b8a5cd

File tree

4 files changed

+128
-2
lines changed

4 files changed

+128
-2
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`ssh ephemeral access should call ssm: stderr 1`] = `
4+
[
5+
[
6+
"a message",
7+
],
8+
[
9+
"Will wait up to 5 minutes for this request to complete...",
10+
],
11+
[
12+
"Your request was approved",
13+
],
14+
[
15+
"Waiting for access to be provisioned",
16+
],
17+
]
18+
`;
19+
20+
exports[`ssh persistent access should call ssm: stderr 1`] = `
21+
[
22+
[
23+
"Waiting for access to be provisioned",
24+
],
25+
]
26+
`;

src/commands/__tests__/ssh.test.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { fetchCommand } from "../../drivers/api";
2+
import { print1, print2 } from "../../drivers/stdio";
3+
import { ssm } from "../../plugins/aws/ssm";
4+
import { mockGetDoc } from "../../testing/firestore";
5+
import { sleep } from "../../util";
6+
import { sshCommand } from "../ssh";
7+
import { onSnapshot } from "firebase/firestore";
8+
import yargs from "yargs";
9+
10+
jest.mock("../../drivers/api");
11+
jest.mock("../../drivers/auth");
12+
jest.mock("../../drivers/stdio");
13+
jest.mock("../../plugins/aws/ssm");
14+
15+
const mockFetchCommand = fetchCommand as jest.Mock;
16+
const mockSsm = ssm as jest.Mock;
17+
const mockPrint1 = print1 as jest.Mock;
18+
const mockPrint2 = print2 as jest.Mock;
19+
20+
mockGetDoc({
21+
workflows: {
22+
items: [
23+
{
24+
identifier: "test-account",
25+
state: "installed",
26+
type: "aws",
27+
},
28+
],
29+
},
30+
});
31+
32+
mockSsm.mockResolvedValue({});
33+
34+
describe("ssh", () => {
35+
describe.each([
36+
["persistent", true],
37+
["ephemeral", false],
38+
])("%s access", (_, isPersistent) => {
39+
beforeEach(() => {
40+
jest.clearAllMocks();
41+
mockFetchCommand.mockResolvedValue({
42+
ok: true,
43+
message: "a message",
44+
id: "abcefg",
45+
isPreexisting: false,
46+
isPersistent,
47+
});
48+
});
49+
50+
it("should wait for access grant", async () => {
51+
const promise = sshCommand(yargs()).parse(`ssh some-instance`);
52+
const wait = sleep(100);
53+
await Promise.race([wait, promise]);
54+
await expect(wait).resolves.toBeUndefined();
55+
});
56+
57+
it("should wait for provisioning", async () => {
58+
const promise = sshCommand(yargs()).parse(`ssh some-instance`);
59+
await sleep(100); // Need to wait for listen before trigger in tests
60+
(onSnapshot as any).trigger({
61+
status: "APPROVED",
62+
});
63+
const wait = sleep(100);
64+
await Promise.race([wait, promise]);
65+
await expect(wait).resolves.toBeUndefined();
66+
});
67+
68+
it("should call ssm", async () => {
69+
const promise = sshCommand(yargs()).parse(`ssh some-instance`);
70+
await sleep(100); // Need to wait for listen before trigger in tests
71+
(onSnapshot as any).trigger({
72+
status: "APPROVED",
73+
});
74+
await sleep(100); // Need to wait for listen before trigger in tests
75+
(onSnapshot as any).trigger({
76+
status: "DONE",
77+
});
78+
await expect(promise).resolves.toBeDefined();
79+
expect(mockPrint2.mock.calls).toMatchSnapshot("stderr");
80+
expect(mockPrint1).not.toHaveBeenCalled();
81+
expect(mockSsm).toHaveBeenCalled();
82+
});
83+
});
84+
});

src/commands/ssh.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ const waitForProvisioning = async <P extends PluginRequest>(
7777
unsubscribe();
7878
}
7979
);
80+
// Skip timeout in test; it holds a ref longer than the test lasts
81+
if (process.env.NODE_ENV === "test") return;
8082
cancel = setTimeout(() => {
8183
if (!isResolved) {
8284
unsubscribe();

src/util.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
1-
export const sleep = (timeoutMillis: number) =>
2-
new Promise<void>((resolve) => setTimeout(resolve, timeoutMillis));
1+
/** Waits the specified delay (in ms)
2+
*
3+
* The returned promise is cancelable:
4+
* ```
5+
* const wait = sleep(10);
6+
* ...
7+
* wait.cancel();
8+
* ```
9+
*/
10+
export const sleep = (timeoutMillis: number) => {
11+
let timer: NodeJS.Timeout | undefined = undefined;
12+
const promise = new Promise<void>((resolve) => {
13+
timer = setTimeout(resolve, timeoutMillis);
14+
});
15+
return Object.assign(promise, { cancel: () => clearTimeout(timer) });
16+
};
317

418
export const noop = () => {};
519

0 commit comments

Comments
 (0)