CoursesController.java

1
package edu.ucsb.cs.scaffold.controller;
2
3
import com.fasterxml.jackson.databind.JsonNode;
4
import com.fasterxml.jackson.databind.ObjectMapper;
5
import edu.ucsb.cs.scaffold.entity.Course;
6
import edu.ucsb.cs.scaffold.entity.CourseStaff;
7
import edu.ucsb.cs.scaffold.entity.PatCredential;
8
import edu.ucsb.cs.scaffold.entity.PlInstance;
9
import edu.ucsb.cs.scaffold.entity.PlRepo;
10
import edu.ucsb.cs.scaffold.entity.RosterStudent;
11
import edu.ucsb.cs.scaffold.entity.User;
12
import edu.ucsb.cs.scaffold.enums.PatPlatform;
13
import edu.ucsb.cs.scaffold.enums.School;
14
import edu.ucsb.cs.scaffold.errors.EntityNotFoundException;
15
import edu.ucsb.cs.scaffold.errors.ForbiddenException;
16
import edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobFactory;
17
import edu.ucsb.cs.scaffold.model.CurrentUser;
18
import edu.ucsb.cs.scaffold.repository.AdminRepository;
19
import edu.ucsb.cs.scaffold.repository.CourseRepository;
20
import edu.ucsb.cs.scaffold.repository.CourseStaffRepository;
21
import edu.ucsb.cs.scaffold.repository.InstructorRepository;
22
import edu.ucsb.cs.scaffold.repository.PatCredentialRepository;
23
import edu.ucsb.cs.scaffold.repository.PlInstanceRepository;
24
import edu.ucsb.cs.scaffold.repository.PlRepoRepository;
25
import edu.ucsb.cs.scaffold.repository.RosterStudentRepository;
26
import edu.ucsb.cs.scaffold.repository.UserRepository;
27
import edu.ucsb.cs.scaffold.services.GithubService;
28
import edu.ucsb.cs.scaffold.services.PatEncryptionService;
29
import edu.ucsb.cs.scaffold.services.PrairieLearnService;
30
import edu.ucsb.cs156.jobs.repositories.JobsRepository;
31
import edu.ucsb.cs156.jobs.services.JobService;
32
import io.swagger.v3.oas.annotations.Operation;
33
import io.swagger.v3.oas.annotations.Parameter;
34
import io.swagger.v3.oas.annotations.tags.Tag;
35
import java.security.NoSuchAlgorithmException;
36
import java.security.spec.InvalidKeySpecException;
37
import java.util.ArrayList;
38
import java.util.HashSet;
39
import java.util.List;
40
import java.util.Map;
41
import java.util.Objects;
42
import java.util.Set;
43
import java.util.stream.Collectors;
44
import java.util.stream.StreamSupport;
45
import lombok.extern.slf4j.Slf4j;
46
import org.springframework.beans.factory.annotation.Autowired;
47
import org.springframework.security.access.prepost.PreAuthorize;
48
import org.springframework.transaction.annotation.Transactional;
49
import org.springframework.web.bind.annotation.DeleteMapping;
50
import org.springframework.web.bind.annotation.GetMapping;
51
import org.springframework.web.bind.annotation.PathVariable;
52
import org.springframework.web.bind.annotation.PostMapping;
53
import org.springframework.web.bind.annotation.PutMapping;
54
import org.springframework.web.bind.annotation.RequestMapping;
55
import org.springframework.web.bind.annotation.RequestParam;
56
import org.springframework.web.bind.annotation.RestController;
57
import org.springframework.web.client.HttpClientErrorException;
58
59
@Tag(name = "Course")
60
@RequestMapping("/api/courses")
61
@RestController
62
@Slf4j
63
public class CoursesController extends ApiController {
64
65
  @Autowired private CourseRepository courseRepository;
66
67
  @Autowired private UserRepository userRepository;
68
69
  @Autowired private RosterStudentRepository rosterStudentRepository;
70
71
  @Autowired private CourseStaffRepository courseStaffRepository;
72
73
  @Autowired private InstructorRepository instructorRepository;
74
75
  @Autowired private AdminRepository adminRepository;
76
77
  @Autowired private JobsRepository jobsRepository;
78
79
  @Autowired private PatCredentialRepository patCredentialRepository;
80
81
  @Autowired private PatEncryptionService patEncryptionService;
82
83
  @Autowired private PlRepoRepository plRepoRepository;
84
85
  @Autowired private GithubService githubService;
86
87
  @Autowired private PlInstanceRepository plInstanceRepository;
88
89
  @Autowired private PrairieLearnService prairieLearnService;
90
91
  @Autowired private SyncCourseWithPlRepoJobFactory syncCourseWithPlRepoJobFactory;
92
93
  @Autowired private JobService jobService;
94
95
  /**
96
   * This method creates a new Course.
97
   *
98
   * @param courseName the name of the course
99
   * @param term the term of the course
100
   * @param school the school of the course
101
   * @param canvasApiToken the Canvas API token (optional)
102
   * @param canvasCourseId the Canvas course ID (optional)
103
   */
104
  @Operation(summary = "Create a new course")
105
  @PreAuthorize("hasRole('ROLE_ADMIN') || hasRole('ROLE_INSTRUCTOR')")
106
  @PostMapping("/post")
107
  public InstructorCourseView postCourse(
108
      @Parameter(name = "courseName") @RequestParam String courseName,
109
      @Parameter(name = "term") @RequestParam String term,
110
      @Parameter(name = "school") @RequestParam School school,
111
      @Parameter(name = "canvasApiToken") @RequestParam(required = false) String canvasApiToken,
112
      @Parameter(name = "canvasCourseId") @RequestParam(required = false) String canvasCourseId) {
113
    // get current date right now and set status to pending
114
    edu.ucsb.cs.scaffold.model.CurrentUser currentUser = getCurrentUser();
115
    Course course =
116
        Course.builder()
117
            .courseName(courseName)
118
            .term(term)
119
            .school(school)
120
            .instructorEmail(currentUser.getUser().getEmail().strip())
121
            .canvasApiToken(canvasApiToken)
122
            .canvasCourseId(canvasCourseId)
123
            .build();
124
    Course savedCourse = courseRepository.save(course);
125
126 1 1. postCourse : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::postCourse → KILLED
    return new InstructorCourseView(savedCourse);
127
  }
128
129
  /** Projection of Course entity with fields that are relevant for instructors and admins */
130
  public static record InstructorCourseView(
131
      Long id,
132
      String courseName,
133
      String term,
134
      School school,
135
      String instructorEmail,
136
      int numStudents,
137
      int numStaff,
138
      Long plRepoId,
139
      Long plInstanceId,
140
      // Human-readable details of the PL associations; resolved only by the
141
      // single-course endpoints (getCourseById and the two update endpoints),
142
      // null in course lists.
143
      String plRepoName,
144
      String plInstanceShortName,
145
      Long plInstanceNumericId,
146
      int xSpacing,
147
      int ySpacing) {
148
149
    // Creates view from Course entity
150
    public InstructorCourseView(Course c) {
151
      this(
152
          c.getId(),
153
          c.getCourseName(),
154
          c.getTerm(),
155
          c.getSchool(),
156
          c.getInstructorEmail(),
157 1 1. <init> : negated conditional → KILLED
          c.getRosterStudents() != null ? c.getRosterStudents().size() : 0,
158 1 1. <init> : negated conditional → KILLED
          c.getCourseStaff() != null ? c.getCourseStaff().size() : 0,
159
          c.getPlRepoId(),
160
          c.getPlInstanceId(),
161
          null,
162
          null,
163
          null,
164
          c.getXSpacing(),
165
          c.getYSpacing());
166
    }
167
  }
168
169
  /**
170
   * Builds an InstructorCourseView with the PL association details (repo name, instance short name,
171
   * instance numeric id) resolved from their tables — used by the single-course endpoints so the
172
   * frontend can show what is currently associated. List endpoints use the plain constructor and
173
   * leave these null.
174
   */
175
  private InstructorCourseView viewWithPlDetails(Course c) {
176
    String plRepoName =
177 1 1. viewWithPlDetails : negated conditional → KILLED
        c.getPlRepoId() == null
178
            ? null
179
            : plRepoRepository.findById(c.getPlRepoId()).map(PlRepo::getRepoName).orElse(null);
180
    PlInstance plInstance =
181 1 1. viewWithPlDetails : negated conditional → KILLED
        c.getPlInstanceId() == null
182
            ? null
183
            : plInstanceRepository.findById(c.getPlInstanceId()).orElse(null);
184 1 1. viewWithPlDetails : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::viewWithPlDetails → KILLED
    return new InstructorCourseView(
185
        c.getId(),
186
        c.getCourseName(),
187
        c.getTerm(),
188
        c.getSchool(),
189
        c.getInstructorEmail(),
190 1 1. viewWithPlDetails : negated conditional → KILLED
        c.getRosterStudents() != null ? c.getRosterStudents().size() : 0,
191 1 1. viewWithPlDetails : negated conditional → KILLED
        c.getCourseStaff() != null ? c.getCourseStaff().size() : 0,
192
        c.getPlRepoId(),
193
        c.getPlInstanceId(),
194
        plRepoName,
195 1 1. viewWithPlDetails : negated conditional → KILLED
        plInstance == null ? null : plInstance.getShortName(),
196 1 1. viewWithPlDetails : negated conditional → KILLED
        plInstance == null ? null : plInstance.getNumericId(),
197
        c.getXSpacing(),
198
        c.getYSpacing());
199
  }
200
201
  /**
202
   * This method returns a list of courses.
203
   *
204
   * @return a list of all courses for an instructor.
205
   */
206
  @Operation(summary = "List all courses for an instructor")
207
  @PreAuthorize("hasRole('ROLE_INSTRUCTOR')")
208
  @GetMapping("/list/instructors")
209
  public Iterable<InstructorCourseView> allForInstructors() {
210
    CurrentUser currentUser = getCurrentUser();
211
    String instructorEmail = currentUser.getUser().getEmail();
212
    List<Course> courses = courseRepository.findByInstructorEmail(instructorEmail);
213
214
    List<InstructorCourseView> courseViews =
215
        courses.stream().map(InstructorCourseView::new).collect(Collectors.toList());
216 1 1. allForInstructors : replaced return value with Collections.emptyList for edu/ucsb/cs/scaffold/controller/CoursesController::allForInstructors → KILLED
    return courseViews;
217
  }
218
219
  /**
220
   * This method returns a list of courses.
221
   *
222
   * @return a list of all courses for an admin.
223
   */
224
  @Operation(summary = "List all courses for an admin")
225
  @PreAuthorize("hasRole('ROLE_ADMIN')")
226
  @GetMapping("/list/admins")
227
  public Iterable<InstructorCourseView> allForAdmins() {
228
    List<Course> courses = courseRepository.findAll();
229
230
    List<InstructorCourseView> courseViews =
231
        courses.stream().map(InstructorCourseView::new).collect(Collectors.toList());
232 1 1. allForAdmins : replaced return value with Collections.emptyList for edu/ucsb/cs/scaffold/controller/CoursesController::allForAdmins → KILLED
    return courseViews;
233
  }
234
235
  /**
236
   * This method returns single course by its id
237
   *
238
   * @return a course
239
   */
240
  @Operation(summary = "Get course by id")
241
  @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #id)")
242
  @GetMapping("/{id}")
243
  public InstructorCourseView getCourseById(@Parameter(name = "id") @PathVariable Long id) {
244
    Course course =
245
        courseRepository
246
            .findById(id)
247 1 1. lambda$getCourseById$0 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$getCourseById$0 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, id));
248 1 1. getCourseById : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::getCourseById → KILLED
    return viewWithPlDetails(course);
