CoursesController.java

1
package edu.ucsb.cs156.frontiers.controllers;
2
3
import java.security.NoSuchAlgorithmException;
4
import java.security.spec.InvalidKeySpecException;
5
import java.util.ArrayList;
6
import java.util.HashMap;
7
import java.util.Map;
8
import java.util.Optional;
9
10
import org.springframework.beans.factory.annotation.Autowired;
11
import org.springframework.http.HttpHeaders;
12
import org.springframework.http.HttpStatus;
13
import org.springframework.http.ResponseEntity;
14
import org.springframework.security.access.prepost.PreAuthorize;
15
import org.springframework.web.bind.annotation.ExceptionHandler;
16
import org.springframework.web.bind.annotation.GetMapping;
17
import org.springframework.web.bind.annotation.PostMapping;
18
import org.springframework.web.bind.annotation.RequestMapping;
19
import org.springframework.web.bind.annotation.RequestParam;
20
import org.springframework.web.bind.annotation.ResponseStatus;
21
import org.springframework.web.bind.annotation.RestController;
22
23
import com.fasterxml.jackson.core.JsonProcessingException;
24
25
import edu.ucsb.cs156.frontiers.entities.Course;
26
import edu.ucsb.cs156.frontiers.entities.RosterStudent;
27
import edu.ucsb.cs156.frontiers.entities.CourseStaff; 
28
import edu.ucsb.cs156.frontiers.errors.EntityNotFoundException;
29
import edu.ucsb.cs156.frontiers.errors.InvalidInstallationTypeException;
30
import edu.ucsb.cs156.frontiers.models.CurrentUser;
31
import edu.ucsb.cs156.frontiers.repositories.CourseRepository;
32
import edu.ucsb.cs156.frontiers.repositories.CourseStaffRepository; 
33
import edu.ucsb.cs156.frontiers.repositories.RosterStudentRepository;
34
import edu.ucsb.cs156.frontiers.services.OrganizationLinkerService;
35
import io.swagger.v3.oas.annotations.Operation;
36
import io.swagger.v3.oas.annotations.Parameter;
37
import io.swagger.v3.oas.annotations.tags.Tag;
38
import lombok.extern.slf4j.Slf4j;
39
40
import java.util.*; 
41
42
@Tag(name = "Course")
43
@RequestMapping("/api/courses")
44
@RestController
45
@Slf4j
46
public class CoursesController extends ApiController {
47
    
48
    @Autowired
49
    private CourseRepository courseRepository;
50
51
    @Autowired
52
    private RosterStudentRepository rosterStudentRepository;
53
54
    @Autowired
55
    private CourseStaffRepository courseStaffRepository;
56
57
    @Autowired private OrganizationLinkerService linkerService;
58
59
     /**
60
     * This method creates a new Course.
61
     * 
62
    * @param orgName the name of the organization
63
    * @param courseName the name of the course
64
    * @param term the term of the course
65
    * @param school the school of the course
66
    * @return the created course
67
     */
68
69
    @Operation(summary = "Create a new course")
70
    @PreAuthorize("hasRole('ROLE_ADMIN')")
71
    @PostMapping("/post")
72
    public Course postCourse(
73
            @Parameter(name = "orgName") @RequestParam String orgName,
74
            @Parameter(name = "courseName") @RequestParam String courseName,
75
            @Parameter(name = "term") @RequestParam String term,
76
            @Parameter(name = "school") @RequestParam String school
77
           )
78
            {
79
        //get current date right now and set status to pending
80
        CurrentUser currentUser = getCurrentUser();
81
        Course course = Course.builder()
82
                .orgName(orgName)
83
                .courseName(courseName)
84
                .term(term)
85
                .school(school)
86
                .creator(currentUser.getUser())
87
                .build();
88
        Course savedCourse = courseRepository.save(course);
89
90 1 1. postCourse : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::postCourse → KILLED
        return savedCourse;
91
    }
92
93
      /**
94
     * This method returns a list of courses.
95
     * @return a list of all courses.
96
     */
97
    @Operation(summary = "List all courses")
98
    @PreAuthorize("hasRole('ROLE_ADMIN')")
99
    @GetMapping("/all")
100
    public Iterable<Course> allCourses(
101
    ) {
102
        Iterable<Course> courses = courseRepository.findAll();
103 1 1. allCourses : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/controllers/CoursesController::allCourses → KILLED
        return courses;
104
    }
105
106
107
    /**
108
     * <p>This is the outgoing method, redirecting from Frontiers to GitHub to allow a Course to be linked to a GitHub Organization.
109
     * It redirects from Frontiers to the GitHub app installation process, and will return with the {@link #addInstallation(Optional, String, String, Long) addInstallation()} endpoint
110
     * </p>
111
     * @param courseId id of the course to be linked to
112
     * @return dynamically loaded url to install Frontiers to a Github Organization, with the courseId marked as the state parameter, which GitHub will return.
113
     *
114
     */
115
    @Operation(summary = "Authorize Frontiers to a Github Course")
116
    @PreAuthorize("hasRole('ROLE_PROFESSOR')")
117
    @GetMapping("/redirect")
118
    public ResponseEntity<Void> linkCourse(@Parameter Long courseId) throws JsonProcessingException, NoSuchAlgorithmException, InvalidKeySpecException {
119
        String newUrl = linkerService.getRedirectUrl();
120
        newUrl += "/installations/new?state="+courseId;
121
        //found this convenient solution here: https://stackoverflow.com/questions/29085295/spring-mvc-restcontroller-and-redirect
122 1 1. linkCourse : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::linkCourse → KILLED
        return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY).header(HttpHeaders.LOCATION, newUrl).build();
123
    }
