GithubGraphQLService.java

1
package edu.ucsb.cs156.frontiers.services;
2
3
import com.fasterxml.jackson.core.JsonProcessingException;
4
import com.fasterxml.jackson.databind.JsonNode;
5
import com.fasterxml.jackson.databind.ObjectMapper;
6
import edu.ucsb.cs156.frontiers.entities.Course;
7
import edu.ucsb.cs156.frontiers.entities.DownloadRequest;
8
import edu.ucsb.cs156.frontiers.entities.DownloadedCommit;
9
import edu.ucsb.cs156.frontiers.errors.NoLinkedOrganizationException;
10
import edu.ucsb.cs156.frontiers.repositories.DownloadedCommitRepository;
11
import java.security.NoSuchAlgorithmException;
12
import java.security.spec.InvalidKeySpecException;
13
import java.time.Instant;
14
import java.util.ArrayList;
15
import java.util.List;
16
import java.util.Map;
17
import lombok.extern.slf4j.Slf4j;
18
import org.springframework.graphql.GraphQlResponse;
19
import org.springframework.graphql.client.HttpSyncGraphQlClient;
20
import org.springframework.http.HttpHeaders;
21
import org.springframework.http.MediaType;
22
import org.springframework.stereotype.Service;
23
import org.springframework.web.client.RestClient;
24
25
@Service
26
@Slf4j
27
public class GithubGraphQLService {
28
29
  private final HttpSyncGraphQlClient graphQlClient;
30
31
  private final JwtService jwtService;
32
33
  private final String githubBaseUrl = "https://api.github.com/graphql";
34
35
  private final ObjectMapper jacksonObjectMapper;
36
  private final DownloadedCommitRepository downloadedCommitRepository;
37
38
  public GithubGraphQLService(
39
      RestClient.Builder builder,
40
      JwtService jwtService,
41
      ObjectMapper jacksonObjectMapper,
42
      DownloadedCommitRepository downloadedCommitRepository) {
43
    this.jwtService = jwtService;
44
    this.graphQlClient =
45
        HttpSyncGraphQlClient.builder(builder.baseUrl(githubBaseUrl).build())
46
            .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
47
            .build();
48
    this.jacksonObjectMapper = jacksonObjectMapper;
49
    this.downloadedCommitRepository = downloadedCommitRepository;
50
  }
51
52
  /**
53
   * Retrieves the name of the default branch for a given GitHub repository.
54
   *
55
   * @param owner The owner (username or organization) of the repository.
56
   * @param repo The name of the repository.
57
   * @return A Mono emitting the default branch name, or an empty Mono if not found.
58
   */
59
  public String getDefaultBranchName(Course course, String owner, String repo)
60
      throws JsonProcessingException,
61
          NoSuchAlgorithmException,
62
          InvalidKeySpecException,
63
          NoLinkedOrganizationException {
64
    log.info(
65
        "getDefaultBranchName called with course.getId(): {} owner: {}, repo: {}",
66
        course.getId(),
67
        owner,
68
        repo);
69
    String githubToken = jwtService.getInstallationToken(course);
70
71
    // language=GraphQL
72
    String query =
73
        """
74
        query getDefaultBranch($owner: String!, $repo: String!) {
75
          repository(owner: $owner, name: $repo) {
76
            defaultBranchRef {
77
              name
78
            }
79
          }
80
        }
81
        """;
82
83 1 1. getDefaultBranchName : replaced return value with "" for edu/ucsb/cs156/frontiers/services/GithubGraphQLService::getDefaultBranchName → KILLED
    return graphQlClient
84
        .mutate()
85
        .header("Authorization", "Bearer " + githubToken)
86
        .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
87
        .build()
88
        .document(query)
89
        .variable("owner", owner)
90
        .variable("repo", repo)
91
        .retrieveSync("repository.defaultBranchRef.name")
92
        .toEntity(String.class);
93
  }
94
95
  public String getDefaultBasePermission(Course course)
96
      throws JsonProcessingException,
97
          NoSuchAlgorithmException,
98
          InvalidKeySpecException,
99
          NoLinkedOrganizationException {
100
    log.info("getDefaultBasePermission called with course.getId(): {}", course.getId());
101
    String githubToken = jwtService.getInstallationToken(course);
102
103
    // language=GraphQL
104
    String query =
105
        """
106
        query GetOrgDefaultPermission($login: String!) {
107
          organization(login: $login) {
108
            name
109
            defaultRepositoryPermission
110
          }
111
        }
112
        """;
113
114 1 1. getDefaultBasePermission : replaced return value with "" for edu/ucsb/cs156/frontiers/services/GithubGraphQLService::getDefaultBasePermission → KILLED
    return graphQlClient
115
        .mutate()
116
        .header("Authorization", "Bearer " + githubToken)
117
        .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
118
        .build()
119
        .document(query)
120
        .variable("login", course.getOrgName())
121
        .retrieveSync("organization.defaultRepositoryPermission")
122
        .toEntity(String.class);
123
  }
124
125
  public String getCommits(
126
      Course course, String owner, String repo, String branch, int first, String after)
127
      throws JsonProcessingException,
128
          NoSuchAlgorithmException,
129
          InvalidKeySpecException,
130
          NoLinkedOrganizationException {
131 1 1. getCommits : replaced return value with "" for edu/ucsb/cs156/frontiers/services/GithubGraphQLService::getCommits → KILLED
    return getCommits(course, owner, repo, branch, null, null, first, after);
132
  }
133
134
  /**
135
   * Retrieves the commit history for a specified branch of a GitHub repository within a given time
136
   * range.
137
   *
138
   * @param course The course entity, used to fetch the associated GitHub installation token.
139
   * @param owner The owner of the GitHub repository.
140
   * @param repo The name of the GitHub repository.
141
   * @param branch The branch of the repository for which the commit history is retrieved.
142
   * @param since The start time for fetching commits (inclusive). Optional. Can be null.
143
   * @param until The end time for fetching commits (exclusive). Optional. Can be null.
144
   * @param size The maximum number of commits to retrieve in one request.
145
   * @param cursor The pagination cursor pointing to the start of the commit history to fetch.
146
   *     Optional. Can be null.
147
   * @return A JSON string representing the commit history and associated metadata.
148
   * @throws NoLinkedOrganizationException If no linked organization exists for the specified
149
   *     course.
150
   */
151
  public String getCommits(
152
      Course course,
153
      String owner,
154
      String repo,
155
      String branch,
156
      Instant since,
157
      Instant until,
158
      int size,
159
      String cursor)
160
      throws JsonProcessingException,
161
          NoSuchAlgorithmException,
162
          InvalidKeySpecException,
163
          NoLinkedOrganizationException {
164
    String githubToken = jwtService.getInstallationToken(course);
165
    // language=GraphQL
166
    String query =
167
        """
168
            query GetBranchCommits($owner: String!, $repo: String!, $branch: String!, $first: Int!, $after: String, $since: GitTimestamp, $until: GitTimestamp) {
169
              repository(owner: $owner, name: $repo) {
170
                ref(qualifiedName: $branch) {
171
                  target {
172
                    ... on Commit {
173
                      history(first: $first, after: $after, since: $since, until: $until) {
174
                        pageInfo {
175
                          hasNextPage
176
                          endCursor
177
                        }
178
                        edges {
179
                          node {
180
                            oid
181
                            url
182
                            messageHeadline
183
                            committedDate
184
                            author {
185
                              name
186
                              email
187
                              user {
188
                                login
189
                              }
190
                            }
191
                            committer {
192
                              name
193
                              email
194
                              user {
195
                                login
196
                              }
197
                            }
198
                          }
199
                        }
200
                      }
201
                    }
202
                  }
203
                }
204
              }
205
            }
206
            """;
207
208
    GraphQlResponse response =
209
        graphQlClient
210
            .mutate()
211
            .header("Authorization", "Bearer " + githubToken)
212
            .build()
213
            .document(query)
214
            .variable("owner", owner)
215
            .variable("repo", repo)
216
            .variable("branch", branch)
217
            .variable("first", size)
218
            .variable("after", cursor)
219
            .variable("since", since)
220
            .variable("until", until)
221
            .executeSync();
222
223
    Map<String, Object> data = response.getData();
224
    String jsonData = jacksonObjectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(data);
225 1 1. getCommits : replaced return value with "" for edu/ucsb/cs156/frontiers/services/GithubGraphQLService::getCommits → KILLED
    return jsonData;
226
  }
227
228
  public void downloadCommitHistory(DownloadRequest downloadRequest)
229
      throws NoSuchAlgorithmException, InvalidKeySpecException, JsonProcessingException {
230
    String pointer = null;
231
    boolean hasNextPage;
232
    List<DownloadedCommit> downloadedCommits = new ArrayList<>(4000);
233
    do {
234
      JsonNode currentPage =
235
          jacksonObjectMapper.readTree(
236
              getCommits(
237
                  downloadRequest.getCourse(),
238
                  downloadRequest.getOrg(),
239
                  downloadRequest.getRepo(),
240
                  downloadRequest.getBranch(),
241
                  downloadRequest.getStartDate(),
242
                  downloadRequest.getEndDate(),
243
                  100,
244
                  pointer));
245
      pointer =
246
          currentPage
247
              .path("repository")
248
              .path("ref")
249
              .path("target")
250
              .path("history")
251
              .path("pageInfo")
252
              .path("endCursor")
253
              .asText();
254
      hasNextPage =
255
          currentPage
256
              .path("repository")
257
              .path("ref")
258
              .path("target")
259
              .path("history")
260
              .path("pageInfo")
261
              .path("hasNextPage")
262
              .asBoolean();
263
      JsonNode commits =
264
          currentPage.path("repository").path("ref").path("target").path("history").path("edges");
265
      for (JsonNode node : commits) {
266
        DownloadedCommit newCommit =
267
            jacksonObjectMapper.treeToValue(node.get("node"), DownloadedCommit.class);
268 1 1. downloadCommitHistory : removed call to edu/ucsb/cs156/frontiers/entities/DownloadedCommit::setRequest → KILLED
        newCommit.setRequest(downloadRequest);
269
        downloadedCommits.add(newCommit);
270
      }
271
272 1 1. downloadCommitHistory : negated conditional → KILLED
    } while (hasNextPage);
273
    downloadedCommitRepository.saveAll(downloadedCommits);
274
  }
275
}