249
  }
250
251
  /**
252
   * This method returns the Canvas course ID and partially obscured Canvas token for a course by
253
   * its id. If the token is less than or equal to 3 characters long, it is returned in full.
254
   * Otherwise, all but the last three characters are replaced with asterisks. This is okay because
255
   * such short tokens are not generated by Canvas.
256
   *
257
   * @param courseId the id of the course
258
   * @return a map with courseId, canvasCourseId, and obscured canvasApiToken
259
   */
260
  @Operation(summary = "Get course Canvas course ID and Canvas token (partially obscured)")
261
  @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)")
262
  @GetMapping("getCanvasInfo")
263
  public Map<String, String> getCourseCanvasInfo(
264
      @Parameter(name = "courseId") @RequestParam Long courseId) {
265
    Course course =
266
        courseRepository
267
            .findById(courseId)
268 1 1. lambda$getCourseCanvasInfo$1 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$getCourseCanvasInfo$1 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
269
270
    String obscuredToken = null;
271
272 1 1. getCourseCanvasInfo : negated conditional → KILLED
    if (course.getCanvasApiToken() != null) {
273
      String token = course.getCanvasApiToken();
274 2 1. getCourseCanvasInfo : negated conditional → KILLED
2. getCourseCanvasInfo : changed conditional boundary → KILLED
      if (token.length() < 4) {
275
        obscuredToken = token;
276
      } else {
277 1 1. getCourseCanvasInfo : Replaced integer subtraction with addition → KILLED
        String lastThree = token.substring(token.length() - 3);
278 1 1. getCourseCanvasInfo : Replaced integer subtraction with addition → KILLED
        obscuredToken = "*".repeat(token.length() - 3) + lastThree;
279
      }
280
    }
281 1 1. getCourseCanvasInfo : replaced return value with Collections.emptyMap for edu/ucsb/cs/scaffold/controller/CoursesController::getCourseCanvasInfo → KILLED
    return Map.of(
282
        "courseId", course.getId().toString(),
283 1 1. getCourseCanvasInfo : negated conditional → KILLED
        "canvasCourseId", course.getCanvasCourseId() != null ? course.getCanvasCourseId() : "",
284 1 1. getCourseCanvasInfo : negated conditional → KILLED
        "canvasApiToken", obscuredToken != null ? obscuredToken : "");
285
  }
286
287
  public record RosterStudentCoursesDTO(
288
      Long id, String courseName, String term, String school, Long rosterStudentId) {}
289
290
  /**
291
   * This method returns a list of courses that the current user is enrolled.
292
   *
293
   * @return a list of courses in the DTO form along with the student status in the organization.
294
   */
