CourseStaffCSVController.java

1
package edu.ucsb.cs156.frontiers.controllers;
2
3
import com.opencsv.CSVReader;
4
import com.opencsv.exceptions.CsvException;
5
import edu.ucsb.cs156.frontiers.entities.Course;
6
import edu.ucsb.cs156.frontiers.entities.CourseStaff;
7
import edu.ucsb.cs156.frontiers.enums.OrgStatus;
8
import edu.ucsb.cs156.frontiers.errors.EntityNotFoundException;
9
import edu.ucsb.cs156.frontiers.repositories.CourseRepository;
10
import edu.ucsb.cs156.frontiers.repositories.CourseStaffRepository;
11
import edu.ucsb.cs156.frontiers.services.UpdateUserService;
12
import io.swagger.v3.oas.annotations.Operation;
13
import io.swagger.v3.oas.annotations.Parameter;
14
import io.swagger.v3.oas.annotations.tags.Tag;
15
import java.io.BufferedInputStream;
16
import java.io.IOException;
17
import java.io.InputStream;
18
import java.io.InputStreamReader;
19
import java.util.Map;
20
import lombok.extern.slf4j.Slf4j;
21
import org.springframework.beans.factory.annotation.Autowired;
22
import org.springframework.http.HttpStatus;
23
import org.springframework.security.access.prepost.PreAuthorize;
24
import org.springframework.web.bind.annotation.PostMapping;
25
import org.springframework.web.bind.annotation.RequestMapping;
26
import org.springframework.web.bind.annotation.RequestParam;
27
import org.springframework.web.bind.annotation.RestController;
28
import org.springframework.web.multipart.MultipartFile;
29
import org.springframework.web.server.ResponseStatusException;
30
31
@Tag(name = "CourseStaff")
32
@RequestMapping("/api/coursestaff")
33
@RestController("CourseStaffCSVController")
34
@Slf4j
35
public class CourseStaffCSVController extends ApiController {
36
37
  @Autowired private CourseStaffRepository courseStaffRepository;
38
39
  @Autowired private CourseRepository courseRepository;
40
41
  @Autowired private UpdateUserService updateUserService;
42
43
  public static final String STAFF_CSV_HEADERS = "firstName,lastName,email";
44
45
  @Operation(summary = "Upload Course Staff from CSV")
46
  @PreAuthorize("@CourseSecurity.hasInstructorPermissions(#root, #courseId)")
47
  @PostMapping(
48
      value = "/upload/csv",
49
      consumes = {"multipart/form-data"})
50
  public Map<String, Integer> uploadStaffCSV(
51
      @Parameter(name = "courseId") @RequestParam Long courseId,
52
      @Parameter(name = "file") @RequestParam("file") MultipartFile file)
53
      throws IOException, CsvException {
54
55
    Course course =
56
        courseRepository
57
            .findById(courseId)
58 1 1. lambda$uploadStaffCSV$0 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CourseStaffCSVController::lambda$uploadStaffCSV$0 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId.toString()));
59
60
    int count = 0;
61
62
    try (InputStream inputStream = new BufferedInputStream(file.getInputStream());
63
        InputStreamReader reader = new InputStreamReader(inputStream);
64
        CSVReader csvReader = new CSVReader(reader)) {
65
66
      String[] headers = csvReader.readNext();
67 1 1. uploadStaffCSV : negated conditional → KILLED
      if (headers == null) {
68
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "CSV file is empty");
69
      }
70
71 1 1. uploadStaffCSV : removed call to edu/ucsb/cs156/frontiers/controllers/CourseStaffCSVController::validateHeaders → KILLED
      validateHeaders(headers);
72
73
      String[] row;
74 1 1. uploadStaffCSV : negated conditional → KILLED
      while ((row = csvReader.readNext()) != null) {
75 2 1. uploadStaffCSV : negated conditional → KILLED
2. uploadStaffCSV : changed conditional boundary → KILLED
        if (row.length < 3) {
76
          throw new ResponseStatusException(
77
              HttpStatus.BAD_REQUEST,
78
              String.format(
79
                  "CSV row does not have enough columns. Expected at least 3, got %d", row.length));
80
        }
81
82
        CourseStaff courseStaff =
83
            CourseStaff.builder()
84
                .firstName(row[0].trim())
85
                .lastName(row[1].trim())
86
                .email(row[2].trim())
87
                .course(course)
88
                .build();
89
90 1 1. uploadStaffCSV : negated conditional → KILLED
        if (course.getInstallationId() != null) {
91 1 1. uploadStaffCSV : removed call to edu/ucsb/cs156/frontiers/entities/CourseStaff::setOrgStatus → KILLED
          courseStaff.setOrgStatus(OrgStatus.JOINCOURSE);
92
        } else {
93 1 1. uploadStaffCSV : removed call to edu/ucsb/cs156/frontiers/entities/CourseStaff::setOrgStatus → KILLED
          courseStaff.setOrgStatus(OrgStatus.PENDING);
94
        }
95
96
        CourseStaff savedCourseStaff = courseStaffRepository.save(courseStaff);
97 1 1. uploadStaffCSV : removed call to edu/ucsb/cs156/frontiers/services/UpdateUserService::attachUserToCourseStaff → KILLED
        updateUserService.attachUserToCourseStaff(savedCourseStaff);
98 1 1. uploadStaffCSV : Changed increment from 1 to -1 → KILLED
        count++;
99
      }