124
125
126
    /**
127
     *
128
     * @param installation_id id of the incoming GitHub Organization installation
129
     * @param setup_action whether the permissions are installed or updated. Required RequestParam but not used by the method.
130
     * @param code token to be exchanged with GitHub to ensure the request is legitimate and not spoofed.
131
     * @param state id of the Course to be linked with the GitHub installation.
132
     * @return ResponseEntity, returning /success if the course was successfully linked or /noperms if the user does not have the permission to install the application on GitHub. Alternately returns 403 Forbidden if the user is not the creator.
133
     */
134
    @Operation(summary = "Link a Course to a Github Course")
135
    @PreAuthorize("hasRole('ROLE_PROFESSOR')")
136
    @GetMapping("link")
137
    public ResponseEntity<Void> addInstallation(@Parameter(name = "installationId") @RequestParam Optional<String> installation_id,
138
                                                @Parameter(name = "setupAction") @RequestParam String setup_action,
139
                                                @Parameter(name = "code") @RequestParam String code,
140
                                                @Parameter(name = "state") @RequestParam Long state) throws NoSuchAlgorithmException, InvalidKeySpecException, JsonProcessingException {
141 1 1. addInstallation : negated conditional → KILLED
        if(installation_id.isEmpty()) {
142 1 1. addInstallation : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::addInstallation → KILLED
            return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY).header(HttpHeaders.LOCATION, "/courses/nopermissions").build();
143
        }else {
144 1 1. lambda$addInstallation$0 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::lambda$addInstallation$0 → KILLED
            Course course = courseRepository.findById(state).orElseThrow(() -> new EntityNotFoundException(Course.class, state));
145 1 1. addInstallation : negated conditional → KILLED
            if(!(course.getCreator().getId() ==getCurrentUser().getUser().getId())) {
146 1 1. addInstallation : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::addInstallation → KILLED
                return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
147
            }else{
148
                String orgName = linkerService.getOrgName(installation_id.get());
149 1 1. addInstallation : removed call to edu/ucsb/cs156/frontiers/entities/Course::setInstallationId → KILLED
                course.setInstallationId(installation_id.get());
150 1 1. addInstallation : removed call to edu/ucsb/cs156/frontiers/entities/Course::setOrgName → KILLED
                course.setOrgName(orgName);
151
                courseRepository.save(course);
152 1 1. addInstallation : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::addInstallation → KILLED
                return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY).header(HttpHeaders.LOCATION, "/admin/courses?success=True&course=" + state).build();
153
            }
