CoursesController.java

1
package edu.ucsb.cs156.frontiers.controllers;
2
3
import com.fasterxml.jackson.core.JsonProcessingException;
4
import edu.ucsb.cs156.frontiers.entities.Course;
5
import edu.ucsb.cs156.frontiers.entities.CourseStaff;
6
import edu.ucsb.cs156.frontiers.entities.RosterStudent;
7
import edu.ucsb.cs156.frontiers.entities.User;
8
import edu.ucsb.cs156.frontiers.enums.OrgStatus;
9
import edu.ucsb.cs156.frontiers.enums.School;
10
import edu.ucsb.cs156.frontiers.errors.EntityNotFoundException;
11
import edu.ucsb.cs156.frontiers.errors.InvalidInstallationTypeException;
12
import edu.ucsb.cs156.frontiers.models.CourseWarning;
13
import edu.ucsb.cs156.frontiers.models.CurrentUser;
14
import edu.ucsb.cs156.frontiers.repositories.AdminRepository;
15
import edu.ucsb.cs156.frontiers.repositories.CourseRepository;
16
import edu.ucsb.cs156.frontiers.repositories.CourseStaffRepository;
17
import edu.ucsb.cs156.frontiers.repositories.InstructorRepository;
18
import edu.ucsb.cs156.frontiers.repositories.JobsRepository;
19
import edu.ucsb.cs156.frontiers.repositories.RosterStudentRepository;
20
import edu.ucsb.cs156.frontiers.repositories.UserRepository;
21
import edu.ucsb.cs156.frontiers.services.CanvasApiTokenSecurityService;
22
import edu.ucsb.cs156.frontiers.services.OrganizationLinkerService;
23
import io.swagger.v3.oas.annotations.Operation;
24
import io.swagger.v3.oas.annotations.Parameter;
25
import io.swagger.v3.oas.annotations.tags.Tag;
26
import java.security.NoSuchAlgorithmException;
27
import java.security.spec.InvalidKeySpecException;
28
import java.util.ArrayList;
29
import java.util.List;
30
import java.util.Map;
31
import java.util.Objects;
32
import java.util.Optional;
33
import java.util.stream.Collectors;
34
import java.util.stream.StreamSupport;
35
import lombok.extern.slf4j.Slf4j;
36
import org.springframework.beans.factory.annotation.Autowired;
37
import org.springframework.http.HttpHeaders;
38
import org.springframework.http.HttpStatus;
39
import org.springframework.http.ResponseEntity;
40
import org.springframework.security.access.prepost.PreAuthorize;
41
import org.springframework.transaction.annotation.Transactional;
42
import org.springframework.web.bind.annotation.*;
43
44
@Tag(name = "Course")
45
@RequestMapping("/api/courses")
46
@RestController
47
@Slf4j
48
public class CoursesController extends ApiController {
49
50
  @Autowired private CourseRepository courseRepository;
51
52
  @Autowired private UserRepository userRepository;
53
54
  @Autowired private RosterStudentRepository rosterStudentRepository;
55
56
  @Autowired private CourseStaffRepository courseStaffRepository;
57
58
  @Autowired private InstructorRepository instructorRepository;
59
60
  @Autowired private AdminRepository adminRepository;
61
62
  @Autowired private CanvasApiTokenSecurityService canvasApiTokenSecurityService;
63
64
  @Autowired private OrganizationLinkerService linkerService;
65
66
  @Autowired private JobsRepository jobsRepository;
67
68
  /**
69
   * This method creates a new Course.
70
   *
71
   * @param courseName the name of the course
72
   * @param term the term of the course
73
   * @param school the school of the course
74
   * @param canvasApiToken the Canvas API token (optional)
75
   * @param canvasCourseId the Canvas course ID (optional)
76
   */
77
  @Operation(summary = "Create a new course")
78
  @PreAuthorize("hasRole('ROLE_ADMIN') || hasRole('ROLE_INSTRUCTOR')")
79
  @PostMapping("/post")
80
  public InstructorCourseView postCourse(
81
      @Parameter(name = "courseName") @RequestParam String courseName,
82
      @Parameter(name = "term") @RequestParam String term,
83
      @Parameter(name = "school") @RequestParam School school,
84
      @Parameter(name = "canvasApiToken") @RequestParam(required = false) String canvasApiToken,
85
      @Parameter(name = "canvasCourseId") @RequestParam(required = false) String canvasCourseId) {
86
    // get current date right now and set status to pending
87
    CurrentUser currentUser = getCurrentUser();
88
    Course course =
89
        Course.builder()
90
            .courseName(courseName)
91
            .term(term)
92
            .school(school)
93
            .instructorEmail(currentUser.getUser().getEmail().strip())
94
            .canvasApiToken(canvasApiTokenSecurityService.encrypt(canvasApiToken))
95
            .canvasCourseId(canvasCourseId)
96
            .build();
97
    Course savedCourse = courseRepository.save(course);
98
99 1 1. postCourse : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::postCourse → KILLED
    return new InstructorCourseView(savedCourse);
100
  }
101
102
  /** Projection of Course entity with fields that are relevant for instructors and admins */
103
  public static record InstructorCourseView(
104
      Long id,
105
      String installationId,
106
      String orgName,
107
      String courseName,
108
      String term,
109
      School school,
110
      String instructorEmail,
111
      boolean hideBasePermissionWarning,
112
      int numStudents,
113
      int numStaff) {
114
115
    // Creates view from Course entity
116
    public InstructorCourseView(Course c) {
117
      this(
118
          c.getId(),
119
          c.getInstallationId(),
120
          c.getOrgName(),
121
          c.getCourseName(),
122
          c.getTerm(),
123
          c.getSchool(),
124
          c.getInstructorEmail(),
125
          c.getHideBasePermissionWarning(),
126 1 1. <init> : negated conditional → KILLED
          c.getRosterStudents() != null ? c.getRosterStudents().size() : 0,
127 1 1. <init> : negated conditional → KILLED
          c.getCourseStaff() != null ? c.getCourseStaff().size() : 0);
128
    }
129
  }
130
131
  /**
132
   * This method returns a list of courses.
133
   *
134
   * @return a list of all courses for an instructor.
135
   */
136
  @Operation(summary = "List all courses for an instructor")
137
  @PreAuthorize("hasRole('ROLE_INSTRUCTOR')")
138
  @GetMapping("/allForInstructors")
139
  public Iterable<InstructorCourseView> allForInstructors() {
140
    CurrentUser currentUser = getCurrentUser();
141
    String instructorEmail = currentUser.getUser().getEmail();
142
    List<Course> courses = courseRepository.findByInstructorEmail(instructorEmail);
143
144
    List<InstructorCourseView> courseViews =
145
        courses.stream().map(InstructorCourseView::new).collect(Collectors.toList());
146 1 1. allForInstructors : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/controllers/CoursesController::allForInstructors → KILLED
    return courseViews;
147
  }
148
149
  /**
150
   * This method returns a list of courses.
151
   *
152
   * @return a list of all courses for an admin.
153
   */
154
  @Operation(summary = "List all courses for an admin")
155
  @PreAuthorize("hasRole('ROLE_ADMIN')")
156
  @GetMapping("/allForAdmins")
157
  public Iterable<InstructorCourseView> allForAdmins() {
158
    List<Course> courses = courseRepository.findAll();
159
160
    List<InstructorCourseView> courseViews =
161
        courses.stream().map(InstructorCourseView::new).collect(Collectors.toList());
162 1 1. allForAdmins : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/controllers/CoursesController::allForAdmins → KILLED
    return courseViews;
163
  }
164
165
  /**
166
   * This method returns single course by its id
167
   *
168
   * @return a course
169
   */
170
  @Operation(summary = "Get course by id")
171
  @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #id)")
172
  @GetMapping("/{id}")
173
  public InstructorCourseView getCourseById(@Parameter(name = "id") @PathVariable Long id) {
174
    Course course =
175
        courseRepository
176
            .findById(id)
177 1 1. lambda$getCourseById$0 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$getCourseById$0 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, id));
178
    // Convert to InstructorCourseView
