chore: Add GitHub get pull request API method (#341)

Add GitHub getPullRequest API method
Use the API method insted of reading html content of a pull-request page when creating a factory from GitHub url.
pull/337/head
Igor Vinokur 2022-08-15 15:17:38 +03:00 committed by GitHub
parent bd9cd9db44
commit 97b7431bda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 281 additions and 226 deletions

View File

@ -11,7 +11,7 @@
*/
package org.eclipse.che.api.factory.server.bitbucket;
import static org.eclipse.che.api.factory.server.DevfileToApiExceptionMapper.toApiException;
import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException;
import java.io.IOException;
import javax.inject.Inject;

View File

@ -11,7 +11,7 @@
*/
package org.eclipse.che.api.factory.server.bitbucket;
import static org.eclipse.che.api.factory.server.DevfileToApiExceptionMapper.toApiException;
import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException;
import jakarta.validation.constraints.NotNull;
import java.io.IOException;

View File

@ -116,6 +116,25 @@ public class GithubApiClient {
});
}
public GithubPullRequest getPullRequest(
String id, String username, String repoName, String authenticationToken)
throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException {
final URI uri =
apiServerUrl.resolve(String.format("/repos/%1s/%2s/pulls/%3s", username, repoName, id));
HttpRequest request = buildGithubApiRequest(uri, authenticationToken);
LOG.trace("executeRequest={}", request);
return executeRequest(
httpClient,
request,
response -> {
try {
return OBJECT_MAPPER.readValue(response.body(), GithubPullRequest.class);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
/**
* Returns the scopes of the OAuth token.
*

View File

@ -99,11 +99,17 @@ public class GithubFactoryParametersResolver extends DefaultFactoryParameterReso
@Override
public FactoryMetaDto createFactory(@NotNull final Map<String, String> factoryParameters)
throws ApiException {
// no need to check null value of url parameter as accept() method has performed the check
final GithubUrl githubUrl = githubUrlParser.parse(factoryParameters.get(URL_PARAMETER_NAME));
boolean skipAuthentication =
factoryParameters.get(ERROR_QUERY_NAME) != null
&& factoryParameters.get(ERROR_QUERY_NAME).equals("access_denied");
// no need to check null value of url parameter as accept() method has performed the check
final GithubUrl githubUrl;
if (skipAuthentication) {
githubUrl =
githubUrlParser.parseWithoutAuthentication(factoryParameters.get(URL_PARAMETER_NAME));
} else {
githubUrl = githubUrlParser.parse(factoryParameters.get(URL_PARAMETER_NAME));
}
// create factory from the following location if location exists, else create default factory
return urlFactoryBuilder

View File

@ -0,0 +1,80 @@
/*
* Copyright (c) 2012-2022 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.api.factory.server.github;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
class GithubRepo {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
class GithubHead {
private String ref;
private GithubUser user;
private GithubRepo repo;
public String getRef() {
return ref;
}
public void setRef(String ref) {
this.ref = ref;
}
public GithubUser getUser() {
return user;
}
public void setUser(GithubUser user) {
this.user = user;
}
public GithubRepo getRepo() {
return repo;
}
public void setRepo(GithubRepo repo) {
this.repo = repo;
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
public class GithubPullRequest {
private String state;
private GithubHead head;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public GithubHead getHead() {
return head;
}
public void setHead(GithubHead head) {
this.head = head;
}
}

View File

@ -11,7 +11,7 @@
*/
package org.eclipse.che.api.factory.server.github;
import static org.eclipse.che.api.factory.server.DevfileToApiExceptionMapper.toApiException;
import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException;
import jakarta.validation.constraints.NotNull;
import java.io.IOException;

View File

@ -11,13 +11,29 @@
*/
package org.eclipse.che.api.factory.server.github;
import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException;
import jakarta.validation.constraints.NotNull;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.factory.server.scm.PersonalAccessToken;
import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager;
import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException;
import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException;
import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException;
import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException;
import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException;
import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException;
import org.eclipse.che.api.factory.server.scm.exception.UnsatisfiedScmPreconditionException;
import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider;
import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Parser of String Github URLs and provide {@link GithubUrl} objects.
@ -27,15 +43,19 @@ import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
@Singleton
public class GithubURLParser {
/** Fetcher to grab PR data */
private final URLFetcher urlFetcher;
private static final Logger LOG = LoggerFactory.getLogger(GithubURLParser.class);
private final PersonalAccessTokenManager tokenManager;
private final DevfileFilenamesProvider devfileFilenamesProvider;
private final GithubApiClient apiClient;
@Inject
public GithubURLParser(URLFetcher urlFetcher, DevfileFilenamesProvider devfileFilenamesProvider) {
this.urlFetcher = urlFetcher;
public GithubURLParser(
PersonalAccessTokenManager tokenManager,
DevfileFilenamesProvider devfileFilenamesProvider,
GithubApiClient apiClient) {
this.tokenManager = tokenManager;
this.devfileFilenamesProvider = devfileFilenamesProvider;
this.apiClient = apiClient;
}
/**
@ -46,17 +66,19 @@ public class GithubURLParser {
Pattern.compile(
"^(?:http)(?:s)?(?:\\:\\/\\/)github.com/(?<repoUser>[^/]++)/(?<repoName>[^/]++)((/)|(?:/tree/(?<branchName>[^/]++)(?:/(?<subFolder>.*))?)|(/pull/(?<pullRequestId>[^/]++)))?$");
/** Regexp to find repository and branch name from PR link */
protected static final Pattern PR_DATA_PATTERN =
Pattern.compile(
".*class=\"State[\\s]State--(?<prState>closed|open|merged).*<span title=\"(?<prRepoUser>[^\\\\/]+)\\/(?<prRepoName>[^\\:]+):(?<prBranch>[^\\\"]+).*",
Pattern.DOTALL);
public boolean isValid(@NotNull String url) {
return GITHUB_PATTERN.matcher(url).matches();
}
public GithubUrl parse(String url) {
public GithubUrl parseWithoutAuthentication(String url) throws ApiException {
return parse(url, false);
}
public GithubUrl parse(String url) throws ApiException {
return parse(url, true);
}
private GithubUrl parse(String url, boolean authenticationRequired) throws ApiException {
// Apply github url to the regexp
Matcher matcher = GITHUB_PATTERN.matcher(url);
if (!matcher.matches()) {
@ -75,25 +97,42 @@ public class GithubURLParser {
String pullRequestId = matcher.group("pullRequestId");
if (pullRequestId != null) {
// there is a Pull Request ID, analyze content to extract repository and branch to use
String prData = this.urlFetcher.fetchSafely(url);
Matcher prMatcher = PR_DATA_PATTERN.matcher(prData);
if (prMatcher.matches()) {
String prState = prMatcher.group("prState");
if (!"open".equalsIgnoreCase(prState)) {
throw new IllegalArgumentException(
String.format(
"The given Pull Request url %s is not Opened, (found %s), thus it can't be opened as branch may have been removed.",
url, prState));
try {
String githubEndpoint = "https://github.com";
Subject subject = EnvironmentContext.getCurrent().getSubject();
PersonalAccessToken personalAccessToken = null;
Optional<PersonalAccessToken> tokenOptional = tokenManager.get(subject, githubEndpoint);
if (tokenOptional.isPresent()) {
personalAccessToken = tokenOptional.get();
} else if (authenticationRequired) {
personalAccessToken = tokenManager.fetchAndSave(subject, githubEndpoint);
}
repoUser = prMatcher.group("prRepoUser");
repoName = prMatcher.group("prRepoName");
branchName = prMatcher.group("prBranch");
} else {
throw new IllegalArgumentException(
String.format(
"The given Pull Request github url %s is not a valid Pull Request URL github url. Unable to extract the data",
url));
if (personalAccessToken != null) {
GithubPullRequest pullRequest =
this.apiClient.getPullRequest(
pullRequestId, repoUser, repoName, personalAccessToken.getToken());
String prState = pullRequest.getState();
if (!"open".equalsIgnoreCase(prState)) {
throw new IllegalArgumentException(
String.format(
"The given Pull Request url %s is not Opened, (found %s), thus it can't be opened as branch may have been removed.",
url, prState));
}
GithubHead pullRequestHead = pullRequest.getHead();
repoUser = pullRequestHead.getUser().getLogin();
repoName = pullRequestHead.getRepo().getName();
branchName = pullRequestHead.getRef();
}
} catch (ScmUnauthorizedException e) {
throw toApiException(e);
} catch (ScmCommunicationException
| UnsatisfiedScmPreconditionException
| ScmConfigurationPersistenceException e) {
LOG.error("Failed to authenticate to GitHub", e);
} catch (ScmItemNotFoundException | ScmBadRequestException e) {
LOG.error("Failed retrieve GitHub Pull Request", e);
} catch (UnknownScmProviderException e) {
LOG.warn(e.getMessage());
}
}

View File

@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -100,7 +101,9 @@ public class GithubFactoryParametersResolverTest {
@BeforeMethod
protected void init() {
githubUrlParser = new GithubURLParser(urlFetcher, devfileFilenamesProvider);
githubUrlParser =
new GithubURLParser(
personalAccessTokenManager, devfileFilenamesProvider, mock(GithubApiClient.class));
assertNotNull(this.githubUrlParser);
githubFactoryParametersResolver =
new GithubFactoryParametersResolver(

View File

@ -13,6 +13,7 @@ package org.eclipse.che.api.factory.server.github;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.*;
@ -43,7 +44,9 @@ public class GithubScmFileResolverTest {
@BeforeMethod
protected void init() {
githubURLParser = new GithubURLParser(urlFetcher, devfileFilenamesProvider);
githubURLParser =
new GithubURLParser(
personalAccessTokenManager, devfileFilenamesProvider, mock(GithubApiClient.class));
assertNotNull(this.githubURLParser);
githubScmFileResolver =
new GithubScmFileResolver(

View File

@ -12,17 +12,23 @@
package org.eclipse.che.api.factory.server.github;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.util.Optional;
import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.factory.server.scm.PersonalAccessToken;
import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager;
import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider;
import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
import org.eclipse.che.commons.subject.Subject;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@ -35,23 +41,23 @@ import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class GithubURLParserTest {
@Mock private URLFetcher urlFetcher;
@Mock private DevfileFilenamesProvider devfileFilenamesProvider;
/** Instance of component that will be tested. */
@InjectMocks private GithubURLParser githubUrlParser;
@InjectMocks
private PersonalAccessTokenManager personalAccessTokenManager =
mock(PersonalAccessTokenManager.class);
@InjectMocks private GithubApiClient githubApiClient = mock(GithubApiClient.class);
/** Check invalid url (not a github one) */
@Test(expectedExceptions = IllegalArgumentException.class)
public void invalidUrl() {
public void invalidUrl() throws ApiException {
githubUrlParser.parse("http://www.eclipse.org");
}
@BeforeMethod
public void init() {
lenient().when(urlFetcher.fetchSafely(any(String.class))).thenReturn("");
}
/** Check URLs are valid with regexp */
@Test(dataProvider = "UrlsProvider")
public void checkRegexp(String url) {
@ -61,7 +67,8 @@ public class GithubURLParserTest {
/** Compare parsing */
@Test(dataProvider = "parsing")
public void checkParsing(
String url, String username, String repository, String branch, String subfolder) {
String url, String username, String repository, String branch, String subfolder)
throws ApiException {
GithubUrl githubUrl = githubUrlParser.parse(url);
assertEquals(githubUrl.getUsername(), username);
@ -72,7 +79,8 @@ public class GithubURLParserTest {
/** Compare parsing */
@Test(dataProvider = "parsingBadRepository")
public void checkParsingBadRepositoryDoNotModifiesInitialInput(String url, String repository) {
public void checkParsingBadRepositoryDoNotModifiesInitialInput(String url, String repository)
throws ApiException {
GithubUrl githubUrl = githubUrlParser.parse(url);
assertEquals(githubUrl.getRepository(), repository);
}
@ -144,39 +152,9 @@ public class GithubURLParserTest {
/** Check Pull Request with data inside the repository */
@Test
public void checkPullRequestFromRepository() {
public void checkPullRequestFromRepository() throws ApiException {
String url = "https://github.com/eclipse/che/pull/21276";
when(urlFetcher.fetchSafely(url))
.thenReturn(
" </div>\n"
+ " <div class=\"d-flex flex-items-center flex-wrap mt-0 gh-header-meta\">\n"
+ " <div class=\"flex-shrink-0 mb-2 flex-self-start flex-md-self-center\">\n"
+ " <span reviewable_state=\"ready\" title=\"Status: Open\" data-view-component=\"true\" class=\"State State--open\">\n"
+ " <svg height=\"16\" class=\"octicon octicon-git-pull-request\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z\"></path></svg> Open\n"
+ "</span>\n"
+ " </div>\n"
+ "\n"
+ "\n"
+ "\n"
+ " <div class=\"flex-auto min-width-0 mb-2\">\n"
+ " <a class=\"author Link--secondary text-bold css-truncate css-truncate-target expandable\" data-hovercard-type=\"user\" data-hovercard-url=\"/users/che-bot/hovercard\" data-octo-click=\"hovercard-link-click\" data-octo-dimensions=\"link_type:self\" href=\"/che-bot\">che-bot</a>\n"
+ "\n"
+ " wants to merge\n"
+ " <span class=\"js-updating-pull-request-commits-count\">1</span>\n"
+ " commit into\n"
+ "\n"
+ "\n"
+ "\n"
+ " <span title=\"eclipse/che:main\" class=\"commit-ref css-truncate user-select-contain expandable base-ref\"><a title=\"eclipse/che:main\" class=\"no-underline \" href=\"/eclipse/che\"><span class=\"css-truncate-target\">main</span></a></span><span></span>\n"
+ "\n"
+ " <div class=\"commit-ref-dropdown\">\n"
+ " <details class=\"details-reset details-overlay select-menu commitish-suggester\" id=\"branch-select-menu\">\n"
+ " <summary class=\"btn btn-sm select-menu-button branch\" title=\"Choose a base branch\">\n"
+ " <i>base:</i>\n"
+ " <span class=\"css-truncate css-truncate-target\" title=\"main\">main</span>\n"
+ " </summary>\n"
+ " <input-demux-context-wrapper data-context-type=\"baseChange\">");
GithubUrl githubUrl = githubUrlParser.parse(url);
assertEquals(githubUrl.getUsername(), "eclipse");
@ -186,38 +164,26 @@ public class GithubURLParserTest {
/** Check Pull Request with data outside the repository (fork) */
@Test
public void checkPullRequestFromForkedRepository() {
public void checkPullRequestFromForkedRepository() throws Exception {
PersonalAccessToken personalAccessToken = mock(PersonalAccessToken.class);
GithubPullRequest githubPullRequest = mock(GithubPullRequest.class);
GithubHead githubHead = mock(GithubHead.class);
GithubRepo githubRepo = mock(GithubRepo.class);
GithubUser githubUser = mock(GithubUser.class);
when(githubUser.getLogin()).thenReturn("eclipse");
when(githubRepo.getName()).thenReturn("che");
when(githubHead.getRef()).thenReturn("main");
when(githubHead.getRepo()).thenReturn(githubRepo);
when(githubHead.getUser()).thenReturn(githubUser);
when(githubPullRequest.getState()).thenReturn("open");
when(githubPullRequest.getHead()).thenReturn(githubHead);
when(personalAccessToken.getToken()).thenReturn("token");
when(personalAccessTokenManager.get(any(Subject.class), anyString()))
.thenReturn(Optional.of(personalAccessToken));
when(githubApiClient.getPullRequest(anyString(), anyString(), anyString(), anyString()))
.thenReturn(githubPullRequest);
String url = "https://github.com/eclipse/che/pull/20189";
when(urlFetcher.fetchSafely(url))
.thenReturn(
" <div class=\"d-flex flex-items-center flex-wrap mt-0 gh-header-meta\">\n"
+ " <div class=\"flex-shrink-0 mb-2 flex-self-start flex-md-self-center\">\n"
+ " <span reviewable_state=\"ready\" title=\"Status: Open\" data-view-component=\"true\" class=\"State State--open\">\n"
+ " <svg height=\"16\" class=\"octicon octicon-git-pull-request\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z\"></path></svg> Open\n"
+ "</span>\n"
+ " </div>\n"
+ "\n"
+ "\n"
+ "\n"
+ " <div class=\"flex-auto min-width-0 mb-2\">\n"
+ " <a class=\"author Link--secondary text-bold css-truncate css-truncate-target expandable\" data-hovercard-type=\"user\" data-hovercard-url=\"/users/apupier/hovercard\" data-octo-click=\"hovercard-link-click\" data-octo-dimensions=\"link_type:self\" href=\"/apupier\">apupier</a>\n"
+ "\n"
+ " wants to merge\n"
+ " <span class=\"js-updating-pull-request-commits-count\">1</span>\n"
+ " commit into\n"
+ "\n"
+ "\n"
+ "\n"
+ " <span title=\"eclipse/che:main\" class=\"commit-ref css-truncate user-select-contain expandable base-ref\"><a title=\"eclipse/che:main\" class=\"no-underline \" href=\"/eclipse/che\"><span class=\"css-truncate-target\">eclipse</span>:<span class=\"css-truncate-target\">main</span></a></span><span></span>\n"
+ "\n"
+ " <div class=\"commit-ref-dropdown\">\n"
+ " <details class=\"details-reset details-overlay select-menu commitish-suggester\" id=\"branch-select-menu\">\n"
+ " <summary class=\"btn btn-sm select-menu-button branch\" title=\"Choose a base branch\">\n"
+ " <i>base:</i>\n"
+ " <span class=\"css-truncate css-truncate-target\" title=\"main\">main</span>\n"
+ " </summary>\n"
+ " <input-demux-context-wrapper data-context-type=\"baseChange\">");
GithubUrl githubUrl = githubUrlParser.parse(url);
assertEquals(githubUrl.getUsername(), "eclipse");
@ -225,98 +191,31 @@ public class GithubURLParserTest {
assertEquals(githubUrl.getBranch(), "main");
}
@Test
public void checkPullRequestFromForkedRepositoryWithoutAuthentication() throws Exception {
String url = "https://github.com/eclipse/che/pull/21276";
GithubUrl githubUrl = githubUrlParser.parseWithoutAuthentication(url);
assertEquals(githubUrl.getUsername(), "eclipse");
assertEquals(githubUrl.getRepository(), "che");
verify(personalAccessTokenManager, never()).fetchAndSave(any(Subject.class), anyString());
}
/** Check Pull Request is failing with Merged state */
@Test(
expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = ".*found merged.*")
public void checkPullRequestMergedState() {
public void checkPullRequestMergedState() throws Exception {
PersonalAccessToken personalAccessToken = mock(PersonalAccessToken.class);
GithubPullRequest githubPullRequest = mock(GithubPullRequest.class);
when(githubPullRequest.getState()).thenReturn("merged");
when(personalAccessToken.getToken()).thenReturn("token");
when(personalAccessTokenManager.get(any(Subject.class), anyString()))
.thenReturn(Optional.of(personalAccessToken));
when(githubApiClient.getPullRequest(anyString(), anyString(), anyString(), anyString()))
.thenReturn(githubPullRequest);
String url = "https://github.com/eclipse/che/pull/11103";
when(urlFetcher.fetchSafely(url))
.thenReturn(
" <div class=\"d-flex flex-items-center flex-wrap mt-0 gh-header-meta\">\n"
+ " <div class=\"flex-shrink-0 mb-2 flex-self-start flex-md-self-center\">\n"
+ " <span reviewable_state=\"ready\" title=\"Status: Merged\" data-view-component=\"true\" class=\"State State--merged\">\n"
+ " <svg height=\"16\" class=\"octicon octicon-git-merge\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M5 3.254V3.25v.005a.75.75 0 110-.005v.004zm.45 1.9a2.25 2.25 0 10-1.95.218v5.256a2.25 2.25 0 101.5 0V7.123A5.735 5.735 0 009.25 9h1.378a2.251 2.251 0 100-1.5H9.25a4.25 4.25 0 01-3.8-2.346zM12.75 9a.75.75 0 100-1.5.75.75 0 000 1.5zm-8.5 4.5a.75.75 0 100-1.5.75.75 0 000 1.5z\"></path></svg> Merged\n"
+ "</span>\n"
+ " </div>\n"
+ "\n"
+ "\n"
+ "\n"
+ " <div class=\"flex-auto min-width-0 mb-2\">\n"
+ " <a class=\"author Link--secondary text-bold css-truncate css-truncate-target expandable\" data-hovercard-type=\"user\" data-hovercard-url=\"/users/benoitf/hovercard\" data-octo-click=\"hovercard-link-click\" data-octo-dimensions=\"link_type:self\" href=\"/benoitf\">benoitf</a>\n"
+ " merged 1 commit into\n"
+ "\n"
+ "\n"
+ "\n"
+ " <span title=\"eclipse/che:master\" class=\"commit-ref css-truncate user-select-contain expandable \"><a title=\"eclipse/che:master\" class=\"no-underline \" href=\"/eclipse/che/tree/master\"><span class=\"css-truncate-target\">master</span></a></span><span></span>\n"
+ "\n"
+ "from\n"
+ "\n"
+ "<span title=\"eclipse/che:cleanup-e2e-theia\" class=\"commit-ref css-truncate user-select-contain expandable head-ref\"><a title=\"eclipse/che:cleanup-e2e-theia\" class=\"no-underline \" href=\"/eclipse/che/tree/cleanup-e2e-theia\"><span class=\"css-truncate-target\">cleanup-e2e-theia</span></a></span><span><clipboard-copy aria-label=\"Copy\" data-copy-feedback=\"Copied!\" value=\"cleanup-e2e-theia\" data-view-component=\"true\" class=\"Link--onHover js-copy-branch color-fg-muted d-inline-block ml-1\">\n"
+ " <svg aria-hidden=\"true\" height=\"16\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" data-view-component=\"true\" class=\"octicon octicon-copy\">\n"
+ " <path fill-rule=\"evenodd\" d=\"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z\"></path><path fill-rule=\"evenodd\" d=\"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z\"></path>\n"
+ "</svg>\n"
+ " <svg style=\"display: none;\" aria-hidden=\"true\" height=\"16\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" data-view-component=\"true\" class=\"octicon octicon-check color-fg-success\">\n"
+ " <path fill-rule=\"evenodd\" d=\"M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z\"></path>\n"
+ "</svg>\n"
+ "</clipboard-copy></span>\n"
+ "\n"
+ "\n"
+ " <relative-time datetime=\"2018-09-07T08:00:49Z\" class=\"no-wrap\">Sep 7, 2018</relative-time>\n"
+ "\n"
+ " </div>\n"
+ " </div>\n"
+ "");
githubUrlParser.parse(url);
}
/** Check Pull Request is failing with Closed state */
@Test(
expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = ".*found closed.*")
public void checkPullRequestClosedState() {
String url = "https://github.com/eclipse/che/pull/20754";
when(urlFetcher.fetchSafely(url))
.thenReturn(
" </div>\n"
+ " <div class=\"d-flex flex-items-center flex-wrap mt-0 gh-header-meta\">\n"
+ " <div class=\"flex-shrink-0 mb-2 flex-self-start flex-md-self-center\">\n"
+ " <span reviewable_state=\"ready\" title=\"Status: Closed\" data-view-component=\"true\" class=\"State State--closed\">\n"
+ " <svg height=\"16\" class=\"octicon octicon-git-pull-request-closed\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M10.72 1.227a.75.75 0 011.06 0l.97.97.97-.97a.75.75 0 111.06 1.061l-.97.97.97.97a.75.75 0 01-1.06 1.06l-.97-.97-.97.97a.75.75 0 11-1.06-1.06l.97-.97-.97-.97a.75.75 0 010-1.06zM12.75 6.5a.75.75 0 00-.75.75v3.378a2.251 2.251 0 101.5 0V7.25a.75.75 0 00-.75-.75zm0 5.5a.75.75 0 100 1.5.75.75 0 000-1.5zM2.5 3.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.25 1a2.25 2.25 0 00-.75 4.372v5.256a2.251 2.251 0 101.5 0V5.372A2.25 2.25 0 003.25 1zm0 11a.75.75 0 100 1.5.75.75 0 000-1.5z\"></path></svg> Closed\n"
+ "</span>\n"
+ " </div>\n"
+ "\n"
+ "\n"
+ "\n"
+ " <div class=\"flex-auto min-width-0 mb-2\">\n"
+ " <a class=\"author Link--secondary text-bold css-truncate css-truncate-target expandable\" data-hovercard-type=\"user\" data-hovercard-url=\"/users/Ohrimenko1988/hovercard\" data-octo-click=\"hovercard-link-click\" data-octo-dimensions=\"link_type:self\" href=\"/Ohrimenko1988\">Ohrimenko1988</a>\n"
+ "\n"
+ " wants to merge\n"
+ " <span class=\"js-updating-pull-request-commits-count\">10</span>\n"
+ " commits into\n"
+ "\n"
+ "\n"
+ "\n"
+ " <span title=\"eclipse/che:7.38.x\" class=\"commit-ref css-truncate user-select-contain expandable \"><a title=\"eclipse/che:7.38.x\" class=\"no-underline \" href=\"/eclipse/che/tree/7.38.x\"><span class=\"css-truncate-target\">eclipse</span>:<span class=\"css-truncate-target\">7.38.x</span></a></span><span></span>\n"
+ "\n"
+ "from\n"
+ "\n"
+ "<span title=\"Ohrimenko1988/che:iokhrime-chromedriver-7.38.x\" class=\"commit-ref css-truncate user-select-contain expandable head-ref\"><a title=\"Ohrimenko1988/che:iokhrime-chromedriver-7.38.x\" class=\"no-underline \" href=\"/Ohrimenko1988/che/tree/iokhrime-chromedriver-7.38.x\"><span class=\"css-truncate-target\">Ohrimenko1988</span>:<span class=\"css-truncate-target\">iokhrime-chromedriver-7.38.x</span></a></span><span><clipboard-copy aria-label=\"Copy\" data-copy-feedback=\"Copied!\" value=\"Ohrimenko1988:iokhrime-chromedriver-7.38.x\" data-view-component=\"true\" class=\"Link--onHover js-copy-branch color-fg-muted d-inline-block ml-1\">\n"
+ " <svg aria-hidden=\"true\" height=\"16\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" data-view-component=\"true\" class=\"octicon octicon-copy\">\n"
+ " <path fill-rule=\"evenodd\" d=\"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z\"></path><path fill-rule=\"evenodd\" d=\"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z\"></path>\n"
+ "</svg>\n"
+ " <svg style=\"display: none;\" aria-hidden=\"true\" height=\"16\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" data-view-component=\"true\" class=\"octicon octicon-check color-fg-success\">\n"
+ " <path fill-rule=\"evenodd\" d=\"M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z\"></path>\n"
+ "</svg>\n"
+ "</clipboard-copy></span>\n"
+ "\n"
+ "\n"
+ "\n"
+ " </div>\n"
+ " </div>\n"
+ "");
githubUrlParser.parse(url);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* Copyright (c) 2012-2022 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
@ -18,6 +18,7 @@ import static org.testng.Assert.assertNotNull;
import java.util.Arrays;
import java.util.Iterator;
import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider;
import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation;
import org.mockito.InjectMocks;
@ -45,7 +46,7 @@ public class GithubUrlTest {
/** Setup objects/ */
@BeforeMethod
protected void init() {
protected void init() throws ApiException {
when(devfileFilenamesProvider.getConfiguredDevfileFilenames())
.thenReturn(Arrays.asList("devfile.yaml", "foo.bar"));
this.githubUrl = this.githubUrlParser.parse("https://github.com/eclipse/che");

View File

@ -11,7 +11,7 @@
*/
package org.eclipse.che.api.factory.server.gitlab;
import static org.eclipse.che.api.factory.server.DevfileToApiExceptionMapper.toApiException;
import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException;
import java.io.IOException;
import javax.inject.Inject;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* Copyright (c) 2012-2022 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
@ -23,24 +23,34 @@ import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLoc
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException;
/**
* Helps to convert {@link DevfileException}s with some specific causes into REST-friendly {@link
* Helps to convert {@link Exception}s with some specific causes into REST-friendly {@link
* ApiException}
*/
public class DevfileToApiExceptionMapper {
public class ApiExceptionMapper {
public static ApiException toApiException(DevfileException devfileException) {
ApiException cause = getApiException(devfileException);
return (cause != null)
? cause
ApiException apiException = getApiException(devfileException.getCause());
return (apiException != null)
? apiException
: new BadRequestException(
"Error occurred during file content retrieval."
+ "Cause: "
+ devfileException.getMessage());
}
public static ApiException toApiException(ScmUnauthorizedException scmUnauthorizedException) {
ApiException apiException = getApiException(scmUnauthorizedException);
return (apiException != null)
? apiException
: new BadRequestException(
"Error occurred during SCM authorisation."
+ "Cause: "
+ scmUnauthorizedException.getMessage());
}
public static ApiException toApiException(
DevfileException devfileException, DevfileLocation location) {
ApiException cause = getApiException(devfileException);
ApiException cause = getApiException(devfileException.getCause());
return (cause != null)
? cause
: new BadRequestException(
@ -50,10 +60,9 @@ public class DevfileToApiExceptionMapper {
+ devfileException.getMessage());
}
private static ApiException getApiException(DevfileException devfileException) {
Throwable cause = devfileException.getCause();
if (cause instanceof ScmUnauthorizedException) {
ScmUnauthorizedException scmCause = (ScmUnauthorizedException) cause;
private static ApiException getApiException(Throwable throwable) {
if (throwable instanceof ScmUnauthorizedException) {
ScmUnauthorizedException scmCause = (ScmUnauthorizedException) throwable;
return new UnauthorizedException(
"SCM Authentication required",
401,
@ -61,14 +70,14 @@ public class DevfileToApiExceptionMapper {
"oauth_version", scmCause.getOauthVersion(),
"oauth_provider", scmCause.getOauthProvider(),
"oauth_authentication_url", scmCause.getAuthenticateUrl()));
} else if (cause instanceof UnknownScmProviderException) {
} else if (throwable instanceof UnknownScmProviderException) {
return new ServerException(
"Provided location is unknown or misconfigured on the server side. Error message: "
+ cause.getMessage());
} else if (cause instanceof ScmCommunicationException) {
+ throwable.getMessage());
} else if (throwable instanceof ScmCommunicationException) {
return new ServerException(
"There is an error happened when communicate with SCM server. Error message: "
+ cause.getMessage());
+ throwable.getMessage());
}
return null;
}

View File

@ -12,7 +12,7 @@
package org.eclipse.che.api.factory.server.urlfactory;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.eclipse.che.api.factory.server.DevfileToApiExceptionMapper.toApiException;
import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException;
import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION;
import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION;
import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* Copyright (c) 2012-2022 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
@ -25,7 +25,7 @@ import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderExcept
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException;
import org.testng.annotations.Test;
public class DevfileToApiExceptionMapperTest {
public class ApiExceptionMapperTest {
@Test
public void shouldReturnUnauthorizedExceptionIfCauseIsScmUnauthorized() {
@ -35,8 +35,7 @@ public class DevfileToApiExceptionMapperTest {
"msg", "gitlab", "2.0", "http://gitlab.com/oauth/authenticate");
ApiException exception =
DevfileToApiExceptionMapper.toApiException(
new DevfileException("text", scmUnauthorizedException));
ApiExceptionMapper.toApiException(new DevfileException("text", scmUnauthorizedException));
assertTrue(exception instanceof UnauthorizedException);
assertEquals(((ExtendedError) exception.getServiceError()).getErrorCode(), 401);
assertEquals(((ExtendedError) exception.getServiceError()).getAttributes().size(), 3);
@ -55,8 +54,7 @@ public class DevfileToApiExceptionMapperTest {
UnknownScmProviderException scmProviderException =
new UnknownScmProviderException("unknown", "http://gitlab.com/oauth/authenticate");
ApiException exception =
DevfileToApiExceptionMapper.toApiException(
new DevfileException("text", scmProviderException));
ApiExceptionMapper.toApiException(new DevfileException("text", scmProviderException));
assertTrue(exception instanceof ServerException);
}
@ -65,8 +63,7 @@ public class DevfileToApiExceptionMapperTest {
ScmCommunicationException communicationException = new ScmCommunicationException("unknown");
ApiException exception =
DevfileToApiExceptionMapper.toApiException(
new DevfileException("text", communicationException));
ApiExceptionMapper.toApiException(new DevfileException("text", communicationException));
assertTrue(exception instanceof ServerException);
}
@ -75,8 +72,7 @@ public class DevfileToApiExceptionMapperTest {
ScmItemNotFoundException itemNotFoundException = new ScmItemNotFoundException("unknown");
ApiException exception =
DevfileToApiExceptionMapper.toApiException(
new DevfileException("text", itemNotFoundException));
ApiExceptionMapper.toApiException(new DevfileException("text", itemNotFoundException));
assertTrue(exception instanceof BadRequestException);
}
}