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

Mutations

89

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

94

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

119

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

120

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

139

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

155

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

170

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

173

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

193

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

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

199

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

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

202

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

208

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

209

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

231

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

256

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

257

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

264

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

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

266

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

267

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

270

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

271

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

274

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

276

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

280

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

282

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

285

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

301

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

328

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

329

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

343

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

374

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

388

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

405

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

411

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

415

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

418

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

429

1.1
Location : lambda$deleteCourse$8
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$8 → KILLED

432

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

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

436

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

437

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

438

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

461

1.1
Location : lambda$updateCourse$9
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$9 → KILLED

463

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

464

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

465

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

469

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

493

1.1
Location : lambda$updateCourseWithCanvasToken$10
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$10 → KILLED

495

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

496

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

497

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

498

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

501

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

502

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

503

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

504

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

509

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

518

1.1
Location : lambda$warnings$11
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$11 → KILLED

519

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

Active mutators

Tests examined


Report generated by PIT 1.17.0