From a50b0ec3b194f56269ca3f218e26b8637796748b Mon Sep 17 00:00:00 2001 From: jycr Date: Tue, 29 Oct 2024 14:39:58 +0100 Subject: [PATCH] Adds SonarQube integration tests --- pom.xml | 139 ++++++ .../LaunchSonarqubeAndBuildProjectIT.java | 440 ++++++++++++++++++ .../tests/profile/ProfileBackup.java | 163 +++++++ .../tests/profile/ProfileMetadata.java | 42 ++ .../tests/profile/RuleMetadata.java | 40 ++ 5 files changed, 824 insertions(+) create mode 100644 src/it/java/io/ecocode/java/integration/tests/LaunchSonarqubeAndBuildProjectIT.java create mode 100644 src/it/java/io/ecocode/java/integration/tests/profile/ProfileBackup.java create mode 100644 src/it/java/io/ecocode/java/integration/tests/profile/ProfileMetadata.java create mode 100644 src/it/java/io/ecocode/java/integration/tests/profile/RuleMetadata.java diff --git a/pom.xml b/pom.xml index ede7da7..9c9e681 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,19 @@ 1.6.5 + + https://repo1.maven.org/maven2 + + false + + + ${sonarqube.version} + + + ${sonarjava.version} + + + @@ -147,6 +160,44 @@ 0.10.2 test + + + + org.sonarsource.orchestrator + sonar-orchestrator-junit5 + 4.9.0.1920 + test + + + org.sonarsource.java + test-classpath-reader + 8.5.0.37199 + test + + + org.sonarsource.sonarqube + sonar-ws + ${sonarqube.version} + test + + + io.github.jycr + java-data-url-handler + 0.0.1 + test + + + org.slf4j + slf4j-api + 2.0.13 + test + + + ch.qos.logback + logback-classic + 1.5.6 + test + @@ -356,6 +407,94 @@ + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.0 + + + add-integration-test-sources + process-test-sources + + add-test-source + + + + ${project.basedir}/src/it/java + + + + + add-integration-test-resources + generate-test-resources + + add-test-resource + + + + + ${project.basedir}/src/it/resources + + + true + ${project.basedir}/src/it/resources-filtered + + + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.2.5 + + + + integration-test + verify + + + + ${test-it.sonarqube.keepRunning} + ${test-it.orchestrator.artifactory.url} + ${test-it.sonarqube.version} + ${test-it.sonarqube.port} + + + ${project.baseUri}/target/${project.artifactId}-${project.version}.jar, + org.sonarsource.java|sonar-java-plugin|${test-it.sonarjava.version}, + + + + ${project.baseUri}/src/main/resources/fr/greencodeinitiative/java/ecoCode_way_profile.json, + + + + io.ecocode:ecocode-java-plugin-test-project|ecoCode Java Sonar Plugin Test Project|${project.baseUri}/src/it/test-projects/ecocode-java-plugin-test-project/pom.xml, + + + + java|ecoCode way, + + + + + + + + + + keep-running + + true + 9000 + + + diff --git a/src/it/java/io/ecocode/java/integration/tests/LaunchSonarqubeAndBuildProjectIT.java b/src/it/java/io/ecocode/java/integration/tests/LaunchSonarqubeAndBuildProjectIT.java new file mode 100644 index 0000000..f865cc1 --- /dev/null +++ b/src/it/java/io/ecocode/java/integration/tests/LaunchSonarqubeAndBuildProjectIT.java @@ -0,0 +1,440 @@ +package io.ecocode.java.integration.tests; + +import java.net.MalformedURLException; +import java.net.URI; +import java.nio.file.Path; +import java.text.MessageFormat; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Scanner; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.MavenBuild; +import com.sonar.orchestrator.container.Server; +import com.sonar.orchestrator.junit5.OrchestratorExtension; +import com.sonar.orchestrator.junit5.OrchestratorExtensionBuilder; +import com.sonar.orchestrator.locator.FileLocation; +import com.sonar.orchestrator.locator.Location; +import com.sonar.orchestrator.locator.MavenLocation; +import com.sonar.orchestrator.locator.URLLocation; +import io.ecocode.java.integration.tests.profile.ProfileBackup; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.sonarqube.ws.Issues; +import org.sonarqube.ws.Measures; +import org.sonarqube.ws.client.HttpConnector; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsClientFactories; +import org.sonarqube.ws.client.issues.SearchRequest; +import org.sonarqube.ws.client.measures.ComponentRequest; + +import static java.lang.System.Logger.Level.INFO; +import static java.util.Optional.ofNullable; +import static java.util.function.Predicate.not; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonarqube.ws.Common.RuleType.CODE_SMELL; +import static org.sonarqube.ws.Common.Severity.MINOR; + +class LaunchSonarqubeAndBuildProjectIT { + private static final System.Logger LOGGER = System.getLogger(LaunchSonarqubeAndBuildProjectIT.class.getName()); + + private static OrchestratorExtension orchestrator; + private static List analyzedProjects; + + private static void launchSonarqube() { + String orchestratorArtifactoryUrl = systemProperty("test-it.orchestrator.artifactory.url"); + String sonarqubeVersion = systemProperty("test-it.sonarqube.version"); + Optional sonarqubePort = ofNullable(System.getProperty("test-it.sonarqube.port")).map(String::trim).filter(not(String::isEmpty)); + + OrchestratorExtensionBuilder orchestratorExtensionBuilder = OrchestratorExtension + .builderEnv() + .useDefaultAdminCredentialsForBuilds(true) + .setOrchestratorProperty("orchestrator.artifactory.url", orchestratorArtifactoryUrl) + .setSonarVersion(sonarqubeVersion) + .setServerProperty("sonar.forceAuthentication", "false") + .setServerProperty("sonar.web.javaOpts", "-Xmx1G"); + + sonarqubePort.ifPresent(s -> orchestratorExtensionBuilder.setServerProperty("sonar.web.port", s)); + + additionalPluginsToInstall().forEach(orchestratorExtensionBuilder::addPlugin); + additionalProfiles().forEach(orchestratorExtensionBuilder::restoreProfileAtStartup); + + orchestrator = orchestratorExtensionBuilder.build(); + orchestrator.start(); + LOGGER.log(INFO, () -> MessageFormat.format("SonarQube server available on: {0}", orchestrator.getServer().getUrl())); + } + + @BeforeAll + static void setup() { + LOGGER.log( + INFO, + "\n" + + "====================================================================================================\n" + + "Launching SonarQube server with following JAVA System properties: {0}\n" + + "====================================================================================================\n" + , + Stream + .of( + "test-it.sonarqube.keepRunning", + "test-it.orchestrator.artifactory.url", + "test-it.sonarqube.version", + "test-it.plugins", + "test-it.additional-profile-uris", + "test-it.test-projects", + "test-it.test-project-profile-by-language" + ) + .filter(k -> System.getProperty(k) != null) + .map(k -> MessageFormat + .format( + "-D{0}=\"{1}\"", + k, + System.getProperty(k).replaceAll("\\s+", " ") + ) + ) + .collect(Collectors.joining("\n", "\n\n", "\n\n")) + ); + launchSonarqube(); + launchAnalysis(); + } + + private static void launchAnalysis() { + Server server = orchestrator.getServer(); + Map qualityProfileByLanguage = testProjectProfileByLanguage(); + + analyzedProjects = getProjectsToAnalyze(); + + analyzedProjects + .stream() + // - Prepare/create SonarQube project for the test project + .peek(projectToAnalyze -> projectToAnalyze.provisionProjectIntoServer(server)) + // - Configure the test project + .peek(projectToAnalyze -> projectToAnalyze.associateProjectToQualityProfile(server, qualityProfileByLanguage)) + .map(ProjectToAnalyze::createMavenBuild) + // - Run SonarQube Scanner on test project + .peek(p -> LOGGER.log(INFO, () -> MessageFormat.format("Running SonarQube Scanner on project: {0}", p.getPom()))) + .forEach(orchestrator::executeBuild); + } + + @Test + void test() { + String projectKey = analyzedProjects.get(0).projectKey; + + Map measures = getMeasures(projectKey); + + assertThat(ofNullable(measures.get("code_smells")).map(Measures.Measure::getValue).map(Integer::parseInt).orElse(0)) + .isGreaterThan(1); + + List projectIssues = issuesForComponent(projectKey); + assertThat(projectIssues).isNotEmpty(); + + List issuesForArrayCopyCheck = issuesForFile(projectKey, "src/main/java/fr/greencodeinitiative/java/checks/AvoidGettingSizeCollectionInForLoopIgnored.java"); + + assertThat(issuesForArrayCopyCheck) + .hasSize(1) + .first().satisfies(issue -> { + assertThat(issue.getRule()).isEqualTo("ecocode-java:EC69"); + assertThat(issue.getSeverity()).isEqualTo(MINOR); + assertThat(issue.getLine()).isEqualTo(18); + assertThat(issue.getTextRange().getStartLine()).isEqualTo(18); + assertThat(issue.getTextRange().getEndLine()).isEqualTo(18); + assertThat(issue.getTextRange().getStartOffset()).isEqualTo(15); + assertThat(issue.getTextRange().getEndOffset()).isEqualTo(27); + assertThat(issue.getMessage()).isEqualTo("Do not call a function when declaring a for-type loop"); + assertThat(issue.getDebt()).isEqualTo("5min"); + assertThat(issue.getEffort()).isEqualTo("5min"); + assertThat(issue.getType()).isEqualTo(CODE_SMELL); + }); + } + + @AfterAll + static void tearDown() { + if ("true".equalsIgnoreCase(System.getProperty("test-it.sonarqube.keepRunning"))) { + try (Scanner in = new Scanner(System.in)) { + LOGGER.log(INFO, () -> + MessageFormat.format( + "\n" + + "\n====================================================================================================" + + "\nSonarQube available at: {0} (to login: admin/admin)" + + "\n====================================================================================================" + + "\n", + orchestrator.getServer().getUrl() + ) + ); + do { + LOGGER.log(INFO, "✍ Please press CTRL+C to stop"); + } + while (!in.nextLine().isEmpty()); + } + } + if (orchestrator != null) { + orchestrator.stop(); + } + } + + private static String systemProperty(String propertyName) { + return ofNullable(System.getProperty(propertyName)) + .orElseThrow(() -> new IllegalStateException( + String.format( + "System property `%s` must be defined. See `%s` (in section: `plugin[maven-failsafe-plugin]/systemPropertyVariables`) for sample value.", + propertyName, + Path.of("pom.xml").toAbsolutePath() + ) + )); + } + + /** + * Projects to analyze + */ + private static List getProjectsToAnalyze() { + return commaSeparatedValues(systemProperty("test-it.test-projects")) + .map(projectToAnalyzeDefinition -> pipeSeparatedValues(projectToAnalyzeDefinition).collect(toList())) + .filter(projectToAnalyzeDefinition -> projectToAnalyzeDefinition.size() == 3) + .map(projectToAnalyzeDefinition -> { + // Project Key + String projectKey = projectToAnalyzeDefinition.get(0); + // Project Name + String projectName = projectToAnalyzeDefinition.get(1); + // Project POM URI + URI projectPom = URI.create(projectToAnalyzeDefinition.get(2)); + return new ProjectToAnalyze(projectPom, projectKey, projectName); + }) + .collect(toList()); + } + + private static Stream commaSeparatedValues(String value) { + return splitAndTrim(value, "\\s*,\\s*"); + } + + private static Stream pipeSeparatedValues(String value) { + return splitAndTrim(value, "\\s*\\|\\s*"); + } + + private static Stream splitAndTrim(String value, String regexSeparator) { + return Stream + .of(value.split(regexSeparator)) + .map(String::trim) + .filter(not(String::isEmpty)); + } + + private static Set additionalPluginsToInstall() { + return commaSeparatedValues(systemProperty("test-it.plugins")) + .map(LaunchSonarqubeAndBuildProjectIT::toPluginLocation) + .collect(Collectors.toSet()); + } + + private static Set additionalProfiles() { + return commaSeparatedValues(systemProperty("test-it.additional-profile-uris")) + .map(URI::create) + .map(ProfileBackup::new) + .map(ProfileBackup::profileDataUri) + .map(URLLocation::create) + .collect(Collectors.toSet()); + } + + private static Map testProjectProfileByLanguage() { + // Comma separated list of profiles to associate to each "test project" + // Syntaxe: `language:profileName` + return commaSeparatedValues(systemProperty("test-it.test-project-profile-by-language")) + .map(languageAndProfileDefinitions -> pipeSeparatedValues(languageAndProfileDefinitions).collect(toList())) + .filter(languageAndProfile -> languageAndProfile.size() == 2) + .collect(toMap( + // Language + languageAndProfile -> languageAndProfile.get(0), + // Profile name + languageAndProfile -> languageAndProfile.get(1) + )); + } + + private static Location toPluginLocation(String location) { + if (location.startsWith("file://")) { + try { + return FileLocation.of(URI.create(location).toURL()); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(e); + } + } + List pluginGAVvalues = pipeSeparatedValues(location).collect(toList()); + if (pluginGAVvalues.size() != 3) { + throw new IllegalArgumentException("Invalid plugin GAV definition (`groupId|artifactId|version`): " + location); + } + return MavenLocation.of( + // groupId + pluginGAVvalues.get(0), + // artifactId + pluginGAVvalues.get(1), + // version + pluginGAVvalues.get(2) + ); + } + + private static class ProjectToAnalyze { + private final Path pom; + private final String projectKey; + private final String projectName; + + private ProjectToAnalyze(URI pom, String projectKey, String projectName) { + this.pom = Path.of(pom); + assertThat(this.pom).isRegularFile(); + this.projectKey = projectKey; + this.projectName = projectName; + } + + public MavenBuild createMavenBuild() { + return MavenBuild.create(pom.toFile()) + .setCleanPackageSonarGoals() + .setProperty("sonar.projectKey", projectKey) + .setProperty("sonar.projectName", projectName) + .setProperty("sonar.scm.disabled", "true"); + } + + private void provisionProjectIntoServer(Server server) { + server.provisionProject(projectKey, projectName); + + } + + private void associateProjectToQualityProfile(Server server, Map qualityProfileByLanguage) { + qualityProfileByLanguage.forEach((language, profileName) -> server.associateProjectToQualityProfile(projectKey, language, profileName)); + } + } + + private static List issuesForFile(String projectKey, String file) { + return issuesForComponent(projectKey + ":" + file); + } + + private static List issuesForComponent(String componentKey) { + return newWsClient(orchestrator) + .issues() + .search(new SearchRequest().setComponentKeys(Collections.singletonList(componentKey))) + .getIssuesList(); + } + + private static Map getMeasures(String componentKey) { + List metricKeys = List.of( + "alert_status", + "blocker_violations", + "branch_coverage", + "bugs", + "class_complexity", + "classes", + "code_smells", + "cognitive_complexity", + "comment_lines", + "comment_lines_data", + "comment_lines_density", + "complexity", + "complexity_in_classes", + "complexity_in_functions", + "conditions_to_cover", + "confirmed_issues", + "coverage", + "critical_violations", + "development_cost", + "directories", + "duplicated_blocks", + "duplicated_files", + "duplicated_lines", + "duplicated_lines_density", + "duplications_data", + "effort_to_reach_maintainability_rating_a", + "executable_lines_data", + "false_positive_issues", + "file_complexity", + "file_complexity_distribution", + "files", + "function_complexity", + "function_complexity_distribution", + "functions", + "generated_lines", + "generated_ncloc", + "info_violations", + "last_commit_date", + "line_coverage", + "lines", + "lines_to_cover", + "major_violations", + "minor_violations", + "ncloc", + "ncloc_data", + "ncloc_language_distribution", + "new_blocker_violations", + "new_branch_coverage", + "new_bugs", + "new_code_smells", + "new_conditions_to_cover", + "new_coverage", + "new_critical_violations", + "new_development_cost", + "new_duplicated_blocks", + "new_duplicated_lines", + "new_duplicated_lines_density", + "new_info_violations", + "new_line_coverage", + "new_lines", + "new_lines_to_cover", + "new_maintainability_rating", + "new_major_violations", + "new_minor_violations", + "new_reliability_rating", + "new_reliability_remediation_effort", + "new_security_hotspots", + "new_security_hotspots_reviewed", + "new_security_hotspots_reviewed_status", + "new_security_hotspots_to_review_status", + "new_security_rating", + "new_security_remediation_effort", + "new_security_review_rating", + "new_technical_debt", + "new_violations", + "new_vulnerabilities", + "open_issues", + "projects", + "public_api", + "public_documented_api_density", + "public_undocumented_api", + "quality_gate_details", + "quality_profiles", + "reliability_rating", + "reliability_remediation_effort", + "reopened_issues", + "security_hotspots", + "security_hotspots_reviewed", + "security_hotspots_reviewed_status", + "security_hotspots_to_review_status", + "security_rating", + "security_remediation_effort", + "security_review_rating", + "skipped_tests", + "sqale_rating", + "statements", + "unanalyzed_c", + "unanalyzed_cpp", + "violations" + ); + return newWsClient(orchestrator) + .measures() + .component( + new ComponentRequest() + .setComponent(componentKey) + .setMetricKeys(metricKeys) + ) + .getComponent().getMeasuresList() + .stream() + .collect(Collectors.toMap(Measures.Measure::getMetric, Function.identity())); + } + + + private static WsClient newWsClient(Orchestrator orchestrator) { + return WsClientFactories.getDefault().newClient(HttpConnector.newBuilder() + .url(orchestrator.getServer().getUrl()) + .build()); + } +} \ No newline at end of file diff --git a/src/it/java/io/ecocode/java/integration/tests/profile/ProfileBackup.java b/src/it/java/io/ecocode/java/integration/tests/profile/ProfileBackup.java new file mode 100644 index 0000000..4b67287 --- /dev/null +++ b/src/it/java/io/ecocode/java/integration/tests/profile/ProfileBackup.java @@ -0,0 +1,163 @@ +package io.ecocode.java.integration.tests.profile; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.text.MessageFormat; +import java.util.Base64; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; + +/** + * Manage XML Backup file of profile based on JSON official profile. + * + *

