Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@ jobs:

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for SonarCloud to get full git history
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn -B package --file pom.xml
- name: Build, Test and Coverage
run: mvn -B verify --file pom.xml
- name: SonarCloud Analysis
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: mvn sonar:sonar -Dsonar.token=$SONAR_TOKEN

# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
- name: Update dependency graph
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.3.4] - 2026-04-01

### Fixed
- OAR029 - StandardResponseSchemaCheck Test
- OAR080 - SecuritySchemasCheck Test
- OAR112 - RegexCheck Test

- OpenAPICustomPlugin Test
- OpenAPICustomProfileDefinition Test
- OpenAPICustomRuleRepository Test
- OpenAPICustomRulesDefinition Test

## [1.3.3] - 2026-03-20

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@


# 🛠️ sonaropenapi-rules ![Release](https://img.shields.io/badge/release-1.2.1-purple) ![Java](https://img.shields.io/badge/java-%23ED8B00.svg?style=flat&logo=openjdk&logoColor=white) [![License: LGPL v3](https://img.shields.io/badge/license-LGPL_v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
# 🛠️ sonaropenapi-rules ![Release](https://img.shields.io/badge/release-1.3.4-purple) ![Java](https://img.shields.io/badge/java-%23ED8B00.svg?style=flat&logo=openjdk&logoColor=white) [![License: LGPL v3](https://img.shields.io/badge/license-LGPL_v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)

This repository contains a set of custom SonarQube rules specifically designed to analyze and improve the quality of OpenAPI specifications. By integrating these rules, teams can ensure best practices, maintainability, and consistency in their API definitions.

Expand Down
26 changes: 19 additions & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.apiaddicts.apitools.dosonarapi</groupId>
<artifactId>sonaropenapi-rules-community</artifactId>
<version>1.3.3</version>
<version>1.3.4</version>
<packaging>sonar-plugin</packaging>

<name>SonarQube OpenAPI Community Rules</name>
Expand Down Expand Up @@ -71,9 +71,12 @@
<assertj.version>3.22.0</assertj.version>

<jacoco.maven.plugin.version>0.8.6</jacoco.maven.plugin.version>
<sonar.maven.plugin.version>3.7.0.1746</sonar.maven.plugin.version>
<sonar.maven.plugin.version>5.5.0.6356</sonar.maven.plugin.version>
<sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin>
<sonar.jacoco.reportPaths>${project.basedir}/../target/jacoco.exec</sonar.jacoco.reportPaths>
<sonar.coverage.jacoco.xmlReportPaths>${project.basedir}/target/site/jacoco/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<sonar.organization>apiaddicts</sonar.organization>
<sonar.projectKey>apiaddicts_sonaropenapi-rules</sonar.projectKey>
<sonar.language>java</sonar.language>
<sonar.exclusions>**/*.html,**/*.json</sonar.exclusions>
<sonar.cpd.exclusions>**/*.html,**/*.json</sonar.cpd.exclusions>
Expand Down Expand Up @@ -264,21 +267,30 @@
</executions>
</plugin>

<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>${sonar.maven.plugin.version}</version>
</plugin>

