diff --git a/github/src/main/java/io/fundrequest/platform/github/scraper/GithubSolverResolver.java b/github/src/main/java/io/fundrequest/platform/github/scraper/GithubSolverResolver.java index 6d216701e..f85346816 100644 --- a/github/src/main/java/io/fundrequest/platform/github/scraper/GithubSolverResolver.java +++ b/github/src/main/java/io/fundrequest/platform/github/scraper/GithubSolverResolver.java @@ -18,6 +18,7 @@ public class GithubSolverResolver { private static final List CLOSING_KEYWORDS = Arrays.asList("close", "closes", "closed", "fix", "fixes", "fixed", "resolve", "resolves", "resolved"); + private static final String CLOSING_KEYWORD_ISSUE_MATCHER_REGEX = "(?:)?\\b%1$s\\b(?:)?:?\\s*(?:%2$s/%3$s)?#%4$s"; private final GithubGateway githubGateway; @@ -31,9 +32,9 @@ public Optional resolve(final Document document, final GithubId issueGit .filter(this::isPullRequest) .filter(this::isMerged) .map(this::resolvePullRequestGithubId) - .map(pullRequestGithubId -> fetchAuthorFromPullRequest(pullRequestGithubId, issueGithubId)) - .filter(Optional::isPresent) - .map(Optional::get) + .map(this::fetchPullrequest) + .filter(pullRequest -> pullRequest != null && pullRequestFixesIssue(pullRequest, issueGithubId)) + .map(pullRequest -> pullRequest.getUser().getLogin()) .filter(StringUtils::isNotEmpty) .findFirst(); } @@ -56,21 +57,21 @@ private GithubId getPullRequestGithubIdFromInlineDiscussionItem(final Element di .orElseThrow(() -> new RuntimeException("No pullrequest identifier is found")); } - private Optional fetchAuthorFromPullRequest(final GithubId pullRequestGithubId, final GithubId issueGithubId) { - final GithubResult pullRequest = githubGateway.getPullrequest(pullRequestGithubId.getOwner(), pullRequestGithubId.getRepo(), pullRequestGithubId.getNumber()); - if (pullRequest != null && pullRequestFixesIssue(pullRequest, issueGithubId)) { - return Optional.of(pullRequest.getUser().getLogin()); - } - return Optional.empty(); + private GithubResult fetchPullrequest(GithubId pullRequestGithubId) { + return githubGateway.getPullrequest(pullRequestGithubId.getOwner(), pullRequestGithubId.getRepo(), pullRequestGithubId.getNumber()); } private boolean pullRequestFixesIssue(final GithubResult pullRequest, final GithubId issueGithubId) { - final String pullRequestBody = pullRequest.getBody(); - + final String pullRequestBody = pullRequest.getBodyHtml(); return pullRequestBody != null && CLOSING_KEYWORDS.stream() - .anyMatch(keyword -> Pattern.compile("\\b" + keyword.toLowerCase() + "\\b:?\\s*#" + issueGithubId.getNumber()) - .matcher(pullRequestBody.toLowerCase()) - .find()); + .map(keyword -> String.format(CLOSING_KEYWORD_ISSUE_MATCHER_REGEX, + keyword.toLowerCase(), + issueGithubId.getOwner(), + issueGithubId.getRepo(), + issueGithubId.getNumber())) + .anyMatch(regex -> Pattern.compile(regex) + .matcher(pullRequestBody.toLowerCase()) + .find()); } private boolean isPullRequestInSingleDiscussionItem(final Element discussionItem) { diff --git a/github/src/test/java/io/fundrequest/platform/github/scraper/GithubScraperIntegrationTest.java b/github/src/test/java/io/fundrequest/platform/github/scraper/GithubScraperIntegrationTest.java index ef4cf5aec..9d3eeb0e0 100644 --- a/github/src/test/java/io/fundrequest/platform/github/scraper/GithubScraperIntegrationTest.java +++ b/github/src/test/java/io/fundrequest/platform/github/scraper/GithubScraperIntegrationTest.java @@ -108,4 +108,17 @@ public void fetch_githubClosingKeywordsAreFound() { assertThat(githubIssue.getSolver()).isEqualTo("katibest"); assertThat(githubIssue.getStatus()).isEqualTo("Closed"); } + + @Test + public void fetch_issueFixedByPullRequestFromOtherRepo() { + final String owner = "brave"; + final String repo = "brave-browser"; + final String number = "240"; + + final GithubIssue githubIssue = scraper.fetchGithubIssue(owner, repo, number); + + assertThat(githubIssue.getNumber()).isEqualTo("240"); + assertThat(githubIssue.getSolver()).isEqualTo("cezaraugusto"); + assertThat(githubIssue.getStatus()).isEqualTo("Closed"); + } } diff --git a/github/src/test/java/io/fundrequest/platform/github/scraper/GithubSolverResolverTest.java b/github/src/test/java/io/fundrequest/platform/github/scraper/GithubSolverResolverTest.java index c595d7322..f2bd757d4 100644 --- a/github/src/test/java/io/fundrequest/platform/github/scraper/GithubSolverResolverTest.java +++ b/github/src/test/java/io/fundrequest/platform/github/scraper/GithubSolverResolverTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.CsvSource; import java.util.Optional; @@ -50,14 +50,54 @@ void parse() { .withPullrequestReference(pullrequestGithubId, false) .build()) .build(); + final String solvingBodyHtml = buildBodyHtmlFor("Fixes", " ", issueGithubId.getNumber()); when(githubGateway.getPullrequest("xnbf", "afds", "53")).thenReturn(GithubResult.builder() .user(GithubUser.builder().login("hgfcjgv").build()) - .body("fixes #" + issueGithubId.getNumber()) + .bodyHtml(buildBodyHtmlFor("fixes", " ", issueGithubId.getNumber())) .build()); when(githubGateway.getPullrequest(pullrequestGithubId.getOwner(), pullrequestGithubId.getRepo(), pullrequestGithubId.getNumber())).thenReturn(GithubResult.builder() .user(solverUser) - .body("Fixes #" + issueGithubId.getNumber()) + .bodyHtml(solvingBodyHtml) + .build()); + + final Optional result = parser.resolve(doc, issueGithubId); + + assertThat(result).contains(solver); + } + + @Test + void parse_pullRequestInOtherRepo() { + final String solver = "dfgh"; + final GithubUser solverUser = GithubUser.builder().login(solver).build(); + final GithubId issueGithubId = GithubId.builder().owner("tfjgk").repo("hfcjgv").number("435").build(); + final GithubId pullrequestGithubId = GithubId.builder().owner("gb").repo("awerg").number("765").build(); + final Document doc = DocumentMockBuilder.documentBuilder() + .addDiscussionItem(DocumentMockBuilder.discussionItemBuilder() + .isPullRequest(false) + .build()) + .addDiscussionItem(DocumentMockBuilder.discussionItemBuilder() + .isPullRequest(true) + .isMerged(false) + .withAuthor("hgfcjgv") + .withPullrequestReference(GithubId.builder().owner("xnbf").repo("afds").number("53").build(), false) + .build()) + .addDiscussionItem(DocumentMockBuilder.discussionItemBuilder() + .isPullRequest(true) + .isMerged(true) + .withAuthor("gdhfh") + .withPullrequestReference(pullrequestGithubId, false) + .build()) + .build(); + final String solvingBodyHtml = buildBodyHtmlFor("Fixes ", issueGithubId.getOwner(), issueGithubId.getRepo(), issueGithubId.getNumber()); + + when(githubGateway.getPullrequest("xnbf", "afds", "53")).thenReturn(GithubResult.builder() + .user(GithubUser.builder().login("hgfcjgv").build()) + .bodyHtml(buildBodyHtmlFor("fixes ", issueGithubId.getOwner(), issueGithubId.getRepo(), issueGithubId.getNumber())) + .build()); + when(githubGateway.getPullrequest(pullrequestGithubId.getOwner(), pullrequestGithubId.getRepo(), pullrequestGithubId.getNumber())).thenReturn(GithubResult.builder() + .user(solverUser) + .bodyHtml(solvingBodyHtml) .build()); final Optional result = parser.resolve(doc, issueGithubId); @@ -88,13 +128,15 @@ void parse_pullRequestMerged_noSolverOnPage() { .withPullrequestReference(pullrequestGithubId, false) .build()) .build(); + final String solvingBodyHtml = buildBodyHtmlFor("fixes", " ", issueGithubId.getNumber()); + when(githubGateway.getPullrequest("xnbf", "afds", "53")).thenReturn(GithubResult.builder() .user(GithubUser.builder().login("hgfcjgv").build()) - .body("fixes #" + issueGithubId.getNumber()) + .bodyHtml(solvingBodyHtml) .build()); when(githubGateway.getPullrequest(pullrequestGithubId.getOwner(), pullrequestGithubId.getRepo(), pullrequestGithubId.getNumber())).thenReturn(GithubResult.builder() .user(solverUser) - .body("fixes #" + issueGithubId.getNumber()) + .bodyHtml(solvingBodyHtml) .build()); final Optional result = parser.resolve(doc, issueGithubId); @@ -167,9 +209,11 @@ void parse_noSolver() { .withPullrequestReference(pullrequestGithubId, true) .build()) .build(); + final String bodyHtml = buildBodyHtmlFor("fixes", " ", issueGithubId.getNumber()); + when(githubGateway.getPullrequest(pullrequestGithubId.getOwner(), pullrequestGithubId.getRepo(), pullrequestGithubId.getNumber())).thenReturn(GithubResult.builder() .user(GithubUser.builder().login("").build()) - .body("fixes #" + issueGithubId.getNumber()) + .bodyHtml(bodyHtml) .build()); final Optional result = parser.resolve(doc, issueGithubId); @@ -178,10 +222,10 @@ void parse_noSolver() { } @ParameterizedTest - @ValueSource(strings = {"close ", "closes ", "closed ", "fix ", "fixes ", "fixed ", "resolve ", "resolves ", "resolved ", "close: ", "closes: ", "closed: ", "fix: ", "fixes: ", "fixed: ", "resolve: ", - "resolves: ", "resolved: ", "close:", "closes:", "closed:", "fix:", "fixes:", "fixed:", "resolve:", - "resolves:", "resolved:", "Close ", "Closes ", "Closed ", "Fix ", "Fixes ", "Fixed ", "Resolve ", "Resolves ", "Resolved", "close: ", "closes: "}) - void parse_withClosingKeywords(final String keyword) { + @CsvSource(value = {"close,' '", "closes,' '", "closed,' '", "fix,' '", "fixes,' '", "fixed,' '", "resolve,' '", "resolves,' '", "resolved,' '", "close,': '", "closes,': '", "closed,': '", "fix,': '", + "fixes,': '", "fixed,': '", "resolve,': '", "resolves,': '", "resolved,': '", "close,':'", "closes,':'", "closed,':'", "fix,':'", "fixes,':'", "fixed,':'", "resolve,':'", "resolves,':'", + "resolved,':'", "Close,' '", "Closes,' '", "Closed,' '", "Fix,' '", "Fixes,' '", "Fixed,' '", "Resolve,' '", "Resolves,' '", "Resolved,''", "close,': '", "closes,': '"}) + void parse_withClosingKeywords(final String keyword, final String separator) { final String solver = "dfgh"; final GithubUser solverUser = GithubUser.builder().login(solver).build(); final GithubId issueGithubId = GithubId.builder().owner("tfjgk").repo("hfcjgv").number("435").build(); @@ -203,16 +247,15 @@ void parse_withClosingKeywords(final String keyword) { .withPullrequestReference(pullrequestGithubId, false) .build()) .build(); + final String solvingBodyHtml = buildBodyHtmlFor(keyword, separator, issueGithubId.getNumber()); when(githubGateway.getPullrequest("xnbf", "afds", "53")).thenReturn(GithubResult.builder() .user(GithubUser.builder().login("hgfcjgv").build()) - .body("fixes #" + issueGithubId.getNumber()) + .bodyHtml(buildBodyHtmlFor("fixes", " ", issueGithubId.getNumber())) .build()); when(githubGateway.getPullrequest(pullrequestGithubId.getOwner(), pullrequestGithubId.getRepo(), pullrequestGithubId.getNumber())).thenReturn(GithubResult.builder() .user(solverUser) - .body(String.format("%s#%s", - keyword, - issueGithubId.getNumber())) + .bodyHtml(solvingBodyHtml) .build()); final Optional result = parser.resolve(doc, issueGithubId); @@ -246,11 +289,11 @@ void parse_noClosingKeyword() { when(githubGateway.getPullrequest("xnbf", "afds", "53")).thenReturn(GithubResult.builder() .user(GithubUser.builder().login("hgfcjgv").build()) - .body("fixes #" + issueGithubId.getNumber()) + .bodyHtml(buildBodyHtmlFor("fixes", " ", issueGithubId.getNumber())) .build()); when(githubGateway.getPullrequest(pullrequestGithubId.getOwner(), pullrequestGithubId.getRepo(), pullrequestGithubId.getNumber())).thenReturn(GithubResult.builder() .user(solverUser) - .body("") + .bodyHtml("") .build()); final Optional result = parser.resolve(doc, issueGithubId); @@ -284,15 +327,23 @@ void parse_noClosingKeywordReferenceToIssue() { when(githubGateway.getPullrequest("xnbf", "afds", "53")).thenReturn(GithubResult.builder() .user(GithubUser.builder().login("hgfcjgv").build()) - .body("fixes #" + issueGithubId.getNumber()) + .bodyHtml(buildBodyHtmlFor("fixes", " ", issueGithubId.getNumber())) .build()); when(githubGateway.getPullrequest(pullrequestGithubId.getOwner(), pullrequestGithubId.getRepo(), pullrequestGithubId.getNumber())).thenReturn(GithubResult.builder() .user(solverUser) - .body("#" + issueGithubId.getNumber()) + .bodyHtml("#" + issueGithubId.getNumber()) .build()); final Optional result = parser.resolve(doc, issueGithubId); assertThat(result).isEmpty(); } + + private String buildBodyHtmlFor(final String keyword, final String separator, final String number) { + return String.format("%s%s#%s", keyword, separator, number); + } + + private String buildBodyHtmlFor(final String keyword, String owner, String repo, String number) { + return String.format("%s%s/%s#%s", keyword, owner, repo, number); + } }