Fetch and render plugin license from Gerrit-CI Enrich the plugin information with a new 'license' field that captures the first 2 lines of the LICENSE file published by Gerrit-CI. All Gerrit core plugins are associated with the Apache 2.0 license. Whenever a plugin exposes a LICENSE file in its source code, the plugin manager displays a "legal" icon that shows the license type on mouse hover. Also add test coverage for JenkinsCiPluginsRepository for fetching the plugin information from a successful Jenkins Job build run. Old plugin card : https://www.pasteboard.co/s87jpkcCcKxa.png New plugin card : https://www.pasteboard.co/ZG3qMXgQqY81.png Hover on license: https://www.pasteboard.co/cYAPqhlB2HZY.png Change-Id: Ieb274a7f236c0c6d3a03db07838b5ea2a1926767 (cherry picked from commit 37f52d04052f6e763e45e74bd4c21f2947b8f981)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/manager/gson/SmartJson.java b/src/main/java/com/googlesource/gerrit/plugins/manager/gson/SmartJson.java index 9dd1b6d..2da0fb0 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/manager/gson/SmartJson.java +++ b/src/main/java/com/googlesource/gerrit/plugins/manager/gson/SmartJson.java
@@ -14,7 +14,9 @@ package com.googlesource.gerrit.plugins.manager.gson; +import com.google.gson.Gson; import com.google.gson.JsonElement; +import java.util.Map; import java.util.Optional; import java.util.function.Function; @@ -30,6 +32,10 @@ return new SmartJson(fromJson); } + public static SmartJson of(Map<?, ?> jsonTreeMap) { + return of(new Gson().toJsonTree(jsonTreeMap, Map.class)); + } + public Optional<String> getOptionalString(String fieldName) { return getOptional(fieldName) .map( @@ -66,4 +72,8 @@ public JsonElement get() { return jsonElem; } + + public void add(String property, JsonElement value) { + jsonElem.getAsJsonObject().add(property, value); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/manager/repository/CorePluginsRepository.java b/src/main/java/com/googlesource/gerrit/plugins/manager/repository/CorePluginsRepository.java index 2e175ea..767d3ca 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/manager/repository/CorePluginsRepository.java +++ b/src/main/java/com/googlesource/gerrit/plugins/manager/repository/CorePluginsRepository.java
@@ -37,6 +37,12 @@ import java.util.jar.Manifest; public class CorePluginsRepository implements PluginsRepository { + public static final String GERRIT_LICENSE = + """ + Apache License + Version 2.0, January 2004\ + """; + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final String GERRIT_VERSION = Version.getVersion(); @@ -75,7 +81,8 @@ pluginsDescriptions.get(pluginName).orElse(""), pluginAttributes.getValue("Implementation-Version"), "", - pluginUrl.toString()); + pluginUrl.toString(), + GERRIT_LICENSE); }) .orElseGet( () -> @@ -84,7 +91,8 @@ "", "", "", - pluginUrl.toString())); + pluginUrl.toString(), + GERRIT_LICENSE)); } catch (IOException e) { logger.atSevere().withCause(e).log("Unable to open plugin %s", pluginUrl); return null;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/manager/repository/JenkinsCiPluginsRepository.java b/src/main/java/com/googlesource/gerrit/plugins/manager/repository/JenkinsCiPluginsRepository.java index dc3ffd2..44c690d 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/manager/repository/JenkinsCiPluginsRepository.java +++ b/src/main/java/com/googlesource/gerrit/plugins/manager/repository/JenkinsCiPluginsRepository.java
@@ -40,6 +40,9 @@ public class JenkinsCiPluginsRepository implements PluginsRepository { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + private static final int FETCH_ALL_LINES = 0; + private static final int FETCH_LICENSE_LINES = 2; + public static final String LICENSE_FILE = "LICENSE"; private final PluginManagerConfig config; @@ -159,6 +162,8 @@ Optional<String> pluginVersion = fetchArtifact(buildExecution.get(), artifacts.get(), ".jar-version"); + Optional<String> pluginLicense = + fetchArtifact(buildExecution.get(), artifacts.get(), LICENSE_FILE, FETCH_LICENSE_LINES); Optional<String> pluginDescription = fetchArtifactJson(buildExecution.get(), artifacts.get(), ".json") .flatMap(json -> json.getOptionalString("description")); @@ -171,7 +176,13 @@ String sha1 = lastBuildRevision.get().getString("SHA1").substring(0, 8); return pluginVersion.map( version -> - new PluginInfo(pluginName, pluginDescription.orElse(""), version, sha1, pluginUrl)); + new PluginInfo( + pluginName, + pluginDescription.orElse(""), + version, + sha1, + pluginUrl, + pluginLicense.orElse(null))); } } @@ -192,6 +203,8 @@ Optional<String> pluginVersion = fetchArtifact(buildExecution.get(), artifacts.get(), ".js-version"); + Optional<String> pluginLicense = + fetchArtifact(buildExecution.get(), artifacts.get(), LICENSE_FILE, FETCH_LICENSE_LINES); Optional<String> pluginDescription = fetchArtifactJson(buildExecution.get(), artifacts.get(), ".json") .flatMap(json -> json.getOptionalString("description")); @@ -204,7 +217,13 @@ String sha1 = lastBuildRevision.get().getString("SHA1").substring(0, 8); return pluginVersion.map( version -> - new PluginInfo(pluginName, pluginDescription.orElse(""), version, sha1, pluginUrl)); + new PluginInfo( + pluginName, + pluginDescription.orElse(""), + version, + sha1, + pluginUrl, + pluginLicense.orElse(null))); } } @@ -213,6 +232,11 @@ private Optional<String> fetchArtifact( SmartJson buildExecution, JsonArray artifacts, String artifactSuffix) { + return fetchArtifact(buildExecution, artifacts, artifactSuffix, FETCH_ALL_LINES); + } + + private Optional<String> fetchArtifact( + SmartJson buildExecution, JsonArray artifacts, String artifactSuffix, int maxLines) { StringBuilder artifactBody = new StringBuilder(); Optional<SmartJson> verArtifactJson = findArtifact(artifacts, artifactSuffix); if (verArtifactJson.isPresent()) { @@ -223,7 +247,9 @@ try (BufferedReader reader = new BufferedReader(gsonProvider.get().getReader(versionUrl), 4096)) { String line; - while ((line = reader.readLine()) != null) { + for (int numLines = 0; + (maxLines == 0 || numLines < maxLines) && (line = reader.readLine()) != null; + numLines++) { if (artifactBody.length() > 0) { artifactBody.append("\n"); } @@ -252,7 +278,7 @@ @VisibleForTesting Optional<SmartJson> tryGetJson(String url) { try { - return Optional.of(gsonProvider.get().get(url)); + return Optional.ofNullable(gsonProvider.get().get(url)); } catch (JsonSyntaxException jse) { logger.atWarning().withCause(jse).log("Invalid JSON from %s", url); } catch (IOException e) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/manager/repository/PluginInfo.java b/src/main/java/com/googlesource/gerrit/plugins/manager/repository/PluginInfo.java index ac30a64..a2683f5 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/manager/repository/PluginInfo.java +++ b/src/main/java/com/googlesource/gerrit/plugins/manager/repository/PluginInfo.java
@@ -14,6 +14,7 @@ package com.googlesource.gerrit.plugins.manager.repository; +import com.google.gerrit.common.Nullable; import com.google.gerrit.extensions.restapi.Url; public class PluginInfo { @@ -23,13 +24,21 @@ public final String version; public final String sha1; public final String url; + public final String license; - public PluginInfo(String name, String description, String version, String sha1, String url) { + public PluginInfo( + String name, + String description, + String version, + String sha1, + String url, + @Nullable String license) { this.id = Url.encode(name); this.name = name; this.description = description; this.version = version; this.sha1 = sha1; this.url = url; + this.license = license; } }
diff --git a/src/main/resources/static/css/style.css b/src/main/resources/static/css/style.css index fbf00be..35cb664 100644 --- a/src/main/resources/static/css/style.css +++ b/src/main/resources/static/css/style.css
@@ -64,6 +64,57 @@ border-radius: 0.5rem; flex-shrink: 0; } +.license-icon-wrapper { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + background-color: var(--bg-light); + color: var(--primary-blue); + border-radius: 0.5rem; + flex-shrink: 0; +} +/* Push the footer to the bottom and space children horizontally */ +.feature-footer { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: auto; + width: 100%; +} + +/* Updated License Container */ +.feature-license { + position: relative; + display: inline-flex; + cursor: help; +} + +/* Tooltip base state (Hidden) */ +.feature-license .license-popup { + visibility: hidden; + opacity: 0; + position: absolute; + bottom: 120%; + right: 0; + background-color: var(--primary-blue); + color: white; + padding: 0.4rem 0.75rem; + border-radius: 0.5rem; + font-size: 0.75rem; + white-space: nowrap; + pointer-events: none; + transition: opacity 0.2s ease, visibility 0.2s ease; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + z-index: 10; +} + +/* Tooltip hover state (Visible) */ +.feature-license:hover .license-popup { + visibility: visible; + opacity: 1; +} .feature-title { margin-bottom: 0; }
diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 7f3468c..0a8cb3b 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html
@@ -79,28 +79,37 @@ <p class="feature-text">{{prop.description.split('.')[0]}}</p> - <div class="feature-actions"> - <!-- Status Labels --> - <span id="installing-{{prop.id}}" class="status-label status-default hidden">Installing...</span> - <span id="installed-{{prop.id}}" - class="status-label status-success {{ (prop.version != '' && prop.update_version == '') ? '':'hidden' }}">Up to date</span> - <span id="failed-{{prop.id}}" class="status-label status-warning hidden">Failed</span> + <div class="feature-footer"> + <div class="feature-actions"> + <!-- Status Labels --> + <span id="installing-{{prop.id}}" class="status-label status-default hidden">Installing...</span> + <span id="installed-{{prop.id}}" + class="status-label status-success {{ (prop.version != '' && prop.update_version == '') ? '':'hidden' }}">Up to date</span> + <span id="failed-{{prop.id}}" class="status-label status-warning hidden">Failed</span> - <!-- Action Buttons --> - <button id="install-{{prop.id}}" type="button" - class="install-btn card-btn {{ (prop.version == '' && prop.update_version != undefined) ? '':'hidden' }}" - ng-click="install(prop.id,prop.url)"> - Install - </button> + <!-- Action Buttons --> + <button id="install-{{prop.id}}" type="button" + class="install-btn card-btn {{ (prop.version == '' && prop.update_version != undefined) ? '':'hidden' }}" + ng-click="install(prop.id,prop.url)"> + Install + </button> - <button id="upgrade-{{prop.id}}" type="button" - class="install-btn card-btn {{ (prop.version != '' && prop.update_version != '') ? '':'hidden' }}" - style="{{ (prop.version != '' && prop.update_version != '') ? 'background-color: #f97316; box-shadow: 0 4px 6px -1px rgba(249, 115, 22, 0.4);' : '' }}" - onmouseover="this.style.backgroundColor='#ea580c'" - onmouseout="this.style.backgroundColor='#f97316'" - ng-click="install(prop.id,prop.url)"> - Upgrade - </button> + <button id="upgrade-{{prop.id}}" type="button" + class="install-btn card-btn {{ (prop.version != '' && prop.update_version != '') ? '':'hidden' }}" + style="{{ (prop.version != '' && prop.update_version != '') ? 'background-color: #f97316; box-shadow: 0 4px 6px -1px rgba(249, 115, 22, 0.4);' : '' }}" + onmouseover="this.style.backgroundColor='#ea580c'" + onmouseout="this.style.backgroundColor='#f97316'" + ng-click="install(prop.id,prop.url)"> + Upgrade + </button> + </div> + + <div class="feature-license" ng-if="prop.license && prop.license.length > 0"> + <div class="license-icon-wrapper" tabindex="0" aria-label="{{ prop.license }}"> + <svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-law mr-2" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8.75.75V2h.985c.304 0 .603.08.867.231l1.29.736c.038.022.08.033.124.033h2.234a.75.75 0 0 1 0 1.5h-.427l2.111 4.692a.75.75 0 0 1-.154.838l-.53-.53.529.531-.001.002-.002.002-.006.006-.006.005-.01.01-.045.04c-.21.176-.441.327-.686.45C14.556 10.78 13.88 11 13 11a4.498 4.498 0 0 1-2.023-.454 3.544 3.544 0 0 1-.686-.45l-.045-.04-.016-.015-.006-.006-.004-.004v-.001a.75.75 0 0 1-.154-.838L12.178 4.5h-.162c-.305 0-.604-.079-.868-.231l-1.29-.736a.245.245 0 0 0-.124-.033H8.75V13h2.5a.75.75 0 0 1 0 1.5h-6.5a.75.75 0 0 1 0-1.5h2.5V3.5h-.984a.245.245 0 0 0-.124.033l-1.289.737c-.265.15-.564.23-.869.23h-.162l2.112 4.692a.75.75 0 0 1-.154.838l-.53-.53.529.531-.001.002-.002.002-.006.006-.016.015-.045.04c-.21.176-.441.327-.686.45C4.556 10.78 3.88 11 3 11a4.498 4.498 0 0 1-2.023-.454 3.544 3.544 0 0 1-.686-.45l-.045-.04-.016-.015-.006-.006-.004-.004v-.001a.75.75 0 0 1-.154-.838L2.178 4.5H1.75a.75.75 0 0 1 0-1.5h2.234a.249.249 0 0 0 .125-.033l1.288-.737c.265-.15.564-.23.869-.23h.984V.75a.75.75 0 0 1 1.5 0Zm2.945 8.477c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L13 6.327Zm-10 0c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L3 6.327Z"></path></svg> + </div> + <span class="license-popup">{{ prop.license }}</span> + </div> </div> </div> </div>
diff --git a/src/main/resources/static/js/plugin-manager.js b/src/main/resources/static/js/plugin-manager.js index 3b1e5a4..20a8d6c 100644 --- a/src/main/resources/static/js/plugin-manager.js +++ b/src/main/resources/static/js/plugin-manager.js
@@ -63,18 +63,20 @@ version : plugin.version, sha1 : '', url : plugin.url, - update_version : '' + update_version : '', + license: plugin.license, }); } else { plugins.list[currPluginIdx] = { - id : plugin.id, - description : plugin.description, - index_url : plugin.index_url, - version : plugin.version, - sha1 : '', - url : plugin.url, - update_version : '' - } + id: plugin.id, + description: plugin.description, + index_url: plugin.index_url, + version: plugin.version, + sha1: "", + url: plugin.url, + update_version: "", + license: plugin.license, + }; } } }); @@ -110,6 +112,7 @@ currPlugin.sha1 = plugin.sha1; currPlugin.url = plugin.url; currPlugin.description = plugin.description; + currPlugin.license = plugin.license; if (currRow < 0) { plugins.list.push(currPlugin);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/manager/repository/JenkinsCiPluginsRepositoryTest.java b/src/test/java/com/googlesource/gerrit/plugins/manager/repository/JenkinsCiPluginsRepositoryTest.java new file mode 100644 index 0000000..c30f1ba --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/manager/repository/JenkinsCiPluginsRepositoryTest.java
@@ -0,0 +1,224 @@ +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.manager.repository; + +import static com.google.common.truth.Truth.assertThat; +import static com.googlesource.gerrit.plugins.manager.repository.JenkinsCiPluginsRepository.LICENSE_FILE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import com.google.common.collect.Iterables; +import com.google.gerrit.common.Nullable; +import com.google.gson.JsonObject; +import com.google.inject.util.Providers; +import com.googlesource.gerrit.plugins.manager.PluginManagerConfig; +import com.googlesource.gerrit.plugins.manager.gson.SmartGson; +import com.googlesource.gerrit.plugins.manager.gson.SmartJson; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.apache.commons.lang3.ArrayUtils; +import org.jspecify.annotations.NonNull; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class JenkinsCiPluginsRepositoryTest { + private static final String TEST_GERRIT_VERSION = "3.0"; + private static final String TEST_JENKINS_URL = "http://jenkins.test.localdomain"; + private static final String JENKINS_JSON_API = "/api/json"; + private static final String JENKINS_JOB_SUCCESS_COLOR = "blue"; + + private static final String TEST_JAR_PLUGIN_NAME = "test-jar-plugin"; + private static final String TEST_JS_PLUGIN_NAME = "test-js-plugin"; + private static final String TEST_JENKINS_JOB_BUILD_SHA1 = + "59e8a0d32c1252f7df949d9377ceab0b39d6339f"; + private static final String FOO_LICENSE = + """ + Foo License 2.0 + April 2005 + This is foo + """; + private static final String BAR_LICENSE = + """ + Bar License 2.0 + April 2006 + This is foo + """; + private static final String FOO_LICENSE_FIRST_TWO_LINES = + FOO_LICENSE.lines().limit(2).collect(Collectors.joining("\n")); + private static final String BAR_LICENSE_FIRST_TWO_LINES = + BAR_LICENSE.lines().limit(2).collect(Collectors.joining("\n")); + private static final String TEST_PLUGIN_VERSION_1_0 = "v1.0"; + private static final String TEST_PLUGIN_DESCRIPTION = "Test plugin"; + + @Mock SmartGson gsonMock; + + @Mock PluginManagerConfig configMock; + + private JenkinsCiPluginsRepository.View testView; + + @Before + public void setUp() throws IOException { + testView = new JenkinsCiPluginsRepository.View(); + testView.jobs = new JenkinsCiPluginsRepository.Job[0]; + when(gsonMock.get(any(String.class), eq(JenkinsCiPluginsRepository.View.class))) + .thenReturn(testView); + when(configMock.getJenkinsUrl()).thenReturn(TEST_JENKINS_URL); + } + + @Test + public void noArtifactsPublished_listOfPluginShouldBeEmpty() throws IOException { + JenkinsCiPluginsRepository pluginRepo = + new JenkinsCiPluginsRepository(Providers.of(gsonMock), configMock); + assertThat(pluginRepo.list(TEST_GERRIT_VERSION)).isEmpty(); + } + + @Test + public void artifactPublishedWithLicense_listOfPluginsShouldHaveOnePluginWithLicense() + throws IOException { + addPluginToJenkinsCiMock( + TEST_JAR_PLUGIN_NAME, "jar", TEST_PLUGIN_VERSION_1_0, TEST_PLUGIN_DESCRIPTION, FOO_LICENSE); + addPluginToJenkinsCiMock( + TEST_JS_PLUGIN_NAME, "js", TEST_PLUGIN_VERSION_1_0, TEST_PLUGIN_DESCRIPTION, BAR_LICENSE); + + JenkinsCiPluginsRepository pluginRepo = + new JenkinsCiPluginsRepository(Providers.of(gsonMock), configMock); + + List<PluginInfo> pluginsList = pluginRepo.list(TEST_GERRIT_VERSION); + assertThat(pluginsList).hasSize(2); + + PluginInfo jarPluginInfo = getPluginWithName(pluginsList, TEST_JAR_PLUGIN_NAME); + assertThat(jarPluginInfo).isNotNull(); + assertPluginInfoDetails( + jarPluginInfo, + TEST_PLUGIN_VERSION_1_0, + TEST_PLUGIN_DESCRIPTION, + FOO_LICENSE_FIRST_TWO_LINES); + + PluginInfo jsPluginInfo = getPluginWithName(pluginsList, TEST_JS_PLUGIN_NAME); + assertThat(jsPluginInfo).isNotNull(); + assertPluginInfoDetails( + jsPluginInfo, + TEST_PLUGIN_VERSION_1_0, + TEST_PLUGIN_DESCRIPTION, + BAR_LICENSE_FIRST_TWO_LINES); + } + + @Test + public void artifactPublishedWithoutLicense_pluginLicenseShouldBeNull() throws IOException { + addPluginToJenkinsCiMock( + TEST_JAR_PLUGIN_NAME, "jar", TEST_PLUGIN_VERSION_1_0, TEST_PLUGIN_DESCRIPTION, null); + + PluginInfo plugin = + new JenkinsCiPluginsRepository(Providers.of(gsonMock), configMock) + .list(TEST_GERRIT_VERSION) + .getFirst(); + + assertThat(plugin.license).isNull(); + } + + private static @Nullable PluginInfo getPluginWithName( + List<PluginInfo> pluginsList, String pluginName) { + return Iterables.tryFind(pluginsList, pi -> pi.name.equals(pluginName)).orNull(); + } + + private static void assertPluginInfoDetails( + PluginInfo pluginInfo, String pluginVersion, String pluginDescription, String pluginLicense) { + assertThat(pluginInfo.version).isEqualTo(pluginVersion); + assertThat(pluginInfo.description).isEqualTo(pluginDescription); + assertThat(pluginInfo.license).isEqualTo(pluginLicense); + } + + private void addPluginToJenkinsCiMock( + String pluginName, + String pluginFileExtension, + String pluginVersion, + String pluginDescription, + @Nullable String pluginLicense) + throws IOException { + JenkinsCiPluginsRepository.Job testPluginJob = new JenkinsCiPluginsRepository.Job(); + testPluginJob.color = JENKINS_JOB_SUCCESS_COLOR; + testPluginJob.name = pluginName + "-job"; + testPluginJob.url = TEST_JENKINS_URL + "/job/" + testPluginJob.name; + + testView.jobs = ArrayUtils.addAll(testView.jobs, testPluginJob); + + String testPluginJobLastSuccessfulBuild = testPluginJob.url + "/lastSuccessfulBuild"; + + when(gsonMock.get(eq(testPluginJob.url + JENKINS_JSON_API))) + .thenReturn( + SmartJson.of( + Map.of("lastSuccessfulBuild", Map.of("url", testPluginJobLastSuccessfulBuild)))); + + String pluginVersionResourcePath = pluginName + "." + pluginFileExtension + "-version"; + String pluginJsonResourcePath = pluginName + ".json"; + SmartJson testBuildDetailJson = + SmartJson.of( + Map.of( + "artifacts", + List.of( + jsonArtifact(pluginName, pluginName + "." + pluginFileExtension), + jsonArtifact(pluginName, pluginVersionResourcePath), + jsonArtifact(pluginName, pluginJsonResourcePath), + jsonArtifact(pluginName, LICENSE_FILE)), + "url", + testPluginJobLastSuccessfulBuild, + "actions", + List.of(Map.of("lastBuiltRevision", Map.of("SHA1", TEST_JENKINS_JOB_BUILD_SHA1))))); + when(gsonMock.get(eq(testPluginJobLastSuccessfulBuild + JENKINS_JSON_API))) + .thenReturn(testBuildDetailJson); + + when(gsonMock.getReader( + getArtifactUrl( + testPluginJobLastSuccessfulBuild, pluginName, pluginVersionResourcePath))) + .thenReturn(inputStreamReaderOf(pluginVersion)); + if (pluginLicense != null) { + when(gsonMock.getReader( + getArtifactUrl(testPluginJobLastSuccessfulBuild, pluginName, LICENSE_FILE))) + .thenReturn(inputStreamReaderOf(pluginLicense)); + } + when(gsonMock.get( + getArtifactUrl(testPluginJobLastSuccessfulBuild, pluginName, pluginJsonResourcePath))) + .thenReturn(SmartJson.of(Map.of("description", pluginDescription))); + } + + private static @NonNull String getArtifactUrl( + String lastSuccessfulBuildUrl, String pluginName, String artifactPath) { + return lastSuccessfulBuildUrl + "/artifact/" + pluginName + "/" + artifactPath; + } + + private InputStreamReader inputStreamReaderOf(String streamContent) { + return new InputStreamReader( + new ByteArrayInputStream(streamContent.getBytes(StandardCharsets.UTF_8)), + StandardCharsets.UTF_8); + } + + private JsonObject jsonArtifact(String pluginName, String artifactName) { + JsonObject artifactJson = new JsonObject(); + artifactJson.addProperty("displayPath", artifactName); + artifactJson.addProperty("fileName", artifactName); + artifactJson.addProperty("relativePath", pluginName + "/" + artifactName); + return artifactJson; + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/manager/repository/PluginsRepositoryTest.java b/src/test/java/com/googlesource/gerrit/plugins/manager/repository/PluginsRepositoryTest.java index 0e14aa1..1ac70a3 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/manager/repository/PluginsRepositoryTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/manager/repository/PluginsRepositoryTest.java
@@ -29,6 +29,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; +import java.util.stream.Collectors; import org.junit.Test; public class PluginsRepositoryTest { @@ -58,6 +59,8 @@ assertThat(plugins.stream().map(p -> p.name).sorted().collect(toList())) .containsExactlyElementsIn(GERRIT_CORE_PLUGINS) .inOrder(); + assertThat(plugins.stream().map(p -> p.license).collect(Collectors.toSet())) + .containsExactly(CorePluginsRepository.GERRIT_LICENSE); } @Test