<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.maven.plugin.version}</version>
<configuration>
<destFile>${sonar.jacoco.reportPaths}</destFile>
<append>true</append>
</configuration>
<executions>
<execution>
<id>agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
Expand Down
3 changes: 3 additions & 0 deletions sonar-project.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
sonar.host.url=https://sonarcloud.io
sonar.organization=apiaddicts
sonar.projectKey=apiaddicts_sonaropenapi-rules
sonar.sources=.
sonar.exclusions=**/*.html,**/*.json
sonar.cpd.exclusions=**/*.html,**/*.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public Set<AstNodeType> subscribedKinds() {
@Override
protected void visitFile(JsonNode root) {
JsonNode security = root.get("security");
hasGlobalSecurity = !(security.isMissing() || security.isNull() || security.elements().isEmpty());
hasGlobalSecurity = !(security.isMissing() || security.elements().isEmpty());

expectedSecuritySchemes = Arrays.stream(expectedSecurityScheme.split(","))
.map(String::trim)
Expand All @@ -61,11 +61,7 @@ protected void visitFile(JsonNode root) {
@Override
public void visitNode(JsonNode node) {
if (hasGlobalSecurity) return;

if (node.is(OpenApi2Grammar.PATH, OpenApi3Grammar.PATH, OpenApi31Grammar.PATH,
OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION, OpenApi31Grammar.OPERATION)) {
visitOperationNode(node);
}
visitOperationNode(node);
}

private void visitOperationNode(JsonNode node) {
Expand All @@ -75,7 +71,7 @@ private void visitOperationNode(JsonNode node) {
}

JsonNode security = node.get("security");
if (security.isMissing() || security.isNull() || security.elements().isEmpty()) {
if (security.isMissing() || security.elements().isEmpty()) {
addIssue(KEY, translate(MESSAGE), node.key());
return;
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package apiaddicts.sonar.openapi;

import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.sonar.api.Plugin;
import org.sonar.api.SonarEdition;
import org.sonar.api.SonarProduct;
import org.sonar.api.SonarQubeSide;
import org.sonar.api.SonarRuntime;
import org.sonar.api.utils.Version;

public class OpenAPICustomPluginTest {

@Test
public void testDefine() {
OpenAPICustomPlugin plugin = new OpenAPICustomPlugin();
SonarRuntime runtime = new SonarRuntime() {
@Override public Version getApiVersion() { return Version.create(9, 9); }
@Override public SonarProduct getProduct() { return SonarProduct.SONARQUBE; }
@Override public SonarQubeSide getSonarQubeSide() { return SonarQubeSide.SERVER; }
@Override public SonarEdition getEdition() { return SonarEdition.COMMUNITY; }
};
Plugin.Context context = new Plugin.Context(runtime);
plugin.define(context);
assertThat(context.getExtensions()).hasSize(3);
assertThat(context.getExtensions()).contains(
OpenAPICustomProfileDefinition.class,
OpenAPICustomRulesDefinition.class,
OpenAPICustomRuleRepository.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package apiaddicts.sonar.openapi;

import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile;

import apiaddicts.sonar.openapi.checks.RulesLists;

public class OpenAPICustomProfileDefinitionTest {

@Test
public void testDefine() {
I18nContext.setLang("en");
OpenAPICustomProfileDefinition profileDefinition = new OpenAPICustomProfileDefinition();
BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
profileDefinition.define(context);
BuiltInQualityProfile profile = context.profile("openapi", OpenAPICustomProfileDefinition.MY_COMPANY_WAY);
assertThat(profile).isNotNull();
assertThat(profile.language()).isEqualTo("openapi");
assertThat(profile.name()).isEqualTo(OpenAPICustomProfileDefinition.MY_COMPANY_WAY);

assertThat(profile.rules()).hasSize(RulesLists.getAllChecks().size() - 1);
assertThat(profile.rules().stream().noneMatch(r -> r.ruleKey().equals("OAR112"))).isTrue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package apiaddicts.sonar.openapi;

import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;

import apiaddicts.sonar.openapi.checks.RulesLists;

public class OpenAPICustomRuleRepositoryTest {

@Test
public void testRepositoryKey() {
OpenAPICustomRuleRepository repository = new OpenAPICustomRuleRepository();
assertThat(repository.repositoryKey()).isEqualTo(OpenAPICustomRulesDefinition.REPOSITORY_KEY);
}

@Test
public void testCheckClasses() {
OpenAPICustomRuleRepository repository = new OpenAPICustomRuleRepository();
assertThat(repository.checkClasses()).isEqualTo(RulesLists.getAllChecks());
assertThat(repository.checkClasses()).isNotEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package apiaddicts.sonar.openapi;

import static org.assertj.core.api.Assertions.assertThat;
import java.lang.reflect.Method;
import org.junit.Test;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.api.server.rule.RulesDefinition.Repository;
Expand All @@ -20,4 +21,32 @@ public void testRepository() {
assertThat(repository.language()).isEqualTo("openapi");
assertThat(repository.rules()).hasSize(RulesLists.getAllChecks().size());
}

@Test
public void testGetPathWithSpanishLanguage() throws Exception {
I18nContext.setLang("es");
OpenAPICustomRulesDefinition rulesDefinition = new OpenAPICustomRulesDefinition();

Method method = OpenAPICustomRulesDefinition.class.getDeclaredMethod("getPath", String.class);
method.setAccessible(true);
String path = (String) method.invoke(rulesDefinition, "security");

assertThat(path).contains("/es/");
}

@Test
public void testMarkAsTemplateWithNonExistentRule() throws Exception {
I18nContext.setLang("en");
OpenAPICustomRulesDefinition rulesDefinition = new OpenAPICustomRulesDefinition();
RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.NewRepository newRepo = context.createRepository("test-repo", "openapi").setName("Test");

Method method = OpenAPICustomRulesDefinition.class.getDeclaredMethod(
"markAsTemplate", RulesDefinition.NewRepository.class, String.class);
method.setAccessible(true);
method.invoke(rulesDefinition, newRepo, "NON_EXISTENT_RULE");

newRepo.done();
assertThat(context.repository("test-repo").rules()).isEmpty();
}
}
Loading
Loading