Skip to content

Commit df9129d

Browse files
committed
execute tests
1 parent b2adebf commit df9129d

File tree

4 files changed

+223
-17
lines changed

4 files changed

+223
-17
lines changed

dist/index.js

+10-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Source hash: e8cd3b171b3e2dec0d6b326f6c882aee3964a5aaa1075e6d967444ed1228a86c
1+
// Source hash: 33b5220d26be2ad53ce68484968fb534f4b30e903214d53708f3403cf579cf1d
22
import { createRequire as createRequire2 } from "node:module";
33
var __create = Object.create;
44
var __getProtoOf = Object.getPrototypeOf;
@@ -25759,7 +25759,12 @@ class StartWorkspaceAction {
2575925759
assert(this.input.githubUsername, "GitHub username is required");
2576025760
this.logger.log(`Getting Coder username for GitHub user ${this.input.githubUsername}`);
2576125761
const userId = await this.githubGetUserIdFromUsername(this.input.githubUsername);
25762-
coderUsername = this.parseCoderUsersListOutput(await this.coderUsersList(userId));
25762+
try {
25763+
coderUsername = this.parseCoderUsersListOutput(await this.coderUsersList(userId));
25764+
} catch (error) {
25765+
const externalAuthPage = `${this.input.coderUrl}/settings/external-auth`;
25766+
throw new UserFacingError(`No matching Coder user found for GitHub user @${this.input.githubUsername}. Please connect your GitHub account with Coder: ${externalAuthPage}`);
25767+
}
2576325768
this.logger.log(`Coder username for GitHub user ${this.input.githubUsername} is ${coderUsername}`);
2576425769
} else {
2576525770
this.logger.log(`Using Coder username ${this.input.coderUsername}`);
@@ -25772,31 +25777,28 @@ class StartWorkspaceAction {
2577225777
commentId: this.input.githubStatusCommentId
2577325778
});
2577425779
commentBody = commentBody + `
25775-
Workspace will be available here: ${workspaceUrl}
25776-
25777-
`;
25780+
Workspace will be available here: ${workspaceUrl}`;
2577825781
await this.githubUpdateIssueComment({
2577925782
owner: this.input.githubRepoOwner,
2578025783
repo: this.input.githubRepoName,
2578125784
commentId: this.input.githubStatusCommentId,
2578225785
comment: commentBody
2578325786
});
2578425787
const parametersFilePath = await this.createParametersFile(this.input.workspaceParameters);
25785-
console.log("Starting workspace");
25788+
this.logger.log("Starting workspace");
2578625789
await this.coderStartWorkspace({
2578725790
coderUsername,
2578825791
templateName: this.input.templateName,
2578925792
workspaceName: this.input.workspaceName,
2579025793
parametersFilePath
2579125794
});
25792-
console.log("Workspace started");
25795+
this.logger.log("Workspace started");
2579325796
await fs3.unlink(parametersFilePath);
2579425797
await this.githubUpdateIssueComment({
2579525798
owner: this.input.githubRepoOwner,
2579625799
repo: this.input.githubRepoName,
2579725800
commentId: this.input.githubStatusCommentId,
2579825801
comment: `✅ Workspace started: ${workspaceUrl}
25799-
2580025802
View [Github Actions logs](${this.input.githubWorkflowRunUrl}).`
2580125803
});
2580225804
}

src/index.test.ts

+193-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from ".";
99
import dedent from "dedent";
1010
import fs from "fs/promises";
11+
import { unwrap } from "./utils";
1112

1213
class TestLogger implements Logger {
1314
logs: string[] = [];
@@ -34,20 +35,44 @@ interface ActionParams {
3435
}
3536

3637
const newAction = (params?: ActionParams) => {
38+
const defaults: ActionInput = {
39+
githubUsername: "github-user",
40+
coderUsername: "coder-user",
41+
coderUrl: "https://example.com",
42+
coderToken: "coder-token",
43+
workspaceName: "workspace-name",
44+
githubStatusCommentId: 123,
45+
githubRepoOwner: "github-repo-owner",
46+
githubRepoName: "github-repo-name",
47+
githubToken: "github-token",
48+
githubWorkflowRunUrl: "https://github.com/workflow-run",
49+
templateName: "ubuntu",
50+
workspaceParameters: dedent`
51+
key: value
52+
key2: value2
53+
key3: value3
54+
`.trim(),
55+
};
56+
// Loop through the input rather than use {...defaults, ...(params?.input ?? {})}
57+
// to also allow overriding defaults with undefined values
58+
for (const [key, value] of Object.entries(params?.input ?? {})) {
59+
(defaults as any)[key] = value;
60+
}
61+
3762
const action = new StartWorkspaceAction(
3863
params?.logger ?? new TestLogger(),
3964
params?.quietExec ?? true,
4065
{
4166
githubUsername: "github-user",
42-
coderUsername: "coder-user",
67+
coderUsername: undefined,
4368
coderUrl: "https://example.com",
4469
coderToken: "coder-token",
4570
workspaceName: "workspace-name",
4671
githubStatusCommentId: 123,
4772
githubRepoOwner: "github-repo-owner",
4873
githubRepoName: "github-repo-name",
4974
githubToken: "github-token",
50-
githubWorkflowRunUrl: "https://example.com/workflow-run",
75+
githubWorkflowRunUrl: "https://github.com/workflow-run",
5176
templateName: "ubuntu",
5277
workspaceParameters: dedent`
5378
key: value
@@ -201,4 +226,170 @@ describe("StartWorkspaceAction", () => {
201226
"bash -c \"yes '' || true\" | coder create --yes --template ubuntu hugo/test-workspace --rich-parameter-file /tmp/coder-parameters-123.yml"
202227
);
203228
});
229+
230+
describe("execute", () => {
231+
type MockForExecuteResult = {
232+
workspaceStarted: boolean;
233+
startWorkspaceArgs:
234+
| Parameters<
235+
typeof StartWorkspaceAction.prototype.coderStartWorkspace
236+
>[0]
237+
| undefined;
238+
issueComments: string[];
239+
error?: unknown;
240+
};
241+
242+
interface MockForExecuteParams {
243+
coderUsersList?: string;
244+
initialIssueComment?: string;
245+
githubUserId?: number;
246+
}
247+
248+
const mockForExecute = (
249+
action: StartWorkspaceAction,
250+
params: MockForExecuteParams
251+
): MockForExecuteResult => {
252+
const result: MockForExecuteResult = {
253+
workspaceStarted: false,
254+
startWorkspaceArgs: undefined,
255+
issueComments: [params.initialIssueComment ?? ""],
256+
};
257+
action.coderStartWorkspace = async (args) => {
258+
result.workspaceStarted = true;
259+
result.startWorkspaceArgs = args;
260+
return "";
261+
};
262+
action.coderUsersList = async () => {
263+
return params.coderUsersList ?? "";
264+
};
265+
action.githubGetIssueCommentBody = async () => {
266+
return unwrap(result.issueComments[result.issueComments.length - 1]);
267+
};
268+
action.githubUpdateIssueComment = async (args) => {
269+
result.issueComments.push(args.comment);
270+
};
271+
action.githubGetUserIdFromUsername = async () => {
272+
return params.githubUserId ?? 123;
273+
};
274+
275+
return result;
276+
};
277+
278+
const executeTest = async (
279+
actionParams: ActionParams,
280+
mockParams: MockForExecuteParams,
281+
expected: {
282+
issueComments: string[];
283+
workspaceStarted: boolean;
284+
startWorkspace?: {
285+
coderUsername: string;
286+
templateName: string;
287+
workspaceName: string;
288+
};
289+
}
290+
): Promise<MockForExecuteResult> => {
291+
const action = newAction(actionParams);
292+
const mock = mockForExecute(action, mockParams);
293+
try {
294+
await action.execute();
295+
} catch (error) {
296+
mock.error = error;
297+
}
298+
299+
expect(mock.issueComments).toEqual(expected.issueComments);
300+
expect(mock.workspaceStarted).toBe(expected.workspaceStarted);
301+
expect(mock.startWorkspaceArgs?.coderUsername).toBe(
302+
expected.startWorkspace?.coderUsername as any
303+
);
304+
expect(mock.startWorkspaceArgs?.templateName).toBe(
305+
expected.startWorkspace?.templateName as any
306+
);
307+
expect(mock.startWorkspaceArgs?.workspaceName).toBe(
308+
expected.startWorkspace?.workspaceName as any
309+
);
310+
if (mock.startWorkspaceArgs != null) {
311+
expect(mock.startWorkspaceArgs?.parametersFilePath).toMatch(
312+
/^\/tmp\/coder-parameters-\d+\.yml$/
313+
);
314+
}
315+
if (mock.workspaceStarted) {
316+
expect(() =>
317+
fs.stat(unwrap(mock.startWorkspaceArgs?.parametersFilePath))
318+
).toThrow("no such file or directory");
319+
}
320+
return mock;
321+
};
322+
323+
it("happy path", async () => {
324+
await executeTest(
325+
{},
326+
{
327+
coderUsersList: dedent`
328+
USERNAME
329+
hugo
330+
`.trim(),
331+
initialIssueComment: "Initial comment",
332+
githubUserId: 123,
333+
},
334+
{
335+
issueComments: [
336+
"Initial comment",
337+
"Initial comment\nWorkspace will be available here: https://example.com/hugo/workspace-name",
338+
"✅ Workspace started: https://example.com/hugo/workspace-name\nView [Github Actions logs](https://github.com/workflow-run).",
339+
],
340+
workspaceStarted: true,
341+
startWorkspace: {
342+
coderUsername: "hugo",
343+
templateName: "ubuntu",
344+
workspaceName: "workspace-name",
345+
},
346+
}
347+
);
348+
});
349+
350+
it("happy path with coder username", async () => {
351+
await executeTest(
352+
{
353+
input: { coderUsername: "hugo-coder", githubUsername: undefined },
354+
},
355+
{
356+
initialIssueComment: "Initial comment",
357+
},
358+
{
359+
issueComments: [
360+
"Initial comment",
361+
"Initial comment\nWorkspace will be available here: https://example.com/hugo-coder/workspace-name",
362+
"✅ Workspace started: https://example.com/hugo-coder/workspace-name\nView [Github Actions logs](https://github.com/workflow-run).",
363+
],
364+
workspaceStarted: true,
365+
startWorkspace: {
366+
coderUsername: "hugo-coder",
367+
templateName: "ubuntu",
368+
workspaceName: "workspace-name",
369+
},
370+
}
371+
);
372+
});
373+
374+
it("no username mapping", async () => {
375+
const mock = await executeTest(
376+
{
377+
input: { githubUsername: "hugo" },
378+
},
379+
{
380+
coderUsersList: dedent`
381+
USERNAME
382+
`.trim(),
383+
},
384+
{
385+
issueComments: [""],
386+
workspaceStarted: false,
387+
}
388+
);
389+
expect(mock.error).toBeInstanceOf(UserFacingError);
390+
expect((mock.error as any).message).toEqual(
391+
`No matching Coder user found for GitHub user @hugo. Please connect your GitHub account with Coder: https://example.com/settings/external-auth`
392+
);
393+
});
394+
});
204395
});