295
  @Operation(summary = "List all courses for the current student, including their org status")
296
  @PreAuthorize("hasRole('ROLE_USER')")
297
  @GetMapping("/list/students")
298
  public List<RosterStudentCoursesDTO> listCoursesForCurrentUser() {
299
    String email = getCurrentUser().getUser().getEmail();
300
    Iterable<RosterStudent> rosterStudentsIterable = rosterStudentRepository.findAllByEmail(email);
301
    List<RosterStudent> rosterStudents = new ArrayList<>();
302 1 1. listCoursesForCurrentUser : removed call to java/lang/Iterable::forEach → KILLED
    rosterStudentsIterable.forEach(rosterStudents::add);
303 1 1. listCoursesForCurrentUser : replaced return value with Collections.emptyList for edu/ucsb/cs/scaffold/controller/CoursesController::listCoursesForCurrentUser → KILLED
    return rosterStudents.stream()
304
        .map(
305
            rs -> {
306
              Course course = rs.getCourse();
307
              RosterStudentCoursesDTO rsDto =
308
                  new RosterStudentCoursesDTO(
309
                      course.getId(),
310
                      course.getCourseName(),
311
                      course.getTerm(),
312
                      course.getSchool().getDisplayName(),
313
                      rs.getId());
314 1 1. lambda$listCoursesForCurrentUser$2 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$listCoursesForCurrentUser$2 → KILLED
              return rsDto;
315
            })
316
        .collect(Collectors.toList());
317
  }
318
319
  public record StaffCoursesDTO(
320
      Long id, String courseName, String term, School school, Long staffId) {}
321
322
  public enum EmailTypes {
323
    STUDENTS,
324
    STAFF,
325
    ALL
326
  }
327
328
  public enum EmailFormats {
329
    COMMA_SEPARATED,
330
    ONE_PER_LINE
331
  }
332
333
  /**
334
   * student see what courses they appear as staff in
335
   *
336
   * @param studentId the id of the student making request
337
   * @return a list of all courses student is staff in
338
   */
339
  @Operation(summary = "Student see what courses they appear as staff in")
340
  @PreAuthorize("hasRole('ROLE_USER')")
341
  @GetMapping("/list/staff")
342
  public List<StaffCoursesDTO> staffCourses() {
343
    CurrentUser currentUser = getCurrentUser();
344
    User user = currentUser.getUser();
345
346
    String email = user.getEmail();
347
348
    List<CourseStaff> staffMembers = courseStaffRepository.findAllByEmail(email);
349 1 1. staffCourses : replaced return value with Collections.emptyList for edu/ucsb/cs/scaffold/controller/CoursesController::staffCourses → KILLED
    return staffMembers.stream()
350
        .map(
351
            s -> {
352
              Course course = s.getCourse();
353
              StaffCoursesDTO sDto =
354
                  new StaffCoursesDTO(
355
                      course.getId(),
356
                      course.getCourseName(),
357
                      course.getTerm(),
358
                      course.getSchool(),
359
                      s.getId());
360 1 1. lambda$staffCourses$3 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$staffCourses$3 → KILLED
              return sDto;
361
            })
362
        .collect(Collectors.toList());
363
  }
364
365
  /** DTO representing a course along with the ways in which the current user has access to it. */
366
  public record CourseListDTO(
367
      Long id,
368
      String courseName,
369
      String term,
370
      School school,
371
      String instructorEmail,
372
      boolean studentAccess,
373
      boolean staffAccess,
374
      boolean instructorAccess,
375
      boolean adminAccess) {}
376
377
  /**
378
   * This method returns a unified list of courses that the current user has access to, whether as a
379
   * student, staff member, instructor, or admin.
380
   *
381
   * @return a list of courses along with the access flags for the current user.
382
   */
383
  @Operation(summary = "List all courses the current user has access to")
384
  @PreAuthorize("hasRole('ROLE_USER')")
385
  @GetMapping("/list")
386
  public List<CourseListDTO> listCoursesForCurrentUserUnified() {
387
    String email = getCurrentUser().getUser().getEmail();
388
389
    boolean isAdmin = adminRepository.existsByEmail(email);
390
391
    Set<Long> studentCourseIds =
392
        rosterStudentRepository.findAllByEmail(email).stream()
393 1 1. lambda$listCoursesForCurrentUserUnified$4 : replaced Long return value with 0L for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$listCoursesForCurrentUserUnified$4 → KILLED
            .map(rs -> rs.getCourse().getId())
394
            .collect(Collectors.toSet());
395
396
    Set<Long> staffCourseIds =
397
        courseStaffRepository.findAllByEmail(email).stream()
398 1 1. lambda$listCoursesForCurrentUserUnified$5 : replaced Long return value with 0L for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$listCoursesForCurrentUserUnified$5 → KILLED
            .map(cs -> cs.getCourse().getId())
399
            .collect(Collectors.toSet());
400
401
    Set<Long> instructorCourseIds =
402
        courseRepository.findByInstructorEmail(email).stream()
403
            .map(Course::getId)
404
            .collect(Collectors.toSet());
405
406
    List<Course> courses;
407 1 1. listCoursesForCurrentUserUnified : negated conditional → KILLED
    if (isAdmin) {
408
      courses = courseRepository.findAll();
409
    } else {
410
      Set<Long> accessibleCourseIds = new HashSet<>();
411
      accessibleCourseIds.addAll(studentCourseIds);
412
      accessibleCourseIds.addAll(staffCourseIds);
413
      accessibleCourseIds.addAll(instructorCourseIds);
414
      courses = courseRepository.findAllById(accessibleCourseIds);
415
    }
416
417 1 1. listCoursesForCurrentUserUnified : replaced return value with Collections.emptyList for edu/ucsb/cs/scaffold/controller/CoursesController::listCoursesForCurrentUserUnified → KILLED
    return courses.stream()
418
        .map(
419
            c ->
420 1 1. lambda$listCoursesForCurrentUserUnified$6 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$listCoursesForCurrentUserUnified$6 → KILLED
                new CourseListDTO(
421
                    c.getId(),
422
                    c.getCourseName(),
423
                    c.getTerm(),
424
                    c.getSchool(),
425
                    c.getInstructorEmail(),
426
                    studentCourseIds.contains(c.getId()),
427
                    staffCourseIds.contains(c.getId()),
428
                    instructorCourseIds.contains(c.getId()),
429
                    isAdmin))
430
        .collect(Collectors.toList());
431
  }
432
433
  /**
434
   * This method returns the unified access info for a single course, if the current user has access
435
   * to it. If the user does not have access, or the course does not exist, a 404 is returned.
436
   *
437
   * @param courseId the id of the course
438
   * @return the course along with the access flags for the current user.
439
   */
440
  @Operation(summary = "Get unified course access info for a single course")
441
  @PreAuthorize("hasRole('ROLE_USER')")
442
  @GetMapping("/list/{courseId}")
