@@ -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)); | |||
} |
@@ -24,6 +24,7 @@ | |||
"POL" | |||
], | |||
"version": "6.2", | |||
"versionEOL": "2025-01-01", | |||
"productionDatabase": true, | |||
"canAdmin": false, | |||
"standalone": true, |
@@ -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}; | |||
} | |||
} |
@@ -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() | |||
} |
@@ -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)); | |||
} | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |