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 getCommits(
96
      Course course, String owner, String repo, String branch, int first, String after)
97
      throws JsonProcessingException,
98
          NoSuchAlgorithmException,
99
          InvalidKeySpecException,
100
          NoLinkedOrganizationException {
101 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);
102
  }
103
104
  /**
105
   * Retrieves the commit history for a specified branch of a GitHub repository within a given time
106
   * range.
107
   *
108
   * @param course The course entity, used to fetch the associated GitHub installation token.
109
   * @param owner The owner of the GitHub repository.
110
   * @param repo The name of the GitHub repository.
111
   * @param branch The branch of the repository for which the commit history is retrieved.
112
   * @param since The start time for fetching commits (inclusive). Optional. Can be null.
113
   * @param until The end time for fetching commits (exclusive). Optional. Can be null.
114
   * @param size The maximum number of commits to retrieve in one request.
115
   * @param cursor The pagination cursor pointing to the start of the commit history to fetch.
116
   *     Optional. Can be null.
117
   * @return A JSON string representing the commit history and associated metadata.
118
   * @throws NoLinkedOrganizationException If no linked organization exists for the specified
119
   *     course.
120
   */
121
  public String getCommits(
122
      Course course,
123
      String owner,
124
      String repo,
125
      String branch,
126
      Instant since,
127
      Instant until,
128
      int size,
129
      String cursor)
130
      throws JsonProcessingException,
131
          NoSuchAlgorithmException,
132
          InvalidKeySpecException,
133
          NoLinkedOrganizationException {
134
    String githubToken = jwtService.getInstallationToken(course);
135
    // language=GraphQL
136
    String query =
137
        """
138
            query GetBranchCommits($owner: String!, $repo: String!, $branch: String!, $first: Int!, $after: String, $since: GitTimestamp, $until: GitTimestamp) {
139
              repository(owner: $owner, name: $repo) {
140
                ref(qualifiedName: $branch) {
141
                  target {
142
                    ... on Commit {
143
                      history(first: $first, after: $after, since: $since, until: $until) {
144
                        pageInfo {
145
                          hasNextPage
146
                          endCursor
147
                        }
148
                        edges {
149
                          node {
150
                            oid
151
                            url
152
                            messageHeadline
153
                            committedDate
154
                            author {
155
                              name
156
                              email
157
                              user {
158
                                login
159
                              }
160
                            }
161
                            committer {
162
                              name
163
                              email
164
                              user {
165
                                login
166
                              }
167
                            }
168
                          }
169
                        }
170
                      }
171
                    }
172
                  }
173
                }
174
              }
175
            }
176
            """;
177
178
    GraphQlResponse response =
179
        graphQlClient
180
            .mutate()
181
            .header("Authorization", "Bearer " + githubToken)
182
            .build()
183
            .document(query)
184
            .variable("owner", owner)
185
            .variable("repo", repo)
186
            .variable("branch", branch)
187
            .variable("first", size)
188
            .variable("after", cursor)
189
            .variable("since", since)
190
            .variable("until", until)
191
            .executeSync();
192
193
    Map<String, Object> data = response.getData();
194
    String jsonData = jacksonObjectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(data);
195 1 1. getCommits : replaced return value with "" for edu/ucsb/cs156/frontiers/services/GithubGraphQLService::getCommits → KILLED
    return jsonData;
196
  }
197
198
  public void downloadCommitHistory(DownloadRequest downloadRequest)
199
      throws NoSuchAlgorithmException, InvalidKeySpecException, JsonProcessingException {
200
    String pointer = null;
201
    boolean hasNextPage;
202
    List<DownloadedCommit> downloadedCommits = new ArrayList<>(4000);
203
    do {
204
      JsonNode currentPage =
205
          jacksonObjectMapper.readTree(
206
              getCommits(
207
                  downloadRequest.getCourse(),
208
                  downloadRequest.getOrg(),
209
                  downloadRequest.getRepo(),
210
                  downloadRequest.getBranch(),
211
                  downloadRequest.getStartDate(),
212
                  downloadRequest.getEndDate(),
213
                  100,
214
                  pointer));
215
      pointer =
216
          currentPage
217
              .path("repository")
218
              .path("ref")
219
              .path("target")
220
              .path("history")
221
              .path("pageInfo")
222
              .path("endCursor")
223
              .asText();
224
      hasNextPage =
225
          currentPage
226
              .path("repository")
227
              .path("ref")
228
              .path("target")
229
              .path("history")
230
              .path("pageInfo")
231
              .path("hasNextPage")
232
              .asBoolean();
233
      JsonNode commits =
234
          currentPage.path("repository").path("ref").path("target").path("history").path("edges");
235
      for (JsonNode node : commits) {
236
        DownloadedCommit newCommit =
237
            jacksonObjectMapper.treeToValue(node.get("node"), DownloadedCommit.class);
238 1 1. downloadCommitHistory : removed call to edu/ucsb/cs156/frontiers/entities/DownloadedCommit::setRequest → KILLED
        newCommit.setRequest(downloadRequest);
239
        downloadedCommits.add(newCommit);
240
      }
241
242 1 1. downloadCommitHistory : negated conditional → KILLED
    } while (hasNextPage);
243
    downloadedCommitRepository.saveAll(downloadedCommits);
244
  }
245
}

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

101

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

195

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

238

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

242

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