443
  public CourseListDTO getCourseAccessInfo(
444
      @Parameter(name = "courseId") @PathVariable Long courseId) {
445
    String email = getCurrentUser().getUser().getEmail();
446
447
    Course course =
448
        courseRepository
449
            .findById(courseId)
450 1 1. lambda$getCourseAccessInfo$7 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$getCourseAccessInfo$7 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
451
452
    boolean isAdmin = adminRepository.existsByEmail(email);
453
454
    boolean studentAccess =
455
        rosterStudentRepository.findAllByEmail(email).stream()
456 2 1. lambda$getCourseAccessInfo$8 : replaced boolean return with false for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$getCourseAccessInfo$8 → KILLED
2. lambda$getCourseAccessInfo$8 : replaced boolean return with true for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$getCourseAccessInfo$8 → KILLED
            .anyMatch(rs -> rs.getCourse().getId().equals(courseId));
457
458
    boolean staffAccess =
459
        courseStaffRepository.findAllByEmail(email).stream()
460 2 1. lambda$getCourseAccessInfo$9 : replaced boolean return with false for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$getCourseAccessInfo$9 → KILLED
2. lambda$getCourseAccessInfo$9 : replaced boolean return with true for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$getCourseAccessInfo$9 → KILLED
            .anyMatch(cs -> cs.getCourse().getId().equals(courseId));
461
462
    boolean instructorAccess = email.equals(course.getInstructorEmail());
463
464 4 1. getCourseAccessInfo : negated conditional → KILLED
2. getCourseAccessInfo : negated conditional → KILLED
3. getCourseAccessInfo : negated conditional → KILLED
4. getCourseAccessInfo : negated conditional → KILLED
    if (!isAdmin && !studentAccess && !staffAccess && !instructorAccess) {
465
      throw new EntityNotFoundException(Course.class, courseId);
466
    }
467
468 1 1. getCourseAccessInfo : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::getCourseAccessInfo → KILLED
    return new CourseListDTO(
469
        course.getId(),
470
        course.getCourseName(),
471
        course.getTerm(),
472
        course.getSchool(),
473
        course.getInstructorEmail(),
474
        studentAccess,
475
        staffAccess,
476
        instructorAccess,
477
        isAdmin);
478
  }
479
480
  @Operation(summary = "Update instructor email for a course (admin only)")
481
  @PreAuthorize("hasRole('ROLE_ADMIN')")
482
  @PutMapping("/updateInstructor")
483
  public InstructorCourseView updateInstructorEmail(
484
      @Parameter(name = "courseId") @RequestParam Long courseId,
485
      @Parameter(name = "instructorEmail") @RequestParam String instructorEmail) {
486
487
    instructorEmail = instructorEmail.strip();
488
489
    Course course =
490
        courseRepository
491
            .findById(courseId)
492 1 1. lambda$updateInstructorEmail$10 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updateInstructorEmail$10 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
493
494
    // Validate that the email exists in either instructor or admin table
495
    boolean isInstructor = instructorRepository.existsByEmail(instructorEmail);
496
    boolean isAdmin = adminRepository.existsByEmail(instructorEmail);
497
498 2 1. updateInstructorEmail : negated conditional → KILLED
2. updateInstructorEmail : negated conditional → KILLED
    if (!isInstructor && !isAdmin) {
499
      throw new IllegalArgumentException("Email must belong to either an instructor or admin");
500
    }
501
502 1 1. updateInstructorEmail : removed call to edu/ucsb/cs/scaffold/entity/Course::setInstructorEmail → KILLED
    course.setInstructorEmail(instructorEmail);
503
    Course savedCourse = courseRepository.save(course);
504
505 1 1. updateInstructorEmail : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::updateInstructorEmail → KILLED
    return new InstructorCourseView(savedCourse);
506
  }
507
508
  @Operation(summary = "Get course emails")
509
  @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)")
510
  @GetMapping("/emails")
511
  public String getCourseEmails(
512
      @Parameter(name = "courseId") @RequestParam Long courseId,
513
      @Parameter(name = "type") @RequestParam(defaultValue = "STUDENTS") EmailTypes type,
514
      @Parameter(name = "format") @RequestParam(defaultValue = "ONE_PER_LINE")
515
          EmailFormats format) {
516
517
    List<String> staffEmails =
518
        StreamSupport.stream(courseStaffRepository.findByCourseId(courseId).spliterator(), false)
519
            .map(CourseStaff::getEmail)
520
            .filter(Objects::nonNull)
521
            .sorted()
522
            .collect(Collectors.toList());
523
524
    List<String> studentEmails =
525
        StreamSupport.stream(rosterStudentRepository.findByCourseId(courseId).spliterator(), false)
526
            .map(RosterStudent::getEmail)
527
            .filter(Objects::nonNull)
528
            .sorted()
529
            .collect(Collectors.toList());
530
531
    List<String> emails = studentEmails;
532 1 1. getCourseEmails : negated conditional → KILLED
    if (type == EmailTypes.STAFF) {
533
      emails = staffEmails;
534 1 1. getCourseEmails : negated conditional → KILLED
    } else if (type == EmailTypes.ALL) {
535
      emails = new ArrayList<>(staffEmails);
536
      emails.addAll(studentEmails);
537
    }
538
539 1 1. getCourseEmails : negated conditional → KILLED
    String separator = format == EmailFormats.COMMA_SEPARATED ? "," : "\r\n";
540 1 1. getCourseEmails : replaced return value with "" for edu/ucsb/cs/scaffold/controller/CoursesController::getCourseEmails → KILLED
    return String.join(separator, emails);
541
  }
542
543
  @Operation(summary = "Delete a course")
544
  @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)")
545
  @DeleteMapping("")
546
  @Transactional
547
  public Object deleteCourse(@RequestParam Long courseId)
548
      throws NoSuchAlgorithmException, InvalidKeySpecException {
549
    Course course =
550
        courseRepository
551
            .findById(courseId)
552 1 1. lambda$deleteCourse$11 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$deleteCourse$11 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
553
554
    // Check if course has roster students or staff
555 2 1. deleteCourse : negated conditional → KILLED
2. deleteCourse : negated conditional → KILLED
    if (!course.getRosterStudents().isEmpty() || !course.getCourseStaff().isEmpty()) {
556
      throw new IllegalArgumentException("Cannot delete course with students or staff");
557
    }
558
559 1 1. deleteCourse : removed call to edu/ucsb/cs156/jobs/repositories/JobsRepository::deleteByScopeTypeAndScopeId → KILLED
    jobsRepository.deleteByScopeTypeAndScopeId("course", courseId);
560 1 1. deleteCourse : removed call to edu/ucsb/cs/scaffold/repository/CourseRepository::delete → KILLED
    courseRepository.delete(course);
561 1 1. deleteCourse : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::deleteCourse → KILLED
    return genericMessage("Course with id %s deleted".formatted(course.getId()));
562
  }
563
564
  /**
565
   * This method updates an existing course.
566
   *
567
   * @param courseId the id of the course to update
568
   * @param courseName the new name of the course
569
   * @param term the new term of the course
570
   * @param school the new school of the course
571
   * @return the updated course
572
   */
573
  @Operation(summary = "Update an existing course")
574
  @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)")
575
  @PutMapping("")
