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

Mutations

96

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

123

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

124

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

143

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

159

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

174

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

177

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

197

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

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

203

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_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_returnsCorrectOutput()]
Replaced integer subtraction with addition → 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()]
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_obscuresCorrectlyForThreeCharacters()]
replaced return value with Collections.emptyMap for edu/ucsb/cs156/frontiers/controllers/CoursesController::getCourseCanvasInfo → KILLED

212

1.1
Location : getCourseCanvasInfo
Killed by : edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CoursesControllerTests]/[method:getCanvasInfo_obscuresCorrectlyForThreeCharacters()]
negated conditional → 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_obscuresCorrectlyForThreeCharacters()]
negated conditional → KILLED

235

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

260

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

261

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

268

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

269

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

270

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

271

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

274

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

275

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

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$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

284

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

286

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

289

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

305

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

332

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

333

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

347

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

389

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

403

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

420

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

426

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

430

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

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

455

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

462

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

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

469

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

470

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

482

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

485

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

489

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

490

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

491

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

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

515

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

517

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

518

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

519

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

523

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

547

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

549

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

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

551

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

552

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

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

557

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

558

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

563

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

573

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

574

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

584

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

586

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

589

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