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

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

114

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

115

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

134

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

150

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

165

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

168

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

190

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

215

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

216

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

223

1.1
Location : lambda$addInstallation$1
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$1 → KILLED

224

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

225

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

226

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

229

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

230

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

233

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

235

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:testLinkCourseSuccessfully()]
removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setOrgStatus → KILLED

239

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

241

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/CourseStaff::setOrgStatus → KILLED

244

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

260

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

287

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

288

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

302

1.1
Location : lambda$listCoursesForCurrentUser$4
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$4 → KILLED

333

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

347

1.1
Location : lambda$staffCourses$5
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$5 → KILLED

364

1.1
Location : lambda$updateInstructorEmail$6
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$6 → KILLED

370

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

374

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

377

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

388

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

391

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

395

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

396

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

397

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

420

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

422

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

423

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

424

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

428

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

452

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

454

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

455

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

458

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

467

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

468

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