Mutations

83

1.1
Location : getDefaultBranchName
Killed by : edu.ucsb.cs156.frontiers.services.GithubGraphQLServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.GithubGraphQLServiceTests]/[method:testGetDefaultBranchName()]
replaced return value with "" for edu/ucsb/cs156/frontiers/services/GithubGraphQLService::getDefaultBranchName → KILLED

114

1.1
Location : getDefaultBasePermission
Killed by : edu.ucsb.cs156.frontiers.services.GithubGraphQLServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.GithubGraphQLServiceTests]/[method:testGetDefaultBasePermission_readPermission()]
replaced return value with "" for edu/ucsb/cs156/frontiers/services/GithubGraphQLService::getDefaultBasePermission → KILLED

131

1.1
Location : getCommits
Killed by : edu.ucsb.cs156.frontiers.services.GithubGraphQLServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.GithubGraphQLServiceTests]/[method:testGetCommits()]
replaced return value with "" for edu/ucsb/cs156/frontiers/services/GithubGraphQLService::getCommits → KILLED

225

1.1
Location : getCommits
Killed by : edu.ucsb.cs156.frontiers.services.GithubGraphQLServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.GithubGraphQLServiceTests]/[method:testGetCommits()]
replaced return value with "" for edu/ucsb/cs156/frontiers/services/GithubGraphQLService::getCommits → KILLED

268

1.1
Location : downloadCommitHistory
Killed by : edu.ucsb.cs156.frontiers.services.GithubGraphQLServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.GithubGraphQLServiceTests]/[method:handles_two_pages()]
removed call to edu/ucsb/cs156/frontiers/entities/DownloadedCommit::setRequest → KILLED

272

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

Active mutators

Tests examined


Report generated by PIT 1.17.0