179
    InstructorCourseView courseView = new InstructorCourseView(course);
180 1 1. getCourseById : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::getCourseById → KILLED
    return courseView;
181
  }
182
183
  /**
184
   * This method returns the Canvas course ID and partially obscured Canvas token for a course by
185
   * its id. If the token is less than or equal to 3 characters long, it is returned in full.
186
   * Otherwise, all but the last three characters are replaced with asterisks. This is okay because
187
   * such short tokens are not generated by Canvas.
188
   *
189
   * @param courseId the id of the course
190
   * @return a map with courseId, canvasCourseId, and obscured canvasApiToken
191
   */
192
  @Operation(summary = "Get course Canvas course ID and Canvas token (partially obscured)")
193
  @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)")
194
  @GetMapping("getCanvasInfo")
195
  public Map<String, String> getCourseCanvasInfo(
196
      @Parameter(name = "courseId") @RequestParam Long courseId) {
197
    Course course =
198
        courseRepository
199
            .findById(courseId)
200 1 1. lambda$getCourseCanvasInfo$1 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$getCourseCanvasInfo$1 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
201
202
    String obscuredToken = null;
203
204 1 1. getCourseCanvasInfo : negated conditional → KILLED
    if (course.getCanvasApiToken() != null) {
205
      String token = canvasApiTokenSecurityService.decrypt(course.getCanvasApiToken());
206 2 1. getCourseCanvasInfo : negated conditional → KILLED
2. getCourseCanvasInfo : changed conditional boundary → KILLED
      if (token.length() < 4) {
207
        obscuredToken = token;
208
      } else {
209 1 1. getCourseCanvasInfo : Replaced integer subtraction with addition → KILLED
        String lastThree = token.substring(token.length() - 3);
210 1 1. getCourseCanvasInfo : Replaced integer subtraction with addition → KILLED
        obscuredToken = "*".repeat(token.length() - 3) + lastThree;
211
      }
212
    }
213 1 1. getCourseCanvasInfo : replaced return value with Collections.emptyMap for edu/ucsb/cs156/frontiers/controllers/CoursesController::getCourseCanvasInfo → KILLED
    return Map.of(
214
        "courseId", course.getId().toString(),
215 1 1. getCourseCanvasInfo : negated conditional → KILLED
        "canvasCourseId", course.getCanvasCourseId() != null ? course.getCanvasCourseId() : "",
216 1 1. getCourseCanvasInfo : negated conditional → KILLED
        "canvasApiToken", obscuredToken != null ? obscuredToken : "");
217
  }
