github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/web/interceptor/access_control_test.go (about)

     1  package interceptor_test
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"connectrpc.com/connect"
     8  	"github.com/quickfeed/quickfeed/internal/qtest"
     9  	"github.com/quickfeed/quickfeed/qf"
    10  	"github.com/quickfeed/quickfeed/web"
    11  	"github.com/quickfeed/quickfeed/web/auth"
    12  	"github.com/quickfeed/quickfeed/web/interceptor"
    13  )
    14  
    15  type accessTest struct {
    16  	cookie     string
    17  	userID     uint64
    18  	courseID   uint64
    19  	groupID    uint64
    20  	wantAccess bool
    21  	wantCode   connect.Code
    22  }
    23  
    24  func TestAccessControl(t *testing.T) {
    25  	db, cleanup := qtest.TestDB(t)
    26  	defer cleanup()
    27  	logger := qtest.Logger(t)
    28  
    29  	tm, err := auth.NewTokenManager(db)
    30  	if err != nil {
    31  		t.Fatal(err)
    32  	}
    33  	client := web.MockClient(t, db, connect.WithInterceptors(
    34  		interceptor.NewUserInterceptor(logger, tm),
    35  		interceptor.NewAccessControlInterceptor(tm),
    36  	))
    37  	ctx := context.Background()
    38  
    39  	courseAdmin := qtest.CreateFakeUser(t, db)
    40  	groupStudent := qtest.CreateFakeCustomUser(t, db, &qf.User{Name: "group student", Login: "group student"})
    41  	student := qtest.CreateFakeCustomUser(t, db, &qf.User{Name: "student", Login: "student"})
    42  	user := qtest.CreateFakeCustomUser(t, db, &qf.User{Name: "user", Login: "user"})
    43  	admin := qtest.CreateFakeUser(t, db)
    44  	admin.IsAdmin = true
    45  	if err := db.UpdateUser(admin); err != nil {
    46  		t.Fatal(err)
    47  	}
    48  
    49  	course := &qf.Course{
    50  		Code:                "test101",
    51  		Year:                2022,
    52  		ScmOrganizationID:   1,
    53  		ScmOrganizationName: "test",
    54  		CourseCreatorID:     courseAdmin.ID,
    55  	}
    56  	if err := db.CreateCourse(courseAdmin.ID, course); err != nil {
    57  		t.Fatal(err)
    58  	}
    59  	qtest.EnrollStudent(t, db, groupStudent, course)
    60  	qtest.EnrollStudent(t, db, student, course)
    61  	group := &qf.Group{
    62  		CourseID: course.ID,
    63  		Name:     "Test",
    64  		Users:    []*qf.User{groupStudent},
    65  	}
    66  	if err := db.CreateGroup(group); err != nil {
    67  		t.Fatal(err)
    68  	}
    69  
    70  	assignment := &qf.Assignment{
    71  		CourseID: course.ID,
    72  		Name:     "Test Assignment",
    73  		Order:    1,
    74  	}
    75  	if err := db.CreateAssignment(assignment); err != nil {
    76  		t.Fatal(err)
    77  	}
    78  	if err := db.CreateSubmission(&qf.Submission{
    79  		AssignmentID: assignment.ID,
    80  		UserID:       groupStudent.ID,
    81  	}); err != nil {
    82  		t.Fatal(err)
    83  	}
    84  
    85  	f := func(t *testing.T, id uint64) string {
    86  		cookie, err := tm.NewAuthCookie(id)
    87  		if err != nil {
    88  			t.Fatal(err)
    89  		}
    90  		return cookie.String()
    91  	}
    92  	courseAdminCookie := f(t, courseAdmin.ID)
    93  	groupStudentCookie := f(t, groupStudent.ID)
    94  	studentCookie := f(t, student.ID)
    95  	userCookie := f(t, user.ID)
    96  	adminCookie := f(t, admin.ID)
    97  
    98  	freeAccessTest := map[string]accessTest{
    99  		"admin":             {cookie: courseAdminCookie, courseID: course.ID, wantAccess: true, wantCode: connect.CodePermissionDenied},
   100  		"student":           {cookie: studentCookie, courseID: course.ID, wantAccess: true, wantCode: connect.CodePermissionDenied},
   101  		"group student":     {cookie: groupStudentCookie, courseID: course.ID, wantAccess: true, wantCode: connect.CodePermissionDenied},
   102  		"user":              {cookie: userCookie, courseID: course.ID, wantAccess: true, wantCode: connect.CodePermissionDenied},
   103  		"non-teacher admin": {cookie: adminCookie, courseID: course.ID, wantAccess: true, wantCode: connect.CodePermissionDenied},
   104  		"empty context":     {wantAccess: false, wantCode: connect.CodeUnauthenticated},
   105  	}
   106  	for name, tt := range freeAccessTest {
   107  		t.Run("UnrestrictedAccess/"+name, func(t *testing.T) {
   108  			_, err := client.GetUser(ctx, qtest.RequestWithCookie(&qf.Void{}, tt.cookie))
   109  			checkAccess(t, "GetUser", err, tt.wantCode, tt.wantAccess)
   110  			_, err = client.GetCourse(ctx, qtest.RequestWithCookie(&qf.CourseRequest{CourseID: tt.courseID}, tt.cookie))
   111  			checkAccess(t, "GetCourse", err, tt.wantCode, tt.wantAccess)
   112  			_, err = client.GetCourses(ctx, qtest.RequestWithCookie(&qf.Void{}, tt.cookie))
   113  			checkAccess(t, "GetCourses", err, tt.wantCode, tt.wantAccess)
   114  		})
   115  	}
   116  
   117  	userAccessTests := map[string]accessTest{
   118  		"correct user ID":   {cookie: userCookie, userID: user.ID, courseID: course.ID, wantAccess: true, wantCode: connect.CodePermissionDenied},
   119  		"incorrect user ID": {cookie: userCookie, groupID: groupStudent.ID, courseID: course.ID, wantAccess: false, wantCode: connect.CodePermissionDenied},
   120  	}
   121  	for name, tt := range userAccessTests {
   122  		t.Run("UserAccess/"+name, func(t *testing.T) {
   123  			enrol := &qf.Enrollment{
   124  				CourseID: tt.courseID,
   125  				UserID:   tt.userID,
   126  			}
   127  			enrolRequest := &qf.EnrollmentRequest{
   128  				FetchMode: &qf.EnrollmentRequest_UserID{
   129  					UserID: tt.userID,
   130  				},
   131  			}
   132  			_, err := client.CreateEnrollment(ctx, qtest.RequestWithCookie(enrol, tt.cookie))
   133  			checkAccess(t, "CreateEnrollment", err, tt.wantCode, tt.wantAccess)
   134  			_, err = client.UpdateCourseVisibility(ctx, qtest.RequestWithCookie(enrol, tt.cookie))
   135  			checkAccess(t, "UpdateCourseVisibility", err, tt.wantCode, tt.wantAccess)
   136  			_, err = client.UpdateUser(ctx, qtest.RequestWithCookie(&qf.User{ID: tt.userID}, tt.cookie))
   137  			checkAccess(t, "UpdateUser", err, tt.wantCode, tt.wantAccess)
   138  			_, err = client.GetEnrollments(ctx, qtest.RequestWithCookie(enrolRequest, tt.cookie))
   139  			checkAccess(t, "GetEnrollments", err, tt.wantCode, tt.wantAccess)
   140  			_, err = client.UpdateUser(ctx, qtest.RequestWithCookie(&qf.User{ID: tt.userID}, tt.cookie))
   141  			checkAccess(t, "UpdateUser", err, tt.wantCode, tt.wantAccess)
   142  		})
   143  	}
   144  
   145  	studentAccessTests := map[string]accessTest{
   146  		"course admin":                     {cookie: courseAdminCookie, userID: courseAdmin.ID, courseID: course.ID, wantAccess: true, wantCode: connect.CodePermissionDenied},
   147  		"admin, not enrolled in a course":  {cookie: adminCookie, userID: admin.ID, courseID: course.ID, wantAccess: false, wantCode: connect.CodePermissionDenied},
   148  		"user, not enrolled in the course": {cookie: userCookie, userID: user.ID, courseID: course.ID, wantAccess: false, wantCode: connect.CodePermissionDenied},
   149  		"student":                          {cookie: studentCookie, userID: student.ID, courseID: course.ID, wantAccess: true, wantCode: connect.CodePermissionDenied},
   150  		"student of another course":        {cookie: studentCookie, userID: student.ID, courseID: 123, wantAccess: false, wantCode: connect.CodePermissionDenied},
   151  	}
   152  	for name, tt := range studentAccessTests {
   153  		t.Run("StudentAccess/"+name, func(t *testing.T) {
   154  			_, err := client.GetSubmissions(ctx, qtest.RequestWithCookie(&qf.SubmissionRequest{
   155  				CourseID: tt.courseID,
   156  				FetchMode: &qf.SubmissionRequest_UserID{
   157  					UserID: tt.userID,
   158  				},
   159  			}, tt.cookie))
   160  			checkAccess(t, "GetSubmissions", err, tt.wantCode, tt.wantAccess)
   161  			_, err = client.GetAssignments(ctx, qtest.RequestWithCookie(&qf.CourseRequest{CourseID: tt.courseID}, tt.cookie))
   162  			checkAccess(t, "GetAssignments", err, tt.wantCode, tt.wantAccess)
   163  			_, err = client.GetEnrollments(ctx, qtest.RequestWithCookie(&qf.EnrollmentRequest{
   164  				FetchMode: &qf.EnrollmentRequest_UserID{
   165  					UserID: tt.userID,
   166  				},
   167  			}, tt.cookie))
   168  			checkAccess(t, "GetEnrollments", err, tt.wantCode, tt.wantAccess)
   169  			_, err = client.GetRepositories(ctx, qtest.RequestWithCookie(&qf.CourseRequest{CourseID: tt.courseID}, tt.cookie))
   170  			checkAccess(t, "GetRepositories", err, tt.wantCode, tt.wantAccess)
   171  		})
   172  	}
   173  
   174  	groupAccessTests := map[string]accessTest{
   175  		"student in a group":                            {cookie: groupStudentCookie, userID: groupStudent.ID, courseID: course.ID, groupID: group.ID, wantAccess: true, wantCode: connect.CodePermissionDenied},
   176  		"student, not in a group":                       {cookie: studentCookie, userID: student.ID, courseID: course.ID, groupID: group.ID, wantAccess: false, wantCode: connect.CodePermissionDenied},
   177  		"student in a group, wrong group ID in request": {cookie: studentCookie, userID: student.ID, courseID: course.ID, groupID: 123, wantAccess: false, wantCode: connect.CodePermissionDenied},
   178  	}
   179  	for name, tt := range groupAccessTests {
   180  		t.Run("GroupAccess/"+name, func(t *testing.T) {
   181  			_, err := client.GetGroup(ctx, qtest.RequestWithCookie(&qf.GroupRequest{
   182  				CourseID: tt.courseID,
   183  				GroupID:  tt.groupID,
   184  			}, tt.cookie))
   185  			checkAccess(t, "GetGroup", err, tt.wantCode, tt.wantAccess)
   186  		})
   187  	}
   188  
   189  	teacherAccessTests := map[string]accessTest{
   190  		"course teacher":                    {cookie: courseAdminCookie, userID: groupStudent.ID, courseID: course.ID, groupID: group.ID, wantAccess: true, wantCode: connect.CodePermissionDenied},
   191  		"student":                           {cookie: studentCookie, userID: student.ID, courseID: course.ID, groupID: group.ID, wantAccess: false, wantCode: connect.CodePermissionDenied},
   192  		"admin, not enrolled in the course": {cookie: adminCookie, userID: admin.ID, courseID: course.ID, groupID: group.ID, wantAccess: false, wantCode: connect.CodePermissionDenied},
   193  	}
   194  	for name, tt := range teacherAccessTests {
   195  		t.Run("TeacherAccess/"+name, func(t *testing.T) {
   196  			_, err := client.GetGroup(ctx, qtest.RequestWithCookie(&qf.GroupRequest{
   197  				CourseID: tt.courseID,
   198  				GroupID:  tt.groupID,
   199  			}, tt.cookie))
   200  			checkAccess(t, "GetGroup", err, tt.wantCode, tt.wantAccess)
   201  			_, err = client.GetGroup(ctx, qtest.RequestWithCookie(&qf.GroupRequest{
   202  				CourseID: tt.courseID,
   203  				UserID:   tt.userID,
   204  			}, tt.cookie))
   205  			checkAccess(t, "GetGroup", err, tt.wantCode, tt.wantAccess)
   206  			_, err = client.DeleteGroup(ctx, qtest.RequestWithCookie(&qf.GroupRequest{
   207  				GroupID:  tt.groupID,
   208  				CourseID: tt.courseID,
   209  				UserID:   tt.userID,
   210  			}, tt.cookie))
   211  			checkAccess(t, "DeleteGroup", err, tt.wantCode, tt.wantAccess)
   212  			_, err = client.UpdateGroup(ctx, qtest.RequestWithCookie(&qf.Group{CourseID: tt.courseID}, tt.cookie))
   213  			checkAccess(t, "UpdateGroup", err, tt.wantCode, tt.wantAccess)
   214  			_, err = client.UpdateCourse(ctx, qtest.RequestWithCookie(course, tt.cookie))
   215  			checkAccess(t, "UpdateCourse", err, tt.wantCode, tt.wantAccess)
   216  			_, err = client.UpdateEnrollments(ctx, qtest.RequestWithCookie(&qf.Enrollments{
   217  				Enrollments: []*qf.Enrollment{{ID: 1, CourseID: tt.courseID}},
   218  			}, tt.cookie))
   219  			checkAccess(t, "UpdateEnrollments", err, tt.wantCode, tt.wantAccess)
   220  			_, err = client.UpdateAssignments(ctx, qtest.RequestWithCookie(&qf.CourseRequest{CourseID: tt.courseID}, tt.cookie))
   221  			checkAccess(t, "UpdateAssignments", err, tt.wantCode, tt.wantAccess)
   222  			_, err = client.UpdateSubmission(ctx, qtest.RequestWithCookie(&qf.UpdateSubmissionRequest{SubmissionID: 1, CourseID: tt.courseID}, tt.cookie))
   223  			checkAccess(t, "UpdateSubmission", err, tt.wantCode, tt.wantAccess)
   224  			_, err = client.UpdateSubmissions(ctx, qtest.RequestWithCookie(&qf.UpdateSubmissionsRequest{AssignmentID: 1, CourseID: tt.courseID}, tt.cookie))
   225  			checkAccess(t, "UpdateSubmissions", err, tt.wantCode, tt.wantAccess)
   226  			_, err = client.RebuildSubmissions(ctx, qtest.RequestWithCookie(&qf.RebuildRequest{
   227  				AssignmentID: 1,
   228  				CourseID:     tt.courseID,
   229  			}, tt.cookie))
   230  			checkAccess(t, "RebuildSubmissions", err, tt.wantCode, tt.wantAccess)
   231  			_, err = client.CreateBenchmark(ctx, qtest.RequestWithCookie(&qf.GradingBenchmark{CourseID: tt.courseID, AssignmentID: 1}, tt.cookie))
   232  			checkAccess(t, "CreateBenchmark", err, tt.wantCode, tt.wantAccess)
   233  			_, err = client.UpdateBenchmark(ctx, qtest.RequestWithCookie(&qf.GradingBenchmark{CourseID: tt.courseID, AssignmentID: 1}, tt.cookie))
   234  			checkAccess(t, "UpdateBenchmark", err, tt.wantCode, tt.wantAccess)
   235  			_, err = client.DeleteBenchmark(ctx, qtest.RequestWithCookie(&qf.GradingBenchmark{CourseID: tt.courseID, AssignmentID: 1}, tt.cookie))
   236  			checkAccess(t, "DeleteBenchmark", err, tt.wantCode, tt.wantAccess)
   237  			_, err = client.CreateCriterion(ctx, qtest.RequestWithCookie(&qf.GradingCriterion{CourseID: tt.courseID, BenchmarkID: 1}, tt.cookie))
   238  			checkAccess(t, "CreateCriterion", err, tt.wantCode, tt.wantAccess)
   239  			_, err = client.UpdateCriterion(ctx, qtest.RequestWithCookie(&qf.GradingCriterion{CourseID: tt.courseID, BenchmarkID: 1}, tt.cookie))
   240  			checkAccess(t, "UpdateCriterion", err, tt.wantCode, tt.wantAccess)
   241  			_, err = client.DeleteCriterion(ctx, qtest.RequestWithCookie(&qf.GradingCriterion{CourseID: tt.courseID, BenchmarkID: 1}, tt.cookie))
   242  			checkAccess(t, "DeleteCriterion", err, tt.wantCode, tt.wantAccess)
   243  			_, err = client.CreateReview(ctx, qtest.RequestWithCookie(&qf.ReviewRequest{
   244  				CourseID: tt.courseID,
   245  				Review: &qf.Review{
   246  					SubmissionID: 1,
   247  					ReviewerID:   1,
   248  				},
   249  			}, tt.cookie))
   250  			checkAccess(t, "CreateReview", err, tt.wantCode, tt.wantAccess)
   251  			_, err = client.UpdateReview(ctx, qtest.RequestWithCookie(&qf.ReviewRequest{
   252  				CourseID: tt.courseID,
   253  				Review: &qf.Review{
   254  					SubmissionID: 1,
   255  					ReviewerID:   1,
   256  				},
   257  			}, tt.cookie))
   258  			checkAccess(t, "UpdateReview", err, tt.wantCode, tt.wantAccess)
   259  			_, err = client.IsEmptyRepo(ctx, qtest.RequestWithCookie(&qf.RepositoryRequest{CourseID: tt.courseID}, tt.cookie))
   260  			checkAccess(t, "IsEmptyRepo", err, tt.wantCode, tt.wantAccess)
   261  		})
   262  	}
   263  
   264  	courseAdminTests := map[string]accessTest{
   265  		"admin, not enrolled": {cookie: adminCookie, courseID: course.ID, wantAccess: false, wantCode: connect.CodePermissionDenied},
   266  	}
   267  	for name, tt := range courseAdminTests {
   268  		t.Run("CourseAdminAccess/"+name, func(t *testing.T) {
   269  			_, err = client.GetSubmissionsByCourse(ctx, qtest.RequestWithCookie(&qf.SubmissionRequest{
   270  				CourseID: tt.courseID,
   271  			}, tt.cookie))
   272  			checkAccess(t, "GetSubmissionsByCourse", err, tt.wantCode, tt.wantAccess)
   273  		})
   274  	}
   275  
   276  	adminAccessTests := map[string]accessTest{
   277  		"admin (accessing own info)":              {cookie: courseAdminCookie, userID: courseAdmin.ID, courseID: course.ID, groupID: group.ID, wantAccess: true, wantCode: connect.CodePermissionDenied},
   278  		"admin (accessing other user's info)":     {cookie: courseAdminCookie, userID: user.ID, courseID: course.ID, groupID: group.ID, wantAccess: true, wantCode: connect.CodePermissionDenied},
   279  		"non admin (accessing admin's info)":      {cookie: studentCookie, userID: courseAdmin.ID, courseID: course.ID, groupID: group.ID, wantAccess: false, wantCode: connect.CodePermissionDenied},
   280  		"non admin (accessing other user's info)": {cookie: studentCookie, userID: user.ID, courseID: course.ID, groupID: group.ID, wantAccess: false, wantCode: connect.CodePermissionDenied},
   281  	}
   282  	for name, tt := range adminAccessTests {
   283  		t.Run("AdminAccess/"+name, func(t *testing.T) {
   284  			_, err := client.UpdateUser(ctx, qtest.RequestWithCookie(&qf.User{ID: tt.userID}, tt.cookie))
   285  			checkAccess(t, "UpdateUser", err, tt.wantCode, tt.wantAccess)
   286  			_, err = client.GetUsers(ctx, qtest.RequestWithCookie(&qf.Void{}, tt.cookie))
   287  			checkAccess(t, "GetUsers", err, tt.wantCode, tt.wantAccess)
   288  			_, err = client.GetOrganization(ctx, qtest.RequestWithCookie(&qf.Organization{ScmOrganizationName: "test"}, tt.cookie))
   289  			checkAccess(t, "GetOrganization", err, tt.wantCode, tt.wantAccess)
   290  			_, err = client.CreateCourse(ctx, qtest.RequestWithCookie(course, tt.cookie))
   291  			checkAccess(t, "CreateCourse", err, tt.wantCode, tt.wantAccess)
   292  		})
   293  	}
   294  
   295  	createGroupTests := map[string]struct {
   296  		cookie     string
   297  		group      *qf.Group
   298  		wantAccess bool
   299  		wantCode   connect.Code
   300  	}{
   301  		"valid student, not in the request group": {cookie: studentCookie, group: &qf.Group{
   302  			CourseID: course.ID,
   303  		}, wantAccess: false, wantCode: connect.CodePermissionDenied},
   304  		"valid student": {cookie: studentCookie, group: &qf.Group{
   305  			Name:     "test",
   306  			CourseID: course.ID,
   307  			Users:    []*qf.User{student},
   308  		}, wantAccess: true, wantCode: connect.CodePermissionDenied},
   309  		"course teacher": {cookie: courseAdminCookie, group: &qf.Group{
   310  			CourseID: course.ID,
   311  			Users:    []*qf.User{courseAdmin},
   312  		}, wantAccess: true, wantCode: connect.CodePermissionDenied},
   313  		"admin, not a teacher": {cookie: adminCookie, group: &qf.Group{
   314  			CourseID: course.ID,
   315  		}, wantAccess: false, wantCode: connect.CodePermissionDenied},
   316  	}
   317  
   318  	for name, tt := range createGroupTests {
   319  		t.Run("CreateGroupAccess/"+name, func(t *testing.T) {
   320  			_, err := client.CreateGroup(ctx, qtest.RequestWithCookie(tt.group, tt.cookie))
   321  			checkAccess(t, "CreateGroup", err, tt.wantCode, tt.wantAccess)
   322  		})
   323  	}
   324  
   325  	adminStatusChangeTests := map[string]struct {
   326  		cookie     string
   327  		user       *qf.User
   328  		wantAccess bool
   329  		wantCode   connect.Code
   330  	}{
   331  		"admin demoting a user": {cookie: courseAdminCookie, user: &qf.User{
   332  			ID:      admin.ID,
   333  			IsAdmin: false,
   334  		}, wantAccess: true, wantCode: connect.CodePermissionDenied},
   335  		"admin promoting a user": {cookie: courseAdminCookie, user: &qf.User{
   336  			ID:      admin.ID,
   337  			IsAdmin: true,
   338  		}, wantAccess: true, wantCode: connect.CodePermissionDenied},
   339  		"admin demoting self": {cookie: courseAdminCookie, user: &qf.User{
   340  			ID:      courseAdmin.ID,
   341  			IsAdmin: false,
   342  		}, wantAccess: true, wantCode: connect.CodePermissionDenied},
   343  		"user promoting another user": {cookie: userCookie, user: &qf.User{
   344  			ID:      groupStudent.ID,
   345  			IsAdmin: true,
   346  		}, wantAccess: false, wantCode: connect.CodePermissionDenied},
   347  		"user promoting self": {cookie: userCookie, user: &qf.User{
   348  			ID:      user.ID,
   349  			IsAdmin: true,
   350  		}, wantAccess: false, wantCode: connect.CodePermissionDenied},
   351  	}
   352  
   353  	for name, tt := range adminStatusChangeTests {
   354  		t.Run("AdminStatusChange/"+name, func(t *testing.T) {
   355  			_, err := client.UpdateUser(ctx, qtest.RequestWithCookie(tt.user, tt.cookie))
   356  			checkAccess(t, "UpdateUser", err, tt.wantCode, tt.wantAccess)
   357  		})
   358  	}
   359  
   360  	adminGetEnrollmentsTests := map[string]accessTest{
   361  		"admin, not enrolled in the course": {cookie: adminCookie, courseID: course.ID, userID: student.ID, wantAccess: true, wantCode: connect.CodePermissionDenied},
   362  	}
   363  
   364  	for name, tt := range adminGetEnrollmentsTests {
   365  		t.Run("AdminGetEnrollments/"+name, func(t *testing.T) {
   366  			_, err := client.GetEnrollments(ctx, qtest.RequestWithCookie(&qf.EnrollmentRequest{
   367  				FetchMode: &qf.EnrollmentRequest_CourseID{
   368  					CourseID: tt.courseID,
   369  				},
   370  			}, tt.cookie))
   371  			checkAccess(t, "GetEnrollments", err, tt.wantCode, tt.wantAccess)
   372  			_, err = client.GetEnrollments(ctx, qtest.RequestWithCookie(&qf.EnrollmentRequest{
   373  				FetchMode: &qf.EnrollmentRequest_UserID{
   374  					UserID: tt.userID,
   375  				},
   376  			}, tt.cookie))
   377  			checkAccess(t, "GetEnrollments", err, tt.wantCode, tt.wantAccess)
   378  		})
   379  	}
   380  }
   381  
   382  func checkAccess(t *testing.T, method string, err error, wantCode connect.Code, wantAccess bool) {
   383  	t.Helper()
   384  	if connErr, ok := err.(*connect.Error); ok {
   385  		gotCode := connErr.Code()
   386  		gotAccess := gotCode == wantCode
   387  		if gotAccess == wantAccess {
   388  			t.Errorf("%23s: (%v == %v) = %t, want %t", method, gotCode, wantCode, gotAccess, !wantAccess)
   389  			t.Log(err)
   390  		}
   391  	} else if err != nil && wantAccess {
   392  		// got error and want access; expected non-error or not access
   393  		t.Errorf("%23s: got %v (%t), want <nil> (%t)", method, err, wantAccess, !wantAccess)
   394  	}
   395  }