DeleteRepoJob.java

1
package edu.ucsb.cs156.frontiers.jobs;
2
3
import com.fasterxml.jackson.databind.JsonNode;
4
import com.fasterxml.jackson.databind.ObjectMapper;
5
import edu.ucsb.cs156.frontiers.entities.Course;
6
import edu.ucsb.cs156.frontiers.services.JwtService;
7
import edu.ucsb.cs156.frontiers.services.jobs.JobContext;
8
import edu.ucsb.cs156.frontiers.services.jobs.JobContextConsumer;
9
import java.util.ArrayList;
10
import java.util.List;
11
import lombok.Builder;
12
import org.springframework.http.HttpEntity;
13
import org.springframework.http.HttpHeaders;
14
import org.springframework.http.HttpMethod;
15
import org.springframework.http.HttpStatus;
16
import org.springframework.http.ResponseEntity;
17
import org.springframework.web.client.HttpClientErrorException;
18
import org.springframework.web.client.RestTemplate;
19
20
@Builder
21
public class DeleteRepoJob implements JobContextConsumer {
22
23
  Course course;
24
  String prefix;
25
  JwtService jwtService;
26
  RestTemplate restTemplate;
27
  ObjectMapper mapper;
28
29
  @Override
30
  public Course getCourse() {
31 1 1. getCourse : replaced return value with null for edu/ucsb/cs156/frontiers/jobs/DeleteRepoJob::getCourse → KILLED
    return course;
32
  }
33
34
  @Override
35
  public void accept(JobContext ctx) throws Exception {
36
    String orgName = course.getOrgName();
37
    String token = jwtService.getInstallationToken(course);
38
39
    HttpHeaders headers = new HttpHeaders();
40 1 1. accept : removed call to org/springframework/http/HttpHeaders::add → KILLED
    headers.add("Authorization", "Bearer " + token);
41 1 1. accept : removed call to org/springframework/http/HttpHeaders::add → KILLED
    headers.add("Accept", "application/vnd.github+json");
42 1 1. accept : removed call to org/springframework/http/HttpHeaders::add → KILLED
    headers.add("X-GitHub-Api-Version", "2022-11-28");
43
    HttpEntity<String> entity = new HttpEntity<>(headers);
44
45
    List<String> matchedRepos = new ArrayList<>();
46
47
    // 1. Fetch all repositories for the organization (Handling Pagination)
48
    String reposUrl = "https://api.github.com/orgs/" + orgName + "/repos?per_page=100";
49
    boolean hasNext = true;
50
51 1 1. accept : negated conditional → KILLED
    while (hasNext) {
52
      ResponseEntity<String> response =
53
          restTemplate.exchange(reposUrl, HttpMethod.GET, entity, String.class);
54
      JsonNode reposNode = mapper.readTree(response.getBody());
55
56
      for (JsonNode repoNode : reposNode) {
57
        String repoName = repoNode.get("name").asText();
58 1 1. accept : negated conditional → KILLED
        if (repoName.startsWith(prefix)) {
59
          matchedRepos.add(repoName);
60
        }
61
      }
62
63
      // Check for pagination "Link" header to get the next page
64
      List<String> linkHeaders = response.getHeaders().get("Link");
65
      hasNext = false;
66 2 1. accept : negated conditional → KILLED
2. accept : negated conditional → KILLED
      if (linkHeaders != null && !linkHeaders.isEmpty()) {
67
        String linkHeader = linkHeaders.get(0);
68
        String[] parts = linkHeader.split(",");
69
        for (String part : parts) {
70 1 1. accept : negated conditional → KILLED
          if (part.contains("rel=\"next\"")) {
71 1 1. accept : Replaced integer addition with subtraction → KILLED
            reposUrl = part.substring(part.indexOf('<') + 1, part.indexOf('>'));
72
            hasNext = true;
73
            break;
74
          }
75
        }
76
      }
77
    }
78
79
    ctx.log(String.format("%d repos found with prefix %s", matchedRepos.size(), prefix));
80
81
    int reposDeleted = 0;
82
    int reposRetained = 0;
83
    int errors = 0;
84
85
    // 2. Iterate through matched repos to check for commits and delete
86
    for (String repoName : matchedRepos) {
87
      try {
88
        // Sleep delay to prevent hitting GitHub API rate limits
89 1 1. accept : removed call to java/lang/Thread::sleep → KILLED
        Thread.sleep(1000);
90
91
        String commitsUrl = "https://api.github.com/repos/" + orgName + "/" + repoName + "/commits";
92
        boolean hasCommits = true;
93
94
        try {
95
          ResponseEntity<String> commitsResponse =
96
              restTemplate.exchange(commitsUrl, HttpMethod.GET, entity, String.class);
97
          JsonNode commitsNode = mapper.readTree(commitsResponse.getBody());
98 2 1. accept : negated conditional → KILLED
2. accept : negated conditional → KILLED
          hasCommits = commitsNode.isArray() && !commitsNode.isEmpty();
99
        } catch (HttpClientErrorException e) {
100
          // GitHub API returns 409 Conflict if a repository is completely empty (no commits)
101 1 1. accept : negated conditional → KILLED
          if (e.getStatusCode().equals(HttpStatus.CONFLICT)) {
102
            hasCommits = false;
103
          } else {
104
            throw e; // Rethrow actual errors to be caught below
105
          }
106
        }
107
108 1 1. accept : negated conditional → KILLED
        if (hasCommits) {
109 1 1. accept : Changed increment from 1 to -1 → KILLED
          reposRetained++;
110
          ctx.log(String.format("Repo %s not delete; commits exist.", repoName));
111
        } else {
112
          String deleteUrl = "https://api.github.com/repos/" + orgName + "/" + repoName;
113
          restTemplate.exchange(deleteUrl, HttpMethod.DELETE, entity, String.class);
114 1 1. accept : Changed increment from 1 to -1 → KILLED
          reposDeleted++;
115
        }
116
      } catch (Exception e) {
117 1 1. accept : Changed increment from 1 to -1 → KILLED
        errors++;
118
        ctx.log(String.format("Error processing repo %s: %s", repoName, e.getMessage()));
119
      }
120
    }
121
122
    // 3. Final Logging
123
    ctx.log(String.format("%d repos deleted", reposDeleted));
124
    ctx.log(String.format("%d repos retained", reposRetained));
125
    ctx.log(String.format("%d errors", errors));
126
  }
127
}