218
219
  /**
220
   * This is the outgoing method, redirecting from Frontiers to GitHub to allow a Course to be
221
   * linked to a GitHub Organization. It redirects from Frontiers to the GitHub app installation
222
   * process, and will return with the {@link #addInstallation(Optional, String, String, Long)
223
   * addInstallation()} endpoint
224
   *
225
   * @param courseId id of the course to be linked to
226
   * @return dynamically loaded url to install Frontiers to a Github Organization, with the courseId
227
   *     marked as the state parameter, which GitHub will return.
228
   */
229
  @Operation(summary = "Authorize Frontiers to a Github Course")
230
  @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)")
231
  @GetMapping("/redirect")
232
  public ResponseEntity<Void> linkCourse(@Parameter Long courseId)
233
      throws JsonProcessingException, NoSuchAlgorithmException, InvalidKeySpecException {
234
    String newUrl = linkerService.getRedirectUrl();
235
    newUrl += "/installations/new?state=" + courseId;
236
    // found this convenient solution here:
237
    // https://stackoverflow.com/questions/29085295/spring-mvc-restcontroller-and-redirect
238 1 1. linkCourse : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::linkCourse → KILLED
    return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY)
239
        .header(HttpHeaders.LOCATION, newUrl)
240
        .build();
241
  }
242
243
  /**
244
   * @param installation_id id of the incoming GitHub Organization installation
245
   * @param setup_action whether the permissions are installed or updated. Required RequestParam but
246
   *     not used by the method.
247
   * @param code token to be exchanged with GitHub to ensure the request is legitimate and not
248
   *     spoofed.
249
   * @param state id of the Course to be linked with the GitHub installation.
250
   * @return ResponseEntity, returning /success if the course was successfully linked or /noperms if
251
   *     the user does not have the permission to install the application on GitHub. Alternately
252
   *     returns 403 Forbidden if the user is not the creator.
253
   */
254
  @Operation(summary = "Link a Course to a Github Organization by installing Github App")
255
  @PreAuthorize("hasRole('ROLE_ADMIN') || hasRole('ROLE_INSTRUCTOR')")
256
  @GetMapping("link")
257
  public ResponseEntity<Void> addInstallation(
258
      @Parameter(name = "installationId") @RequestParam Optional<String> installation_id,
259
      @Parameter(name = "setupAction") @RequestParam String setup_action,
260
      @Parameter(name = "code") @RequestParam String code,
261
      @Parameter(name = "state") @RequestParam Long state)
