diff --git a/src/main/java/us/muit/fs/a4i/control/strategies/GetDataPullRequest.java b/src/main/java/us/muit/fs/a4i/control/strategies/GetDataPullRequest.java new file mode 100644 index 00000000..297af708 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/control/strategies/GetDataPullRequest.java @@ -0,0 +1,45 @@ +package us.muit.fs.a4i.control.strategies; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +import org.json.JSONArray; + +public class GetDataPullRequest { + + private final HttpClient httpClient; + + public GetDataPullRequest() { + this.httpClient = HttpClient.newHttpClient(); + } + + public int getTotalPullRequests(String owner, String repo) throws IOException, InterruptedException { + JSONArray prArray = fetchPullRequests(owner, repo, "all"); + return prArray.length(); + } + + public int getClosedPullRequests(String owner, String repo) throws IOException, InterruptedException { + JSONArray prArray = fetchPullRequests(owner, repo, "closed"); + return prArray.length(); + } + + private JSONArray fetchPullRequests(String owner, String repo, String state) throws IOException, InterruptedException { + String url = String.format("https://api.github.com/repos/%s/%s/pulls?state=%s&per_page=100", owner, repo, state); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Accept", "application/vnd.github.v3+json") + .build(); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() != 200) { + throw new IOException("GitHub API error: " + response.statusCode()); + } + + return new JSONArray(response.body()); + } +} diff --git a/src/main/java/us/muit/fs/a4i/control/strategies/IndicatorPullRequest.java b/src/main/java/us/muit/fs/a4i/control/strategies/IndicatorPullRequest.java new file mode 100644 index 00000000..1d70f815 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/control/strategies/IndicatorPullRequest.java @@ -0,0 +1,66 @@ +import java.util.List; +import java.util.Optional; +import java.util.Arrays; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.IndicatorI.IndicatorState; +import us.muit.fs.a4i.model.entities.ReportItem; + +public class PullRequestIndicatorStrategy implements IndicatorStrategy { //this defines the indicator as a double + + private static final Logger log = Logger.getLogger(PullRequestIndicatorStrategy.class.getName()); //to show error messages during the execution + private static final List REQUIRED_METRICS = Arrays.asList("totalPullReq", "closedPullReq"); //this defines the mandatory metrics to evaluate the inidcator, so the total and closed pull requests + + @Override + public ReportItem calcIndicator(List> metrics) throws NotAvailableMetricException { //method that evaluate the actual indicator, it obtains as an input the list of pullrequests + + ReportItem indicatorReport = null; + IndicatorState estado = IndicatorState.UNDEFINED; + + // Filter necessary metrics + Optional> totalPullReq = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(0).equals(m.getName())) + .findAny(); + + Optional> closedPullReq = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(1).equals(m.getName())) + .findAny(); + + //here search for the mandatory metrics, and saves them in an Optional + + // If both metrics are present it evaluates the inidcaotr, if one of them is missing goes to the else and gives an error + if (totalPullReq.isPresent() && closedPullReq.isPresent()) { + // Calculate the indicator + Double pullRequestIndicator = 0.0; + if (totalPullReq.get().getValue() > 0) { //this is done to remove the possibility of dividign by zero + pullRequestIndicator = 100 * closedPullReq.get().getValue() / totalPullReq.get().getValue(); + } + + // Determine indicator state + if (pullRequestIndicator > 75) { + estado = IndicatorState.Correct; + } else if (pullRequestIndicator > 50) { + estado = IndicatorState.Precaution; + } else { + estado = IndicatorState.Critical; + } + + // this create an object report that conatins the result of the class, so it has a name: PullRequestIndicator, the evaluated number, the metrics used for the calculation, the state + try { + indicatorReport = new ReportItem.ReportItemBuilder("pullRequestIndicator", pullRequestIndicator) + .metrics(Arrays.asList(totalPullReq.get(), closedPullReq.get())) + .indicator(estado) + .build(); + } else { + log.info("Some required metrics are missing"); + throw new NotAvailableMetricException(REQUIRED_METRICS.toString()); + } + + return indicatorReport; //return the indicator + } + + @Override + public List requiredMetrics() { + return REQUIRED_METRICS; //retrurn the metrics used to evaluate the indicator + } +} diff --git a/src/test/java/us/muit/fs/a4i/test/control/strategies/GetDataPullRequestTest.java b/src/test/java/us/muit/fs/a4i/test/control/strategies/GetDataPullRequestTest.java new file mode 100644 index 00000000..c73bd14a --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/control/strategies/GetDataPullRequestTest.java @@ -0,0 +1,25 @@ +import static org.junit.jupiter.api.Assertions.*; //JUnit methods +import org.junit.jupiter.api.Test; //to indicate that is a test +import java.util.List; + +public class GetDataPullRequestTest { + + @Test + public void testGetPullRequests_basic() throws Exception { + GetDataPullRequest enquirer = new GetDataPullRequest("owner", "repo", "token"); //to get information about the repository + + List pullRequests = enquirer.getPullRequests(); //this calls the method to get the list of PUllRequest + + assertNotNull(pullRequests, "the list cannot be null"); //control that the list is not null unless it gives the error message + + // assertFalse(pullRequests.isEmpty(), "the list cannot be empty"); //this is to control that the list is not empty, only if we always want that at least one pull request is done + + for (PullRequest pr : pullRequests) { + assertNotNull(pr.getState(), "every PullRequest must have a state"); + assertTrue(pr.getState().equals("open") || pr.getState().equals("closed"), + "Lo stato deve essere 'open' o 'closed'"); + //this controls the parameter of the pull request, so it control that the object are passed correctly from JSON to java, the pull request must have a state and the state has to be or open or closed + //the indicator then evalyate the percentage of closed pull request on the total number + } + } +} diff --git a/src/test/java/us/muit/fs/a4i/test/control/strategies/IndicatorPullRequestTest.java b/src/test/java/us/muit/fs/a4i/test/control/strategies/IndicatorPullRequestTest.java new file mode 100644 index 00000000..752953d1 --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/control/strategies/IndicatorPullRequestTest.java @@ -0,0 +1,64 @@ +package us.muit.fs.a4i.test.control.strategies; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class IndicatorPullRequestTest { + + IndicatorPullRequest indicator = new IndicatorPullRequest(); + + @Test + public void testEfficiencyCalculationSimple() { + double efficiency = indicator.calculateEfficiency(75, 100); + assertEquals(75.0, efficiency); + } + + @Test + public void testQualityLevelCorrecto() { + String level = indicator.evaluateQualityLevel(80.0); + assertEquals("Correcto", level); + } + + @Test + public void testQualityLevelPrecaucion() { + String level = indicator.evaluateQualityLevel(60.0); + assertEquals("Precaución", level); + } + + @Test + public void testQualityLevelCritico() { + String level = indicator.evaluateQualityLevel(40.0); + assertEquals("Crítico", level); + } + + @Test + public void testZeroTotalPRs() { + double efficiency = indicator.calculateEfficiency(0, 0); + assertEquals(0.0, efficiency); + } + + @Test + public void testMoreClosedThanTotal() { + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + indicator.calculateEfficiency(110, 100); + }); + assertEquals("Closed PRs cannot exceed total PRs", exception.getMessage()); + } + + @Test + public void testNegativeInputs() { + assertThrows(IllegalArgumentException.class, () -> { + indicator.calculateEfficiency(-1, 100); + }); + assertThrows(IllegalArgumentException.class, () -> { + indicator.calculateEfficiency(10, -100); + }); + } + + @Test + public void testPerfectScore() { + double efficiency = indicator.calculateEfficiency(10, 10); + assertEquals(100.0, efficiency); + assertEquals("Correcto", indicator.evaluateQualityLevel(efficiency)); + } +}