github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/web/courses_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 "google.golang.org/protobuf/testing/protocmp" 12 ) 13 14 func TestCreateAndGetCourse(t *testing.T) { 15 db, cleanup := qtest.TestDB(t) 16 defer cleanup() 17 18 client, tm, _ := MockClientWithUser(t, db) 19 20 admin := qtest.CreateFakeCustomUser(t, db, &qf.User{Name: "admin", Login: "admin"}) 21 cookie := Cookie(t, tm, admin) 22 23 wantCourse := qtest.MockCourses[0] 24 createdCourse, err := client.CreateCourse(context.Background(), qtest.RequestWithCookie(wantCourse, cookie)) 25 if err != nil { 26 t.Fatal(err) 27 } 28 29 gotCourse, err := client.GetCourse(context.Background(), qtest.RequestWithCookie(&qf.CourseRequest{ 30 CourseID: createdCourse.Msg.ID, 31 }, cookie)) 32 if err != nil { 33 t.Error(err) 34 } 35 36 wantCourse.ID = createdCourse.Msg.ID 37 if diff := cmp.Diff(wantCourse, gotCourse.Msg, protocmp.Transform()); diff != "" { 38 t.Errorf("GetCourse() mismatch (-wantCourse +gotCourse):\n%s", diff) 39 } 40 if diff := cmp.Diff(createdCourse.Msg, gotCourse.Msg, protocmp.Transform()); diff != "" { 41 t.Errorf("GetCourse() mismatch (-createdCourse +gotCourse):\n%s", diff) 42 } 43 } 44 45 func TestGetCourseWithoutDockerfileDigest(t *testing.T) { 46 db, cleanup := qtest.TestDB(t) 47 defer cleanup() 48 49 client, tm, _ := MockClientWithUser(t, db) 50 51 admin := qtest.CreateFakeCustomUser(t, db, &qf.User{Name: "admin", Login: "admin"}) 52 cookie := Cookie(t, tm, admin) 53 54 wantCourse := qtest.MockCourses[0] 55 createdCourse, err := client.CreateCourse(context.Background(), qtest.RequestWithCookie(wantCourse, cookie)) 56 if err != nil { 57 t.Fatal(err) 58 } 59 60 resp, err := client.GetCourse(context.Background(), qtest.RequestWithCookie(&qf.CourseRequest{ 61 CourseID: createdCourse.Msg.ID, 62 }, cookie)) 63 if err != nil { 64 t.Error(err) 65 } 66 67 course := resp.Msg 68 if course.DockerfileDigest != "" { 69 t.Errorf("expected empty DockerfileDigest, got %s", course.DockerfileDigest) 70 } 71 dockerfile := "FROM golang:latest" 72 want := true 73 got := course.UpdateDockerfile(dockerfile) 74 if got != want { 75 t.Errorf("UpdateDockerfile(%q) = %t, want %t", dockerfile, got, want) 76 } 77 // Update the course's DockerfileDigest in the database 78 // To simulate the behavior in assignments.UpdateFromTestsRepo() 79 if err := db.UpdateCourse(course); err != nil { 80 t.Error(err) 81 } 82 83 // GetCourse again to check that the digest is not returned in the response. 84 resp, err = client.GetCourse(context.Background(), qtest.RequestWithCookie(&qf.CourseRequest{ 85 CourseID: createdCourse.Msg.ID, 86 }, cookie)) 87 if err != nil { 88 t.Error(err) 89 } 90 91 // Check that the digest is not returned in the response. 92 course = resp.Msg 93 if course.DockerfileDigest != "" { 94 t.Errorf("expected DockerfileDigest to be removed, got %s", course.DockerfileDigest) 95 } 96 } 97 98 func TestCreateAndGetCourses(t *testing.T) { 99 db, cleanup := qtest.TestDB(t) 100 defer cleanup() 101 102 client, tm, _ := MockClientWithUser(t, db) 103 104 admin := qtest.CreateFakeCustomUser(t, db, &qf.User{Name: "admin", Login: "admin"}) 105 cookie := Cookie(t, tm, admin) 106 107 for _, wantCourse := range qtest.MockCourses { 108 gotCourse, err := client.CreateCourse(context.Background(), qtest.RequestWithCookie(wantCourse, cookie)) 109 if err != nil { 110 t.Error(err) 111 } 112 // copy the ID from the created course to the expected course 113 wantCourse.ID = gotCourse.Msg.ID 114 if diff := cmp.Diff(wantCourse, gotCourse.Msg, protocmp.Transform()); diff != "" { 115 t.Errorf("CreateCourse() mismatch (-wantCourse +gotCourse):\n%s", diff) 116 } 117 } 118 119 wantCourses := qtest.MockCourses 120 foundCourses, err := client.GetCourses(context.Background(), qtest.RequestWithCookie(&qf.Void{}, cookie)) 121 if err != nil { 122 t.Error(err) 123 } 124 gotCourses := foundCourses.Msg.Courses 125 if diff := cmp.Diff(wantCourses, gotCourses, protocmp.Transform()); diff != "" { 126 t.Errorf("GetCourses() mismatch (-wantCourses +gotCourses):\n%s", diff) 127 } 128 } 129 130 func TestNewCourseExistingRepos(t *testing.T) { 131 db, cleanup := qtest.TestDB(t) 132 defer cleanup() 133 134 client, tm := MockClientWithUserAndCourse(t, db) 135 136 admin := qtest.CreateFakeCustomUser(t, db, &qf.User{Name: "admin", Login: "admin"}) 137 cookie := Cookie(t, tm, admin) 138 139 ctx := context.Background() 140 course, err := client.CreateCourse(ctx, qtest.RequestWithCookie(qtest.MockCourses[0], cookie)) 141 if course != nil { 142 t.Fatal("expected CreateCourse to fail with AlreadyExists") 143 } 144 if err != nil && connect.CodeOf(err) != connect.CodeAlreadyExists { 145 t.Fatalf("expected CreateCourse to fail with AlreadyExists, but got: %v", err) 146 } 147 } 148 149 func TestEnrollmentProcess(t *testing.T) { 150 db, cleanup := qtest.TestDB(t) 151 defer cleanup() 152 153 admin := qtest.CreateFakeCustomUser(t, db, &qf.User{Name: "admin", Login: "admin"}) 154 client, tm, _ := MockClientWithUser(t, db) 155 156 ctx := context.Background() 157 course, err := client.CreateCourse(ctx, qtest.RequestWithCookie(qtest.MockCourses[0], Cookie(t, tm, admin))) 158 if err != nil { 159 t.Fatal(err) 160 } 161 162 stud1 := qtest.CreateFakeCustomUser(t, db, &qf.User{Name: "student1", Login: "student1"}) 163 enrollStud1 := &qf.Enrollment{CourseID: course.Msg.ID, UserID: stud1.ID} 164 if _, err = client.CreateEnrollment(ctx, qtest.RequestWithCookie(enrollStud1, Cookie(t, tm, stud1))); err != nil { 165 t.Error(err) 166 } 167 168 // verify that a pending enrollment was indeed created for the user 169 enrollStatusReq := &qf.EnrollmentRequest{ 170 FetchMode: &qf.EnrollmentRequest_UserID{ 171 UserID: stud1.ID, 172 }, 173 Statuses: []qf.Enrollment_UserStatus{ 174 qf.Enrollment_PENDING, 175 }, 176 } 177 userEnrollments, err := client.GetEnrollments(ctx, qtest.RequestWithCookie(enrollStatusReq, Cookie(t, tm, stud1))) 178 if err != nil { 179 t.Fatal(err) 180 } 181 var pendingUserEnrollment *qf.Enrollment 182 for _, enrollment := range userEnrollments.Msg.Enrollments { 183 if enrollment.CourseID == course.Msg.ID { 184 if enrollment.Status == qf.Enrollment_PENDING { 185 pendingUserEnrollment = enrollment 186 } else { 187 t.Errorf("expected student %d to have pending enrollment in course %d", stud1.ID, course.Msg.ID) 188 } 189 } 190 } 191 192 // verify that a pending enrollment was indeed created for the course. 193 enrollReq := &qf.EnrollmentRequest{ 194 FetchMode: &qf.EnrollmentRequest_CourseID{ 195 CourseID: course.Msg.ID, 196 }, 197 } 198 courseEnrollments, err := client.GetEnrollments(ctx, qtest.RequestWithCookie(enrollReq, Cookie(t, tm, admin))) 199 if err != nil { 200 t.Error(err) 201 } 202 var pendingCourseEnrollment *qf.Enrollment 203 for _, enrollment := range courseEnrollments.Msg.Enrollments { 204 if enrollment.UserID == stud1.ID { 205 if enrollment.Status == qf.Enrollment_PENDING { 206 pendingCourseEnrollment = enrollment 207 } else { 208 t.Errorf("expected student %d to have pending enrollment in course %d", stud1.ID, course.Msg.ID) 209 } 210 } 211 } 212 if diff := cmp.Diff(pendingUserEnrollment, pendingCourseEnrollment, protocmp.Transform()); diff != "" { 213 t.Errorf("EnrollmentProcess mismatch (-pendingUserEnrollment +pendingCourseEnrollment):\n%s", diff) 214 } 215 216 wantEnrollment := &qf.Enrollment{ 217 ID: pendingCourseEnrollment.ID, 218 CourseID: course.Msg.ID, 219 UserID: stud1.ID, 220 Status: qf.Enrollment_PENDING, 221 State: qf.Enrollment_VISIBLE, 222 Course: course.Msg, 223 User: stud1, 224 UsedSlipDays: []*qf.UsedSlipDays{}, 225 } 226 if diff := cmp.Diff(wantEnrollment, pendingCourseEnrollment, protocmp.Transform()); diff != "" { 227 t.Errorf("EnrollmentProcess mismatch (-wantEnrollment +pendingEnrollment):\n%s", diff) 228 } 229 230 enrollStud1.Status = qf.Enrollment_STUDENT 231 enrollStud1.Course = course.Msg 232 if _, err = client.UpdateEnrollments(ctx, qtest.RequestWithCookie(&qf.Enrollments{ 233 Enrollments: []*qf.Enrollment{enrollStud1}, 234 }, Cookie(t, tm, admin))); err != nil { 235 t.Error(err) 236 } 237 238 // verify that the enrollment was updated to student status. 239 gotEnrollment, err := db.GetEnrollmentByCourseAndUser(course.Msg.ID, stud1.ID) 240 if err != nil { 241 t.Error(err) 242 } 243 wantEnrollment.Status = qf.Enrollment_STUDENT 244 if diff := cmp.Diff(wantEnrollment, gotEnrollment, protocmp.Transform()); diff != "" { 245 t.Errorf("EnrollmentProcess mismatch (-wantEnrollment +gotEnrollment):\n%s", diff) 246 } 247 248 // create another user and enroll as student 249 250 stud2 := qtest.CreateFakeCustomUser(t, db, &qf.User{Name: "student2", Login: "student2"}) 251 enrollStud2 := &qf.Enrollment{CourseID: course.Msg.ID, UserID: stud2.ID} 252 if _, err = client.CreateEnrollment(ctx, qtest.RequestWithCookie(enrollStud2, Cookie(t, tm, stud2))); err != nil { 253 t.Error(err) 254 } 255 enrollStud2.Status = qf.Enrollment_STUDENT 256 if _, err = client.UpdateEnrollments(ctx, qtest.RequestWithCookie(&qf.Enrollments{ 257 Enrollments: []*qf.Enrollment{ 258 enrollStud2, 259 }, 260 }, Cookie(t, tm, admin))); err != nil { 261 t.Error(err) 262 } 263 // verify that the stud2 was enrolled with student status. 264 gotEnrollment, err = db.GetEnrollmentByCourseAndUser(course.Msg.ID, stud2.ID) 265 if err != nil { 266 t.Fatal(err) 267 } 268 wantEnrollment.ID = gotEnrollment.ID 269 wantEnrollment.Status = qf.Enrollment_STUDENT 270 wantEnrollment.UserID = stud2.ID 271 wantEnrollment.User = stud2 272 if diff := cmp.Diff(wantEnrollment, gotEnrollment, protocmp.Transform()); diff != "" { 273 t.Errorf("EnrollmentProcess mismatch (-wantEnrollment +gotEnrollment):\n%s", diff) 274 } 275 276 // promote stud2 to teaching assistant 277 278 enrollStud2.Status = qf.Enrollment_TEACHER 279 if _, err = client.UpdateEnrollments(ctx, qtest.RequestWithCookie(&qf.Enrollments{ 280 Enrollments: []*qf.Enrollment{ 281 enrollStud2, 282 }, 283 }, Cookie(t, tm, admin))); err != nil { 284 t.Error(err) 285 } 286 // verify that the stud2 was promoted to teacher status. 287 gotEnrollment, err = db.GetEnrollmentByCourseAndUser(course.Msg.ID, stud2.ID) 288 if err != nil { 289 t.Fatal(err) 290 } 291 wantEnrollment.ID = gotEnrollment.ID 292 wantEnrollment.Status = qf.Enrollment_TEACHER 293 if diff := cmp.Diff(wantEnrollment, gotEnrollment, protocmp.Transform()); diff != "" { 294 t.Errorf("EnrollmentProcess mismatch (-wantEnrollment +gotEnrollment):\n%s", diff) 295 } 296 } 297 298 func TestListCoursesWithEnrollment(t *testing.T) { 299 db, cleanup := qtest.TestDB(t) 300 defer cleanup() 301 302 client, tm, _ := MockClientWithUser(t, db) 303 304 admin := qtest.CreateFakeUser(t, db) 305 user := qtest.CreateFakeUser(t, db) 306 307 var testCourses []*qf.Course 308 for _, course := range qtest.MockCourses { 309 err := db.CreateCourse(admin.ID, course) 310 if err != nil { 311 t.Fatal(err) 312 } 313 testCourses = append(testCourses, course) 314 } 315 316 if err := db.CreateEnrollment(&qf.Enrollment{ 317 UserID: user.ID, 318 CourseID: testCourses[0].ID, 319 }); err != nil { 320 t.Fatal(err) 321 } 322 if err := db.CreateEnrollment(&qf.Enrollment{ 323 UserID: user.ID, 324 CourseID: testCourses[1].ID, 325 }); err != nil { 326 t.Fatal(err) 327 } 328 query := &qf.Enrollment{ 329 UserID: user.ID, 330 CourseID: testCourses[2].ID, 331 } 332 if err := db.CreateEnrollment(query); err != nil { 333 t.Fatal(err) 334 } 335 if err := db.RejectEnrollment(user.ID, testCourses[1].ID); err != nil { 336 t.Fatal(err) 337 } 338 query.Status = qf.Enrollment_STUDENT 339 if err := db.UpdateEnrollment(query); err != nil { 340 t.Fatal(err) 341 } 342 343 gotUser, err := client.GetUser(context.Background(), qtest.RequestWithCookie(&qf.Void{}, Cookie(t, tm, user))) 344 if err != nil { 345 t.Error(err) 346 } 347 348 wantCourses := map[uint64]qf.Enrollment_UserStatus{ 349 testCourses[0].ID: qf.Enrollment_PENDING, 350 testCourses[1].ID: qf.Enrollment_NONE, 351 testCourses[2].ID: qf.Enrollment_STUDENT, 352 testCourses[3].ID: qf.Enrollment_NONE, 353 } 354 for _, enrollment := range gotUser.Msg.GetEnrollments() { 355 course := enrollment.Course 356 wantStatus, ok := wantCourses[course.ID] 357 if !ok { 358 t.Errorf("unexpected course: %+v", course.ID) 359 } 360 if enrollment.Status != wantStatus { 361 t.Errorf("have course %+v want %+v", enrollment.Status, wantStatus) 362 } 363 } 364 } 365 366 func TestListCoursesWithEnrollmentStatuses(t *testing.T) { 367 db, cleanup := qtest.TestDB(t) 368 defer cleanup() 369 370 client, tm, _ := MockClientWithUser(t, db) 371 372 admin := qtest.CreateFakeUser(t, db) 373 var testCourses []*qf.Course 374 for _, course := range qtest.MockCourses { 375 err := db.CreateCourse(admin.ID, course) 376 if err != nil { 377 t.Fatal(err) 378 } 379 testCourses = append(testCourses, course) 380 } 381 382 user := qtest.CreateFakeUser(t, db) 383 384 if err := db.CreateEnrollment(&qf.Enrollment{ 385 UserID: user.ID, 386 CourseID: testCourses[0].ID, 387 }); err != nil { 388 t.Fatal(err) 389 } 390 if err := db.CreateEnrollment(&qf.Enrollment{ 391 UserID: user.ID, 392 CourseID: testCourses[1].ID, 393 }); err != nil { 394 t.Fatal(err) 395 } 396 query := &qf.Enrollment{ 397 UserID: user.ID, 398 CourseID: testCourses[2].ID, 399 } 400 if err := db.CreateEnrollment(query); err != nil { 401 t.Fatal(err) 402 } 403 404 // user enrollment is rejected for course 1 and enrolled for course 2, still pending for course 0 405 if err := db.RejectEnrollment(user.ID, testCourses[1].ID); err != nil { 406 t.Fatal(err) 407 } 408 query.Status = qf.Enrollment_STUDENT 409 if err := db.UpdateEnrollment(query); err != nil { 410 t.Fatal(err) 411 } 412 413 gotUser, err := client.GetUser(context.Background(), qtest.RequestWithCookie(&qf.Void{}, Cookie(t, tm, user))) 414 if err != nil { 415 t.Error(err) 416 } 417 gotCourses := make([]*qf.Course, 0) 418 for _, enrollment := range gotUser.Msg.GetEnrollments() { 419 // since GetUser returns all enrollments, we only keep the student enrollments 420 if enrollment.Status == qf.Enrollment_STUDENT { 421 course := enrollment.Course 422 course.Enrolled = enrollment.Status 423 gotCourses = append(gotCourses, course) 424 } 425 } 426 427 stats := make([]qf.Enrollment_UserStatus, 0) 428 stats = append(stats, qf.Enrollment_STUDENT) 429 course_req := &qf.EnrollmentRequest{ 430 FetchMode: &qf.EnrollmentRequest_UserID{ 431 UserID: user.ID, 432 }, 433 Statuses: stats, 434 } 435 enrollments, err := client.GetEnrollments(context.Background(), qtest.RequestWithCookie(course_req, Cookie(t, tm, user))) 436 if err != nil { 437 t.Error(err) 438 } 439 gotCourses2 := make([]*qf.Course, 0) 440 for _, enrollment := range enrollments.Msg.GetEnrollments() { 441 // since GetEnrollmentsByUser returns only student enrollments 442 course := enrollment.Course 443 course.Enrolled = enrollment.Status 444 gotCourses2 = append(gotCourses2, course) 445 } 446 447 wantCourses, err := db.GetCoursesByUser(user.ID, qf.Enrollment_STUDENT) 448 if err != nil { 449 t.Fatal(err) 450 } 451 if diff := cmp.Diff(wantCourses, gotCourses, protocmp.Transform()); diff != "" { 452 t.Errorf("GetUser() mismatch (-wantCourses +gotCourses):\n%s", diff) 453 } 454 if diff := cmp.Diff(wantCourses, gotCourses2, protocmp.Transform()); diff != "" { 455 t.Errorf("GetEnrollmentsByUser() mismatch (-wantCourses +gotCourses2):\n%s", diff) 456 } 457 } 458 459 func TestPromoteDemoteRejectTeacher(t *testing.T) { 460 db, cleanup := qtest.TestDB(t) 461 defer cleanup() 462 463 client, tm := MockClientWithUserAndCourse(t, db) 464 465 teacher := qtest.CreateFakeCustomUser(t, db, &qf.User{Name: "teacher", Login: "teacher"}) 466 student1 := qtest.CreateFakeCustomUser(t, db, &qf.User{Name: "student1", Login: "student1"}) 467 student2 := qtest.CreateFakeCustomUser(t, db, &qf.User{Name: "student2", Login: "student2"}) 468 ta := qtest.CreateFakeCustomUser(t, db, &qf.User{Name: "TA", Login: "TA"}) 469 470 course := qtest.MockCourses[0] 471 qtest.CreateCourse(t, db, teacher, course) 472 qtest.EnrollStudent(t, db, student1, course) 473 qtest.EnrollStudent(t, db, student2, course) 474 qtest.EnrollStudent(t, db, ta, course) 475 476 student1Enrollment := &qf.Enrollment{ 477 UserID: student1.ID, 478 CourseID: course.ID, 479 Status: qf.Enrollment_TEACHER, 480 } 481 student2Enrollment := &qf.Enrollment{ 482 UserID: student2.ID, 483 CourseID: course.ID, 484 Status: qf.Enrollment_TEACHER, 485 } 486 taEnrollment := &qf.Enrollment{ 487 UserID: ta.ID, 488 CourseID: course.ID, 489 Status: qf.Enrollment_TEACHER, 490 } 491 teacherEnrollment := &qf.Enrollment{ 492 UserID: teacher.ID, 493 CourseID: course.ID, 494 Status: qf.Enrollment_STUDENT, 495 } 496 497 request := &qf.Enrollments{} 498 499 // teacher promotes students to teachers, must succeed 500 ctx := context.Background() 501 request.Enrollments = []*qf.Enrollment{student1Enrollment, student2Enrollment, taEnrollment} 502 if _, err := client.UpdateEnrollments(ctx, qtest.RequestWithCookie(request, Cookie(t, tm, teacher))); err != nil { 503 t.Error(err) 504 } 505 506 // TA attempts to demote self, must succeed 507 taEnrollment.Status = qf.Enrollment_STUDENT 508 request.Enrollments = []*qf.Enrollment{taEnrollment} 509 if _, err := client.UpdateEnrollments(ctx, qtest.RequestWithCookie(request, Cookie(t, tm, ta))); err != nil { 510 t.Error(err) 511 } 512 513 // student2 attempts to demote course creator, must fail 514 teacherEnrollment.Status = qf.Enrollment_STUDENT 515 request.Enrollments = []*qf.Enrollment{teacherEnrollment} 516 if _, err := client.UpdateEnrollments(ctx, qtest.RequestWithCookie(request, Cookie(t, tm, student2))); err == nil { 517 t.Errorf("expected error: 'permission_denied: course creator cannot be demoted', got: '%v'", err) 518 } 519 520 // student2 attempts to reject course creator, must fail 521 teacherEnrollment.Status = qf.Enrollment_NONE 522 if _, err := client.UpdateEnrollments(ctx, qtest.RequestWithCookie(request, Cookie(t, tm, student2))); err == nil { 523 t.Errorf("expected error: 'permission_denied: course creator cannot be demoted', got: '%v'", err) 524 } 525 526 // teacher demotes student1, must succeed 527 student1Enrollment.Status = qf.Enrollment_STUDENT 528 request.Enrollments = []*qf.Enrollment{student1Enrollment} 529 if _, err := client.UpdateEnrollments(ctx, qtest.RequestWithCookie(request, Cookie(t, tm, teacher))); err != nil { 530 t.Error(err) 531 } 532 533 // check that student1 is now enrolled as student 534 enrol, err := db.GetEnrollmentByCourseAndUser(course.ID, student1.ID) 535 if err != nil { 536 t.Error(err) 537 } 538 if enrol.Status != qf.Enrollment_STUDENT { 539 t.Errorf("expected status %s, got %s", qf.Enrollment_STUDENT, enrol.Status) 540 } 541 542 // teacher rejects student2, must succeed 543 student2Enrollment.Status = qf.Enrollment_STUDENT 544 request.Enrollments = []*qf.Enrollment{student2Enrollment} 545 if _, err := client.UpdateEnrollments(ctx, qtest.RequestWithCookie(request, Cookie(t, tm, teacher))); err != nil { 546 t.Error(err) 547 } 548 student2Enrollment.Status = qf.Enrollment_NONE 549 if _, err := client.UpdateEnrollments(ctx, qtest.RequestWithCookie(request, Cookie(t, tm, teacher))); err != nil { 550 t.Error(err) 551 } 552 553 // ensure that student2 is no longer enrolled in the course 554 if _, err := db.GetEnrollmentByCourseAndUser(course.ID, student2.ID); err == nil { 555 t.Error("expected error 'record not found'") 556 } 557 558 // course creator attempts to demote himself, must fail as well 559 teacherEnrollment.Status = qf.Enrollment_STUDENT 560 request.Enrollments = []*qf.Enrollment{teacherEnrollment} 561 if _, err := client.UpdateEnrollments(ctx, qtest.RequestWithCookie(request, Cookie(t, tm, teacher))); err == nil { 562 t.Errorf("expected error: 'permission_denied: course creator cannot be demoted', got: '%v'", err) 563 } 564 565 // same when rejecting 566 teacherEnrollment.Status = qf.Enrollment_NONE 567 if _, err := client.UpdateEnrollments(ctx, qtest.RequestWithCookie(request, Cookie(t, tm, teacher))); err == nil { 568 t.Errorf("expected error: 'permission_denied: course creator cannot be demoted', got: '%v'", err) 569 } 570 571 // ta attempts to demote course creator, must fail 572 teacherEnrollment.Status = qf.Enrollment_STUDENT 573 if _, err := client.UpdateEnrollments(ctx, qtest.RequestWithCookie(request, Cookie(t, tm, ta))); err == nil { 574 t.Errorf("expected error 'permission_denied: access denied for UpdateEnrollments: required roles [4] not satisfied by claims', got: '%v'", err) 575 } 576 } 577 578 func TestUpdateCourseVisibility(t *testing.T) { 579 db, cleanup := qtest.TestDB(t) 580 defer cleanup() 581 582 client, tm := MockClientWithUserAndCourse(t, db) 583 584 teacher := qtest.CreateFakeUser(t, db) 585 user := qtest.CreateFakeUser(t, db) 586 cookie := Cookie(t, tm, user) 587 588 course := qtest.MockCourses[0] 589 if err := db.CreateCourse(teacher.ID, course); err != nil { 590 t.Fatal(err) 591 } 592 if err := db.CreateEnrollment(&qf.Enrollment{ 593 UserID: user.ID, 594 CourseID: course.ID, 595 }); err != nil { 596 t.Fatal(err) 597 } 598 599 ctx := context.Background() 600 req := &qf.EnrollmentRequest{ 601 FetchMode: &qf.EnrollmentRequest_UserID{ 602 UserID: user.ID, 603 }, 604 } 605 enrollments, err := client.GetEnrollments(ctx, qtest.RequestWithCookie(req, cookie)) 606 if err != nil { 607 t.Error(err) 608 } 609 if len(enrollments.Msg.GetEnrollments()) != 1 { 610 t.Errorf("expected 1 enrollment, got %d", len(enrollments.Msg.GetEnrollments())) 611 } 612 613 // pending enrollment should be allowed to change visibility, but not status 614 enrollment := enrollments.Msg.Enrollments[0] 615 enrollment.State = qf.Enrollment_FAVORITE 616 enrollment.Status = qf.Enrollment_TEACHER 617 if _, err := client.UpdateCourseVisibility(ctx, qtest.RequestWithCookie(enrollment, cookie)); err != nil { 618 t.Error(err) 619 } 620 621 gotEnrollments, err := client.GetEnrollments(ctx, qtest.RequestWithCookie(req, cookie)) 622 if err != nil { 623 t.Error(err) 624 } 625 if len(gotEnrollments.Msg.GetEnrollments()) != 1 { 626 t.Errorf("expected 1 enrollment, got %d", len(gotEnrollments.Msg.GetEnrollments())) 627 } 628 629 gotEnrollment := gotEnrollments.Msg.Enrollments[0] 630 if gotEnrollment.State != qf.Enrollment_FAVORITE { 631 // State should have changed to favorite 632 t.Errorf("expected enrollment state %s, got %s", qf.Enrollment_FAVORITE, gotEnrollment.State) 633 } 634 if gotEnrollment.Status != qf.Enrollment_PENDING { 635 // Status should *not* have changed 636 t.Errorf("expected enrollment status %s, got %s", qf.Enrollment_NONE, gotEnrollment.Status) 637 } 638 }