Example, following JSON profile:

+ *
+ * {
+ *  "name": "ecoCode way",
+ *  "language": "java",
+ *  "ruleKeys": [
+ * 	    "EC1",
+ * 	    "EC2"
+ *  ]
+ * }
+ * 
+ *

may produce following XML profile:

+ *
+ * <?xml version='1.0' encoding='UTF-8'?>
+ * <profile>
+ * 	<name>ecoCode way</name>
+ * 	<language>java</language>
+ * 	<rules>
+ * 		<rule>
+ * 			<repositoryKey>ecocode-java</repositoryKey>
+ * 			<key>EC1</key>
+ * 			<type>CODE_SMELL</type>
+ * 			<priority>MINOR</priority>
+ * 			<parameters />
+ * 		</rule>
+ * 		<rule>
+ * 			<repositoryKey>ecocode-java</repositoryKey>
+ * 			<key>EC2</key>
+ * 			<type>CODE_SMELL</type>
+ * 			<priority>MINOR</priority>
+ * 			<parameters />
+ * 		</rule>
+ * 	</rules>
+ * </profile>
+ * 
+ */ +public class ProfileBackup { + private static final MessageFormat TEMPLATE_PROFIL = new MessageFormat( + "\n" + + "\n" + + " {0}\n" + + " {1}\n" + + " \n" + + " {2}\n" + + " \n" + + "\n" + ); + private static final MessageFormat TEMPLATE_RULE = new MessageFormat( + "\n" + + " {0}\n" + + " {1}\n" + + " {2}\n" + + " {3}\n" + + " \n" + + "\n" + ); + + private final ObjectMapper mapper; + private final URI jsonProfile; + + public ProfileBackup(URI jsonProfile) { + this.mapper = new ObjectMapper(); + // Ignore unknown properties + this.mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); + + this.jsonProfile = jsonProfile; + } + + private transient ProfileMetadata profileMetadata; + + private ProfileMetadata profileMetadata() { + if (profileMetadata == null) { + try (InputStream profilJsonFile = jsonProfile.toURL().openStream()) { + profileMetadata = mapper.readValue(profilJsonFile, ProfileMetadata.class); + } catch (IOException e) { + throw new RuntimeException("Unable to load JSON Profile: " + jsonProfile, e); + } + } + return profileMetadata; + } + + private RuleMetadata loadRule(String language, String ruleKey) { + try (InputStream ruleMetadataJsonFile = ClassLoader.getSystemResourceAsStream("io/ecocode/rules/" + language + "/" + ruleKey + ".json")) { + RuleMetadata result = mapper.readValue(ruleMetadataJsonFile, RuleMetadata.class); + result.setKey(ruleKey); + return result; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private String xmlProfile() throws IOException { + ProfileMetadata profileMetadata = profileMetadata(); + String language = profileMetadata.getLanguage(); + List rules = profileMetadata.getRuleKeys().stream() + .map(ruleKey -> this.loadRule(language, ruleKey)) + .collect(Collectors.toList()); + StringBuilder output = new StringBuilder(); + String repositoryKey = "ecocode-" + profileMetadata.getLanguage(); + rules.forEach(rule -> output.append( + xmlRule( + repositoryKey, + rule.getKey(), + rule.getType(), + rule.getDefaultSeverity().toUpperCase() + )) + ); + return TEMPLATE_PROFIL.format(new Object[]{ + profileMetadata.getName(), + profileMetadata.getLanguage(), + output.toString() + }); + } + + private String xmlRule(String repositoryKey, String key, String type, String priority) { + return TEMPLATE_RULE.format(new Object[]{ + repositoryKey, + key, + type, + priority + }); + } + + /** + * Get the content of XML Profil in datauri format. + */ + public URL profileDataUri() { + try { + String xmlProfileContent = xmlProfile(); + String xmlProfileBase64encoded = Base64.getEncoder().encodeToString(xmlProfileContent.getBytes()); + return new URL("data:text/xml;base64," + xmlProfileBase64encoded); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public String language() { + return profileMetadata().getLanguage(); + } + + + public String name() { + return profileMetadata().getName(); + } +} \ No newline at end of file diff --git a/src/it/java/io/ecocode/java/integration/tests/profile/ProfileMetadata.java b/src/it/java/io/ecocode/java/integration/tests/profile/ProfileMetadata.java new file mode 100644 index 0000000..85b65f4 --- /dev/null +++ b/src/it/java/io/ecocode/java/integration/tests/profile/ProfileMetadata.java @@ -0,0 +1,42 @@ +package io.ecocode.java.integration.tests.profile; + +import java.util.List; + +public class ProfileMetadata { + private String name; + private String language; + private List ruleKeys; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public List getRuleKeys() { + return ruleKeys; + } + + public void setRuleKeys(List ruleKeys) { + this.ruleKeys = ruleKeys; + } + + @Override + public String toString() { + return "ProfileMetadata{" + + "name='" + name + '\'' + + ", language='" + language + '\'' + + ", ruleKeys=" + ruleKeys + + '}'; + } +} diff --git a/src/it/java/io/ecocode/java/integration/tests/profile/RuleMetadata.java b/src/it/java/io/ecocode/java/integration/tests/profile/RuleMetadata.java new file mode 100644 index 0000000..21bd763 --- /dev/null +++ b/src/it/java/io/ecocode/java/integration/tests/profile/RuleMetadata.java @@ -0,0 +1,40 @@ +package io.ecocode.java.integration.tests.profile; + +public class RuleMetadata { + private String key; + private String type; + private String defaultSeverity; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getDefaultSeverity() { + return defaultSeverity; + } + + public void setDefaultSeverity(String defaultSeverity) { + this.defaultSeverity = defaultSeverity; + } + + @Override + public String toString() { + return "RuleMetadata{" + + "key='" + key + '\'' + + ", type='" + type + '\'' + + ", defaultSeverity='" + defaultSeverity + '\'' + + '}'; + } +}