262
      throws NoSuchAlgorithmException, InvalidKeySpecException, JsonProcessingException {
263 1 1. addInstallation : negated conditional → KILLED
    if (installation_id.isEmpty()) {
264 1 1. addInstallation : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::addInstallation → KILLED
      return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY)
265
          .header(HttpHeaders.LOCATION, "/courses/nopermissions")
266
          .build();
267
    } else {
268
      Course course =
269
          courseRepository
270
              .findById(state)
271 1 1. lambda$addInstallation$2 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$addInstallation$2 → KILLED
              .orElseThrow(() -> new EntityNotFoundException(Course.class, state));
272 1 1. addInstallation : negated conditional → KILLED
      if (!isCurrentUserAdmin()
273 1 1. addInstallation : negated conditional → KILLED
          && !course.getInstructorEmail().equals(getCurrentUser().getUser().getEmail())) {
274 1 1. addInstallation : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::addInstallation → KILLED
        return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
275
      } else {
276
        String orgName = linkerService.getOrgName(installation_id.get());
277 1 1. addInstallation : removed call to edu/ucsb/cs156/frontiers/entities/Course::setInstallationId → KILLED
        course.setInstallationId(installation_id.get());
278 1 1. addInstallation : removed call to edu/ucsb/cs156/frontiers/entities/Course::setOrgName → KILLED
        course.setOrgName(orgName);
279
        course
280
            .getRosterStudents()
281 1 1. addInstallation : removed call to java/util/List::forEach → KILLED
            .forEach(
282
                rs -> {
283 1 1. lambda$addInstallation$3 : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setOrgStatus → KILLED
                  rs.setOrgStatus(OrgStatus.JOINCOURSE);
284
                });
285
        course
286
            .getCourseStaff()
287 1 1. addInstallation : removed call to java/util/List::forEach → KILLED
            .forEach(
288
                cs -> {
289 1 1. lambda$addInstallation$4 : removed call to edu/ucsb/cs156/frontiers/entities/CourseStaff::setOrgStatus → KILLED
                  cs.setOrgStatus(OrgStatus.JOINCOURSE);
290
                });
291
        courseRepository.save(course);
292 1 1. addInstallation : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::addInstallation → KILLED
        return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY)
293
            .header(HttpHeaders.LOCATION, "/login/success")
294
            .build();
295
      }
296
    }
297
  }
298
299
  /**
300
   * This method handles the InvalidInstallationTypeException.
301
   *
302
   * @param e the exception
303
   * @return a map with the type and message of the exception
304
   */
305
  @ExceptionHandler({InvalidInstallationTypeException.class})
306
  @ResponseStatus(HttpStatus.BAD_REQUEST)
307
  public Object handleInvalidInstallationType(Throwable e) {
308 1 1. handleInvalidInstallationType : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::handleInvalidInstallationType → KILLED
    return Map.of(
309
        "type", e.getClass().getSimpleName(),
310
        "message", e.getMessage());
311
  }
312
313
  public record RosterStudentCoursesDTO(
314
      Long id,
315
      String installationId,
316
      String orgName,
317
      String courseName,
318
      String term,
319
      String school,
320
      OrgStatus studentStatus,
321
      Long rosterStudentId) {}
322
323
  /**
324
   * This method returns a list of courses that the current user is enrolled.
325
   *
326
   * @return a list of courses in the DTO form along with the student status in the organization.
327
   */
328
  @Operation(summary = "List all courses for the current student, including their org status")
329
  @PreAuthorize("hasRole('ROLE_USER')")
330
  @GetMapping("/list")
331
  public List<RosterStudentCoursesDTO> listCoursesForCurrentUser() {
332
    String email = getCurrentUser().getUser().getEmail();
333
    Iterable<RosterStudent> rosterStudentsIterable = rosterStudentRepository.findAllByEmail(email);
334
    List<RosterStudent> rosterStudents = new ArrayList<>();
335 1 1. listCoursesForCurrentUser : removed call to java/lang/Iterable::forEach → KILLED
    rosterStudentsIterable.forEach(rosterStudents::add);
336 1 1. listCoursesForCurrentUser : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/controllers/CoursesController::listCoursesForCurrentUser → KILLED
    return rosterStudents.stream()
337
        .map(
338
            rs -> {
339
              Course course = rs.getCourse();
340
              RosterStudentCoursesDTO rsDto =
341
                  new RosterStudentCoursesDTO(
342
                      course.getId(),
343
                      course.getInstallationId(),
344
                      course.getOrgName(),
345
                      course.getCourseName(),
346
                      course.getTerm(),
347
                      course.getSchool().getDisplayName(),
348
                      rs.getOrgStatus(),
349
                      rs.getId());
350 1 1. lambda$listCoursesForCurrentUser$5 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$listCoursesForCurrentUser$5 → KILLED
              return rsDto;
351
            })
352
        .collect(Collectors.toList());
353
  }
354
355
  public record StaffCoursesDTO(
356
      Long id,
357
      String installationId,
358
      String orgName,
359
      String courseName,
360
      String term,
361
      School school,
362
      OrgStatus studentStatus,
363
      Long staffId) {}
364
365
  public enum EmailTypes {
366
    STUDENTS,
367
    STAFF,
368
    ALL
369
  }
370
371
  public enum EmailFormats {
372
    COMMA_SEPARATED,
373
    ONE_PER_LINE
374
  }
375
376
  /**
377
   * student see what courses they appear as staff in
378
   *
379
   * @param studentId the id of the student making request
380
   * @return a list of all courses student is staff in
381
   */
382
  @Operation(summary = "Student see what courses they appear as staff in")