576
  public InstructorCourseView updateCourse(
577
      @Parameter(name = "courseId") @RequestParam Long courseId,
578
      @Parameter(name = "courseName") @RequestParam String courseName,
579
      @Parameter(name = "term") @RequestParam String term,
580
      @Parameter(name = "school") @RequestParam School school) {
581
    Course course =
582
        courseRepository
583
            .findById(courseId)
584 1 1. lambda$updateCourse$12 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updateCourse$12 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
585
586 1 1. updateCourse : removed call to edu/ucsb/cs/scaffold/entity/Course::setCourseName → KILLED
    course.setCourseName(courseName);
587 1 1. updateCourse : removed call to edu/ucsb/cs/scaffold/entity/Course::setTerm → KILLED
    course.setTerm(term);
588 1 1. updateCourse : removed call to edu/ucsb/cs/scaffold/entity/Course::setSchool → KILLED
    course.setSchool(school);
589
590
    Course savedCourse = courseRepository.save(course);
591
592 1 1. updateCourse : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::updateCourse → KILLED
    return new InstructorCourseView(savedCourse);
593
  }
594
595
  /**
596
   * This method updates an existing course.
597
   *
598
   * @param courseId the id of the course to update
599
   * @param courseName the new name of the course
600
   * @param term the new term of the course
601
   * @param school the new school of the course
602
   * @param canvasApiToken the new Canvas API token for the course
603
   * @param canvasCourseId the new Canvas course ID
604
   * @return the updated course
605
   */
606
  @Operation(summary = "Update an existing course with Canvas token and course ID")
607
  @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)")
608
  @PutMapping("/updateCourseCanvasToken")
609
  public InstructorCourseView updateCourseWithCanvasToken(
610
      @Parameter(name = "courseId") @RequestParam Long courseId,
611
      @Parameter(name = "canvasApiToken") @RequestParam(required = false) String canvasApiToken,
612
      @Parameter(name = "canvasCourseId") @RequestParam(required = false) String canvasCourseId) {
613
    Course course =
614
        courseRepository
615
            .findById(courseId)
616 1 1. lambda$updateCourseWithCanvasToken$13 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updateCourseWithCanvasToken$13 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
617
618 1 1. updateCourseWithCanvasToken : negated conditional → KILLED
    if (canvasApiToken != null
619 1 1. updateCourseWithCanvasToken : negated conditional → KILLED
        && !canvasApiToken.isEmpty()
620 1 1. updateCourseWithCanvasToken : negated conditional → KILLED
        && !canvasApiToken.equals(course.getCanvasApiToken())) {
621 1 1. updateCourseWithCanvasToken : removed call to edu/ucsb/cs/scaffold/entity/Course::setCanvasApiToken → KILLED
      course.setCanvasApiToken(canvasApiToken);
622
    }
623
624 1 1. updateCourseWithCanvasToken : negated conditional → KILLED
    if (canvasCourseId != null
625 1 1. updateCourseWithCanvasToken : negated conditional → KILLED
        && !canvasCourseId.isEmpty()
626 1 1. updateCourseWithCanvasToken : negated conditional → KILLED
        && !canvasCourseId.equals(course.getCanvasCourseId())) {
627 1 1. updateCourseWithCanvasToken : removed call to edu/ucsb/cs/scaffold/entity/Course::setCanvasCourseId → KILLED
      course.setCanvasCourseId(canvasCourseId);
628
    }
629
630
    Course savedCourse = courseRepository.save(course);
631
632 1 1. updateCourseWithCanvasToken : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::updateCourseWithCanvasToken → KILLED
    return new InstructorCourseView(savedCourse);
633
  }
634
635
  /**
636
   * Associates a GitHub repo (PlRepo) with a course, after verifying that the current user's stored
637
   * GitHub PAT has read/write access to the repo. The check is a single GET /repos/{owner}/{repo}
638
   * call: a successful response proves read access and its permissions block reports push (write)
639
   * access, so nothing is written to the repo.
640
   *
641
   * @param courseId the id of the course
642
   * @param repoName the repo in owner/repo form (i.e. the part after https://github.com/)
643
   * @return the updated course
644
   */
645
  @Operation(summary = "Associate a GitHub repo (PlRepo) with a course")
646
  @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)")
647
  @PutMapping("/updateGithubRepo")
648
  public InstructorCourseView updateGithubRepo(
649
      @Parameter(name = "courseId") @RequestParam Long courseId,
650
      @Parameter(name = "repoName", description = "GitHub repo in owner/repo form") @RequestParam
651
          String repoName) {
652
    Course course =
653
        courseRepository
654
            .findById(courseId)
655 1 1. lambda$updateGithubRepo$14 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updateGithubRepo$14 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
656
657
    long userId = getCurrentUser().getUser().getId();
658
    PatCredential credential =
659
        patCredentialRepository
660
            .findByUserIdAndPlatform(userId, PatPlatform.GITHUB)
661 1 1. lambda$updateGithubRepo$15 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updateGithubRepo$15 → KILLED
            .orElseThrow(() -> new ForbiddenException("must set up Github PAT first"));
662
    String token =
663
        patEncryptionService.decrypt(credential.getCiphertext(), credential.getKeyVersion());
664
665
    String trimmedRepoName = repoName.strip();
666
    boolean canWrite;
667
    try {
668
      canWrite = githubService.hasWriteAccess(trimmedRepoName, token);
669
    } catch (HttpClientErrorException e) {
670
      throw new ForbiddenException("No access to repo via Github PAT token");
671
    }
672 1 1. updateGithubRepo : negated conditional → KILLED
    if (!canWrite) {
673
      throw new ForbiddenException("Read/write access to repo via Github PAT is required");
674
    }
675
676
    PlRepo plRepo =
677
        plRepoRepository
678
            .findByRepoName(trimmedRepoName)
679
            .orElseGet(
680 1 1. lambda$updateGithubRepo$16 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updateGithubRepo$16 → KILLED
                () -> plRepoRepository.save(PlRepo.builder().repoName(trimmedRepoName).build()));
681 1 1. updateGithubRepo : removed call to edu/ucsb/cs/scaffold/entity/Course::setPlRepoId → KILLED
    course.setPlRepoId(plRepo.getId());
682 1 1. updateGithubRepo : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::updateGithubRepo → KILLED
    return viewWithPlDetails(courseRepository.save(course));
683
  }
684
685
  /**
686
   * Associates a PrairieLearn course instance (PlInstance) with a course. The numeric instance id
687
   * is verified in two steps: it is fetched from the PrairieLearn API using the caller's
688
   * PrairieLearn PAT, and the course's GitHub repo must contain a matching
689
   * courseInstances/{shortName}/infoCourseInstance.json whose longName agrees. Only the
690
   * PrairieLearn API can supply the numeric id, and only the repo check proves the instance belongs
691
   * to this course's repo.
692
   *
693
   * @param courseId the id of the course
694
   * @param instanceId PrairieLearn's numeric course instance id
695
   * @return the updated course
696
   */
697
  @Operation(summary = "Associate a PrairieLearn course instance (PlInstance) with a course")
698
  @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)")
699
  @PutMapping("/updatePLInstance")
