RepositoryService.java

1
package edu.ucsb.cs156.frontiers.services;
2
3
import com.fasterxml.jackson.core.JsonProcessingException;
4
import com.fasterxml.jackson.databind.ObjectMapper;
5
import edu.ucsb.cs156.frontiers.entities.Course;
6
import edu.ucsb.cs156.frontiers.entities.CourseStaff;
7
import edu.ucsb.cs156.frontiers.entities.RosterStudent;
8
import edu.ucsb.cs156.frontiers.enums.RepositoryPermissions;
9
import java.security.NoSuchAlgorithmException;
10
import java.security.spec.InvalidKeySpecException;
11
import java.util.HashMap;
12
import java.util.Map;
13
import lombok.extern.slf4j.Slf4j;
14
import org.springframework.boot.web.client.RestTemplateBuilder;
15
import org.springframework.http.*;
16
import org.springframework.stereotype.Service;
17
import org.springframework.web.client.HttpClientErrorException;
18
import org.springframework.web.client.RestTemplate;
19
20
@Service
21
@Slf4j
22
public class RepositoryService {
23
  private final JwtService jwtService;
24
  private final RestTemplate restTemplate;
25
  private final ObjectMapper mapper;
26
27
  /**
28
   * Creates a GitHub repository for a user (student or staff), given only their GitHub login.
29
   *
30
   * <p>This helper method contains the shared logic used by both {@link
31
   * #createStudentRepository(Course, RosterStudent, String, Boolean, RepositoryPermissions)} and
32
   * {@link #createStaffRepository(Course, CourseStaff, String, Boolean, RepositoryPermissions)}.
33
   *
34
   * <ul>
35
   *   <li>Checks whether the repository already exists.
36
   *   <li>If not, creates a new repository under the course's organization.
37
   *   <li>Adds the user as a collaborator with the given permission level.
38
   * </ul>
39
   *
40
   * @param course the course whose organization the repo belongs to
41
   * @param githubLogin GitHub username of the student or staff member
42
   * @param repoPrefix prefix for the repository name (repoPrefix-githubLogin)
43
   * @param isPrivate whether the created repository should be private
44
   * @param permissions collaborator permissions to grant the user
45
   * @throws NoSuchAlgorithmException if signing fails
46
   * @throws InvalidKeySpecException if signing fails
47
   * @throws JsonProcessingException if JSON serialization fails
48
   */
49
  private void createRepositoryForStudentOrStaff(
50
      Course course,
51
      String githubLogin,
52
      String repoPrefix,
53
      Boolean isPrivate,
54
      RepositoryPermissions permissions)
55
      throws NoSuchAlgorithmException, InvalidKeySpecException, JsonProcessingException {
56
57
    String newRepoName = repoPrefix + "-" + githubLogin;
58
    String token = jwtService.getInstallationToken(course);
59
60
    String existenceEndpoint =
61
        "https://api.github.com/repos/" + course.getOrgName() + "/" + newRepoName;
62
    String createEndpoint = "https://api.github.com/orgs/" + course.getOrgName() + "/repos";
63
    String provisionEndpoint =
64
        "https://api.github.com/repos/"
65
            + course.getOrgName()
66
            + "/"
67
            + newRepoName
68
            + "/collaborators/"
69
            + githubLogin;
70
71
    HttpHeaders existenceHeaders = new HttpHeaders();
72 1 1. createRepositoryForStudentOrStaff : removed call to org/springframework/http/HttpHeaders::add → KILLED
    existenceHeaders.add("Authorization", "Bearer " + token);
73 1 1. createRepositoryForStudentOrStaff : removed call to org/springframework/http/HttpHeaders::add → KILLED
    existenceHeaders.add("Accept", "application/vnd.github+json");
74 1 1. createRepositoryForStudentOrStaff : removed call to org/springframework/http/HttpHeaders::add → KILLED
    existenceHeaders.add("X-GitHub-Api-Version", "2022-11-28");
75
76
    HttpEntity<String> existenceEntity = new HttpEntity<>(existenceHeaders);
77
78
    try {
79
      restTemplate.exchange(existenceEndpoint, HttpMethod.GET, existenceEntity, String.class);
80
    } catch (HttpClientErrorException e) {
81 1 1. createRepositoryForStudentOrStaff : negated conditional → KILLED
      if (e.getStatusCode().equals(HttpStatus.NOT_FOUND)) {
82
        HttpHeaders createHeaders = new HttpHeaders();
83 1 1. createRepositoryForStudentOrStaff : removed call to org/springframework/http/HttpHeaders::add → KILLED
        createHeaders.add("Authorization", "Bearer " + token);
84 1 1. createRepositoryForStudentOrStaff : removed call to org/springframework/http/HttpHeaders::add → KILLED
        createHeaders.add("Accept", "application/vnd.github+json");
85 1 1. createRepositoryForStudentOrStaff : removed call to org/springframework/http/HttpHeaders::add → KILLED
        createHeaders.add("X-GitHub-Api-Version", "2022-11-28");
86
87
        Map<String, Object> body = new HashMap<>();
88
        body.put("name", newRepoName);
89
        body.put("private", isPrivate);
90
        String bodyAsJson = mapper.writeValueAsString(body);
91
92
        HttpEntity<String> createEntity = new HttpEntity<>(bodyAsJson, createHeaders);
93
94
        restTemplate.exchange(createEndpoint, HttpMethod.POST, createEntity, String.class);
95
      } else {
96
        log.warn(
97
            "Unexpected response code {} when checking for existence of repository {}",
98
            e.getStatusCode(),
99
            newRepoName);
100
        return;
101
      }
102
    }
103
104
    try {
105
      Map<String, Object> provisionBody = new HashMap<>();
106
      provisionBody.put("permission", permissions.getApiName());
107
      String provisionAsJson = mapper.writeValueAsString(provisionBody);
108
109
      HttpEntity<String> provisionEntity = new HttpEntity<>(provisionAsJson, existenceHeaders);
110
      restTemplate.exchange(provisionEndpoint, HttpMethod.PUT, provisionEntity, String.class);
111
    } catch (HttpClientErrorException ignored) {
112
      // silently ignore if provisioning fails (same as before)
113
    }
114
  }
115
116
  public RepositoryService(
117
      JwtService jwtService, RestTemplateBuilder restTemplateBuilder, ObjectMapper mapper) {
118
    this.jwtService = jwtService;
119
    this.restTemplate = restTemplateBuilder.build();
120
    this.mapper = mapper;
121
  }
122
123
  /**
124
   * Creates a single student repository if it doesn't already exist, and provisions access to the
125
   * repository by that student
126
   *
127
   * @param course The Course in question
128
   * @param student RosterStudent of the student the repository should be created for
129
   * @param repoPrefix Name of the project or assignment. Used to title the repository, in the
130
   *     format repoPrefix-githubLogin
131
   * @param isPrivate Whether the repository is private or not
132
   */
133
  public void createStudentRepository(
134
      Course course,
135
      RosterStudent student,
136
      String repoPrefix,
137
      Boolean isPrivate,
138
      RepositoryPermissions permissions)
139
      throws NoSuchAlgorithmException, InvalidKeySpecException, JsonProcessingException {
140 1 1. createStudentRepository : removed call to edu/ucsb/cs156/frontiers/services/RepositoryService::createRepositoryForStudentOrStaff → KILLED
    createRepositoryForStudentOrStaff(
141
        course, student.getGithubLogin(), repoPrefix, isPrivate, permissions);
142
  }
143
144
  /**
145
   * Creates a single staff repository if it doesn't already exist, and provisions access to the
146
   * repository by that staff member
147
   *
148
   * @param course The Course in question
149
   * @param staff CourseStaff of the staff the repository should be created for
150
   * @param repoPrefix Name of the project or assignment. Used to title the repository, in the
151
   *     format repoPrefix-githubLogin
152
   * @param isPrivate Whether the repository is private or not
153
   */
154
  public void createStaffRepository(
155
      Course course,
156
      CourseStaff staff,
157
      String repoPrefix,
158
      Boolean isPrivate,
159
      RepositoryPermissions permissions)
160
      throws NoSuchAlgorithmException, InvalidKeySpecException, JsonProcessingException {
161
162 1 1. createStaffRepository : removed call to edu/ucsb/cs156/frontiers/services/RepositoryService::createRepositoryForStudentOrStaff → KILLED
    createRepositoryForStudentOrStaff(
163
        course, staff.getGithubLogin(), repoPrefix, isPrivate, permissions);
164
  }
165
}

