@@ -213,4 +213,197 @@ suite("cloud/commands/deploy", () => {
213213 )
214214 assert . ok ( fetchStub . calledOnce )
215215 } )
216+
217+ test ( "shows dashboard link on deployment failure" , async ( ) => {
218+ sinon . stub ( vscode . authentication , "getSession" ) . resolves ( {
219+ accessToken : "test-token" ,
220+ account : { id : "test-id" , label : "test-user" } ,
221+ id : "session-id" ,
222+ scopes : [ ] ,
223+ } )
224+ const statusBarItem = { text : "" } as vscode . StatusBarItem
225+ const workspaceRoot = vscode . Uri . file ( "/test/workspace" )
226+
227+ const configService = mockConfigService ( )
228+ configService . getConfig . resolves ( { app_id : "app123" , team_id : "team123" } )
229+
230+ const mockDeployment : Deployment = {
231+ id : "deploy123" ,
232+ slug : "deploy-slug" ,
233+ status : DeploymentStatus . waiting_upload ,
234+ url : "https://app.example.com" ,
235+ dashboard_url :
236+ "https://dashboard.fastapicloud.com/team-slug/apps/my-app/deployments" ,
237+ }
238+ const mockFailedDeployment : Deployment = {
239+ ...mockDeployment ,
240+ status : DeploymentStatus . building_image_failed ,
241+ }
242+
243+ const apiService = mockApiService ( )
244+ apiService . createDeployment . resolves ( mockDeployment )
245+ apiService . getUploadUrl . resolves ( {
246+ url : "https://s3.example.com" ,
247+ fields : { } ,
248+ } )
249+ apiService . completeUpload . resolves ( )
250+ apiService . getDeployment . resolves ( mockFailedDeployment )
251+
252+ sinon
253+ . stub ( vscode . workspace , "findFiles" )
254+ . resolves ( [ vscode . Uri . file ( "/test/workspace/main.py" ) ] )
255+ const fs = stubFs ( )
256+ fs . fake . readFile . resolves ( new Uint8Array ( [ 1 , 2 , 3 ] ) )
257+ sinon . stub ( global , "fetch" ) . resolves ( { ok : true , status : 200 } as Response )
258+
259+ const openExternalStub = sinon
260+ . stub ( vscode . env , "openExternal" )
261+ . resolves ( true )
262+ const errorMessageStub = sinon
263+ . stub ( vscode . window , "showErrorMessage" )
264+ . resolves ( "View Dashboard" as any )
265+
266+ const result = await deploy ( {
267+ workspaceRoot,
268+ configService,
269+ apiService,
270+ statusBarItem,
271+ } )
272+
273+ assert . strictEqual ( result , false )
274+ assert . strictEqual ( statusBarItem . text , "$(cloud) Deploy failed" )
275+ assert . ok ( errorMessageStub . calledOnce )
276+ assert . strictEqual ( errorMessageStub . firstCall . args [ 0 ] , "Deployment failed." )
277+ assert . ok ( openExternalStub . calledOnce )
278+ assert . strictEqual (
279+ openExternalStub . firstCall . args [ 0 ] . toString ( ) ,
280+ "https://dashboard.fastapicloud.com/team-slug/apps/my-app/deployments" ,
281+ )
282+ } )
283+
284+ test ( "does not open dashboard when user dismisses failure dialog" , async ( ) => {
285+ sinon . stub ( vscode . authentication , "getSession" ) . resolves ( {
286+ accessToken : "test-token" ,
287+ account : { id : "test-id" , label : "test-user" } ,
288+ id : "session-id" ,
289+ scopes : [ ] ,
290+ } )
291+ const statusBarItem = { text : "" } as vscode . StatusBarItem
292+ const workspaceRoot = vscode . Uri . file ( "/test/workspace" )
293+
294+ const configService = mockConfigService ( )
295+ configService . getConfig . resolves ( { app_id : "app123" , team_id : "team123" } )
296+
297+ const mockDeployment : Deployment = {
298+ id : "deploy123" ,
299+ slug : "deploy-slug" ,
300+ status : DeploymentStatus . waiting_upload ,
301+ url : "https://app.example.com" ,
302+ dashboard_url :
303+ "https://dashboard.fastapicloud.com/team-slug/apps/my-app/deployments" ,
304+ }
305+
306+ const apiService = mockApiService ( )
307+ apiService . createDeployment . resolves ( mockDeployment )
308+ apiService . getUploadUrl . resolves ( {
309+ url : "https://s3.example.com" ,
310+ fields : { } ,
311+ } )
312+ apiService . completeUpload . resolves ( )
313+ apiService . getDeployment . resolves ( {
314+ ...mockDeployment ,
315+ status : DeploymentStatus . building_image_failed ,
316+ } )
317+
318+ sinon
319+ . stub ( vscode . workspace , "findFiles" )
320+ . resolves ( [ vscode . Uri . file ( "/test/workspace/main.py" ) ] )
321+ const fs = stubFs ( )
322+ fs . fake . readFile . resolves ( new Uint8Array ( [ 1 , 2 , 3 ] ) )
323+ sinon . stub ( global , "fetch" ) . resolves ( { ok : true , status : 200 } as Response )
324+
325+ const openExternalStub = sinon
326+ . stub ( vscode . env , "openExternal" )
327+ . resolves ( true )
328+ sinon . stub ( vscode . window , "showErrorMessage" ) . resolves ( undefined as any )
329+
330+ const result = await deploy ( {
331+ workspaceRoot,
332+ configService,
333+ apiService,
334+ statusBarItem,
335+ } )
336+
337+ assert . strictEqual ( result , false )
338+ assert . ok ( openExternalStub . notCalled )
339+ } )
340+
341+ test ( "does not open dashboard on poll timeout" , async ( ) => {
342+ const clock = sinon . useFakeTimers ( { shouldClearNativeTimers : true } )
343+
344+ sinon . stub ( vscode . authentication , "getSession" ) . resolves ( {
345+ accessToken : "test-token" ,
346+ account : { id : "test-id" , label : "test-user" } ,
347+ id : "session-id" ,
348+ scopes : [ ] ,
349+ } )
350+ const statusBarItem = { text : "" } as vscode . StatusBarItem
351+ const workspaceRoot = vscode . Uri . file ( "/test/workspace" )
352+
353+ const configService = mockConfigService ( )
354+ configService . getConfig . resolves ( { app_id : "app123" , team_id : "team123" } )
355+
356+ const mockDeployment : Deployment = {
357+ id : "deploy123" ,
358+ slug : "deploy-slug" ,
359+ status : DeploymentStatus . waiting_upload ,
360+ url : "https://app.example.com" ,
361+ dashboard_url :
362+ "https://dashboard.fastapicloud.com/team-slug/apps/my-app/deployments" ,
363+ }
364+
365+ const apiService = mockApiService ( )
366+ apiService . createDeployment . resolves ( mockDeployment )
367+ apiService . getUploadUrl . resolves ( {
368+ url : "https://s3.example.com" ,
369+ fields : { } ,
370+ } )
371+ apiService . completeUpload . resolves ( )
372+ apiService . getDeployment . resolves ( {
373+ ...mockDeployment ,
374+ status : DeploymentStatus . building ,
375+ } )
376+
377+ sinon
378+ . stub ( vscode . workspace , "findFiles" )
379+ . resolves ( [ vscode . Uri . file ( "/test/workspace/main.py" ) ] )
380+ const fs = stubFs ( )
381+ fs . fake . readFile . resolves ( new Uint8Array ( [ 1 , 2 , 3 ] ) )
382+ sinon . stub ( global , "fetch" ) . resolves ( { ok : true , status : 200 } as Response )
383+
384+ const openExternalStub = sinon
385+ . stub ( vscode . env , "openExternal" )
386+ . resolves ( true )
387+ sinon
388+ . stub ( vscode . window , "showErrorMessage" )
389+ . resolves ( "View Dashboard" as any )
390+
391+ const resultPromise = deploy ( {
392+ workspaceRoot,
393+ configService,
394+ apiService,
395+ statusBarItem,
396+ } )
397+
398+ // 300 polls x 2000ms = 600000ms
399+ await clock . tickAsync ( 600_000 )
400+
401+ const result = await resultPromise
402+
403+ assert . strictEqual ( result , false )
404+ assert . strictEqual ( statusBarItem . text , "$(cloud) Deploy failed" )
405+ assert . ok ( openExternalStub . notCalled )
406+
407+ clock . restore ( )
408+ } )
216409} )
0 commit comments