383
  @PreAuthorize("hasRole('ROLE_USER')")
384
  @GetMapping("/staffCourses")
385
  public List<StaffCoursesDTO> staffCourses() {
386
    CurrentUser currentUser = getCurrentUser();
387
    User user = currentUser.getUser();
388
389
    String email = user.getEmail();
390
391
    List<CourseStaff> staffMembers = courseStaffRepository.findAllByEmail(email);
392 1 1. staffCourses : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/controllers/CoursesController::staffCourses → KILLED
    return staffMembers.stream()
393
        .map(
394
            s -> {
395
              Course course = s.getCourse();
396
              StaffCoursesDTO sDto =
397
                  new StaffCoursesDTO(
398
                      course.getId(),
399
                      course.getInstallationId(),
400
                      course.getOrgName(),
401
                      course.getCourseName(),
402
                      course.getTerm(),
403
                      course.getSchool(),
404
                      s.getOrgStatus(),
405
                      s.getId());
406 1 1. lambda$staffCourses$6 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$staffCourses$6 → KILLED
              return sDto;
407
            })
408
        .collect(Collectors.toList());
409
  }
410
411
  @Operation(summary = "Update instructor email for a course (admin only)")
412
  @PreAuthorize("hasRole('ROLE_ADMIN')")
413
  @PutMapping("/updateInstructor")
414
  public InstructorCourseView updateInstructorEmail(
415
      @Parameter(name = "courseId") @RequestParam Long courseId,
416
      @Parameter(name = "instructorEmail") @RequestParam String instructorEmail) {
417
418
    instructorEmail = instructorEmail.strip();
419
420
    Course course =
421
        courseRepository
422
            .findById(courseId)
423 1 1. lambda$updateInstructorEmail$7 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$updateInstructorEmail$7 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
424
425
    // Validate that the email exists in either instructor or admin table
426
    boolean isInstructor = instructorRepository.existsByEmail(instructorEmail);
427
    boolean isAdmin = adminRepository.existsByEmail(instructorEmail);
428
429 2 1. updateInstructorEmail : negated conditional → KILLED
2. updateInstructorEmail : negated conditional → KILLED
    if (!isInstructor && !isAdmin) {
430
      throw new IllegalArgumentException("Email must belong to either an instructor or admin");
431
    }
432
433 1 1. updateInstructorEmail : removed call to edu/ucsb/cs156/frontiers/entities/Course::setInstructorEmail → KILLED
    course.setInstructorEmail(instructorEmail);
434
    Course savedCourse = courseRepository.save(course);
435
436 1 1. updateInstructorEmail : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::updateInstructorEmail → KILLED
    return new InstructorCourseView(savedCourse);
437
  }
438
439
  @Operation(summary = "Get course emails")
440
  @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)")
441
  @GetMapping("/emails")
442
  public String getCourseEmails(
443
      @Parameter(name = "courseId") @RequestParam Long courseId,
444
      @Parameter(name = "type") @RequestParam(defaultValue = "STUDENTS") EmailTypes type,
445
      @Parameter(name = "team") @RequestParam(required = false) String team,
446
      @Parameter(name = "format") @RequestParam(defaultValue = "ONE_PER_LINE")
447
          EmailFormats format) {
448
449
    List<String> staffEmails =
450
        StreamSupport.stream(courseStaffRepository.findByCourseId(courseId).spliterator(), false)
451
            .map(CourseStaff::getEmail)
452
            .filter(Objects::nonNull)
453
            .sorted()
454
            .collect(Collectors.toList());
455
456
    List<String> studentEmails =
457
        StreamSupport.stream(rosterStudentRepository.findByCourseId(courseId).spliterator(), false)
458 4 1. lambda$getCourseEmails$8 : negated conditional → KILLED
2. lambda$getCourseEmails$8 : replaced boolean return with true for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$getCourseEmails$8 → KILLED
3. lambda$getCourseEmails$8 : negated conditional → KILLED
4. lambda$getCourseEmails$8 : negated conditional → KILLED
            .filter(student -> team == null || team.isBlank() || student.getTeams().contains(team))
459
            .map(RosterStudent::getEmail)
460
            .filter(Objects::nonNull)
461
            .sorted()
462
            .collect(Collectors.toList());
463
464
    List<String> emails = studentEmails;
465 1 1. getCourseEmails : negated conditional → KILLED
    if (type == EmailTypes.STAFF) {
466
      emails = staffEmails;
467 1 1. getCourseEmails : negated conditional → KILLED
    } else if (type == EmailTypes.ALL) {
468
      emails = new ArrayList<>(staffEmails);
469
      emails.addAll(studentEmails);
470
    }
471
472 1 1. getCourseEmails : negated conditional → KILLED
    String separator = format == EmailFormats.COMMA_SEPARATED ? "," : "\r\n";
473 1 1. getCourseEmails : replaced return value with "" for edu/ucsb/cs156/frontiers/controllers/CoursesController::getCourseEmails → KILLED
    return String.join(separator, emails);
474
  }
