diff options
6 files changed, 114 insertions, 53 deletions
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ui/ws/GlobalAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ui/ws/GlobalAction.java index aa7ab0590a2..5ac651b5469 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ui/ws/GlobalAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ui/ws/GlobalAction.java @@ -28,9 +28,11 @@ import org.sonar.api.config.Configuration; import org.sonar.api.platform.Server; import org.sonar.api.resources.ResourceType; import org.sonar.api.resources.ResourceTypes; +import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService.NewController; +import org.sonar.api.utils.System2; import org.sonar.api.utils.text.JsonWriter; import org.sonar.api.web.page.Page; import org.sonar.core.documentation.DocumentationLinkGenerator; @@ -48,6 +50,7 @@ import org.sonar.server.user.UserSession; import static org.sonar.api.CoreProperties.DEVELOPER_AGGREGATED_INFO_DISABLED; import static org.sonar.api.CoreProperties.RATING_GRID; +import static org.sonar.api.internal.MetadataLoader.loadSqVersionEol; import static org.sonar.core.config.WebConstants.SONAR_LF_ENABLE_GRAVATAR; import static org.sonar.core.config.WebConstants.SONAR_LF_GRAVATAR_SERVER_URL; import static org.sonar.core.config.WebConstants.SONAR_LF_LOGO_URL; @@ -80,9 +83,9 @@ public class GlobalAction implements NavigationWsAction, Startable { private final DocumentationLinkGenerator documentationLinkGenerator; public GlobalAction(PageRepository pageRepository, Configuration config, ResourceTypes resourceTypes, Server server, - NodeInformation nodeInformation, DbClient dbClient, UserSession userSession, PlatformEditionProvider editionProvider, - WebAnalyticsLoader webAnalyticsLoader, IssueIndexSyncProgressChecker issueIndexSyncChecker, - DefaultAdminCredentialsVerifier defaultAdminCredentialsVerifier, DocumentationLinkGenerator documentationLinkGenerator) { + NodeInformation nodeInformation, DbClient dbClient, UserSession userSession, PlatformEditionProvider editionProvider, + WebAnalyticsLoader webAnalyticsLoader, IssueIndexSyncProgressChecker issueIndexSyncChecker, + DefaultAdminCredentialsVerifier defaultAdminCredentialsVerifier, DocumentationLinkGenerator documentationLinkGenerator) { this.pageRepository = pageRepository; this.config = config; this.resourceTypes = resourceTypes; @@ -115,7 +118,8 @@ public class GlobalAction implements NavigationWsAction, Startable { .setHandler(this) .setInternal(true) .setResponseExample(getClass().getResource("global-example.json")) - .setSince("5.2"); + .setSince("5.2") + .setChangelog(new Change("10.5", "Field 'versionEOL' added, to indicate the end of support of installed version.")); } @Override @@ -128,6 +132,7 @@ public class GlobalAction implements NavigationWsAction, Startable { writeDeprecatedLogoProperties(json); writeQualifiers(json); writeVersion(json); + writeVersionEol(json); writeDatabaseProduction(json); writeInstanceUsesDefaultAdminCredentials(json); editionProvider.get().ifPresent(e -> json.prop("edition", e.name().toLowerCase(Locale.ENGLISH))); @@ -179,6 +184,10 @@ public class GlobalAction implements NavigationWsAction, Startable { json.prop("version", displayVersion); } + private void writeVersionEol(JsonWriter json) { + json.prop("versionEOL", loadSqVersionEol(System2.INSTANCE)); + } + private void writeDatabaseProduction(JsonWriter json) { json.prop("productionDatabase", !dbClient.getDatabase().getDialect().getId().equals(H2.ID)); } diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/ui/ws/global-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/ui/ws/global-example.json index d4fe2343741..c3105e83b1b 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/ui/ws/global-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/ui/ws/global-example.json @@ -24,6 +24,7 @@ "POL" ], "version": "6.2", + "versionEOL": "2025-01-01", "productionDatabase": true, "canAdmin": false, "standalone": true, diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java index eebd0f7aab8..9faee23bd61 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java @@ -19,10 +19,13 @@ */ package org.sonar.server.ui.ws; +import java.util.Objects; import java.util.Optional; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.MockedStatic; import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.internal.MetadataLoader; import org.sonar.api.platform.Server; import org.sonar.api.resources.ResourceType; import org.sonar.api.resources.ResourceTypeTree; @@ -51,13 +54,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; import static org.sonar.test.JsonAssert.assertJson; -public class GlobalActionTest { +class GlobalActionTest { - @Rule - public UserSessionRule userSession = UserSessionRule.standalone(); + @RegisterExtension + private final UserSessionRule userSession = UserSessionRule.standalone(); private final MapSettings settings = new MapSettings(); @@ -73,7 +77,7 @@ public class GlobalActionTest { private WsActionTester ws; @Test - public void empty_call() { + void empty_call() { init(); assertJson(call()).isSimilarTo("{" + @@ -84,8 +88,8 @@ public class GlobalActionTest { } @Test - public void return_qualifiers() { - init(new Page[] {}, new ResourceTypeTree[] { + void return_qualifiers() { + init(new Page[]{}, new ResourceTypeTree[]{ ResourceTypeTree.builder() .addType(ResourceType.builder("POL").build()) .addType(ResourceType.builder("LOP").build()) @@ -104,7 +108,7 @@ public class GlobalActionTest { } @Test - public void return_settings() { + void return_settings() { settings.setProperty("sonar.lf.logoUrl", "http://example.com/my-custom-logo.png"); settings.setProperty("sonar.lf.logoWidthPx", 135); settings.setProperty("sonar.lf.gravatarServerUrl", "https://secure.gravatar.com/avatar/{EMAIL_MD5}.jpg?s={SIZE}&d=identicon"); @@ -130,7 +134,7 @@ public class GlobalActionTest { } @Test - public void return_developer_info_disabled_setting() { + void return_developer_info_disabled_setting() { init(); settings.setProperty("sonar.developerAggregatedInfo.disabled", true); @@ -142,7 +146,7 @@ public class GlobalActionTest { } @Test - public void return_deprecated_logo_settings() { + void return_deprecated_logo_settings() { init(); settings.setProperty("sonar.lf.logoUrl", "http://example.com/my-custom-logo.png"); settings.setProperty("sonar.lf.logoWidthPx", 135); @@ -158,8 +162,8 @@ public class GlobalActionTest { } @Test - public void the_returned_global_pages_do_not_include_administration_pages() { - init(createPages(), new ResourceTypeTree[] {}); + void the_returned_global_pages_do_not_include_administration_pages() { + init(createPages(), new ResourceTypeTree[]{}); assertJson(call()).isSimilarTo("{" + " \"globalPages\": [" + @@ -176,7 +180,7 @@ public class GlobalActionTest { } @Test - public void return_sonarqube_version() { + void return_sonarqube_version() { init(); when(server.getVersion()).thenReturn("6.2"); @@ -186,7 +190,16 @@ public class GlobalActionTest { } @Test - public void functional_version_when_4_digits() { + void execute_shouldReturnVersionEol() { + init(); + try (MockedStatic<MetadataLoader> mocked = mockStatic(MetadataLoader.class)) { + mocked.when(() -> MetadataLoader.loadSqVersionEol(any())).thenReturn("2025-01-01"); + assertThat(call()).contains("\"versionEOL\":\"2025-01-01\""); + } + } + + @Test + void functional_version_when_4_digits() { init(); when(server.getVersion()).thenReturn("6.3.1.1234"); @@ -196,7 +209,7 @@ public class GlobalActionTest { } @Test - public void functional_version_when_third_digit_is_0() { + void functional_version_when_third_digit_is_0() { init(); when(server.getVersion()).thenReturn("6.3.0.1234"); @@ -206,7 +219,7 @@ public class GlobalActionTest { } @Test - public void return_if_production_database_or_not() { + void return_if_production_database_or_not() { init(); when(dbClient.getDatabase().getDialect()).thenReturn(new PostgreSql()); @@ -216,7 +229,7 @@ public class GlobalActionTest { } @Test - public void return_need_issue_sync() { + void return_need_issue_sync() { init(); when(indexSyncProgressChecker.isIssueSyncInProgress(any())).thenReturn(true); assertJson(call()).isSimilarTo("{\"needIssueSync\": true}"); @@ -226,7 +239,7 @@ public class GlobalActionTest { } @Test - public void instance_uses_default_admin_credentials() { + void instance_uses_default_admin_credentials() { init(); when(defaultAdminCredentialsVerifier.hasDefaultCredentialUser()).thenReturn(true); @@ -242,7 +255,7 @@ public class GlobalActionTest { } @Test - public void standalone_flag() { + void standalone_flag() { init(); userSession.logIn().setSystemAdministrator(); when(nodeInformation.isStandalone()).thenReturn(true); @@ -251,7 +264,7 @@ public class GlobalActionTest { } @Test - public void not_standalone_flag() { + void not_standalone_flag() { init(); userSession.logIn().setSystemAdministrator(); when(nodeInformation.isStandalone()).thenReturn(false); @@ -260,14 +273,14 @@ public class GlobalActionTest { } @Test - public void test_example_response() { + void test_example_response() { settings.setProperty("sonar.lf.logoUrl", "http://example.com/my-custom-logo.png"); settings.setProperty("sonar.lf.logoWidthPx", 135); settings.setProperty("sonar.lf.gravatarServerUrl", "http://some-server.tld/logo.png"); settings.setProperty("sonar.lf.enableGravatar", true); settings.setProperty("sonar.updatecenter.activate", false); settings.setProperty("sonar.technicalDebt.ratingGrid", "0.05,0.1,0.2,0.5"); - init(createPages(), new ResourceTypeTree[] { + init(createPages(), new ResourceTypeTree[]{ ResourceTypeTree.builder() .addType(ResourceType.builder("POL").build()) .addType(ResourceType.builder("LOP").build()) @@ -285,12 +298,18 @@ public class GlobalActionTest { when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.COMMUNITY)); when(documentationLinkGenerator.getDocumentationLink(null)).thenReturn("http://docs.example.com/10.0"); - String result = call(); - assertJson(result).isSimilarTo(ws.getDef().responseExampleAsString()); + + try (MockedStatic<MetadataLoader> mocked = mockStatic(MetadataLoader.class)) { + mocked.when(() -> MetadataLoader.loadSqVersionEol(any())).thenReturn("2025-01-01"); + + String result = call(); + + assertJson(result).isSimilarTo(Objects.requireNonNull(ws.getDef().responseExampleAsString())); + } } @Test - public void edition_is_not_returned_if_not_defined() { + void edition_is_not_returned_if_not_defined() { init(); when(editionProvider.get()).thenReturn(Optional.empty()); @@ -299,7 +318,7 @@ public class GlobalActionTest { } @Test - public void edition_is_returned_if_defined() { + void edition_is_returned_if_defined() { init(); when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER)); @@ -308,7 +327,7 @@ public class GlobalActionTest { } @Test - public void web_analytics_js_path_is_not_returned_if_not_defined() { + void web_analytics_js_path_is_not_returned_if_not_defined() { init(); when(webAnalyticsLoader.getUrlPathToJs()).thenReturn(Optional.empty()); @@ -317,7 +336,7 @@ public class GlobalActionTest { } @Test - public void web_analytics_js_path_is_returned_if_defined() { + void web_analytics_js_path_is_returned_if_defined() { init(); String path = "static/googleanalytics/analytics.js"; when(webAnalyticsLoader.getUrlPathToJs()).thenReturn(Optional.of(path)); @@ -327,7 +346,7 @@ public class GlobalActionTest { } @Test - public void call_shouldReturnDocumentationUrl() { + void call_shouldReturnDocumentationUrl() { init(); String url = "https://docs.sonarsource.com/sonarqube/10.0"; when(documentationLinkGenerator.getDocumentationLink(null)).thenReturn(url); @@ -337,7 +356,7 @@ public class GlobalActionTest { } private void init() { - init(new org.sonar.api.web.page.Page[] {}, new ResourceTypeTree[] {}); + init(new org.sonar.api.web.page.Page[]{}, new ResourceTypeTree[]{}); } private void init(org.sonar.api.web.page.Page[] pages, ResourceTypeTree[] resourceTypeTrees) { @@ -348,7 +367,7 @@ public class GlobalActionTest { when(pluginRepository.getPluginInfo(any())).thenReturn(new PluginInfo("unused").setVersion(Version.create("1.0"))); CoreExtensionRepository coreExtensionRepository = mock(CoreExtensionRepository.class); when(coreExtensionRepository.isInstalled(any())).thenReturn(false); - PageRepository pageRepository = new PageRepository(pluginRepository, coreExtensionRepository, new PageDefinition[] {context -> { + PageRepository pageRepository = new PageRepository(pluginRepository, coreExtensionRepository, new PageDefinition[]{context -> { for (Page page : pages) { context.addPage(page); } @@ -370,6 +389,6 @@ public class GlobalActionTest { Page anotherPage = Page.builder("another_plugin/page").setName("My Another Page").build(); Page adminPage = Page.builder("my_plugin/admin_page").setName("Admin Page").setAdmin(true).build(); - return new Page[] {page, anotherPage, adminPage}; + return new Page[]{page, anotherPage, adminPage}; } } diff --git a/sonar-plugin-api-impl/build.gradle b/sonar-plugin-api-impl/build.gradle index 8b7216073ed..8dc960886ce 100644 --- a/sonar-plugin-api-impl/build.gradle +++ b/sonar-plugin-api-impl/build.gradle @@ -21,9 +21,14 @@ dependencies { testImplementation 'com.google.guava:guava' testImplementation 'com.tngtech.java:junit-dataprovider' testImplementation 'junit:junit' + testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.assertj:assertj-core' testImplementation 'org.mockito:mockito-core' + testImplementation 'org.mockito:mockito-junit-jupiter' testImplementation 'org.sonarsource.api.plugin:sonar-plugin-api-test-fixtures' + + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' } import org.apache.tools.ant.filters.ReplaceTokens @@ -47,3 +52,7 @@ publishing { } } } +test { + // Enabling the JUnit Platform (see https://github.com/junit-team/junit5-samples/tree/master/junit5-migration-gradle) + useJUnitPlatform() +} diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/internal/MetadataLoader.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/internal/MetadataLoader.java index ad8e12b610a..64e6203fbd2 100644 --- a/sonar-plugin-api-impl/src/main/java/org/sonar/api/internal/MetadataLoader.java +++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/internal/MetadataLoader.java @@ -28,6 +28,7 @@ import org.sonar.api.SonarEdition; import org.sonar.api.utils.System2; import org.sonar.api.utils.Version; +import static java.lang.String.format; import static org.apache.commons.lang3.StringUtils.trimToEmpty; /** @@ -40,6 +41,8 @@ public class MetadataLoader { private static final String SQ_VERSION_FILE_PATH = "/sq-version.txt"; private static final String SONAR_API_VERSION_FILE_PATH = "/sonar-api-version.txt"; private static final String EDITION_FILE_PATH = "/sonar-edition.txt"; + private static final String SQ_VERSION_EOL_FILE_PATH = "/sq-version-eol.txt"; + public static final String CAN_NOT_LOAD_FROM_CLASSPATH = "Can not load %s from classpath"; private MetadataLoader() { // only static methods @@ -48,18 +51,25 @@ public class MetadataLoader { public static Version loadApiVersion(System2 system) { return getVersion(system, SONAR_API_VERSION_FILE_PATH); } + public static Version loadSQVersion(System2 system) { return getVersion(system, SQ_VERSION_FILE_PATH); } + public static String loadSqVersionEol(System2 system) { + return getParamFromFile(system, SQ_VERSION_EOL_FILE_PATH); + } + private static Version getVersion(System2 system, String versionFilePath) { - URL url = system.getResource(versionFilePath); + return Version.parse(getParamFromFile(system, versionFilePath)); + } - try (Scanner scanner = new Scanner(url.openStream(), StandardCharsets.UTF_8.name())) { - String versionInFile = scanner.nextLine(); - return Version.parse(versionInFile); + private static String getParamFromFile(System2 system, String filePath) { + URL url = system.getResource(filePath); + try (Scanner scanner = new Scanner(url.openStream(), StandardCharsets.UTF_8)) { + return scanner.nextLine(); } catch (IOException e) { - throw new IllegalStateException("Can not load " + versionFilePath + " from classpath ", e); + throw new IllegalStateException(format(CAN_NOT_LOAD_FROM_CLASSPATH, filePath), e); } } @@ -68,11 +78,11 @@ public class MetadataLoader { if (url == null) { return SonarEdition.COMMUNITY; } - try (Scanner scanner = new Scanner(url.openStream(), StandardCharsets.UTF_8.name())) { + try (Scanner scanner = new Scanner(url.openStream(), StandardCharsets.UTF_8)) { String editionInFile = scanner.nextLine(); return parseEdition(editionInFile); } catch (IOException e) { - throw new IllegalStateException("Can not load " + EDITION_FILE_PATH + " from classpath", e); + throw new IllegalStateException(format(CAN_NOT_LOAD_FROM_CLASSPATH, EDITION_FILE_PATH), e); } } @@ -81,7 +91,7 @@ public class MetadataLoader { try { return SonarEdition.valueOf(str); } catch (IllegalArgumentException e) { - throw new IllegalStateException(String.format("Invalid edition found in '%s': '%s'", EDITION_FILE_PATH, str)); + throw new IllegalStateException(format("Invalid edition found in '%s': '%s'", EDITION_FILE_PATH, str)); } } } diff --git a/sonar-plugin-api-impl/src/test/java/org/sonar/api/internal/MetadataLoaderTest.java b/sonar-plugin-api-impl/src/test/java/org/sonar/api/internal/MetadataLoaderTest.java index 3ee4ec5df9a..017bfca8777 100644 --- a/sonar-plugin-api-impl/src/test/java/org/sonar/api/internal/MetadataLoaderTest.java +++ b/sonar-plugin-api-impl/src/test/java/org/sonar/api/internal/MetadataLoaderTest.java @@ -21,7 +21,7 @@ package org.sonar.api.internal; import java.io.File; import java.net.MalformedURLException; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.sonar.api.SonarEdition; import org.sonar.api.utils.System2; import org.sonar.api.utils.Version; @@ -32,45 +32,45 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class MetadataLoaderTest { +class MetadataLoaderTest { private final System2 system = mock(System2.class); @Test - public void load_api_version_from_file_in_classpath() { + void load_api_version_from_file_in_classpath() { Version version = MetadataLoader.loadApiVersion(System2.INSTANCE); assertThat(version).isNotNull(); assertThat(version.major()).isGreaterThanOrEqualTo(5); } @Test - public void load_sq_version_from_file_in_classpath() { + void load_sq_version_from_file_in_classpath() { Version version = MetadataLoader.loadSQVersion(System2.INSTANCE); assertThat(version).isNotNull(); assertThat(version.major()).isGreaterThanOrEqualTo(5); } @Test - public void load_edition_from_file_in_classpath() { + void load_edition_from_file_in_classpath() { SonarEdition edition = MetadataLoader.loadEdition(System2.INSTANCE); assertThat(edition).isNotNull(); } @Test - public void load_edition_defaults_to_community_if_file_not_found() throws MalformedURLException { + void load_edition_defaults_to_community_if_file_not_found() throws MalformedURLException { when(system.getResource(anyString())).thenReturn(new File("target/unknown").toURI().toURL()); SonarEdition edition = MetadataLoader.loadEdition(System2.INSTANCE); assertThat(edition).isEqualTo(SonarEdition.COMMUNITY); } @Test - public void throw_ISE_if_edition_is_invalid() { + void throw_ISE_if_edition_is_invalid() { assertThatThrownBy(() -> MetadataLoader.parseEdition("trash")) .isInstanceOf(IllegalStateException.class) .hasMessage("Invalid edition found in '/sonar-edition.txt': 'TRASH'"); } @Test - public void throw_ISE_if_fail_to_load_version() throws Exception { + void throw_ISE_if_fail_to_load_version() throws Exception { when(system.getResource(anyString())).thenReturn(new File("target/unknown").toURI().toURL()); assertThatThrownBy(() -> MetadataLoader.loadApiVersion(system)) @@ -78,4 +78,17 @@ public class MetadataLoaderTest { .hasMessageContaining("Can not load /sonar-api-version.txt from classpath"); } + @Test + void loadSqVersionEol_shouldLoadCorrectEol() { + String eol = MetadataLoader.loadSqVersionEol(System2.INSTANCE); + assertThat(eol).isNotNull(); + } + + @Test + void loadSqVersionEol_whenFileNotFound_shouldThrowException() throws MalformedURLException { + when(system.getResource(anyString())).thenReturn(new File("target/unknown").toURI().toURL()); + assertThatThrownBy(() -> MetadataLoader.loadSqVersionEol(system)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Can not load /sq-version-eol.txt from classpath"); + } } |