Mutations

72

1.1
Location : createRepositoryForStudentOrStaff
Killed by : edu.ucsb.cs156.frontiers.services.RepositoryServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.RepositoryServiceTests]/[method:exits_if_not_not_found()]
removed call to org/springframework/http/HttpHeaders::add → KILLED

73

1.1
Location : createRepositoryForStudentOrStaff
Killed by : edu.ucsb.cs156.frontiers.services.RepositoryServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.RepositoryServiceTests]/[method:exits_if_not_not_found()]
removed call to org/springframework/http/HttpHeaders::add → KILLED

74

1.1
Location : createRepositoryForStudentOrStaff
Killed by : edu.ucsb.cs156.frontiers.services.RepositoryServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.RepositoryServiceTests]/[method:exits_if_not_not_found()]
removed call to org/springframework/http/HttpHeaders::add → KILLED

81

1.1
Location : createRepositoryForStudentOrStaff
Killed by : edu.ucsb.cs156.frontiers.services.RepositoryServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.RepositoryServiceTests]/[method:exits_if_not_not_found()]
negated conditional → KILLED

83

1.1
Location : createRepositoryForStudentOrStaff
Killed by : edu.ucsb.cs156.frontiers.services.RepositoryServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.RepositoryServiceTests]/[method:successfully_creates_staff_repo_public()]
removed call to org/springframework/http/HttpHeaders::add → KILLED

84

1.1
Location : createRepositoryForStudentOrStaff
Killed by : edu.ucsb.cs156.frontiers.services.RepositoryServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.RepositoryServiceTests]/[method:successfully_creates_staff_repo_public()]
removed call to org/springframework/http/HttpHeaders::add → KILLED

85

1.1
Location : createRepositoryForStudentOrStaff
Killed by : edu.ucsb.cs156.frontiers.services.RepositoryServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.RepositoryServiceTests]/[method:successfully_creates_staff_repo_public()]
removed call to org/springframework/http/HttpHeaders::add → KILLED

140

1.1
Location : createStudentRepository
Killed by : edu.ucsb.cs156.frontiers.services.RepositoryServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.RepositoryServiceTests]/[method:exits_if_not_not_found()]
removed call to edu/ucsb/cs156/frontiers/services/RepositoryService::createRepositoryForStudentOrStaff → KILLED

162

1.1
Location : createStaffRepository
Killed by : edu.ucsb.cs156.frontiers.services.RepositoryServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.RepositoryServiceTests]/[method:successfully_creates_staff_repo_public()]
removed call to edu/ucsb/cs156/frontiers/services/RepositoryService::createRepositoryForStudentOrStaff → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0