@@ -16,12 +16,17 @@ package user
1616
1717import (
1818 "bytes"
19+ "cmp"
1920 "encoding/json"
21+ "fmt"
22+ "slices"
2023 "strings"
2124 "testing"
2225
2326 "github.com/go-openapi/strfmt"
27+ "github.com/goharbor/go-client/pkg/sdk/v2.0/client/user"
2428 "github.com/goharbor/go-client/pkg/sdk/v2.0/models"
29+ "github.com/goharbor/harbor-cli/pkg/api"
2530 log "github.com/sirupsen/logrus"
2631 "github.com/spf13/viper"
2732 "go.yaml.in/yaml/v4"
@@ -172,3 +177,301 @@ func TestPrintUsers(t *testing.T) {
172177 })
173178 }
174179}
180+
181+ /*
182+ [X] make a test opts, only Page and PageSize fields needs to be there
183+ [X] make a helper to populate the users
184+ [X] mock api.List
185+ [X] mock authentication (no need, just make the api mock to pass an error with 403 in case you need to check for unauthenticated user)
186+ [X] test error logs
187+ [X] check if the users received are correct
188+
189+ */
190+ /*
191+ Logic for mocking api.ListUsers
192+ -> should return (*user.ListUsersOK, error) ; Payload should be populated with the users
193+ -> shoud return the PageSize*(Page-1) th user till PageSize*(Page) -1 th user (0 based indexing)
194+ Helper for populating users:
195+ -> models.UserResp ko append krna hoga api.ListFlags type ke payload me
196+ */
197+ type MockUserLister struct {
198+ numberOfUsersforTesting int64
199+ usersForTesting []* models.UserResp
200+ expectAuthError bool
201+ }
202+
203+ func (m * MockUserLister ) populateUsers () []* models.UserResp {
204+ users := make ([]* models.UserResp , 0 , m .numberOfUsersforTesting )
205+ for i := 0 ; i < int (m .numberOfUsersforTesting ); i ++ {
206+ user := & models.UserResp {
207+ UserID : int64 (i + 1 ),
208+ }
209+ users = append (users , user )
210+ }
211+ m .usersForTesting = users
212+ return users
213+ }
214+ func (m * MockUserLister ) UserList (opts ... api.ListFlags ) (* user.ListUsersOK , error ) {
215+ if m .expectAuthError {
216+ return nil , fmt .Errorf ("403" )
217+ }
218+ res := & user.ListUsersOK {}
219+ if len (opts ) == 0 {
220+ return res , nil
221+ }
222+ listFlags := opts [0 ]
223+ page , pageSize := listFlags .Page , listFlags .PageSize
224+ users := m .populateUsers ()
225+ lo , hi := max (pageSize * (page - 1 ), 0 ), min (pageSize * page , m .numberOfUsersforTesting )
226+ if lo >= m .numberOfUsersforTesting {
227+ return res , nil
228+ }
229+ res .Payload = users [lo :hi ]
230+ return res , nil
231+ }
232+ func TestGetUsers (t * testing.T ) {
233+ usersAreEqual := func (u1 , u2 []* models.UserResp ) bool {
234+ if len (u1 ) != len (u2 ) {
235+ return false
236+ }
237+ slices .SortFunc (u1 , func (a , b * models.UserResp ) int {
238+ return cmp .Compare (a .UserID , b .UserID )
239+ })
240+ slices .SortFunc (u2 , func (a , b * models.UserResp ) int {
241+ return cmp .Compare (a .UserID , b .UserID )
242+ })
243+ for i := 0 ; i < len (u1 ); i ++ {
244+ if u1 [i ].UserID != u2 [i ].UserID {
245+ return false
246+ }
247+ }
248+ return true
249+ }
250+ tests := []struct {
251+ name string
252+ setup func () (api.ListFlags , * MockUserLister )
253+ wantError bool
254+ errContains string
255+ }{
256+ {
257+ name : "fetch specific page with valid page size" ,
258+ setup : func () (api.ListFlags , * MockUserLister ) {
259+ opts := api.ListFlags {
260+ Page : 2 ,
261+ PageSize : 50 ,
262+ }
263+ m := & MockUserLister {
264+ numberOfUsersforTesting : 102 ,
265+ expectAuthError : false ,
266+ }
267+ return opts , m
268+ },
269+ wantError : false ,
270+ },
271+ {
272+ name : "fetch all users with page size 0 (multiple pages)" ,
273+ setup : func () (api.ListFlags , * MockUserLister ) {
274+ opts := api.ListFlags {
275+ Page : 1 ,
276+ PageSize : 0 ,
277+ }
278+ m := & MockUserLister {
279+ numberOfUsersforTesting : 250 ,
280+ expectAuthError : false ,
281+ }
282+ return opts , m
283+ },
284+ wantError : false ,
285+ },
286+ {
287+ name : "fetch all users when total is exactly divisible by 100" ,
288+ setup : func () (api.ListFlags , * MockUserLister ) {
289+ opts := api.ListFlags {
290+ Page : 1 ,
291+ PageSize : 0 ,
292+ }
293+ m := & MockUserLister {
294+ numberOfUsersforTesting : 200 ,
295+ expectAuthError : false ,
296+ }
297+ return opts , m
298+ },
299+ wantError : false ,
300+ },
301+ {
302+ name : "fetch first page with page size 10" ,
303+ setup : func () (api.ListFlags , * MockUserLister ) {
304+ opts := api.ListFlags {
305+ Page : 1 ,
306+ PageSize : 10 ,
307+ }
308+ m := & MockUserLister {
309+ numberOfUsersforTesting : 50 ,
310+ expectAuthError : false ,
311+ }
312+ return opts , m
313+ },
314+ wantError : false ,
315+ },
316+ {
317+ name : "fetch last page with partial results" ,
318+ setup : func () (api.ListFlags , * MockUserLister ) {
319+ opts := api.ListFlags {
320+ Page : 3 ,
321+ PageSize : 10 ,
322+ }
323+ m := & MockUserLister {
324+ numberOfUsersforTesting : 25 ,
325+ expectAuthError : false ,
326+ }
327+ return opts , m
328+ },
329+ wantError : false ,
330+ },
331+ {
332+ name : "fetch page beyond available data returns empty" ,
333+ setup : func () (api.ListFlags , * MockUserLister ) {
334+ opts := api.ListFlags {
335+ Page : 10 ,
336+ PageSize : 10 ,
337+ }
338+ m := & MockUserLister {
339+ numberOfUsersforTesting : 5 ,
340+ expectAuthError : false ,
341+ }
342+ return opts , m
343+ },
344+ wantError : false ,
345+ },
346+ {
347+ name : "fetch with maximum allowed page size 100" ,
348+ setup : func () (api.ListFlags , * MockUserLister ) {
349+ opts := api.ListFlags {
350+ Page : 1 ,
351+ PageSize : 100 ,
352+ }
353+ m := & MockUserLister {
354+ numberOfUsersforTesting : 150 ,
355+ expectAuthError : false ,
356+ }
357+ return opts , m
358+ },
359+ wantError : false ,
360+ },
361+ {
362+ name : "fetch with zero users in database" ,
363+ setup : func () (api.ListFlags , * MockUserLister ) {
364+ opts := api.ListFlags {
365+ Page : 1 ,
366+ PageSize : 10 ,
367+ }
368+ m := & MockUserLister {
369+ numberOfUsersforTesting : 0 ,
370+ expectAuthError : false ,
371+ }
372+ return opts , m
373+ },
374+ wantError : false ,
375+ },
376+ {
377+ name : "page size exceeds maximum (101)" ,
378+ setup : func () (api.ListFlags , * MockUserLister ) {
379+ opts := api.ListFlags {
380+ Page : 1 ,
381+ PageSize : 101 ,
382+ }
383+ m := & MockUserLister {
384+ numberOfUsersforTesting : 50 ,
385+ expectAuthError : false ,
386+ }
387+ return opts , m
388+ },
389+ wantError : true ,
390+ errContains : "page size should be greater than or equal to 0 and less than or equal to 100" ,
391+ },
392+ {
393+ name : "page size is negative" ,
394+ setup : func () (api.ListFlags , * MockUserLister ) {
395+ opts := api.ListFlags {
396+ Page : 1 ,
397+ PageSize : - 1 ,
398+ }
399+ m := & MockUserLister {
400+ numberOfUsersforTesting : 50 ,
401+ expectAuthError : false ,
402+ }
403+ return opts , m
404+ },
405+ wantError : true ,
406+ errContains : "page size should be greater than or equal to 0 and less than or equal to 100" ,
407+ },
408+ {
409+ name : "authentication error returns permission denied" ,
410+ setup : func () (api.ListFlags , * MockUserLister ) {
411+ opts := api.ListFlags {
412+ Page : 1 ,
413+ PageSize : 10 ,
414+ }
415+ m := & MockUserLister {
416+ numberOfUsersforTesting : 50 ,
417+ expectAuthError : true ,
418+ }
419+ return opts , m
420+ },
421+ wantError : true ,
422+ errContains : "Permission denied" ,
423+ },
424+ {
425+ name : "authentication error during fetch all" ,
426+ setup : func () (api.ListFlags , * MockUserLister ) {
427+ opts := api.ListFlags {
428+ Page : 1 ,
429+ PageSize : 0 ,
430+ }
431+ m := & MockUserLister {
432+ numberOfUsersforTesting : 50 ,
433+ expectAuthError : true ,
434+ }
435+ return opts , m
436+ },
437+ wantError : true ,
438+ errContains : "Permission denied" ,
439+ },
440+ }
441+ for _ , tt := range tests {
442+ t .Run (tt .name , func (t * testing.T ) {
443+ opts , m := tt .setup ()
444+ allUsers , err := GetUsers (opts , m )
445+
446+ // Check if we expected an error but did not get one (or vice-versa)
447+ if (err != nil ) != tt .wantError {
448+ t .Fatalf ("GetUsers() error presence mismatch: got error %v, wantError %v" , err , tt .wantError )
449+ }
450+ if tt .wantError && ! strings .Contains (err .Error (), tt .errContains ) {
451+ t .Errorf ("Expected error to contain '%s', got '%s'" , tt .errContains , err .Error ())
452+ }
453+ if ! tt .wantError {
454+ // check if the returned allUsers are correct according to our mock database of users (which is m.usersForTesting)
455+ if opts .PageSize == 0 {
456+ if ! usersAreEqual (allUsers , m .usersForTesting ) {
457+ t .Errorf ("Expected all of the users to be returned" )
458+ }
459+ } else {
460+ requiredPage , requiredPageSize := opts .Page , opts .PageSize
461+ start := max (requiredPageSize * (requiredPage - 1 ), 0 )
462+ end := min (requiredPageSize * requiredPage , m .numberOfUsersforTesting )
463+
464+ if start >= m .numberOfUsersforTesting {
465+ if len (allUsers ) != 0 {
466+ t .Errorf ("Expected empty result for page beyond data, got %d users" , len (allUsers ))
467+ }
468+ } else {
469+ if ! usersAreEqual (allUsers , m .usersForTesting [start :end ]) {
470+ t .Errorf ("Expected different set of users" )
471+ }
472+ }
473+ }
474+ }
475+ })
476+ }
477+ }
0 commit comments