154
        }
155
    }
156
157
    /**
158
     * This method handles the InvalidInstallationTypeException.
159
     * @param e the exception
160
     * @return a map with the type and message of the exception
161
     */
162
    @ExceptionHandler({ InvalidInstallationTypeException.class })
163
    @ResponseStatus(HttpStatus.BAD_REQUEST)
164
    public Object handleInvalidInstallationType(Throwable e) {
165 1 1. handleInvalidInstallationType : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/CoursesController::handleInvalidInstallationType → KILLED
        return Map.of(
166
                "type", e.getClass().getSimpleName(),
167
                "message", e.getMessage()
168
        );
169
    }
170
171
    @Operation(summary = "Student can see what courses they appear on roster of and their status in each")
172
    @PreAuthorize("hasRole('ROLE_USER')")
173
    @GetMapping("/student")
174
    public List<Map<String, Object>> lookUpStudentCourseRoster() 
175
    {
176
        CurrentUser currentUser = getCurrentUser();
177
        String studentEmail = currentUser.getUser().getEmail();
178
179
        List<Map<String, Object>> matchedCourses = new ArrayList<>();
180
        Iterable<RosterStudent> roster = rosterStudentRepository.findAllByEmail(studentEmail); 
181
182
        for (RosterStudent rs : roster) 
183
        {
184
            Course course = rs.getCourse(); 
185
            Map<String, Object> courseInfo = new HashMap<>();
186
            courseInfo.put("id", course.getId());
187
            courseInfo.put("orgName", course.getOrgName());
188
            courseInfo.put("courseName", course.getCourseName());
189
            courseInfo.put("term", course.getTerm());
190
            courseInfo.put("school", course.getSchool());
191
            courseInfo.put("installationId", course.getInstallationId());
192
            courseInfo.put("status", rs.getOrgStatus().name()); 
193
194
            matchedCourses.add(courseInfo);
195
        }
196
        
197 1 1. lookUpStudentCourseRoster : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/controllers/CoursesController::lookUpStudentCourseRoster → KILLED
        return matchedCourses;
198
    }
199
200
    @Operation(summary = "Student can see what courses they appear on staff roster of and their status in each")
201
    @PreAuthorize("hasRole('ROLE_USER')")
202
    @GetMapping("/staff")
203
    public List<Map<String, Object>> lookUpStaffCourseRoster() 
204
    {
205
        CurrentUser currentUser = getCurrentUser();
206
        String email = currentUser.getUser().getEmail(); 
207
        List<CourseStaff> staffRoster = courseStaffRepository.findAllByEmail(email); 
208
209
        List<Map<String, Object>> matchedCourses = new ArrayList<>();
210
211
        for (CourseStaff st : staffRoster) 
212
        {
213
            Course course = st.getCourse(); 
214
            
215
            Map<String, Object> courseInfo = new HashMap<>();
216
            courseInfo.put("id", course.getId());
217
            courseInfo.put("orgName", course.getOrgName());
218
            courseInfo.put("courseName", course.getCourseName());
219
            courseInfo.put("term", course.getTerm());
220
            courseInfo.put("school", course.getSchool());
221
            courseInfo.put("installationId", course.getInstallationId());
222
            courseInfo.put("status", st.getOrgStatus()); 
223
224
            matchedCourses.add(courseInfo);
225
        }
226 1 1. lookUpStaffCourseRoster : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/controllers/CoursesController::lookUpStaffCourseRoster → KILLED
        return matchedCourses;
227
    }
228
}

Mutations

90

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

103

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

122

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

141

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

142

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

144

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

145

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

146

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

149

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 edu/ucsb/cs156/frontiers/entities/Course::setInstallationId → KILLED

150

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 edu/ucsb/cs156/frontiers/entities/Course::setOrgName → KILLED

152

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

165

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

197

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

226

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

Active mutators

Tests examined


Report generated by PIT 1.17.0