475
476
  @Operation(summary = "Delete a course")
477
  @PreAuthorize("hasRole('ROLE_ADMIN')")
478
  @DeleteMapping("")
479
  @Transactional
480
  public Object deleteCourse(@RequestParam Long courseId)
481
      throws NoSuchAlgorithmException, InvalidKeySpecException {
482
    Course course =
483
        courseRepository
484
            .findById(courseId)
485 1 1. lambda$deleteCourse$9 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$deleteCourse$9 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
486
487
    // Check if course has roster students or staff
488 2 1. deleteCourse : negated conditional → KILLED
2. deleteCourse : negated conditional → KILLED
    if (!course.getRosterStudents().isEmpty() || !course.getCourseStaff().isEmpty()) {
489
      throw new IllegalArgumentException("Cannot delete course with students or staff");
490
    }
491
492 1 1. deleteCourse : removed call to edu/ucsb/cs156/frontiers/services/OrganizationLinkerService::unenrollOrganization → KILLED
    linkerService.unenrollOrganization(course);
493 1 1. deleteCourse : removed call to edu/ucsb/cs156/frontiers/repositories/JobsRepository::deleteByCourse_Id → KILLED
    jobsRepository.deleteByCourse_Id(courseId);
494 1 1. deleteCourse : removed call to edu/ucsb/cs156/frontiers/repositories/CourseRepository::delete → KILLED
    courseRepository.delete(course);
495 1 1. deleteCourse : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::deleteCourse → KILLED
    return genericMessage("Course with id %s deleted".formatted(course.getId()));
496
  }
497
498
  /**
499
   * This method updates an existing course.
500
   *
501
   * @param courseId the id of the course to update
502
   * @param courseName the new name of the course
503
   * @param term the new term of the course
504
   * @param school the new school of the course
505
   * @return the updated course
506
   */
507
  @Operation(summary = "Update an existing course")
508
  @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)")
509
  @PutMapping("")
510
  public InstructorCourseView updateCourse(
511
      @Parameter(name = "courseId") @RequestParam Long courseId,
512
      @Parameter(name = "courseName") @RequestParam String courseName,
513
      @Parameter(name = "term") @RequestParam String term,
514
      @Parameter(name = "school") @RequestParam School school) {
515
    Course course =
516
        courseRepository
517
            .findById(courseId)
518 1 1. lambda$updateCourse$10 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$updateCourse$10 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
519
520 1 1. updateCourse : removed call to edu/ucsb/cs156/frontiers/entities/Course::setCourseName → KILLED
    course.setCourseName(courseName);
521 1 1. updateCourse : removed call to edu/ucsb/cs156/frontiers/entities/Course::setTerm → KILLED
    course.setTerm(term);
522 1 1. updateCourse : removed call to edu/ucsb/cs156/frontiers/entities/Course::setSchool → KILLED
    course.setSchool(school);
523
524
    Course savedCourse = courseRepository.save(course);
525
526 1 1. updateCourse : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::updateCourse → KILLED
    return new InstructorCourseView(savedCourse);
527
  }
528
529
  /**
530
   * This method updates an existing course.
531
   *
532
   * @param courseId the id of the course to update
533
   * @param courseName the new name of the course
534
   * @param term the new term of the course
535
   * @param school the new school of the course
536
   * @param canvasApiToken the new Canvas API token for the course
537
   * @param canvasCourseId the new Canvas course ID
538
   * @return the updated course
539
   */
540
  @Operation(summary = "Update an existing course with Canvas token and course ID")
541
  @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)")
542
  @PutMapping("/updateCourseCanvasToken")