700
  public InstructorCourseView updatePLInstance(
701
      @Parameter(name = "courseId") @RequestParam Long courseId,
702
      @Parameter(name = "instanceId", description = "numeric PrairieLearn course instance id")
703
          @RequestParam
704
          Long instanceId) {
705
    Course course =
706
        courseRepository
707
            .findById(courseId)
708 1 1. lambda$updatePLInstance$17 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updatePLInstance$17 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
709
710
    long userId = getCurrentUser().getUser().getId();
711
    PatCredential githubCredential =
712
        patCredentialRepository
713
            .findByUserIdAndPlatform(userId, PatPlatform.GITHUB)
714 1 1. lambda$updatePLInstance$18 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updatePLInstance$18 → KILLED
            .orElseThrow(() -> new ForbiddenException("must set up Github PAT first"));
715
    PatCredential plCredential =
716
        patCredentialRepository
717
            .findByUserIdAndPlatform(userId, PatPlatform.PRAIRIELEARN)
718 1 1. lambda$updatePLInstance$19 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updatePLInstance$19 → KILLED
            .orElseThrow(() -> new ForbiddenException("must set up PrairieLearn PAT first"));
719 1 1. updatePLInstance : negated conditional → KILLED
    if (course.getPlRepoId() == null) {
720
      throw new ForbiddenException("must associate course with PlRepo first");
721
    }
722
    PlRepo plRepo =
723
        plRepoRepository
724
            .findById(course.getPlRepoId())
725 1 1. lambda$updatePLInstance$20 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updatePLInstance$20 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(PlRepo.class, course.getPlRepoId()));
726
727
    String plToken =
728
        patEncryptionService.decrypt(plCredential.getCiphertext(), plCredential.getKeyVersion());
729
    PrairieLearnService.CourseInstanceInfo info;
730
    try {
731
      info = prairieLearnService.getCourseInstance(instanceId, plToken);
732
    } catch (HttpClientErrorException e) {
733
      throw new ForbiddenException("course instance id not found");
734
    }
735 1 1. updatePLInstance : negated conditional → KILLED
    if (info == null) {
736
      throw new ForbiddenException("course instance id not found");
737
    }
738
739
    String githubToken =
740
        patEncryptionService.decrypt(
741
            githubCredential.getCiphertext(), githubCredential.getKeyVersion());
742 1 1. updatePLInstance : negated conditional → KILLED
    if (!repoConfirmsInstance(plRepo, info, githubToken)) {
743
      throw new ForbiddenException("course instance id not found");
744
    }
745
746
    PlInstance plInstance =
747
        plInstanceRepository
748
            .findByPlRepoIdAndShortName(plRepo.getId(), info.shortName())
749
            .orElseGet(
750
                () ->
751 1 1. lambda$updatePLInstance$21 : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updatePLInstance$21 → KILLED
                    PlInstance.builder()
752
                        .plRepoId(plRepo.getId())
753
                        .shortName(info.shortName())
754
                        .build());
755 1 1. updatePLInstance : removed call to edu/ucsb/cs/scaffold/entity/PlInstance::setLongName → KILLED
    plInstance.setLongName(info.longName());
756 1 1. updatePLInstance : removed call to edu/ucsb/cs/scaffold/entity/PlInstance::setNumericId → KILLED
    plInstance.setNumericId(info.courseInstanceId());
757
    PlInstance savedInstance = plInstanceRepository.save(plInstance);
758
759 1 1. updatePLInstance : removed call to edu/ucsb/cs/scaffold/entity/Course::setPlInstanceId → KILLED
    course.setPlInstanceId(savedInstance.getId());
760
    Course savedCourse = courseRepository.save(course);
761
762
    // A successful association immediately kicks off a sync of the course's questions and
763
    // assessments (issue #69), so the instructor doesn't have to launch it by hand.
764
    jobService.runAsJob(syncCourseWithPlRepoJobFactory.create(userId, savedCourse));
765
766 1 1. updatePLInstance : replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::updatePLInstance → KILLED
    return viewWithPlDetails(savedCourse);
767
  }
768
769
  /**
770
   * True when the course's repo has courseInstances/{shortName}/infoCourseInstance.json and its
771
   * longName matches the one PrairieLearn reported for the instance.
772
   */
773
  private boolean repoConfirmsInstance(
774
      PlRepo plRepo, PrairieLearnService.CourseInstanceInfo info, String githubToken) {
775
    String infoJson;
776
    try {
777
      infoJson =
778
          githubService.getFileContent(
779
              plRepo.getRepoName(),
780
              "courseInstances/" + info.shortName() + "/infoCourseInstance.json",
781
              githubToken);
782
    } catch (HttpClientErrorException e) {
783 1 1. repoConfirmsInstance : replaced boolean return with true for edu/ucsb/cs/scaffold/controller/CoursesController::repoConfirmsInstance → KILLED
      return false;
784
    }
785
    try {
786
      JsonNode longName = new ObjectMapper().readTree(infoJson).path("longName");
787 3 1. repoConfirmsInstance : replaced boolean return with true for edu/ucsb/cs/scaffold/controller/CoursesController::repoConfirmsInstance → KILLED
2. repoConfirmsInstance : negated conditional → KILLED
3. repoConfirmsInstance : negated conditional → KILLED
      return !longName.isMissingNode() && longName.asText().equals(info.longName());
788
    } catch (com.fasterxml.jackson.core.JsonProcessingException e) {
789 1 1. repoConfirmsInstance : replaced boolean return with true for edu/ucsb/cs/scaffold/controller/CoursesController::repoConfirmsInstance → KILLED
      return false;
790
    }
791
  }
792
}

Mutations

126

1.1
Location : postCourse
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testPostCourse_byInstructor()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::postCourse → KILLED

157

1.1
Location : <init>
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testInstructorCourseView_withBothCollectionsNull()]
negated conditional → KILLED

158

1.1
Location : <init>
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testInstructorCourseView_withBothCollectionsNull()]
negated conditional → KILLED

177

1.1
Location : viewWithPlDetails
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCourseById_resolves_pl_association_details()]
negated conditional → KILLED

181

1.1
Location : viewWithPlDetails
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCourseById_resolves_pl_association_details()]
negated conditional → KILLED

184

1.1
Location : viewWithPlDetails
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCourseById_leaves_details_null_when_the_associated_rows_are_missing()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::viewWithPlDetails → KILLED

190

1.1
Location : viewWithPlDetails
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCourseById_leaves_details_null_when_the_associated_rows_are_missing()]
negated conditional → KILLED

191

1.1
Location : viewWithPlDetails
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCourseById_leaves_details_null_when_the_associated_rows_are_missing()]
negated conditional → KILLED

195

1.1
Location : viewWithPlDetails
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCourseById_leaves_details_null_when_the_associated_rows_are_missing()]
negated conditional → KILLED

196

1.1
Location : viewWithPlDetails
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCourseById_leaves_details_null_when_the_associated_rows_are_missing()]
negated conditional → KILLED

216

1.1
Location : allForInstructors
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testAllCourses_ROLE_INSTRUCTOR()]
replaced return value with Collections.emptyList for edu/ucsb/cs/scaffold/controller/CoursesController::allForInstructors → KILLED

232

1.1
Location : allForAdmins
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testAllCourses_ROLE_ADMIN()]
replaced return value with Collections.emptyList for edu/ucsb/cs/scaffold/controller/CoursesController::allForAdmins → KILLED

247