src/index.ts

+14-7
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,16 @@ export class StartWorkspaceAction {
183183
const userId = await this.githubGetUserIdFromUsername(
184184
this.input.githubUsername
185185
);
186-
coderUsername = this.parseCoderUsersListOutput(
187-
await this.coderUsersList(userId)
188-
);
186+
try {
187+
coderUsername = this.parseCoderUsersListOutput(
188+
await this.coderUsersList(userId)
189+
);
190+
} catch (error) {
191+
const externalAuthPage = `${this.input.coderUrl}/settings/external-auth`;
192+
throw new UserFacingError(
193+
`No matching Coder user found for GitHub user @${this.input.githubUsername}. Please connect your GitHub account with Coder: ${externalAuthPage}`
194+
);
195+
}
189196
this.logger.log(
190197
`Coder username for GitHub user ${this.input.githubUsername} is ${coderUsername}`
191198
);
@@ -205,7 +212,7 @@ export class StartWorkspaceAction {
205212
commentId: this.input.githubStatusCommentId,
206213
});
207214
commentBody =
208-
commentBody + `\nWorkspace will be available here: ${workspaceUrl}\n\n`;
215+
commentBody + `\nWorkspace will be available here: ${workspaceUrl}`;
209216

210217
await this.githubUpdateIssueComment({
211218
owner: this.input.githubRepoOwner,
@@ -217,20 +224,20 @@ export class StartWorkspaceAction {
217224
const parametersFilePath = await this.createParametersFile(
218225
this.input.workspaceParameters
219226
);
220-
console.log("Starting workspace");
227+
this.logger.log("Starting workspace");
221228
await this.coderStartWorkspace({
222229
coderUsername,
223230
templateName: this.input.templateName,
224231
workspaceName: this.input.workspaceName,
225232
parametersFilePath,
226233
});
227-
console.log("Workspace started");
234+
this.logger.log("Workspace started");
228235
await fs.unlink(parametersFilePath);
229236
await this.githubUpdateIssueComment({
230237
owner: this.input.githubRepoOwner,
231238
repo: this.input.githubRepoName,
232239
commentId: this.input.githubStatusCommentId,
233-
comment: `✅ Workspace started: ${workspaceUrl}\n\nView [Github Actions logs](${this.input.githubWorkflowRunUrl}).`,
240+
comment: `✅ Workspace started: ${workspaceUrl}\nView [Github Actions logs](${this.input.githubWorkflowRunUrl}).`,
234241
});
235242
}
236243
}

src/utils.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const unwrap = <T>(value: T | undefined | null): T => {
2+
if (value === undefined || value === null) {
3+
throw new Error(`Value is ${value}`);
4+
}
5+
return value;
6+
};

0 commit comments

Comments
 (0)