543
  public InstructorCourseView updateCourseWithCanvasToken(
544
      @Parameter(name = "courseId") @RequestParam Long courseId,
545
      @Parameter(name = "canvasApiToken") @RequestParam(required = false) String canvasApiToken,
546
      @Parameter(name = "canvasCourseId") @RequestParam(required = false) String canvasCourseId) {
547
    Course course =
548
        courseRepository
549
            .findById(courseId)
550 1 1. lambda$updateCourseWithCanvasToken$11 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$updateCourseWithCanvasToken$11 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
551
552
    String existingCanvasToken = canvasApiTokenSecurityService.decrypt(course.getCanvasApiToken());
553 1 1. updateCourseWithCanvasToken : negated conditional → KILLED
    if (canvasApiToken != null
554 1 1. updateCourseWithCanvasToken : negated conditional → KILLED
        && !canvasApiToken.isEmpty()
555 1 1. updateCourseWithCanvasToken : negated conditional → KILLED
        && !canvasApiToken.equals(existingCanvasToken)) {
556 1 1. updateCourseWithCanvasToken : removed call to edu/ucsb/cs156/frontiers/entities/Course::setCanvasApiToken → KILLED
      course.setCanvasApiToken(canvasApiTokenSecurityService.encrypt(canvasApiToken));
557
    }
558
559 1 1. updateCourseWithCanvasToken : negated conditional → KILLED
    if (canvasCourseId != null
560 1 1. updateCourseWithCanvasToken : negated conditional → KILLED
        && !canvasCourseId.isEmpty()
561 1 1. updateCourseWithCanvasToken : negated conditional → KILLED
        && !canvasCourseId.equals(course.getCanvasCourseId())) {
562 1 1. updateCourseWithCanvasToken : removed call to edu/ucsb/cs156/frontiers/entities/Course::setCanvasCourseId → KILLED
      course.setCanvasCourseId(canvasCourseId);
563
    }
564
565
    Course savedCourse = courseRepository.save(course);
566
567 1 1. updateCourseWithCanvasToken : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::updateCourseWithCanvasToken → KILLED
    return new InstructorCourseView(savedCourse);
568
  }
569
570
  @Operation(summary = "Get course warnings")
571
  @GetMapping("/warnings/{courseId}")
572
  @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)")
573
  public CourseWarning warnings(@PathVariable @Parameter Long courseId) throws Exception {
574
    Course course =
575
        courseRepository
576
            .findById(courseId)
577 1 1. lambda$warnings$12 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$warnings$12 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
578 1 1. warnings : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::warnings → KILLED
    return linkerService.checkCourseWarnings(course);
579
  }
580
581
  @Operation(summary = "Hide base permission warning for a course")
582
  @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)")
583
  @PostMapping("/warnings/hideBasePermissionWarning/{courseId}")
584
  public Object hideBasePermissionWarning(@PathVariable @Parameter Long courseId) {
585
    Course course =
586
        courseRepository
587
            .findById(courseId)
588 1 1. lambda$hideBasePermissionWarning$13 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$hideBasePermissionWarning$13 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
589
590 1 1. hideBasePermissionWarning : removed call to edu/ucsb/cs156/frontiers/entities/Course::setHideBasePermissionWarning → KILLED
    course.setHideBasePermissionWarning(true);
591
    courseRepository.save(course);
592
593 1 1. hideBasePermissionWarning : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::hideBasePermissionWarning → KILLED
    return genericMessage(
594
        "hideBasePermissionWarning set to true for course with id %s".formatted(courseId));
595
  }
596
}

Mutations

99

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

126

1.1
Location : <init>
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testInstructorCourseView_withNullRosterStudents()]
negated conditional → KILLED

127

1.1
Location : <init>
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testInstructorCourseView_withNullCourseStaff()]
negated conditional → KILLED

146

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

162

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

177

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

180

1.1
Location : getCourseById
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testGetCourseById()]
replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::getCourseById → KILLED

200

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

204

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

206

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

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

209

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

210

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

213

1.1
Location : getCourseCanvasInfo
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:getCanvasInfo_obscuresCorrectlyForLessThanThreeCharacters()]
replaced return value with Collections.emptyMap for edu/ucsb/cs156/frontiers/controllers/CoursesController::getCourseCanvasInfo → KILLED

215

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

216

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

238

1.1
Location : linkCourse
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testRedirect()]
replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::linkCourse → KILLED

263

1.1
Location : addInstallation
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testNoPerms()]
negated conditional → KILLED

264

1.1
Location : addInstallation
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testNoPerms()]
replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::addInstallation → KILLED

271

1.1
Location : lambda$addInstallation$2
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testCourseLinkNotFound()]
replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$addInstallation$2 → KILLED

272

1.1
Location : addInstallation
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testNotCreator()]
negated conditional → KILLED

273

1.1
Location : addInstallation
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testNotCreator()]
negated conditional → KILLED

274

1.1
Location : addInstallation
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testNotCreator()]
replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::addInstallation → KILLED

277

1.1
Location : addInstallation
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testCourseLinkSuccessWhenAdminNotCreator()]
removed call to edu/ucsb/cs156/frontiers/entities/Course::setInstallationId → KILLED

278

1.1
Location : addInstallation
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testCourseLinkSuccessWhenAdminNotCreator()]
removed call to edu/ucsb/cs156/frontiers/entities/Course::setOrgName → KILLED

281

1.1
Location : addInstallation
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testLinkCourseSuccessfully()]
removed call to java/util/List::forEach → KILLED

