8
8
} from "." ;
9
9
import dedent from "dedent" ;
10
10
import fs from "fs/promises" ;
11
+ import { unwrap } from "./utils" ;
11
12
12
13
class TestLogger implements Logger {
13
14
logs : string [ ] = [ ] ;
@@ -34,20 +35,44 @@ interface ActionParams {
34
35
}
35
36
36
37
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
+
37
62
const action = new StartWorkspaceAction (
38
63
params ?. logger ?? new TestLogger ( ) ,
39
64
params ?. quietExec ?? true ,
40
65
{
41
66
githubUsername : "github-user" ,
42
- coderUsername : "coder-user" ,
67
+ coderUsername : undefined ,
43
68
coderUrl : "https://example.com" ,
44
69
coderToken : "coder-token" ,
45
70
workspaceName : "workspace-name" ,
46
71
githubStatusCommentId : 123 ,
47
72
githubRepoOwner : "github-repo-owner" ,
48
73
githubRepoName : "github-repo-name" ,
49
74
githubToken : "github-token" ,
50
- githubWorkflowRunUrl : "https://example .com/workflow-run" ,
75
+ githubWorkflowRunUrl : "https://github .com/workflow-run" ,
51
76
templateName : "ubuntu" ,
52
77
workspaceParameters : dedent `
53
78
key: value
@@ -201,4 +226,170 @@ describe("StartWorkspaceAction", () => {
201
226
"bash -c \"yes '' || true\" | coder create --yes --template ubuntu hugo/test-workspace --rich-parameter-file /tmp/coder-parameters-123.yml"
202
227
) ;
203
228
} ) ;
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
+ / ^ \/ t m p \/ c o d e r - p a r a m e t e r s - \d + \. y m l $ /
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
+ } ) ;
204
395
} ) ;
0 commit comments