Mutations

31

1.1
Location : getCourse
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_getCourse_returnsCourse()]
replaced return value with null for edu/ucsb/cs156/frontiers/jobs/DeleteRepoJob::getCourse → KILLED

40

1.1
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_delete_empty_repo_success()]
removed call to org/springframework/http/HttpHeaders::add → KILLED

41

1.1
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_delete_empty_repo_success()]
removed call to org/springframework/http/HttpHeaders::add → KILLED

42

1.1
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_delete_empty_repo_success()]
removed call to org/springframework/http/HttpHeaders::add → KILLED

51

1.1
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_no_repos_match_prefix()]
negated conditional → KILLED

58

1.1
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_no_repos_match_prefix()]
negated conditional → KILLED

66

1.1
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_multi_part_link_header_prev_before_next()]
negated conditional → KILLED

2.2
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_no_repos_match_prefix()]
negated conditional → KILLED

70

1.1
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_link_header_present_but_no_next_rel()]
negated conditional → KILLED

71

1.1
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_multi_part_link_header_prev_before_next()]
Replaced integer addition with subtraction → KILLED

89

1.1
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_retains_repo_with_commits()]
removed call to java/lang/Thread::sleep → KILLED

98

1.1
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_commits_node_is_not_an_array()]
negated conditional → KILLED

2.2
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_delete_repo_with_empty_commits_array_200()]
negated conditional → KILLED

101

1.1
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_delete_empty_repo_success()]
negated conditional → KILLED

108

1.1
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_commits_node_is_not_an_array()]
negated conditional → KILLED

109

1.1
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_retains_repo_with_commits()]
Changed increment from 1 to -1 → KILLED

114

1.1
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_delete_empty_repo_success()]
Changed increment from 1 to -1 → KILLED

117

1.1
Location : accept
Killed by : edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.jobs.DeleteRepoJobTest]/[method:test_pagination_and_error_handling()]
Changed increment from 1 to -1 → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0