github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/web/users_test.go (about) 1 package web_test 2 3 import ( 4 "context" 5 "testing" 6 7 "connectrpc.com/connect" 8 "github.com/google/go-cmp/cmp" 9 "github.com/quickfeed/quickfeed/internal/qtest" 10 "github.com/quickfeed/quickfeed/qf" 11 "github.com/quickfeed/quickfeed/web/auth" 12 "github.com/quickfeed/quickfeed/web/interceptor" 13 "google.golang.org/protobuf/testing/protocmp" 14 ) 15 16 func TestGetUsers(t *testing.T) { 17 db, cleanup := qtest.TestDB(t) 18 defer cleanup() 19 client := MockClient(t, db, nil) 20 ctx := context.Background() 21 22 unexpectedUsers, err := client.GetUsers(ctx, &connect.Request[qf.Void]{Msg: &qf.Void{}}) 23 if err == nil && unexpectedUsers != nil && len(unexpectedUsers.Msg.GetUsers()) > 0 { 24 t.Fatalf("found unexpected users %+v", unexpectedUsers) 25 } 26 27 admin := qtest.CreateFakeUser(t, db) 28 user2 := qtest.CreateFakeUser(t, db) 29 30 foundUsers, err := client.GetUsers(ctx, &connect.Request[qf.Void]{Msg: &qf.Void{}}) 31 if err != nil { 32 t.Fatal(err) 33 } 34 35 wantUsers := make([]*qf.User, 0) 36 wantUsers = append(wantUsers, admin, user2) 37 gotUsers := foundUsers.Msg.GetUsers() 38 if diff := cmp.Diff(wantUsers, gotUsers, protocmp.Transform()); diff != "" { 39 t.Errorf("GetUsers() mismatch (-wantUsers +gotUsers):\n%s", diff) 40 } 41 } 42 43 func TestGetEnrollmentsByCourse(t *testing.T) { 44 db, cleanup := qtest.TestDB(t) 45 defer cleanup() 46 client := MockClient(t, db, nil) 47 ctx := context.Background() 48 49 var users []*qf.User 50 for i := 0; i < 10; i++ { 51 user := qtest.CreateFakeUser(t, db) 52 users = append(users, user) 53 } 54 admin := users[0] 55 for _, course := range qtest.MockCourses { 56 err := db.CreateCourse(admin.ID, course) 57 if err != nil { 58 t.Fatal(err) 59 } 60 } 61 62 // users to enroll in course DAT520 Distributed Systems 63 // (excluding admin because admin is enrolled on creation) 64 wantUsers := users[0:6] 65 for i, user := range wantUsers { 66 if i == 0 { 67 // skip enrolling admin as student 68 continue 69 } 70 qtest.EnrollStudent(t, db, user, qtest.MockCourses[0]) 71 } 72 73 // users to enroll in course DAT320 Operating Systems 74 // (excluding admin because admin is enrolled on creation) 75 osUsers := users[3:7] 76 for _, user := range osUsers { 77 qtest.EnrollStudent(t, db, user, qtest.MockCourses[1]) 78 } 79 80 request := &connect.Request[qf.EnrollmentRequest]{ 81 Msg: &qf.EnrollmentRequest{ 82 FetchMode: &qf.EnrollmentRequest_CourseID{ 83 CourseID: qtest.MockCourses[0].ID, 84 }, 85 }, 86 } 87 gotEnrollments, err := client.GetEnrollments(ctx, request) 88 if err != nil { 89 t.Error(err) 90 } 91 var gotUsers []*qf.User 92 for _, e := range gotEnrollments.Msg.Enrollments { 93 gotUsers = append(gotUsers, e.User) 94 } 95 if diff := cmp.Diff(wantUsers, gotUsers, protocmp.Transform()); diff != "" { 96 t.Errorf("GetEnrollmentsByCourse() mismatch (-wantUsers +gotUsers):\n%s", diff) 97 } 98 } 99 100 func TestUpdateUser(t *testing.T) { 101 db, cleanup := qtest.TestDB(t) 102 defer cleanup() 103 logger := qtest.Logger(t) 104 105 tm, err := auth.NewTokenManager(db) 106 if err != nil { 107 t.Fatal(err) 108 } 109 client := MockClient(t, db, connect.WithInterceptors( 110 interceptor.NewUserInterceptor(logger, tm), 111 )) 112 ctx := context.Background() 113 114 firstAdminUser := qtest.CreateFakeUser(t, db) 115 nonAdminUser := qtest.CreateFakeUser(t, db) 116 117 firstAdminCookie, err := tm.NewAuthCookie(firstAdminUser.ID) 118 if err != nil { 119 t.Fatal(err) 120 } 121 122 // we want to update nonAdminUser to become admin 123 nonAdminUser.IsAdmin = true 124 err = db.UpdateUser(nonAdminUser) 125 if err != nil { 126 t.Fatal(err) 127 } 128 129 // we expect the nonAdminUser to now be admin 130 admin, err := db.GetUser(nonAdminUser.ID) 131 if err != nil { 132 t.Fatal(err) 133 } 134 if !admin.IsAdmin { 135 t.Error("expected nonAdminUser to have become admin") 136 } 137 138 nameChangeRequest := connect.NewRequest(&qf.User{ 139 ID: nonAdminUser.ID, 140 IsAdmin: nonAdminUser.IsAdmin, 141 Name: "Scrooge McDuck", 142 StudentID: "99", 143 Email: "test@test.com", 144 AvatarURL: "www.hello.com", 145 }) 146 147 nameChangeRequest.Header().Set(auth.Cookie, firstAdminCookie.String()) 148 _, err = client.UpdateUser(ctx, nameChangeRequest) 149 if err != nil { 150 t.Error(err) 151 } 152 gotUser, err := db.GetUser(nonAdminUser.ID) 153 if err != nil { 154 t.Fatal(err) 155 } 156 wantUser := &qf.User{ 157 ID: gotUser.ID, 158 Name: "Scrooge McDuck", 159 IsAdmin: true, 160 StudentID: "99", 161 Email: "test@test.com", 162 AvatarURL: "www.hello.com", 163 RefreshToken: nonAdminUser.RefreshToken, 164 ScmRemoteID: nonAdminUser.ScmRemoteID, 165 } 166 if diff := cmp.Diff(wantUser, gotUser, protocmp.Transform()); diff != "" { 167 t.Errorf("UpdateUser() mismatch (-wantUser +gotUser):\n%s", diff) 168 } 169 } 170 171 func TestUpdateUserFailures(t *testing.T) { 172 db, cleanup := qtest.TestDB(t) 173 defer cleanup() 174 client, tm, _ := MockClientWithUser(t, db) 175 ctx := context.Background() 176 177 admin := qtest.CreateFakeCustomUser(t, db, &qf.User{Name: "admin", Login: "admin"}) 178 if !admin.IsAdmin { 179 t.Fatalf("expected user %v to be admin", admin) 180 } 181 user := qtest.CreateFakeCustomUser(t, db, &qf.User{Name: "user", Login: "user"}) 182 if user.IsAdmin { 183 t.Fatalf("expected user %v to be non-admin", user) 184 } 185 userCookie := Cookie(t, tm, user) 186 adminCookie := Cookie(t, tm, admin) 187 tests := []struct { 188 name string 189 cookie string 190 req *qf.User 191 wantUser *qf.User 192 wantErr bool 193 }{ 194 { 195 name: "user demotes admin, must fail", 196 cookie: userCookie, 197 req: &qf.User{ 198 ID: admin.ID, 199 IsAdmin: false, 200 Name: admin.Name, 201 Email: admin.Email, 202 StudentID: admin.StudentID, 203 AvatarURL: admin.AvatarURL, 204 }, 205 wantErr: true, 206 }, 207 { 208 name: "user promotes self to admin, must fail", 209 cookie: userCookie, 210 req: &qf.User{ 211 ID: user.ID, 212 Name: user.Name, 213 Email: user.Email, 214 StudentID: user.StudentID, 215 AvatarURL: user.AvatarURL, 216 IsAdmin: true, 217 }, 218 wantErr: true, 219 }, 220 { 221 name: "admin changes own name, must pass", 222 cookie: adminCookie, 223 req: &qf.User{ 224 ID: admin.ID, 225 Name: "super user", 226 }, 227 wantUser: &qf.User{ 228 ID: admin.ID, 229 IsAdmin: true, 230 Login: admin.Login, 231 Name: "super user", 232 RefreshToken: admin.RefreshToken, 233 ScmRemoteID: admin.ScmRemoteID, 234 }, 235 wantErr: false, 236 }, 237 } 238 for _, tt := range tests { 239 t.Run(tt.name, func(t *testing.T) { 240 // UpdateUser returns void, so we cannot check that the user was updated 241 _, err := client.UpdateUser(ctx, qtest.RequestWithCookie(tt.req, tt.cookie)) 242 if (err != nil) != tt.wantErr { 243 t.Errorf("%s: expected error: %v, got = %v", tt.name, tt.wantErr, err) 244 } 245 if !tt.wantErr { 246 // Instead (for success cases), get all users and check that the user was updated 247 users, err := client.GetUsers(ctx, qtest.RequestWithCookie(&qf.Void{}, tt.cookie)) 248 if err != nil { 249 t.Fatal(err) 250 } 251 for _, u := range users.Msg.GetUsers() { 252 if u.ID == tt.wantUser.ID { 253 if diff := cmp.Diff(tt.wantUser, u, protocmp.Transform()); diff != "" { 254 t.Errorf("%s: UpdateUser() mismatch (-wantUser +gotUser):\n%s", tt.name, diff) 255 } 256 } 257 } 258 } 259 }) 260 } 261 }