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-add.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_ADD , ( ) => {
21
+ const communityId = 'eyJfdHlwZSI6Ikdyb3VwIiwiaWQiOiIzNjAyMDAxMTAwOSJ9' ;
22
+ const communityDisplayName = 'All company' ;
23
+ const entraGroupId = 'b6c35b51-ebca-445c-885a-63a67d24cb53' ;
24
+ const userNames = [ 'user1@contoso.com' , 'user2@contoso.com' , 'user3@contoso.com' , 'user4@contoso.com' , 'user5@contoso.com' , 'user6@contoso.com' , 'user7@contoso.com' , 'user8@contoso.com' , 'user9@contoso.com' , 'user10@contoso.com' , 'user11@contoso.com' , 'user12@contoso.com' , 'user13@contoso.com' , 'user14@contoso.com' , 'user15@contoso.com' , 'user16@contoso.com' , 'user17@contoso.com' , 'user18@contoso.com' , 'user19@contoso.com' , 'user20@contoso.com' , 'user21@contoso.com' , 'user22@contoso.com' , 'user23@contoso.com' , 'user24@contoso.com' , 'user25@contoso.com' ] ;
25
+ const userIds = [ '3f2504e0-4f89-11d3-9a0c-0305e82c3301' , '6dcd4ce0-4f89-11d3-9a0c-0305e82c3302' , '9b76f130-4f89-11d3-9a0c-0305e82c3303' , 'c835f5e0-4f89-11d3-9a0c-0305e82c3304' , 'f4f3fa90-4f89-11d3-9a0c-0305e82c3305' , '2230f6a0-4f8a-11d3-9a0c-0305e82c3306' , '4f6df5b0-4f8a-11d3-9a0c-0305e82c3307' , '7caaf4c0-4f8a-11d3-9a0c-0305e82c3308' , 'a9e8f3d0-4f8a-11d3-9a0c-0305e82c3309' , 'd726f2e0-4f8a-11d3-9a0c-0305e82c330a' , '0484f1f0-4f8b-11d3-9a0c-0305e82c330b' , '31e2f100-4f8b-11d3-9a0c-0305e82c330c' , '5f40f010-4f8b-11d3-9a0c-0305e82c330d' , '8c9eef20-4f8b-11d3-9a0c-0305e82c330e' , 'b9fce030-4f8b-11d3-9a0c-0305e82c330f' , 'e73cdf40-4f8b-11d3-9a0c-0305e82c3310' , '1470ce50-4f8c-11d3-9a0c-0305e82c3311' , '41a3cd60-4f8c-11d3-9a0c-0305e82c3312' , '6ed6cc70-4f8c-11d3-9a0c-0305e82c3313' , '9c09cb80-4f8c-11d3-9a0c-0305e82c3314' , 'c93cca90-4f8c-11d3-9a0c-0305e82c3315' , 'f66cc9a0-4f8c-11d3-9a0c-0305e82c3316' , '2368c8b0-4f8d-11d3-9a0c-0305e82c3317' , '5064c7c0-4f8d-11d3-9a0c-0305e82c3318' , '7d60c6d0-4f8d-11d3-9a0c-0305e82c3319' ] ;
26
+
27
+ let log : string [ ] ;
28
+ let logger : Logger ;
29
+ let loggerLogSpy : sinon . SinonSpy ;
30
+ let commandInfo : CommandInfo ;
31
+ let commandOptionsSchema : z . ZodTypeAny ;
32
+
33
+ before ( ( ) => {
34
+ sinon . stub ( auth , 'restoreAuth' ) . resolves ( ) ;
35
+ sinon . stub ( telemetry , 'trackEvent' ) . resolves ( ) ;
36
+ sinon . stub ( pid , 'getProcessName' ) . returns ( '' ) ;
37
+ sinon . stub ( session , 'getId' ) . returns ( '' ) ;
38
+ auth . connection . active = true ;
39
+ commandInfo = cli . getCommandInfo ( command ) ;
40
+ commandOptionsSchema = commandInfo . command . getSchemaToParse ( ) ! ;
41
+ sinon . stub ( entraUser , 'getUserIdsByUpns' ) . resolves ( userIds ) ;
42
+ sinon . stub ( vivaEngage , 'getCommunityByDisplayName' ) . resolves ( { groupId : entraGroupId } ) ;
43
+ sinon . stub ( vivaEngage , 'getCommunityById' ) . resolves ( { groupId : entraGroupId } ) ;
44
+ } ) ;
45
+
46
+ beforeEach ( ( ) => {
47
+ log = [ ] ;
48
+ logger = {
49
+ log : async ( msg : string ) => {
50
+ log . push ( msg ) ;
51
+ } ,
52
+ logRaw : async ( msg : string ) => {
53
+ log . push ( msg ) ;
54
+ } ,
55
+ logToStderr : async ( msg : string ) => {
56
+ log . push ( msg ) ;
57
+ }
58
+ } ;
59
+ loggerLogSpy = sinon . spy ( logger , 'log' ) ;
60
+ } ) ;
61
+
62
+ afterEach ( ( ) => {
63
+ sinonUtil . restore ( [
64
+ request . post
65
+ ] ) ;
66
+ } ) ;
67
+
68
+ after ( ( ) => {
69
+ sinon . restore ( ) ;
70
+ auth . connection . active = false ;
71
+ } ) ;
72
+
73
+ it ( 'has correct name' , ( ) => {
74
+ assert . strictEqual ( command . name , commands . ENGAGE_COMMUNITY_USER_ADD ) ;
75
+ } ) ;
76
+
77
+ it ( 'has a description' , ( ) => {
78
+ assert . notStrictEqual ( command . description , null ) ;
79
+ } ) ;
80
+
81
+ it ( 'fails validation if entraGroupId is not a valid GUID' , ( ) => {
82
+ const actual = commandOptionsSchema . safeParse ( {
83
+ entraGroupId : 'invalid' ,
84
+ role : 'Member' ,
85
+ userNames : userNames . join ( ',' )
86
+ } ) ;
87
+ assert . notStrictEqual ( actual . success , true ) ;
88
+ } ) ;
89
+
90
+ it ( 'fails validation if ids contains invalid guids' , ( ) => {
91
+ const actual = commandOptionsSchema . safeParse ( {
92
+ entraGroupId : entraGroupId ,
93
+ ids : userIds . join ( ',' ) + ',invalid' ,
94
+ role : 'Member'
95
+ } ) ;
96
+ assert . notStrictEqual ( actual . success , true ) ;
97
+ } ) ;
98
+
99
+ it ( 'fails validation if userNames contains invalid user principal names' , ( ) => {
100
+ const actual = commandOptionsSchema . safeParse ( {
101
+ entraGroupId : entraGroupId ,
102
+ userNames : userNames . join ( ',' ) + ',invalid' ,
103
+ role : 'Member'
104
+ } ) ;
105
+ assert . notStrictEqual ( actual . success , true ) ;
106
+ } ) ;
107
+
108
+ it ( 'fails validation if communityId, communityDisplayName or entraGroupId are not specified' , ( ) => {
109
+ const actual = commandOptionsSchema . safeParse ( { } ) ;
110
+ assert . notStrictEqual ( actual . success , true ) ;
111
+ } ) ;
112
+
113
+ it ( 'fails validation if communityId, communityDisplayName and entraGroupId are specified' , ( ) => {
114
+ const actual = commandOptionsSchema . safeParse ( {
115
+ communityId : communityId ,
116
+ communityDisplayName : communityDisplayName ,
117
+ entraGroupId : entraGroupId ,
118
+ ids : userIds . join ( ',' ) ,
119
+ role : 'Member'
120
+ } ) ;
121
+ assert . notStrictEqual ( actual . success , true ) ;
122
+ } ) ;
123
+
124
+ it ( 'fails validation if incorrect role value is specified' , ( ) => {
125
+ const actual = commandOptionsSchema . safeParse ( {
126
+ communityId : communityId ,
127
+ userNames : userNames . join ( ',' ) ,
128
+ role : 'invalid'
129
+ } ) ;
130
+ assert . notStrictEqual ( actual . success , true ) ;
131
+ } ) ;
132
+
133
+ it ( 'passes validation if communityId is specified' , ( ) => {
134
+ const actual = commandOptionsSchema . safeParse ( {
135
+ communityId : communityId ,
136
+ userNames : userNames . join ( ',' ) ,
137
+ role : 'Admin'
138
+ } ) ;
139
+ assert . strictEqual ( actual . success , true ) ;
140
+ } ) ;
141
+
142
+ it ( 'passes validation if entraGroupId is specified with a proper GUID' , ( ) => {
143
+ const actual = commandOptionsSchema . safeParse ( {
144
+ entraGroupId : entraGroupId ,
145
+ userNames : userNames . join ( ',' ) ,
146
+ role : 'Admin'
147
+ } ) ;
148
+ assert . strictEqual ( actual . success , true ) ;
149
+ } ) ;
150
+
151
+ it ( 'passes validation if communityDisplayName is specified' , ( ) => {
152
+ const actual = commandOptionsSchema . safeParse ( {
153
+ communityDisplayName : communityDisplayName ,
154
+ userNames : userNames . join ( ',' ) ,
155
+ role : 'Admin'
156
+ } ) ;
157
+ assert . strictEqual ( actual . success , true ) ;
158
+ } ) ;
159
+
160
+ it ( 'passes validation if role is specified with a proper value' , ( ) => {
161
+ const actual = commandOptionsSchema . safeParse ( {
162
+ communityId : communityId ,
163
+ userNames : userNames . join ( ',' ) ,
164
+ role : 'Admin'
165
+ } ) ;
166
+ assert . strictEqual ( actual . success , true ) ;
167
+ assert ( loggerLogSpy . notCalled ) ;
168
+ } ) ;
169
+
170
+ it ( 'correctly adds users specified by id as owner' , async ( ) => {
171
+ const postStub = sinon . stub ( request , 'post' ) . callsFake ( async ( opts ) => {
172
+ if ( opts . url === `https://graph.microsoft.com/v1.0/$batch` ) {
173
+ return {
174
+ responses : Array ( 2 ) . fill ( {
175
+ status : 204 ,
176
+ body : { }
177
+ } )
178
+ } ;
179
+ }
180
+
181
+ throw 'Invalid request' ;
182
+ } ) ;
183
+
184
+ await command . action ( logger , { options : { communityDisplayName : communityDisplayName , verbose : true , ids : userIds . join ( ',' ) , role : 'Owner' } } ) ;
185
+ assert . deepStrictEqual ( postStub . lastCall . args [ 0 ] . data . requests , [
186
+ {
187
+ id : 1 ,
188
+ method : 'PATCH' ,
189
+ url : `/groups/${ entraGroupId } ` ,
190
+ headers : { 'content-type' : 'application/json;odata.metadata=none' } ,
191
+ body : {
192
+ 'owners@odata.bind' : userIds . slice ( 0 , 20 ) . map ( u => `https://graph.microsoft.com/v1.0/directoryObjects/${ u } ` )
193
+ }
194
+ } ,
195
+ {
196
+ id : 21 ,
197
+ method : 'PATCH' ,
198
+ url : `/groups/${ entraGroupId } ` ,
199
+ headers : { 'content-type' : 'application/json;odata.metadata=none' } ,
200
+ body : {
201
+ 'owners@odata.bind' : userIds . slice ( 20 , 40 ) . map ( u => `https://graph.microsoft.com/v1.0/directoryObjects/${ u } ` )
202
+ }
203
+ }
204
+ ] ) ;
205
+ } ) ;
206
+
207
+ it ( 'correctly adds users specified by ids as member' , async ( ) => {
208
+ const postStub = sinon . stub ( request , 'post' ) . callsFake ( async ( opts ) => {
209
+ if ( opts . url === `https://graph.microsoft.com/v1.0/$batch` ) {
210
+ return {
211
+ responses : Array ( 2 ) . fill ( {
212
+ status : 204 ,
213
+ body : { }
214
+ } )
215
+ } ;
216
+ }
217
+
218
+ throw 'Invalid request' ;
219
+ } ) ;
220
+
221
+ await command . action ( logger , { options : { communityId : communityId , verbose : true , ids : userIds . join ( ',' ) , role : 'Member' } } ) ;
222
+ assert . deepStrictEqual ( postStub . lastCall . args [ 0 ] . data . requests , [
223
+ {
224
+ id : 1 ,
225
+ method : 'PATCH' ,
226
+ url : `/groups/${ entraGroupId } ` ,
227
+ headers : { 'content-type' : 'application/json;odata.metadata=none' } ,
228
+ body : {
229
+ 'members@odata.bind' : userIds . slice ( 0 , 20 ) . map ( u => `https://graph.microsoft.com/v1.0/directoryObjects/${ u } ` )
230
+ }
231
+ } ,
232
+ {
233
+ id : 21 ,
234
+ method : 'PATCH' ,
235
+ url : `/groups/${ entraGroupId } ` ,
236
+ headers : { 'content-type' : 'application/json;odata.metadata=none' } ,
237
+ body : {
238
+ 'members@odata.bind' : userIds . slice ( 20 , 40 ) . map ( u => `https://graph.microsoft.com/v1.0/directoryObjects/${ u } ` )
239
+ }
240
+ }
241
+ ] ) ;
242
+ } ) ;
243
+
244
+ it ( 'correctly adds users specified by userNames as member' , async ( ) => {
245
+ const postStub = sinon . stub ( request , 'post' ) . callsFake ( async ( opts ) => {
246
+ if ( opts . url === `https://graph.microsoft.com/v1.0/$batch` ) {
247
+ return {
248
+ responses : Array ( 2 ) . fill ( {
249
+ status : 204 ,
250
+ body : { }
251
+ } )
252
+ } ; ;
253
+ }
254
+
255
+ throw 'Invalid request' ;
256
+ } ) ;
257
+
258
+ await command . action ( logger , { options : { entraGroupId : entraGroupId , verbose : true , userNames : userNames . join ( ',' ) , role : 'Member' } } ) ;
259
+ assert . deepStrictEqual ( postStub . lastCall . args [ 0 ] . data . requests , [
260
+ {
261
+ id : 1 ,
262
+ method : 'PATCH' ,
263
+ url : `/groups/${ entraGroupId } ` ,
264
+ headers : { 'content-type' : 'application/json;odata.metadata=none' } ,
265
+ body : {
266
+ 'members@odata.bind' : userIds . slice ( 0 , 20 ) . map ( u => `https://graph.microsoft.com/v1.0/directoryObjects/${ u } ` )
267
+ }
268
+ } ,
269
+ {
270
+ id : 21 ,
271
+ method : 'PATCH' ,
272
+ url : `/groups/${ entraGroupId } ` ,
273
+ headers : { 'content-type' : 'application/json;odata.metadata=none' } ,
274
+ body : {
275
+ 'members@odata.bind' : userIds . slice ( 20 , 40 ) . map ( u => `https://graph.microsoft.com/v1.0/directoryObjects/${ u } ` )
276
+ }
277
+ }
278
+ ] ) ;
279
+ } ) ;
280
+
281
+ it ( 'handles API error when adding users to a community' , async ( ) => {
282
+ sinon . stub ( request , 'post' ) . callsFake ( async opts => {
283
+ if ( opts . url === 'https://graph.microsoft.com/v1.0/$batch' ) {
284
+ return {
285
+ responses : [
286
+ {
287
+ id : 1 ,
288
+ status : 204 ,
289
+ body : { }
290
+ } ,
291
+ {
292
+ id : 2 ,
293
+ status : 400 ,
294
+ body : {
295
+ error : {
296
+ message : `One or more added object references already exist for the following modified properties: 'members'.`
297
+ }
298
+ }
299
+ }
300
+ ]
301
+ } ;
302
+ }
303
+
304
+ throw 'Invalid request' ;
305
+ } ) ;
306
+
307
+ await assert . rejects ( command . action ( logger , { options : { entraGroupId : entraGroupId , ids : userIds . join ( ',' ) , role : 'Member' } } ) ,
308
+ new CommandError ( `One or more added object references already exist for the following modified properties: 'members'.` ) ) ;
309
+ } ) ;
310
+ } ) ;
0 commit comments