diff --git a/build.gradle b/build.gradle index b32434f3a..8a062e3c5 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ plugins { id('java') id('jacoco') - id('org.sonarqube') version('2.7') + id('org.sonarqube') version('2.8') id('info.solidsoft.pitest') version('1.4.0') id('com.github.johnrengelman.shadow') version('5.1.0') id('net.researchgate.release') version('2.6.0') @@ -121,3 +121,13 @@ pitest { timestampedReports = false avoidCallsTo = ['org.sonar.api.utils.log.Logger'] } + +jacocoTestReport { + reports { + xml.enabled true + } +} + +plugins.withType(JacocoPlugin) { + tasks["test"].finalizedBy 'jacocoTestReport' +} \ No newline at end of file diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityReportAnalysisComponentProvider.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityReportAnalysisComponentProvider.java index 104bf8b54..95a98e654 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityReportAnalysisComponentProvider.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityReportAnalysisComponentProvider.java @@ -20,11 +20,12 @@ import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PostAnalysisIssueVisitor; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PullRequestPostAnalysisTask; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.server.BitbucketServerPullRequestDecorator; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github.GithubPullRequestDecorator; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github.v3.DefaultLinkHeaderReader; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github.v3.RestApplicationAuthenticationProvider; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github.v4.GraphqlCheckRunProvider; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.GitlabServerPullRequestDecorator; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.server.BitbucketServerPullRequestDecorator; import org.sonar.ce.task.projectanalysis.container.ReportAnalysisComponentProvider; import java.util.Arrays; @@ -39,7 +40,7 @@ public class CommunityReportAnalysisComponentProvider implements ReportAnalysisC public List getComponents() { return Arrays.asList(CommunityBranchLoaderDelegate.class, PullRequestPostAnalysisTask.class, PostAnalysisIssueVisitor.class, GithubPullRequestDecorator.class, - GraphqlCheckRunProvider.class, RestApplicationAuthenticationProvider.class, + GraphqlCheckRunProvider.class, DefaultLinkHeaderReader.class, RestApplicationAuthenticationProvider.class, BitbucketServerPullRequestDecorator.class, GitlabServerPullRequestDecorator.class); } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/DefaultLinkHeaderReader.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/DefaultLinkHeaderReader.java new file mode 100644 index 000000000..b53f5dfbb --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/DefaultLinkHeaderReader.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2020 Michael Clarke + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github.v3; + +import java.util.Arrays; +import java.util.Optional; + +public class DefaultLinkHeaderReader implements LinkHeaderReader { + + @Override + public Optional findNextLink(String linkHeader) { + return Optional.ofNullable(linkHeader) + .flatMap(l -> Arrays.stream(l.split(",")) + .map(i -> i.split(";")) + .filter(i -> i.length > 1) + .filter(i -> { + String[] relParts = i[1].trim().split("="); + + if (relParts.length < 2) { + return false; + } + + if (!"rel".equals(relParts[0])) { + return false; + } + + return "next".equals(relParts[1]) || "\"next\"".equals(relParts[1]); + }) + .map(i -> i[0]) + .map(String::trim) + .filter(i -> i.startsWith("<") && i.endsWith(">")) + .map(i -> i.substring(1, i.length() - 1)) + .findFirst()); + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/LinkHeaderReader.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/LinkHeaderReader.java new file mode 100644 index 000000000..8638977e8 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/LinkHeaderReader.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 Michael Clarke + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github.v3; + +import java.util.Optional; + +interface LinkHeaderReader { + + Optional findNextLink(String linkHeader); + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/RestApplicationAuthenticationProvider.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/RestApplicationAuthenticationProvider.java index 04d653513..3b19f7c7b 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/RestApplicationAuthenticationProvider.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/RestApplicationAuthenticationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * Copyright (C) 2020 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -43,6 +43,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Date; +import java.util.Optional; public class RestApplicationAuthenticationProvider implements GithubApplicationAuthenticationProvider { @@ -53,16 +54,18 @@ public class RestApplicationAuthenticationProvider implements GithubApplicationA private static final String APP_PREVIEW_ACCEPT_HEADER = "application/vnd.github.machine-man-preview+json"; private final Clock clock; + private final LinkHeaderReader linkHeaderReader; private final UrlConnectionProvider urlProvider; - public RestApplicationAuthenticationProvider(Clock clock) { - this(clock, new DefaultUrlConnectionProvider()); + public RestApplicationAuthenticationProvider(Clock clock, LinkHeaderReader linkHeaderReader) { + this(clock, linkHeaderReader, new DefaultUrlConnectionProvider()); } - RestApplicationAuthenticationProvider(Clock clock, UrlConnectionProvider urlProvider) { + RestApplicationAuthenticationProvider(Clock clock, LinkHeaderReader linkHeaderReader, UrlConnectionProvider urlProvider) { super(); this.clock = clock; this.urlProvider = urlProvider; + this.linkHeaderReader = linkHeaderReader; } @Override @@ -97,32 +100,13 @@ public RepositoryAuthenticationToken getInstallationToken(String apiUrl, String try (Reader reader = new InputStreamReader(accessTokenConnection.getInputStream())) { AppToken appToken = objectMapper.readerFor(AppToken.class).readValue(reader); - URLConnection installationRepositoriesConnection = - urlProvider.createUrlConnection(installation.getRepositoriesUrl()); - ((HttpURLConnection) installationRepositoriesConnection).setRequestMethod("GET"); - installationRepositoriesConnection.setRequestProperty(ACCEPT_HEADER, APP_PREVIEW_ACCEPT_HEADER); - installationRepositoriesConnection.setRequestProperty(AUTHORIZATION_HEADER, - BEARER_AUTHORIZATION_HEADER_PREFIX + - appToken.getToken()); - String repositoryNodeId = null; - try (Reader installationRepositoriesReader = new InputStreamReader( - installationRepositoriesConnection.getInputStream())) { - InstallationRepositories installationRepositories = - objectMapper.readerFor(InstallationRepositories.class) - .readValue(installationRepositoriesReader); - for (Repository repository : installationRepositories.getRepositories()) { - if (projectPath.equals(repository.getFullName())) { - repositoryNodeId = repository.getNodeId(); - break; - } - } - if (null == repositoryNodeId) { - continue; - } + String targetUrl = installation.getRepositoriesUrl(); - } + Optional potentialRepositoryAuthenticationToken = findRepositoryAuthenticationToken(appToken, targetUrl, projectPath, objectMapper); - return new RepositoryAuthenticationToken(repositoryNodeId, appToken.getToken()); + if (potentialRepositoryAuthenticationToken.isPresent()) { + return potentialRepositoryAuthenticationToken.get(); + } } } @@ -131,6 +115,35 @@ public RepositoryAuthenticationToken getInstallationToken(String apiUrl, String "No token could be found with access to the requested repository with the given application ID and key"); } + private Optional findRepositoryAuthenticationToken(AppToken appToken, String targetUrl, + String projectPath, ObjectMapper objectMapper) throws IOException { + URLConnection installationRepositoriesConnection = urlProvider.createUrlConnection(targetUrl); + ((HttpURLConnection) installationRepositoriesConnection).setRequestMethod("GET"); + installationRepositoriesConnection.setRequestProperty(ACCEPT_HEADER, APP_PREVIEW_ACCEPT_HEADER); + installationRepositoriesConnection.setRequestProperty(AUTHORIZATION_HEADER, + BEARER_AUTHORIZATION_HEADER_PREFIX + appToken.getToken()); + + try (Reader installationRepositoriesReader = new InputStreamReader( + installationRepositoriesConnection.getInputStream())) { + InstallationRepositories installationRepositories = + objectMapper.readerFor(InstallationRepositories.class).readValue(installationRepositoriesReader); + for (Repository repository : installationRepositories.getRepositories()) { + if (projectPath.equals(repository.getFullName())) { + return Optional.of(new RepositoryAuthenticationToken(repository.getNodeId(), appToken.getToken())); + } + } + + } + + Optional nextLink = linkHeaderReader.findNextLink(installationRepositoriesConnection.getHeaderField("Link")); + + if (!nextLink.isPresent()) { + return Optional.empty(); + } + + return findRepositoryAuthenticationToken(appToken, nextLink.get(), projectPath, objectMapper); + } + private static PrivateKey createPrivateKey(String apiPrivateKey) throws IOException { try (PEMParser pemParser = new PEMParser(new StringReader(apiPrivateKey))) { diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityReportAnalysisComponentProviderTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityReportAnalysisComponentProviderTest.java index 3e2802bf8..5737399a3 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityReportAnalysisComponentProviderTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityReportAnalysisComponentProviderTest.java @@ -32,7 +32,7 @@ public class CommunityReportAnalysisComponentProviderTest { @Test public void testGetComponents() { List result = new CommunityReportAnalysisComponentProvider().getComponents(); - assertEquals(8, result.size()); + assertEquals(9, result.size()); assertEquals(CommunityBranchLoaderDelegate.class, result.get(0)); } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/DefaultLinkHeaderReaderTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/DefaultLinkHeaderReaderTest.java new file mode 100644 index 000000000..ad803e799 --- /dev/null +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/DefaultLinkHeaderReaderTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2020 Michael Clarke + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github.v3; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DefaultLinkHeaderReaderTest { + + @Test + public void findNextLinkEmptyForNoHeader() { + DefaultLinkHeaderReader underTest = new DefaultLinkHeaderReader(); + assertThat(underTest.findNextLink(null)).isEmpty(); + } + + @Test + public void findNextLinkEmptyForEmptyHeader() { + DefaultLinkHeaderReader underTest = new DefaultLinkHeaderReader(); + assertThat(underTest.findNextLink("")).isEmpty(); + } + + @Test + public void findNextLinkEmptyForMissingRelContent() { + DefaultLinkHeaderReader underTest = new DefaultLinkHeaderReader(); + assertThat(underTest.findNextLink("; rel")).isEmpty(); + } + + @Test + public void findNextLinkEmptyForInvalidRelContent() { + DefaultLinkHeaderReader underTest = new DefaultLinkHeaderReader(); + assertThat(underTest.findNextLink("; abc=\"xyz\"")).isEmpty(); + } + + @Test + public void findNextLinkEmptyForIncorrectHeader() { + DefaultLinkHeaderReader underTest = new DefaultLinkHeaderReader(); + assertThat(underTest.findNextLink("dummy")).isEmpty(); + } + + @Test + public void findNextLinkEmptyForMissingUrlPrefix() { + DefaultLinkHeaderReader underTest = new DefaultLinkHeaderReader(); + assertThat(underTest.findNextLink("http://other>; rel=\"next\"")).isEmpty(); + } + + @Test + public void findNextLinkEmptyForMissingUrlPostfix() { + DefaultLinkHeaderReader underTest = new DefaultLinkHeaderReader(); + assertThat(underTest.findNextLink("; rel=\"next\"")).hasValue("http://other"); + } + + @Test + public void findNextLinkReturnsCorrectUrlOnMatchNoSpeechMarksAroundRel() { + DefaultLinkHeaderReader underTest = new DefaultLinkHeaderReader(); + assertThat(underTest.findNextLink("; rel=next")).hasValue("http://other"); + } + + @Test + public void findNextLinkReturnsCorrectUrlOnMatchWithOtherRelEntries() { + DefaultLinkHeaderReader underTest = new DefaultLinkHeaderReader(); + assertThat(underTest.findNextLink("; rel=\"last\", ; rel=\"next\"")).hasValue("http://other2"); + } +} \ No newline at end of file diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/RestApplicationAuthenticationProviderTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/RestApplicationAuthenticationProviderTest.java index 667154149..ce50c1c10 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/RestApplicationAuthenticationProviderTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v3/RestApplicationAuthenticationProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * Copyright (C) 2020 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -29,16 +29,17 @@ import java.net.HttpURLConnection; import java.net.URLConnection; import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; import java.time.Clock; import java.time.Instant; import java.time.ZoneId; import java.util.Arrays; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -47,7 +48,7 @@ public class RestApplicationAuthenticationProviderTest { @Test - public void testTokenRetrievedHappyPath() throws IOException, GeneralSecurityException { + public void testTokenRetrievedHappyPath() throws IOException { UrlConnectionProvider urlProvider = mock(UrlConnectionProvider.class); Clock clock = Clock.fixed(Instant.ofEpochMilli(123456789L), ZoneId.of("UTC")); @@ -83,7 +84,7 @@ public void testTokenRetrievedHappyPath() throws IOException, GeneralSecurityExc apiPrivateKey = IOUtils.toString(inputStream, StandardCharsets.UTF_8); } - RestApplicationAuthenticationProvider testCase = new RestApplicationAuthenticationProvider(clock, urlProvider); + RestApplicationAuthenticationProvider testCase = new RestApplicationAuthenticationProvider(clock, h -> Optional.empty(), urlProvider); RepositoryAuthenticationToken result = testCase.getInstallationToken(apiUrl, appId, apiPrivateKey, projectPath); assertEquals(expectedAuthenticationToken, result.getAuthenticationToken()); @@ -114,7 +115,75 @@ public void testTokenRetrievedHappyPath() throws IOException, GeneralSecurityExc } @Test - public void testExceptionOnNoMatchingToken() throws IOException, GeneralSecurityException { + public void testTokenRetrievedPaginatedHappyPath() throws IOException { + UrlConnectionProvider urlProvider = mock(UrlConnectionProvider.class); + Clock clock = Clock.fixed(Instant.ofEpochMilli(123456789L), ZoneId.of("UTC")); + + String expectedAuthenticationToken = "expected authentication token"; + String projectPath = "project path"; + String expectedRepositoryId = "expected repository Id"; + + URLConnection installationsUrlConnection = mock(URLConnection.class); + doReturn(new ByteArrayInputStream( + "[{\"repositories_url\": \"repositories_url\", \"access_tokens_url\": \"tokens_url\"}]" + .getBytes(StandardCharsets.UTF_8))).when(installationsUrlConnection).getInputStream(); + + HttpURLConnection accessTokensUrlConnection = mock(HttpURLConnection.class); + doReturn(new ByteArrayInputStream( + ("{\"token\": \"" + expectedAuthenticationToken + "\"}").getBytes(StandardCharsets.UTF_8))) + .when(accessTokensUrlConnection).getInputStream(); + doReturn(accessTokensUrlConnection).when(urlProvider).createUrlConnection("tokens_url"); + + + for (int i = 0; i < 2; i ++) { + HttpURLConnection repositoriesUrlConnection = mock(HttpURLConnection.class); + doReturn(new ByteArrayInputStream( + ("{\"repositories\": [{\"node_id\": \"" + expectedRepositoryId + (i == 0 ? "a" : "") + "\", \"full_name\": \"" + + projectPath + (i == 0 ? "a" : "") + "\"}]}").getBytes(StandardCharsets.UTF_8))).when(repositoriesUrlConnection).getInputStream(); + + doReturn(i == 0 ? "a" : null).when(repositoriesUrlConnection).getHeaderField("Link"); + doReturn(repositoriesUrlConnection).when(urlProvider).createUrlConnection(i == 0 ? "repositories_url" : "https://dummy.url/path?param=dummy&page=" + (i + 1)); + } + + + String apiUrl = "apiUrl"; + doReturn(installationsUrlConnection).when(urlProvider).createUrlConnection(eq(apiUrl + "/app/installations")); + + String appId = "appID"; + + String apiPrivateKey; + try (InputStream inputStream = getClass().getResourceAsStream("/rsa-private-key.pem")) { + apiPrivateKey = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + } + + LinkHeaderReader linkHeaderReader = mock(LinkHeaderReader.class); + doReturn(Optional.of("https://dummy.url/path?param=dummy&page=2")).when(linkHeaderReader).findNextLink(eq("a")); + doReturn(Optional.empty()).when(linkHeaderReader).findNextLink(isNull()); + + RestApplicationAuthenticationProvider testCase = new RestApplicationAuthenticationProvider(clock, linkHeaderReader, urlProvider); + RepositoryAuthenticationToken result = testCase.getInstallationToken(apiUrl, appId, apiPrivateKey, projectPath); + + assertEquals(expectedAuthenticationToken, result.getAuthenticationToken()); + assertEquals(expectedRepositoryId, result.getRepositoryId()); + + ArgumentCaptor requestPropertyArgumentCaptor = ArgumentCaptor.forClass(String.class); + verify(installationsUrlConnection, times(2)) + .setRequestProperty(requestPropertyArgumentCaptor.capture(), requestPropertyArgumentCaptor.capture()); + assertEquals(Arrays.asList("Accept", "application/vnd.github.machine-man-preview+json", "Authorization", + "Bearer eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjEyMzQ0NiwiZXhwIjoxMjM1NjYsImlzcyI6ImFwcElEIn0.yMvAoUmmAHli-Mc-RidLbqlX2Cvc2RwPBwkgY6n1R2ZkV-IaY8uBO4s7pp0-3hcJvY4F7-UGnAi1dteGOODY8cOmx86DsSASJIHJ3wxaRxyLGOq2Z8A1KSWZj-F8O6wFf5pm2xzumm0gSSwdd3gQR0FiSn2TIHemjyoieNJfzvG2kgtHPBNIVaJcS8LqkVYBlvAujnAt1nQ1hIAbeQJyEmyVyb_NRMPQZZioBraobTlWdPWdnTQoNTWjmjcopIbUFw8s21uhMcDpA_6lS1iAZcoZKcpzMqsItEvQaiwYQWRccfZT69M_zWaVRjw2-eKsTuFXzumVyq3MnAoxy6R2Xw"), + requestPropertyArgumentCaptor.getAllValues()); + + requestPropertyArgumentCaptor = ArgumentCaptor.forClass(String.class); + verify(accessTokensUrlConnection, times(2)) + .setRequestProperty(requestPropertyArgumentCaptor.capture(), requestPropertyArgumentCaptor.capture()); + verify(accessTokensUrlConnection).setRequestMethod("POST"); + assertEquals(Arrays.asList("Accept", "application/vnd.github.machine-man-preview+json", "Authorization", + "Bearer eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjEyMzQ0NiwiZXhwIjoxMjM1NjYsImlzcyI6ImFwcElEIn0.yMvAoUmmAHli-Mc-RidLbqlX2Cvc2RwPBwkgY6n1R2ZkV-IaY8uBO4s7pp0-3hcJvY4F7-UGnAi1dteGOODY8cOmx86DsSASJIHJ3wxaRxyLGOq2Z8A1KSWZj-F8O6wFf5pm2xzumm0gSSwdd3gQR0FiSn2TIHemjyoieNJfzvG2kgtHPBNIVaJcS8LqkVYBlvAujnAt1nQ1hIAbeQJyEmyVyb_NRMPQZZioBraobTlWdPWdnTQoNTWjmjcopIbUFw8s21uhMcDpA_6lS1iAZcoZKcpzMqsItEvQaiwYQWRccfZT69M_zWaVRjw2-eKsTuFXzumVyq3MnAoxy6R2Xw"), + requestPropertyArgumentCaptor.getAllValues()); + } + + @Test + public void testExceptionOnNoMatchingToken() throws IOException { UrlConnectionProvider urlProvider = mock(UrlConnectionProvider.class); Clock clock = Clock.fixed(Instant.ofEpochMilli(123456789L), ZoneId.of("UTC")); @@ -151,7 +220,7 @@ public void testExceptionOnNoMatchingToken() throws IOException, GeneralSecurity apiPrivateKey = IOUtils.toString(inputStream, StandardCharsets.UTF_8); } - RestApplicationAuthenticationProvider testCase = new RestApplicationAuthenticationProvider(clock, urlProvider); + RestApplicationAuthenticationProvider testCase = new RestApplicationAuthenticationProvider(clock, h -> Optional.empty(), urlProvider); assertThatThrownBy(() -> testCase.getInstallationToken(apiUrl, appId, apiPrivateKey, projectPath)).hasMessage( "No token could be found with access to the requested repository with the given application ID and key") .isExactlyInstanceOf(IllegalStateException.class); @@ -184,7 +253,8 @@ public void testExceptionOnNoMatchingToken() throws IOException, GeneralSecurity @Test public void testDefaultParameters() { Clock clock = mock(Clock.class); - assertThat(new RestApplicationAuthenticationProvider(clock, new DefaultUrlConnectionProvider())) - .usingRecursiveComparison().isEqualTo(new RestApplicationAuthenticationProvider(clock)); + LinkHeaderReader linkHeaderReader = mock(LinkHeaderReader.class); + assertThat(new RestApplicationAuthenticationProvider(clock, linkHeaderReader, new DefaultUrlConnectionProvider())) + .usingRecursiveComparison().isEqualTo(new RestApplicationAuthenticationProvider(clock, linkHeaderReader)); } }