1.1
Location : lambda$getCourseById$0
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testGetCourseById_courseDoesNotExist()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$getCourseById$0 → KILLED

248

1.1
Location : getCourseById
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCourseById_leaves_details_null_when_the_associated_rows_are_missing()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::getCourseById → KILLED

268

1.1
Location : lambda$getCourseCanvasInfo$1
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testGetCanvasInfo_courseDoesNotExist()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$getCourseCanvasInfo$1 → KILLED

272

1.1
Location : getCourseCanvasInfo
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCanvasInfo_obscuresCorrectlyForNoChars()]
negated conditional → KILLED

274

1.1
Location : getCourseCanvasInfo
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCanvasInfo_obscuresCorrectlyForLessThanThreeCharacters()]
negated conditional → KILLED

2.2
Location : getCourseCanvasInfo
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCanvasInfo_obscuresCorrectlyForFourCharacters()]
changed conditional boundary → KILLED

277

1.1
Location : getCourseCanvasInfo
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCanvasInfo_returnsCorrectOutput()]
Replaced integer subtraction with addition → KILLED

278

1.1
Location : getCourseCanvasInfo
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCanvasInfo_returnsCorrectOutput()]
Replaced integer subtraction with addition → KILLED

281

1.1
Location : getCourseCanvasInfo
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCanvasInfo_obscuresCorrectlyForNoChars()]
replaced return value with Collections.emptyMap for edu/ucsb/cs/scaffold/controller/CoursesController::getCourseCanvasInfo → KILLED

283

1.1
Location : getCourseCanvasInfo
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCanvasInfo_obscuresCorrectlyForNoChars()]
negated conditional → KILLED

284

1.1
Location : getCourseCanvasInfo
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCanvasInfo_obscuresCorrectlyForNoChars()]
negated conditional → KILLED

302

1.1
Location : listCoursesForCurrentUser
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testListCoursesForCurrentUser()]
removed call to java/lang/Iterable::forEach → KILLED

303

1.1
Location : listCoursesForCurrentUser
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testListCoursesForCurrentUser()]
replaced return value with Collections.emptyList for edu/ucsb/cs/scaffold/controller/CoursesController::listCoursesForCurrentUser → KILLED

314

1.1
Location : lambda$listCoursesForCurrentUser$2
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testListCoursesForCurrentUser()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$listCoursesForCurrentUser$2 → KILLED

349

1.1
Location : staffCourses
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testStudenIsStaffInCourse()]
replaced return value with Collections.emptyList for edu/ucsb/cs/scaffold/controller/CoursesController::staffCourses → KILLED

360

1.1
Location : lambda$staffCourses$3
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testStudenIsStaffInCourse()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$staffCourses$3 → KILLED

393

1.1
Location : lambda$listCoursesForCurrentUserUnified$4
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testListCoursesForCurrentUserUnified_nonAdmin()]
replaced Long return value with 0L for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$listCoursesForCurrentUserUnified$4 → KILLED

398

1.1
Location : lambda$listCoursesForCurrentUserUnified$5
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testListCoursesForCurrentUserUnified_nonAdmin()]
replaced Long return value with 0L for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$listCoursesForCurrentUserUnified$5 → KILLED

407

1.1
Location : listCoursesForCurrentUserUnified
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testListCoursesForCurrentUserUnified_admin()]
negated conditional → KILLED

417

1.1
Location : listCoursesForCurrentUserUnified
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testListCoursesForCurrentUserUnified_admin()]
replaced return value with Collections.emptyList for edu/ucsb/cs/scaffold/controller/CoursesController::listCoursesForCurrentUserUnified → KILLED

420

1.1
Location : lambda$listCoursesForCurrentUserUnified$6
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testListCoursesForCurrentUserUnified_admin()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$listCoursesForCurrentUserUnified$6 → KILLED

450

1.1
Location : lambda$getCourseAccessInfo$7
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testGetCourseAccessInfo_courseDoesNotExist()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$getCourseAccessInfo$7 → KILLED

456

1.1
Location : lambda$getCourseAccessInfo$8
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testGetCourseAccessInfo_withStudentAccess()]
replaced boolean return with false for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$getCourseAccessInfo$8 → KILLED

2.2
Location : lambda$getCourseAccessInfo$8
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testGetCourseAccessInfo_noAccess()]
replaced boolean return with true for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$getCourseAccessInfo$8 → KILLED

460

1.1
Location : lambda$getCourseAccessInfo$9
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testGetCourseAccessInfo_withStaffAccess()]
replaced boolean return with false for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$getCourseAccessInfo$9 → KILLED

2.2
Location : lambda$getCourseAccessInfo$9
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testGetCourseAccessInfo_noAccess()]
replaced boolean return with true for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$getCourseAccessInfo$9 → KILLED

464

1.1
Location : getCourseAccessInfo
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testGetCourseAccessInfo_withAdminAccessOnly()]
negated conditional → KILLED

2.2
Location : getCourseAccessInfo
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testGetCourseAccessInfo_withStaffAccess()]
negated conditional → KILLED

3.3
Location : getCourseAccessInfo
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testGetCourseAccessInfo_withStudentAccess()]
negated conditional → KILLED

4.4
Location : getCourseAccessInfo
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testGetCourseAccessInfo_noAccess()]
negated conditional → KILLED

468

1.1
Location : getCourseAccessInfo
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testGetCourseAccessInfo_withStaffAccess()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::getCourseAccessInfo → KILLED

492

1.1
Location : lambda$updateInstructorEmail$10
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testUpdateInstructorEmail_courseDoesNotExist()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updateInstructorEmail$10 → KILLED

498

1.1
Location : updateInstructorEmail
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testUpdateInstructorEmail_emailNotFound()]
negated conditional → KILLED

2.2
Location : updateInstructorEmail
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testUpdateInstructorEmail_emailNotFound()]
negated conditional → KILLED

502

1.1
Location : updateInstructorEmail
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testUpdateInstructorEmail_byAdmin_email_is_admin()]
removed call to edu/ucsb/cs/scaffold/entity/Course::setInstructorEmail → KILLED

505

1.1
Location : updateInstructorEmail
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:testUpdateInstructorEmail_byAdmin_email_is_instructor()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::updateInstructorEmail → KILLED

532

1.1
Location : getCourseEmails
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCourseEmails_students_blank_team_is_unfiltered()]
negated conditional → KILLED

534

1.1
Location : getCourseEmails
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCourseEmails_students_explicit_type_comma_separated()]
negated conditional → KILLED

539

1.1
Location : getCourseEmails
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCourseEmails_students_blank_team_is_unfiltered()]
negated conditional → KILLED

540

1.1
Location : getCourseEmails
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:getCourseEmails_students_blank_team_is_unfiltered()]
replaced return value with "" for edu/ucsb/cs/scaffold/controller/CoursesController::getCourseEmails → KILLED

552

1.1
Location : lambda$deleteCourse$11
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:delete_not_found_returns_not_found()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$deleteCourse$11 → KILLED

555

1.1
Location : deleteCourse
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:delete_course_with_staff_throws_illegal_argument()]
negated conditional → KILLED

2.2
Location : deleteCourse
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:delete_success_returns_ok()]
negated conditional → KILLED

