@@ -5,7 +5,7 @@ import { expect } from 'chai';
55import * as vscode from 'vscode' ;
66import * as sinon from 'sinon' ;
77import { addService } from '../../../commands/addService' ;
8- import { IActionContext } from '@microsoft/vscode-azext-utils' ;
8+ import { IActionContext , UserCancelledError } from '@microsoft/vscode-azext-utils' ;
99import { AzureDevCliModel } from '../../../views/workspace/AzureDevCliModel' ;
1010
1111suite ( 'addService' , ( ) => {
@@ -20,9 +20,18 @@ suite('addService', () => {
2020
2121 setup ( ( ) => {
2222 sandbox = sinon . createSandbox ( ) ;
23- mockContext = { } as IActionContext ;
24- showInputBoxStub = sandbox . stub ( vscode . window , 'showInputBox' ) ;
25- showQuickPickStub = sandbox . stub ( vscode . window , 'showQuickPick' ) ;
23+
24+ // Create stubs for context.ui methods
25+ showInputBoxStub = sandbox . stub ( ) ;
26+ showQuickPickStub = sandbox . stub ( ) ;
27+
28+ mockContext = {
29+ ui : {
30+ showInputBox : showInputBoxStub ,
31+ showQuickPick : showQuickPickStub
32+ }
33+ } as unknown as IActionContext ;
34+
2635 showErrorMessageStub = sandbox . stub ( vscode . window , 'showErrorMessage' ) ;
2736 showInformationMessageStub = sandbox . stub ( vscode . window , 'showInformationMessage' ) ;
2837 openTextDocumentStub = sandbox . stub ( vscode . workspace , 'openTextDocument' ) ;
@@ -33,16 +42,21 @@ suite('addService', () => {
3342 sandbox . restore ( ) ;
3443 } ) ;
3544
36- test ( 'returns early when user cancels service name input' , async ( ) => {
45+ test ( 'throws UserCancelledError when user cancels service name input' , async ( ) => {
3746 const mockNode = {
3847 context : {
3948 configurationFile : vscode . Uri . file ( '/test/azure.yaml' )
4049 }
4150 } as AzureDevCliModel ;
4251
43- showInputBoxStub . resolves ( undefined ) ;
52+ showInputBoxStub . rejects ( new UserCancelledError ( ) ) ;
4453
45- await addService ( mockContext , mockNode ) ;
54+ try {
55+ await addService ( mockContext , mockNode ) ;
56+ expect . fail ( 'Should have thrown UserCancelledError' ) ;
57+ } catch ( error ) {
58+ expect ( error ) . to . be . instanceOf ( UserCancelledError ) ;
59+ }
4660
4761 expect ( showQuickPickStub . called ) . to . equal ( false , 'showQuickPick should not be called if service name is cancelled' ) ;
4862 } ) ;
@@ -55,13 +69,22 @@ suite('addService', () => {
5569 } as AzureDevCliModel ;
5670
5771 showInputBoxStub . resolves ( 'my-service' ) ;
72+ showQuickPickStub . onFirstCall ( ) . resolves ( { label : 'python' } ) ;
73+ showQuickPickStub . onSecondCall ( ) . resolves ( { label : 'containerapp' } ) ;
74+
75+ const mockDocument = {
76+ getText : ( ) => `name: test-app\nservices:\n web:\n project: ./web\n` ,
77+ positionAt : ( ) => new vscode . Position ( 0 , 0 )
78+ } ;
79+ openTextDocumentStub . resolves ( mockDocument ) ;
80+ applyEditStub . resolves ( true ) ;
5881
5982 // Call the function to trigger showInputBox
6083 await addService ( mockContext , mockNode ) ;
6184
62- // Get the validator function
63- expect ( showInputBoxStub . called , 'showInputBox should be called' ) . to . exist ;
64- const inputBoxOptions = showInputBoxStub . firstCall ?. args [ 0 ] as vscode . InputBoxOptions ;
85+ // Get the validator function from the showInputBox call
86+ expect ( showInputBoxStub . called , 'showInputBox should be called' ) . to . be . true ;
87+ const inputBoxOptions = showInputBoxStub . firstCall ?. args [ 0 ] ;
6588 const validator = inputBoxOptions ?. validateInput ;
6689
6790 expect ( validator , 'Validator should be provided' ) . to . exist ;
@@ -79,33 +102,43 @@ suite('addService', () => {
79102 }
80103 } ) ;
81104
82- test ( 'returns early when user cancels language selection' , async ( ) => {
105+ test ( 'throws UserCancelledError when user cancels language selection' , async ( ) => {
83106 const mockNode = {
84107 context : {
85108 configurationFile : vscode . Uri . file ( '/test/azure.yaml' )
86109 }
87110 } as AzureDevCliModel ;
88111
89112 showInputBoxStub . resolves ( 'my-service' ) ;
90- showQuickPickStub . onFirstCall ( ) . resolves ( undefined ) ;
113+ showQuickPickStub . onFirstCall ( ) . rejects ( new UserCancelledError ( ) ) ;
91114
92- await addService ( mockContext , mockNode ) ;
115+ try {
116+ await addService ( mockContext , mockNode ) ;
117+ expect . fail ( 'Should have thrown UserCancelledError' ) ;
118+ } catch ( error ) {
119+ expect ( error ) . to . be . instanceOf ( UserCancelledError ) ;
120+ }
93121
94122 expect ( showQuickPickStub . callCount ) . to . equal ( 1 , 'Should only call showQuickPick once for language' ) ;
95123 } ) ;
96124
97- test ( 'returns early when user cancels host selection' , async ( ) => {
125+ test ( 'throws UserCancelledError when user cancels host selection' , async ( ) => {
98126 const mockNode = {
99127 context : {
100128 configurationFile : vscode . Uri . file ( '/test/azure.yaml' )
101129 }
102130 } as AzureDevCliModel ;
103131
104132 showInputBoxStub . resolves ( 'my-service' ) ;
105- showQuickPickStub . onFirstCall ( ) . resolves ( 'python' ) ;
106- showQuickPickStub . onSecondCall ( ) . resolves ( undefined ) ;
133+ showQuickPickStub . onFirstCall ( ) . resolves ( { label : 'python' } ) ;
134+ showQuickPickStub . onSecondCall ( ) . rejects ( new UserCancelledError ( ) ) ;
107135
108- await addService ( mockContext , mockNode ) ;
136+ try {
137+ await addService ( mockContext , mockNode ) ;
138+ expect . fail ( 'Should have thrown UserCancelledError' ) ;
139+ } catch ( error ) {
140+ expect ( error ) . to . be . instanceOf ( UserCancelledError ) ;
141+ }
109142
110143 expect ( showQuickPickStub . callCount ) . to . equal ( 2 , 'Should call showQuickPick twice (language and host)' ) ;
111144 expect ( openTextDocumentStub . called ) . to . equal ( false , 'Should not open document if host is cancelled' ) ;
@@ -120,22 +153,19 @@ suite('addService', () => {
120153
121154 const mockDocument = {
122155 getText : ( ) => `name: test-app\nservices:\n web:\n project: ./web\n language: ts\n host: containerapp\n` ,
123- positionAt : ( offset : number ) => new vscode . Position ( 0 , 0 )
156+ positionAt : ( ) => new vscode . Position ( 0 , 0 )
124157 } ;
125158
126159 showInputBoxStub . resolves ( 'api' ) ;
127- showQuickPickStub . onFirstCall ( ) . resolves ( 'python' ) ;
160+ showQuickPickStub . onFirstCall ( ) . resolves ( { label : 'python' } ) ;
128161 showQuickPickStub . onSecondCall ( ) . resolves ( { label : 'containerapp' , description : 'Azure Container Apps' } ) ;
129162 openTextDocumentStub . resolves ( mockDocument ) ;
130163 applyEditStub . resolves ( true ) ;
131164
132165 await addService ( mockContext , mockNode ) ;
133166
134- expect ( applyEditStub . called , 'applyEdit should be called' ) . to . exist ;
135- expect ( showInformationMessageStub . called , 'Success message should be shown' ) . to . exist ;
136-
137- const successMessage = showInformationMessageStub . firstCall . args [ 0 ] as string ;
138- expect ( successMessage , 'Success message should include service name' ) . to . include ( 'api' ) ;
167+ expect ( applyEditStub . called , 'applyEdit should be called' ) . to . be . true ;
168+ expect ( showInformationMessageStub . called , 'Success message should be shown' ) . to . be . true ;
139169 } ) ;
140170
141171 test ( 'shows error when services section not found in azure.yaml' , async ( ) => {
@@ -147,19 +177,17 @@ suite('addService', () => {
147177
148178 const mockDocument = {
149179 getText : ( ) => `name: test-app\n` ,
150- positionAt : ( offset : number ) => new vscode . Position ( 0 , 0 )
180+ positionAt : ( ) => new vscode . Position ( 0 , 0 )
151181 } ;
152182
153183 showInputBoxStub . resolves ( 'api' ) ;
154- showQuickPickStub . onFirstCall ( ) . resolves ( 'python' ) ;
184+ showQuickPickStub . onFirstCall ( ) . resolves ( { label : 'python' } ) ;
155185 showQuickPickStub . onSecondCall ( ) . resolves ( { label : 'containerapp' , description : 'Azure Container Apps' } ) ;
156186 openTextDocumentStub . resolves ( mockDocument ) ;
157187
158188 await addService ( mockContext , mockNode ) ;
159189
160190 expect ( showErrorMessageStub . called , 'Error message should be shown' ) . to . be . true ;
161- const errorMessage = showErrorMessageStub . firstCall . args [ 0 ] as string ;
162- expect ( errorMessage , 'Error should mention missing services section' ) . to . include ( 'No services section' ) ;
163191 } ) ;
164192
165193 test ( 'searches for azure.yaml when node has no configuration file' , async ( ) => {
@@ -172,19 +200,19 @@ suite('addService', () => {
172200
173201 const mockDocument = {
174202 getText : ( ) => `name: test-app\nservices:\n web:\n project: ./web\n` ,
175- positionAt : ( offset : number ) => new vscode . Position ( 0 , 0 )
203+ positionAt : ( ) => new vscode . Position ( 0 , 0 )
176204 } ;
177205
178206 showInputBoxStub . resolves ( 'api' ) ;
179- showQuickPickStub . onFirstCall ( ) . resolves ( 'python' ) ;
207+ showQuickPickStub . onFirstCall ( ) . resolves ( { label : 'python' } ) ;
180208 showQuickPickStub . onSecondCall ( ) . resolves ( { label : 'function' , description : 'Azure Functions' } ) ;
181209 openTextDocumentStub . resolves ( mockDocument ) ;
182210 applyEditStub . resolves ( true ) ;
183211
184212 await addService ( mockContext ) ;
185213
186- expect ( findFilesStub . called , 'Should search for azure.yaml files' ) . to . exist ;
187- expect ( openTextDocumentStub . called , 'Should open the found azure.yaml file' ) . to . exist ;
214+ expect ( findFilesStub . called , 'Should search for azure.yaml files' ) . to . be . true ;
215+ expect ( openTextDocumentStub . called , 'Should open the found azure.yaml file' ) . to . be . true ;
188216 } ) ;
189217
190218 test ( 'shows error when no workspace folder is open' , async ( ) => {
@@ -193,8 +221,6 @@ suite('addService', () => {
193221 await addService ( mockContext ) ;
194222
195223 expect ( showErrorMessageStub . called , 'Error message should be shown' ) . to . be . true ;
196- const errorMessage = showErrorMessageStub . firstCall . args [ 0 ] as string ;
197- expect ( errorMessage , 'Error should mention no workspace folder' ) . to . include ( 'No workspace folder' ) ;
198224 } ) ;
199225
200226 test ( 'shows error when no azure.yaml found in workspace' , async ( ) => {
@@ -208,8 +234,6 @@ suite('addService', () => {
208234 await addService ( mockContext ) ;
209235
210236 expect ( showErrorMessageStub . called , 'Error message should be shown' ) . to . be . true ;
211- const errorMessage = showErrorMessageStub . firstCall . args [ 0 ] as string ;
212- expect ( errorMessage , 'Error should mention no azure.yaml found' ) . to . include ( 'No azure.yaml file found' ) ;
213237 } ) ;
214238
215239 test ( 'generates correct service snippet with different host types' , async ( ) => {
@@ -221,7 +245,7 @@ suite('addService', () => {
221245
222246 const mockDocument = {
223247 getText : ( ) => `name: test-app\nservices:\n web:\n project: ./web\n` ,
224- positionAt : ( offset : number ) => new vscode . Position ( 0 , 0 )
248+ positionAt : ( ) => new vscode . Position ( 0 , 0 )
225249 } ;
226250
227251 // Test with different host types
@@ -233,18 +257,19 @@ suite('addService', () => {
233257
234258 for ( const host of hostTypes ) {
235259 showInputBoxStub . resolves ( 'api' ) ;
236- showQuickPickStub . onFirstCall ( ) . resolves ( 'python' ) ;
260+ showQuickPickStub . onFirstCall ( ) . resolves ( { label : 'python' } ) ;
237261 showQuickPickStub . onSecondCall ( ) . resolves ( host ) ;
238262 openTextDocumentStub . resolves ( mockDocument ) ;
239263 applyEditStub . resolves ( true ) ;
240264
241265 await addService ( mockContext , mockNode ) ;
242266
243- expect ( applyEditStub . called , `applyEdit should be called for host ${ host . label } ` ) . to . exist ;
267+ expect ( applyEditStub . called , `applyEdit should be called for host ${ host . label } ` ) . to . be . true ;
244268
245269 // Reset stubs for next iteration
246270 applyEditStub . resetHistory ( ) ;
247271 showQuickPickStub . resetHistory ( ) ;
272+ showInputBoxStub . resetHistory ( ) ;
248273 }
249274 } ) ;
250275} ) ;
0 commit comments