1
+
2
+ import assert from 'assert' ;
3
+ import sinon from 'sinon' ;
4
+ import auth from '../../../../Auth.js' ;
5
+ import { Logger } from '../../../../cli/Logger.js' ;
6
+ import { CommandError } from '../../../../Command.js' ;
7
+ import request from '../../../../request.js' ;
8
+ import { telemetry } from '../../../../telemetry.js' ;
9
+ import { pid } from '../../../../utils/pid.js' ;
10
+ import { session } from '../../../../utils/session.js' ;
11
+ import { sinonUtil } from '../../../../utils/sinonUtil.js' ;
12
+ import commands from '../../commands.js' ;
13
+ import command from './engage-community-user-remove.js' ;
14
+ import { CommandInfo } from '../../../../cli/CommandInfo.js' ;
15
+ import { z } from 'zod' ;
16
+ import { cli } from '../../../../cli/cli.js' ;
17
+ import { vivaEngage } from '../../../../utils/vivaEngage.js' ;
18
+ import { entraUser } from '../../../../utils/entraUser.js' ;
19
+
20
+ describe ( commands . ENGAGE_COMMUNITY_USER_REMOVE , ( ) => {
21
+ const communityId = 'eyJfdHlwZSI6Ikdyb3VwIiwiaWQiOiIzNjAyMDAxMTAwOSJ9' ;
22
+ const communityDisplayName = 'All company' ;
23
+ const entraGroupId = 'b6c35b51-ebca-445c-885a-63a67d24cb53' ;
24
+ const userName = 'john@contoso.com' ;
25
+ const userId = '3f2504e0-4f89-11d3-9a0c-0305e82c3301' ;
26
+
27
+ let log : string [ ] ;
28
+ let logger : Logger ;
29
+ let commandInfo : CommandInfo ;
30
+ let commandOptionsSchema : z . ZodTypeAny ;
31
+
32
+ before ( ( ) => {
33
+ sinon . stub ( auth , 'restoreAuth' ) . resolves ( ) ;
34
+ sinon . stub ( telemetry , 'trackEvent' ) . resolves ( ) ;
35
+ sinon . stub ( pid , 'getProcessName' ) . returns ( '' ) ;
36
+ sinon . stub ( session , 'getId' ) . returns ( '' ) ;
37
+ auth . connection . active = true ;
38
+ commandInfo = cli . getCommandInfo ( command ) ;
39
+ commandOptionsSchema = commandInfo . command . getSchemaToParse ( ) ! ;
40
+ sinon . stub ( entraUser , 'getUserIdByUpn' ) . resolves ( userId ) ;
41
+ sinon . stub ( vivaEngage , 'getCommunityByDisplayName' ) . resolves ( { groupId : entraGroupId } ) ;
42
+ sinon . stub ( vivaEngage , 'getCommunityById' ) . resolves ( { groupId : entraGroupId } ) ;
43
+ } ) ;
44
+
45
+ beforeEach ( ( ) => {
46
+ log = [ ] ;
47
+ logger = {
48
+ log : async ( msg : string ) => {
49
+ log . push ( msg ) ;
50
+ } ,
51
+ logRaw : async ( msg : string ) => {
52
+ log . push ( msg ) ;
53
+ } ,
54
+ logToStderr : async ( msg : string ) => {
55
+ log . push ( msg ) ;
56
+ }
57
+ } ;
58
+ } ) ;
59
+
60
+ afterEach ( ( ) => {
61
+ sinonUtil . restore ( [
62
+ request . delete ,
63
+ cli . promptForConfirmation
64
+ ] ) ;
65
+ } ) ;
66
+
67
+ after ( ( ) => {
68
+ sinon . restore ( ) ;
69
+ auth . connection . active = false ;
70
+ } ) ;
71
+
72
+ it ( 'has correct name' , ( ) => {
73
+ assert . strictEqual ( command . name , commands . ENGAGE_COMMUNITY_USER_REMOVE ) ;
74
+ } ) ;
75
+
76
+ it ( 'has a description' , ( ) => {
77
+ assert . notStrictEqual ( command . description , null ) ;
78
+ } ) ;
79
+
80
+ it ( 'fails validation if entraGroupId is not a valid GUID' , ( ) => {
81
+ const actual = commandOptionsSchema . safeParse ( {
82
+ entraGroupId : 'invalid' ,
83
+ userName : userName
84
+ } ) ;
85
+ assert . notStrictEqual ( actual . success , true ) ;
86
+ } ) ;
87
+
88
+ it ( 'fails validation if id is not a valid GUID' , ( ) => {
89
+ const actual = commandOptionsSchema . safeParse ( {
90
+ entraGroupId : entraGroupId ,
91
+ id : 'invalid'
92
+ } ) ;
93
+ assert . notStrictEqual ( actual . success , true ) ;
94
+ } ) ;
95
+
96
+ it ( 'fails validation if userName is invalid user principal name' , ( ) => {
97
+ const actual = commandOptionsSchema . safeParse ( {
98
+ entraGroupId : entraGroupId ,
99
+ userName : 'invalid'
100
+ } ) ;
101
+ assert . notStrictEqual ( actual . success , true ) ;
102
+ } ) ;
103
+
104
+ it ( 'fails validation if communityId, communityDisplayName or entraGroupId are not specified' , ( ) => {
105
+ const actual = commandOptionsSchema . safeParse ( { } ) ;
106
+ assert . notStrictEqual ( actual . success , true ) ;
107
+ } ) ;
108
+
109
+ it ( 'fails validation if communityId, communityDisplayName and entraGroupId are specified' , ( ) => {
110
+ const actual = commandOptionsSchema . safeParse ( {
111
+ communityId : communityId ,
112
+ communityDisplayName : communityDisplayName ,
113
+ entraGroupId : entraGroupId ,
114
+ id : userId
115
+ } ) ;
116
+ assert . notStrictEqual ( actual . success , true ) ;
117
+ } ) ;
118
+
119
+ it ( 'passes validation if communityId is specified' , ( ) => {
120
+ const actual = commandOptionsSchema . safeParse ( {
121
+ communityId : communityId ,
122
+ userName : userName
123
+ } ) ;
124
+ assert . strictEqual ( actual . success , true ) ;
125
+ } ) ;
126
+
127
+ it ( 'passes validation if entraGroupId is specified with a proper GUID' , ( ) => {
128
+ const actual = commandOptionsSchema . safeParse ( {
129
+ entraGroupId : entraGroupId ,
130
+ userName : userName
131
+ } ) ;
132
+ assert . strictEqual ( actual . success , true ) ;
133
+ } ) ;
134
+
135
+ it ( 'passes validation if communityDisplayName is specified' , ( ) => {
136
+ const actual = commandOptionsSchema . safeParse ( {
137
+ communityDisplayName : communityDisplayName ,
138
+ userName : userName
139
+ } ) ;
140
+ assert . strictEqual ( actual . success , true ) ;
141
+ } ) ;
142
+
143
+ it ( 'correctly removes user specified by id' , async ( ) => {
144
+ const deleteStub = sinon . stub ( request , 'delete' ) . callsFake ( async ( opts ) => {
145
+ if ( opts . url === `https://graph.microsoft.com/v1.0/groups/${ entraGroupId } /owners/${ userId } /$ref` ) {
146
+ return ;
147
+ }
148
+
149
+ if ( opts . url === `https://graph.microsoft.com/v1.0/groups/${ entraGroupId } /members/${ userId } /$ref` ) {
150
+ return ;
151
+ }
152
+
153
+ throw 'Invalid request' ;
154
+ } ) ;
155
+
156
+ await command . action ( logger , { options : { communityDisplayName : communityDisplayName , id : userId , force : true , verbose : true } } ) ;
157
+ assert ( deleteStub . calledTwice ) ;
158
+ } ) ;
159
+
160
+ it ( 'correctly removes user by userName' , async ( ) => {
161
+ const deleteStub = sinon . stub ( request , 'delete' ) . callsFake ( async ( opts ) => {
162
+ if ( opts . url === `https://graph.microsoft.com/v1.0/groups/${ entraGroupId } /owners/${ userId } /$ref` ) {
163
+ return ;
164
+ }
165
+
166
+ if ( opts . url === `https://graph.microsoft.com/v1.0/groups/${ entraGroupId } /members/${ userId } /$ref` ) {
167
+ return ;
168
+ }
169
+ throw 'Invalid request' ;
170
+ } ) ;
171
+
172
+ await command . action ( logger , { options : { communityId : communityId , verbose : true , userName : userName , force : true } } ) ;
173
+ assert ( deleteStub . calledTwice ) ;
174
+ } ) ;
175
+
176
+ it ( 'correctly removes user as member by userName' , async ( ) => {
177
+ const deleteStub = sinon . stub ( request , 'delete' ) . callsFake ( async ( opts ) => {
178
+ if ( opts . url === `https://graph.microsoft.com/v1.0/groups/${ entraGroupId } /owners/${ userId } /$ref` ) {
179
+ throw {
180
+ response : {
181
+ status : 404 ,
182
+ data : {
183
+ message : 'Object does not exist...'
184
+ }
185
+ }
186
+ } ;
187
+ }
188
+
189
+ if ( opts . url === `https://graph.microsoft.com/v1.0/groups/${ entraGroupId } /members/${ userId } /$ref` ) {
190
+ return ;
191
+ }
192
+ throw 'Invalid request' ;
193
+ } ) ;
194
+
195
+ sinonUtil . restore ( cli . promptForConfirmation ) ;
196
+ sinon . stub ( cli , 'promptForConfirmation' ) . resolves ( true ) ;
197
+
198
+ await command . action ( logger , { options : { communityId : communityId , verbose : true , userName : userName } } ) ;
199
+ assert ( deleteStub . calledTwice ) ;
200
+ } ) ;
201
+
202
+ it ( 'handles API error when removing user' , async ( ) => {
203
+ const errorMessage = 'Invalid object identifier' ;
204
+ sinon . stub ( request , 'delete' ) . callsFake ( async opts => {
205
+ if ( opts . url === `https://graph.microsoft.com/v1.0/groups/${ entraGroupId } /owners/${ userId } /$ref` ) {
206
+ throw {
207
+ response : {
208
+ status : 400 ,
209
+ data : { error : { 'odata.error' : { message : { value : errorMessage } } } }
210
+ }
211
+ } ;
212
+ }
213
+
214
+ throw 'Invalid request' ;
215
+ } ) ;
216
+
217
+ sinonUtil . restore ( cli . promptForConfirmation ) ;
218
+ sinon . stub ( cli , 'promptForConfirmation' ) . resolves ( true ) ;
219
+
220
+ await assert . rejects ( command . action ( logger , { options : { entraGroupId : entraGroupId , id : userId } } ) ,
221
+ new CommandError ( errorMessage ) ) ;
222
+ } ) ;
223
+
224
+ it ( 'prompts before removal when confirmation argument not passed' , async ( ) => {
225
+ const promptStub : sinon . SinonStub = sinon . stub ( cli , 'promptForConfirmation' ) . resolves ( false ) ;
226
+
227
+ await command . action ( logger , { options : { entraGroupId : entraGroupId , id : userId } } ) ;
228
+
229
+ assert ( promptStub . called ) ;
230
+ } ) ;
231
+
232
+ it ( 'aborts execution when prompt not confirmed' , async ( ) => {
233
+ const deleteStub = sinon . stub ( request , 'delete' ) ;
234
+ sinon . stub ( cli , 'promptForConfirmation' ) . resolves ( false ) ;
235
+
236
+ await command . action ( logger , { options : { entraGroupId : entraGroupId , id : userId } } ) ;
237
+ assert ( deleteStub . notCalled ) ;
238
+ } ) ;
239
+ } ) ;
0 commit comments