100
    }
101
102 1 1. uploadStaffCSV : replaced return value with Collections.emptyMap for edu/ucsb/cs156/frontiers/controllers/CourseStaffCSVController::uploadStaffCSV → KILLED
    return Map.of("count", count);
103
  }
104
105
  static void validateHeaders(String[] headers) {
106
    String[] expectedHeaders = STAFF_CSV_HEADERS.split(",");
107 2 1. validateHeaders : changed conditional boundary → KILLED
2. validateHeaders : negated conditional → KILLED
    if (headers.length < expectedHeaders.length) {
108
      throw new ResponseStatusException(
109
          HttpStatus.BAD_REQUEST,
110
          String.format(
111
              "CSV headers do not match expected format. Expected: %s", STAFF_CSV_HEADERS));
112
    }
113 2 1. validateHeaders : changed conditional boundary → KILLED
2. validateHeaders : negated conditional → KILLED
    for (int i = 0; i < expectedHeaders.length; i++) {
114 1 1. validateHeaders : negated conditional → KILLED
      if (!expectedHeaders[i].trim().equalsIgnoreCase(headers[i].trim())) {
115
        throw new ResponseStatusException(
116
            HttpStatus.BAD_REQUEST,
117
            String.format(
118
                "CSV headers do not match expected format. Expected: %s", STAFF_CSV_HEADERS));
119
      }
120
    }
121
  }
122
}

Mutations

58

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

67

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

71

1.1
Location : uploadStaffCSV
Killed by : edu.ucsb.cs156.frontiers.controllers.CourseStaffCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CourseStaffCSVControllerTests]/[method:upload_returns_bad_request_for_invalid_headers()]
removed call to edu/ucsb/cs156/frontiers/controllers/CourseStaffCSVController::validateHeaders → KILLED

74

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

75

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

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

90

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

91

1.1
Location : uploadStaffCSV
Killed by : edu.ucsb.cs156.frontiers.controllers.CourseStaffCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CourseStaffCSVControllerTests]/[method:instructor_can_upload_staff_csv_with_installation_id()]
removed call to edu/ucsb/cs156/frontiers/entities/CourseStaff::setOrgStatus → KILLED

93

1.1
Location : uploadStaffCSV
Killed by : edu.ucsb.cs156.frontiers.controllers.CourseStaffCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CourseStaffCSVControllerTests]/[method:instructor_can_upload_staff_csv()]
removed call to edu/ucsb/cs156/frontiers/entities/CourseStaff::setOrgStatus → KILLED

97

1.1
Location : uploadStaffCSV
Killed by : edu.ucsb.cs156.frontiers.controllers.CourseStaffCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CourseStaffCSVControllerTests]/[method:instructor_can_upload_staff_csv()]
removed call to edu/ucsb/cs156/frontiers/services/UpdateUserService::attachUserToCourseStaff → KILLED

98

1.1
Location : uploadStaffCSV
Killed by : edu.ucsb.cs156.frontiers.controllers.CourseStaffCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CourseStaffCSVControllerTests]/[method:instructor_can_upload_staff_csv()]
Changed increment from 1 to -1 → KILLED

102

1.1
Location : uploadStaffCSV
Killed by : edu.ucsb.cs156.frontiers.controllers.CourseStaffCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CourseStaffCSVControllerTests]/[method:instructor_can_upload_staff_csv()]
replaced return value with Collections.emptyMap for edu/ucsb/cs156/frontiers/controllers/CourseStaffCSVController::uploadStaffCSV → KILLED

107

1.1
Location : validateHeaders
Killed by : edu.ucsb.cs156.frontiers.controllers.CourseStaffCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CourseStaffCSVControllerTests]/[method:upload_strips_whitespace_from_fields()]
changed conditional boundary → KILLED

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

113

1.1
Location : validateHeaders
Killed by : edu.ucsb.cs156.frontiers.controllers.CourseStaffCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.controllers.CourseStaffCSVControllerTests]/[method:upload_returns_bad_request_for_short_row()]
changed conditional boundary → KILLED

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

114

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

Active mutators

Tests examined


Report generated by PIT 1.17.0