559

1.1
Location : deleteCourse
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:delete_success_returns_ok()]
removed call to edu/ucsb/cs156/jobs/repositories/JobsRepository::deleteByScopeTypeAndScopeId → KILLED

560

1.1
Location : deleteCourse
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:delete_success_returns_ok()]
removed call to edu/ucsb/cs/scaffold/repository/CourseRepository::delete → KILLED

561

1.1
Location : deleteCourse
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:delete_success_returns_ok()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::deleteCourse → KILLED

584

1.1
Location : lambda$updateCourse$12
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:update_course_not_found_returns_not_found()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updateCourse$12 → KILLED

586

1.1
Location : updateCourse
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updateCourse_success_admin()]
removed call to edu/ucsb/cs/scaffold/entity/Course::setCourseName → KILLED

587

1.1
Location : updateCourse
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updateCourse_success_admin()]
removed call to edu/ucsb/cs/scaffold/entity/Course::setTerm → KILLED

588

1.1
Location : updateCourse
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updateCourse_success_admin()]
removed call to edu/ucsb/cs/scaffold/entity/Course::setSchool → KILLED

592

1.1
Location : updateCourse
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updateCourse_success_admin()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::updateCourse → KILLED

616

1.1
Location : lambda$updateCourseWithCanvasToken$13
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updateCourseCanvasToken_not_found_returns_not_found()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updateCourseWithCanvasToken$13 → KILLED

618

1.1
Location : updateCourseWithCanvasToken
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updateCourseCanvasToken_null_params_no_change()]
negated conditional → KILLED

619

1.1
Location : updateCourseWithCanvasToken
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:admin_can_updateCourseCanvasToken_created_by_someone_else()]
negated conditional → KILLED

620

1.1
Location : updateCourseWithCanvasToken
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:admin_can_updateCourseCanvasToken_created_by_someone_else()]
negated conditional → KILLED

621

1.1
Location : updateCourseWithCanvasToken
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:admin_can_updateCourseCanvasToken_created_by_someone_else()]
removed call to edu/ucsb/cs/scaffold/entity/Course::setCanvasApiToken → KILLED

624

1.1
Location : updateCourseWithCanvasToken
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updateCourseCanvasToken_null_params_no_change()]
negated conditional → KILLED

625

1.1
Location : updateCourseWithCanvasToken
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:admin_can_updateCourseCanvasToken_created_by_someone_else()]
negated conditional → KILLED

626

1.1
Location : updateCourseWithCanvasToken
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:admin_can_updateCourseCanvasToken_created_by_someone_else()]
negated conditional → KILLED

627

1.1
Location : updateCourseWithCanvasToken
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:admin_can_updateCourseCanvasToken_created_by_someone_else()]
removed call to edu/ucsb/cs/scaffold/entity/Course::setCanvasCourseId → KILLED

632

1.1
Location : updateCourseWithCanvasToken
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updateCourseCanvasToken_success_admin()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::updateCourseWithCanvasToken → KILLED

655

1.1
Location : lambda$updateGithubRepo$14
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updateGithubRepo_returns_404_when_course_not_found()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updateGithubRepo$14 → KILLED

661

1.1
Location : lambda$updateGithubRepo$15
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updateGithubRepo_returns_403_when_user_has_no_github_pat()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updateGithubRepo$15 → KILLED

672

1.1
Location : updateGithubRepo
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updateGithubRepo_strips_surrounding_whitespace_from_the_repo_name()]
negated conditional → KILLED

680

1.1
Location : lambda$updateGithubRepo$16
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updateGithubRepo_creates_the_pl_repo_when_it_is_not_yet_in_the_table()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updateGithubRepo$16 → KILLED

681

1.1
Location : updateGithubRepo
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updateGithubRepo_records_an_existing_pl_repo_id_on_the_course()]
removed call to edu/ucsb/cs/scaffold/entity/Course::setPlRepoId → KILLED

682

1.1
Location : updateGithubRepo
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updateGithubRepo_records_an_existing_pl_repo_id_on_the_course()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::updateGithubRepo → KILLED

708

1.1
Location : lambda$updatePLInstance$17
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_returns_404_when_course_not_found()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updatePLInstance$17 → KILLED

714

1.1
Location : lambda$updatePLInstance$18
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_returns_403_when_user_has_no_github_pat()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updatePLInstance$18 → KILLED

718

1.1
Location : lambda$updatePLInstance$19
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_returns_403_when_user_has_no_prairielearn_pat()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updatePLInstance$19 → KILLED

719

1.1
Location : updatePLInstance
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_returns_403_when_course_has_no_pl_repo()]
negated conditional → KILLED

725

1.1
Location : lambda$updatePLInstance$20
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_returns_404_when_the_pl_repo_row_is_missing()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updatePLInstance$20 → KILLED

735

1.1
Location : updatePLInstance
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_returns_403_when_prairielearn_returns_no_usable_body()]
negated conditional → KILLED

742

1.1
Location : updatePLInstance
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_returns_403_when_the_long_name_is_missing_from_the_repo_json()]
negated conditional → KILLED

751

1.1
Location : lambda$updatePLInstance$21
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_creates_the_pl_instance_when_it_does_not_exist()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::lambda$updatePLInstance$21 → KILLED

755

1.1
Location : updatePLInstance
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_updates_the_numeric_id_when_the_pl_instance_exists()]
removed call to edu/ucsb/cs/scaffold/entity/PlInstance::setLongName → KILLED

756

1.1
Location : updatePLInstance
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_updates_the_numeric_id_when_the_pl_instance_exists()]
removed call to edu/ucsb/cs/scaffold/entity/PlInstance::setNumericId → KILLED

759

1.1
Location : updatePLInstance
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_updates_the_numeric_id_when_the_pl_instance_exists()]
removed call to edu/ucsb/cs/scaffold/entity/Course::setPlInstanceId → KILLED

766

1.1
Location : updatePLInstance
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_updates_the_numeric_id_when_the_pl_instance_exists()]
replaced return value with null for edu/ucsb/cs/scaffold/controller/CoursesController::updatePLInstance → KILLED

783

1.1
Location : repoConfirmsInstance
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_returns_403_when_the_repo_has_no_matching_instance_directory()]
replaced boolean return with true for edu/ucsb/cs/scaffold/controller/CoursesController::repoConfirmsInstance → KILLED

787

1.1
Location : repoConfirmsInstance
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_returns_403_when_the_long_name_is_missing_from_the_repo_json()]
replaced boolean return with true for edu/ucsb/cs/scaffold/controller/CoursesController::repoConfirmsInstance → KILLED

2.2
Location : repoConfirmsInstance
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_updates_the_numeric_id_when_the_pl_instance_exists()]
negated conditional → KILLED

3.3
Location : repoConfirmsInstance
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_returns_403_when_the_long_names_do_not_match()]
negated conditional → KILLED

789

1.1
Location : repoConfirmsInstance
Killed by : edu.ucsb.cs.scaffold.controller.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.controller.CoursesControllerTests]/[method:updatePLInstance_returns_403_when_the_repo_json_is_unparseable()]
replaced boolean return with true for edu/ucsb/cs/scaffold/controller/CoursesController::repoConfirmsInstance → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0