@@ -33,13 +33,16 @@ import (
33
33
"google.golang.org/grpc/metadata"
34
34
35
35
mockdb "github.com/stacklok/minder/database/mock"
36
+ "github.com/stacklok/minder/internal/auth"
36
37
"github.com/stacklok/minder/internal/auth/jwt"
37
38
mockjwt "github.com/stacklok/minder/internal/auth/jwt/mock"
39
+ mockidentity "github.com/stacklok/minder/internal/auth/mock"
38
40
"github.com/stacklok/minder/internal/authz/mock"
39
41
serverconfig "github.com/stacklok/minder/internal/config/server"
40
42
mockcrypto "github.com/stacklok/minder/internal/crypto/mock"
41
43
"github.com/stacklok/minder/internal/db"
42
44
"github.com/stacklok/minder/internal/events"
45
+ "github.com/stacklok/minder/internal/flags"
43
46
"github.com/stacklok/minder/internal/marketplaces"
44
47
"github.com/stacklok/minder/internal/projects"
45
48
"github.com/stacklok/minder/internal/providers"
@@ -479,3 +482,262 @@ func TestDeleteUser_gRPC(t *testing.T) {
479
482
})
480
483
}
481
484
}
485
+
486
+ func TestListInvitations (t * testing.T ) {
487
+ t .Parallel ()
488
+
489
+
490
+ project := uuid .New ()
491
+ code := "code"
492
+ identitySubject := "subject1"
493
+ role := "viewer"
494
+ projectDisplayName := "Project"
495
+ projectMetadata , err := json .Marshal (
496
+ projects.Metadata {Public : projects.PublicMetadataV1 {DisplayName : projectDisplayName }},
497
+ )
498
+ require .NoError (t , err )
499
+
500
+ testCases := []struct {
501
+ name string
502
+ setup func (store * mockdb.MockStore , idClient * mockidentity.MockResolver )
503
+ expectedError string
504
+ expectedResult * pb.ListInvitationsResponse
505
+ }{
506
+ {
507
+ name : "Success" ,
508
+ setup : func (store * mockdb.MockStore , idClient * mockidentity.MockResolver ) {
509
+ store .EXPECT ().GetInvitationsByEmail (gomock .Any (), userEmail ).Return ([]db.GetInvitationsByEmailRow {
510
+ {
511
+ Project : project ,
512
+ Code : code ,
513
+ IdentitySubject : identitySubject ,
514
+ Email : userEmail ,
515
+ Role : role ,
516
+ },
517
+ }, nil )
518
+ idClient .EXPECT ().Resolve (gomock .Any (), identitySubject ).Return (& auth.Identity {
519
+ UserID : identitySubject ,
520
+ HumanName : "User" ,
521
+ }, nil )
522
+ store .EXPECT ().GetProjectByID (gomock .Any (), project ).Return (db.Project {
523
+ Name : "project1" ,
524
+ Metadata : projectMetadata ,
525
+ }, nil )
526
+ },
527
+ expectedResult : & pb.ListInvitationsResponse {
528
+ Invitations : []* pb.Invitation {
529
+ {
530
+ Project : project .String (),
531
+ ProjectDisplay : projectDisplayName ,
532
+ Code : code ,
533
+ Role : role ,
534
+ Email : userEmail ,
535
+ Sponsor : identitySubject ,
536
+ },
537
+ },
538
+ },
539
+ },
540
+ }
541
+
542
+ for _ , tc := range testCases {
543
+ t .Run (tc .name , func (t * testing.T ) {
544
+ t .Parallel ()
545
+
546
+ ctrl := gomock .NewController (t )
547
+ defer ctrl .Finish ()
548
+
549
+ user := openid .New ()
550
+ assert .NoError (t , user .Set ("email" , userEmail ))
551
+
552
+ ctx := context .Background ()
553
+ ctx = jwt .WithAuthTokenContext (ctx , user )
554
+
555
+ mockStore := mockdb .NewMockStore (ctrl )
556
+ mockIdClient := mockidentity .NewMockResolver (ctrl )
557
+ if tc .setup != nil {
558
+ tc .setup (mockStore , mockIdClient )
559
+ }
560
+
561
+ featureClient := & flags.FakeClient {}
562
+ featureClient .Data = map [string ]any {
563
+ "user_management" : true ,
564
+ }
565
+
566
+ server := & Server {
567
+ store : mockStore ,
568
+ idClient : mockIdClient ,
569
+ featureFlags : featureClient ,
570
+ }
571
+
572
+ response , err := server .ListInvitations (ctx , & pb.ListInvitationsRequest {})
573
+
574
+ if tc .expectedError != "" {
575
+ require .Error (t , err )
576
+ require .Contains (t , err .Error (), tc .expectedError )
577
+ return
578
+ }
579
+
580
+ require .NoError (t , err )
581
+ require .Equal (t , len (tc .expectedResult .Invitations ), len (response .Invitations ))
582
+ require .Equal (t , tc .expectedResult .Invitations [0 ].Email , response .Invitations [0 ].Email )
583
+ require .Equal (t , tc .expectedResult .Invitations [0 ].Project , response .Invitations [0 ].Project )
584
+ require .Equal (t , tc .expectedResult .Invitations [0 ].ProjectDisplay , response .Invitations [0 ].ProjectDisplay )
585
+ require .Equal (t , tc .expectedResult .Invitations [0 ].Code , response .Invitations [0 ].Code )
586
+ require .Equal (t , tc .expectedResult .Invitations [0 ].Role , response .Invitations [0 ].Role )
587
+ require .Equal (t , tc .expectedResult .Invitations [0 ].Sponsor , response .Invitations [0 ].Sponsor )
588
+ })
589
+ }
590
+ }
591
+
592
+ func TestResolveInvitation (t * testing.T ) {
593
+ t .Parallel ()
594
+
595
+
596
+ userSubject := "subject1"
597
+ projectDisplayName := "Project"
598
+ projectMetadata , err := json .Marshal (
599
+ projects.Metadata {Public : projects.PublicMetadataV1 {DisplayName : projectDisplayName }},
600
+ )
601
+ require .NoError (t , err )
602
+
603
+ testCases := []struct {
604
+ name string
605
+ accept bool
606
+ setup func (store * mockdb.MockStore )
607
+ expectedError string
608
+ }{
609
+ {
610
+ name : "code not found" ,
611
+ setup : func (store * mockdb.MockStore ) {
612
+ store .EXPECT ().GetInvitationByCode (gomock .Any (), gomock .Any ()).Return (db.GetInvitationByCodeRow {}, sql .ErrNoRows )
613
+ },
614
+ expectedError : "invitation not found or already used" ,
615
+ },
616
+ {
617
+ name : "user self resolving" ,
618
+ setup : func (store * mockdb.MockStore ) {
619
+ userId := int32 (1 )
620
+ store .EXPECT ().GetInvitationByCode (gomock .Any (), gomock .Any ()).Return (db.GetInvitationByCodeRow {
621
+ Project : projectID ,
622
+ IdentitySubject : userSubject ,
623
+ Sponsor : userId ,
624
+ }, nil )
625
+ store .EXPECT ().GetUserBySubject (gomock .Any (), userSubject ).Return (db.User {
626
+ ID : userId ,
627
+ }, nil )
628
+ },
629
+ expectedError : "user cannot resolve their own invitation" ,
630
+ },
631
+ {
632
+ name : "expired invitation" ,
633
+ setup : func (store * mockdb.MockStore ) {
634
+ store .EXPECT ().GetInvitationByCode (gomock .Any (), gomock .Any ()).Return (db.GetInvitationByCodeRow {
635
+ Project : projectID ,
636
+ IdentitySubject : userSubject ,
637
+ Sponsor : 1 ,
638
+ UpdatedAt : time .Now ().Add (- time .Hour * 24 * 8 ), // updated 8 days ago
639
+ }, nil )
640
+ store .EXPECT ().GetUserBySubject (gomock .Any (), userSubject ).Return (db.User {
641
+ ID : 2 ,
642
+ }, nil )
643
+ },
644
+ expectedError : "invitation expired" ,
645
+ },
646
+ {
647
+ name : "Success accept" ,
648
+ accept : true ,
649
+ setup : func (store * mockdb.MockStore ) {
650
+ store .EXPECT ().GetInvitationByCode (gomock .Any (), gomock .Any ()).Return (db.GetInvitationByCodeRow {
651
+ Project : projectID ,
652
+ IdentitySubject : userSubject ,
653
+ Sponsor : 1 ,
654
+ Role : "viewer" ,
655
+ UpdatedAt : time .Now (),
656
+ }, nil )
657
+ store .EXPECT ().GetUserBySubject (gomock .Any (), userSubject ).Return (db.User {
658
+ ID : 2 ,
659
+ }, nil )
660
+ store .EXPECT ().DeleteInvitation (gomock .Any (), gomock .Any ()).Return (db.UserInvite {
661
+ Project : projectID ,
662
+ Email : userEmail ,
663
+ }, nil )
664
+ store .EXPECT ().GetProjectByID (gomock .Any (), projectID ).Return (db.Project {
665
+ Name : "project1" ,
666
+ Metadata : projectMetadata ,
667
+ }, nil )
668
+ },
669
+ },
670
+ {
671
+ name : "Success decline" ,
672
+ accept : false ,
673
+ setup : func (store * mockdb.MockStore ) {
674
+ store .EXPECT ().GetInvitationByCode (gomock .Any (), gomock .Any ()).Return (db.GetInvitationByCodeRow {
675
+ Project : projectID ,
676
+ IdentitySubject : userSubject ,
677
+ Sponsor : 1 ,
678
+ Role : "viewer" ,
679
+ UpdatedAt : time .Now (),
680
+ }, nil )
681
+ store .EXPECT ().GetUserBySubject (gomock .Any (), userSubject ).Return (db.User {
682
+ ID : 2 ,
683
+ }, nil )
684
+ store .EXPECT ().DeleteInvitation (gomock .Any (), gomock .Any ()).Return (db.UserInvite {
685
+ Project : projectID ,
686
+ Email : userEmail ,
687
+ }, nil )
688
+ store .EXPECT ().GetProjectByID (gomock .Any (), projectID ).Return (db.Project {
689
+ Name : "project1" ,
690
+ Metadata : projectMetadata ,
691
+ }, nil )
692
+ },
693
+ },
694
+ }
695
+
696
+ for _ , tc := range testCases {
697
+ t .Run (tc .name , func (t * testing.T ) {
698
+ t .Parallel ()
699
+
700
+ ctrl := gomock .NewController (t )
701
+ defer ctrl .Finish ()
702
+
703
+ user := openid .New ()
704
+ assert .NoError (t , user .Set ("email" , userEmail ))
705
+ assert .NoError (t , user .Set ("sub" , userSubject ))
706
+
707
+ ctx := context .Background ()
708
+ ctx = jwt .WithAuthTokenContext (ctx , user )
709
+
710
+ mockStore := mockdb .NewMockStore (ctrl )
711
+ if tc .setup != nil {
712
+ tc .setup (mockStore )
713
+ }
714
+
715
+ featureClient := & flags.FakeClient {}
716
+ featureClient .Data = map [string ]any {
717
+ "user_management" : true ,
718
+ }
719
+
720
+ authzClient := & mock.SimpleClient {}
721
+
722
+ server := & Server {
723
+ store : mockStore ,
724
+ featureFlags : featureClient ,
725
+ authzClient : authzClient ,
726
+ }
727
+
728
+ response , err := server .ResolveInvitation (ctx , & pb.ResolveInvitationRequest {
729
+ Code : "code" ,
730
+ Accept : tc .accept ,
731
+ })
732
+
733
+ if tc .expectedError != "" {
734
+ require .Error (t , err )
735
+ require .Contains (t , err .Error (), tc .expectedError )
736
+ return
737
+ }
738
+
739
+ require .NoError (t , err )
740
+ require .Equal (t , tc .accept , response .IsAccepted )
741
+ })
742
+ }
743
+ }
0 commit comments