283

1.1
Location : lambda$addInstallation$3
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testLinkCourseSuccessfully()]
removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setOrgStatus → KILLED

287

1.1
Location : addInstallation
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testLinkCourseSuccessfully()]
removed call to java/util/List::forEach → KILLED

289

1.1
Location : lambda$addInstallation$4
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testLinkCourseSuccessfully()]
removed call to edu/ucsb/cs156/frontiers/entities/CourseStaff::setOrgStatus → KILLED

292

1.1
Location : addInstallation
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testCourseLinkSuccessWhenAdminNotCreator()]
replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::addInstallation → KILLED

308

1.1
Location : handleInvalidInstallationType
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testNotOrganization()]
replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::handleInvalidInstallationType → KILLED

335

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

336

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

350

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

392

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

406

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

423

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

429

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

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

433

1.1
Location : updateInstructorEmail
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testUpdateInstructorEmail_byAdmin_email_is_admin()]
removed call to edu/ucsb/cs156/frontiers/entities/Course::setInstructorEmail → KILLED

436

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

458

1.1
Location : lambda$getCourseEmails$8
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:getCourseEmails_students_blank_team_is_unfiltered()]
negated conditional → KILLED

2.2
Location : lambda$getCourseEmails$8
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:getCourseEmails_students_filtered_by_team()]
replaced boolean return with true for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$getCourseEmails$8 → KILLED

3.3
Location : lambda$getCourseEmails$8
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:getCourseEmails_students_filtered_by_team()]
negated conditional → KILLED

4.4
Location : lambda$getCourseEmails$8
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:getCourseEmails_defaults_to_students_one_per_line()]
negated conditional → KILLED

465

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

467

1.1
Location : getCourseEmails
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:getCourseEmails_all_comma_separated_staff_first()]
negated conditional → KILLED

472

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

473

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

485

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

488

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

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

492

1.1
Location : deleteCourse
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:delete_success_returns_ok()]
removed call to edu/ucsb/cs156/frontiers/services/OrganizationLinkerService::unenrollOrganization → KILLED

493

1.1
Location : deleteCourse
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:delete_success_returns_ok()]
removed call to edu/ucsb/cs156/frontiers/repositories/JobsRepository::deleteByCourse_Id → KILLED

494

1.1
Location : deleteCourse
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:delete_success_returns_ok()]
removed call to edu/ucsb/cs156/frontiers/repositories/CourseRepository::delete → KILLED

495

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

518

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

520

1.1
Location : updateCourse
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:admin_can_update_course_created_by_someone_else()]
removed call to edu/ucsb/cs156/frontiers/entities/Course::setCourseName → KILLED

521

1.1
Location : updateCourse
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:admin_can_update_course_created_by_someone_else()]
removed call to edu/ucsb/cs156/frontiers/entities/Course::setTerm → KILLED

522

1.1
Location : updateCourse
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:admin_can_update_course_created_by_someone_else()]
removed call to edu/ucsb/cs156/frontiers/entities/Course::setSchool → KILLED

526

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

550

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

553

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

554

1.1
Location : updateCourseWithCanvasToken
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:updateCourseCanvasToken_encryptsBeforeSaving()]
negated conditional → KILLED

555

1.1
Location : updateCourseWithCanvasToken
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:updateCourseCanvasToken_encryptsBeforeSaving()]
negated conditional → KILLED

556

1.1
Location : updateCourseWithCanvasToken
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:updateCourseCanvasToken_encryptsBeforeSaving()]
removed call to edu/ucsb/cs156/frontiers/entities/Course::setCanvasApiToken → KILLED

559

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

560

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

561

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

562

1.1
Location : updateCourseWithCanvasToken
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:admin_can_updateCourseCanvasToken_created_by_someone_else()]
removed call to edu/ucsb/cs156/frontiers/entities/Course::setCanvasCourseId → KILLED

567

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

577

1.1
Location : lambda$warnings$12
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:test_warnings_not_found()]
replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$warnings$12 → KILLED

578

1.1
Location : warnings
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:calls_org_service_for_warnings()]
replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::warnings → KILLED

588

1.1
Location : lambda$hideBasePermissionWarning$13
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:hideBasePermissionWarning_notFound()]
replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$hideBasePermissionWarning$13 → KILLED

590

1.1
Location : hideBasePermissionWarning
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:hideBasePermissionWarning_setsFieldTrue()]
removed call to edu/ucsb/cs156/frontiers/entities/Course::setHideBasePermissionWarning → KILLED

593

1.1
Location : hideBasePermissionWarning
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:hideBasePermissionWarning_setsFieldTrue()]
replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::hideBasePermissionWarning → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0