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

Mutations

92

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

117

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

118

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

137

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

153

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

168

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

171

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

191

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

195

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()]
negated conditional → KILLED

197

1.1
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

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

200

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

201

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

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_returnsCorrectOutput()]
replaced return value with Collections.emptyMap for edu/ucsb/cs156/frontiers/controllers/CoursesController::getCourseCanvasInfo → 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_returnsCorrectOutput()]
negated conditional → KILLED

207

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()]
negated conditional → KILLED

229

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

254

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

255

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

262

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

263

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

264

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

265

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

268

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

269

1.1
Location : addInstallation
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:testLinkCourseSuccessfullyProfessorCreator()]
removed call to edu/ucsb/cs156/frontiers/entities/Course::setOrgName → 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:testLinkCourseSuccessfully()]
removed call to java/util/List::forEach → KILLED

274

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

278

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

280

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

283

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

299

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

326

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

327

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

341

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

383

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

397

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

414

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

420

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

424

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

427

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()]
replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::updateInstructorEmail → KILLED

449

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_all_filters_students_by_team_but_keeps_all_staff()]
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_all_filters_students_by_team_but_keeps_all_staff()]
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

456

1.1
Location : getCourseEmails
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

458

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_explicit_type_comma_separated()]
negated conditional → KILLED

463

1.1
Location : getCourseEmails
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

464

1.1
Location : getCourseEmails
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()]
replaced return value with "" for edu/ucsb/cs156/frontiers/controllers/CoursesController::getCourseEmails → KILLED

475

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

478

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()]
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

482

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

483

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

484

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

507

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

509

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

510

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

511

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

515

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

539

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

541

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

542

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

543

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

544

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::setCanvasApiToken → KILLED

547

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

548

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

549

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

550

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

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_success_admin()]
replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::updateCourseWithCanvasToken → KILLED

564

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

565

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

575

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:hide_base_permission_warning_not_found()]
replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$hideBasePermissionWarning$13 → KILLED

577

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

578

1.1
Location : hideBasePermissionWarning
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:hide_base_permission_warning_sets_field_to_true()]
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