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

Mutations

101

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

114

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

133

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

152

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

153

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

155

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

156

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

157

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

160

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

161

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

163

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

176

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

205

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

231

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

Active mutators

Tests examined


Report generated by PIT 1.17.0