diff --git a/CHANGELOG.md b/CHANGELOG.md
index 54061489..a135c7d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,14 @@ 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.6] - 2026-05-05
+
+### Fixed
+ - External `$ref` tests no longer require outbound internet access. Fixtures
+ are now served by a local HTTP server (`ExternalRefHttpServer`) on
+ `http://localhost:18089`, started in `BaseCheckTest`. Affected tests:
+ OAR031 (v2/v3), OAR094, OAR068, OAR086.
+
## [1.3.5] - 2026-04-08
### Fixed
diff --git a/pom.xml b/pom.xml
index 1a24854e..c39e397a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
org.apiaddicts.apitools.dosonarapi
sonaropenapi-rules-community
- 1.3.5
+ 1.3.6
sonar-plugin
SonarQube OpenAPI Community Rules
diff --git a/src/test/java/apiaddicts/sonar/openapi/BaseCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/BaseCheckTest.java
index c4d16112..f68c63ca 100644
--- a/src/test/java/apiaddicts/sonar/openapi/BaseCheckTest.java
+++ b/src/test/java/apiaddicts/sonar/openapi/BaseCheckTest.java
@@ -8,6 +8,8 @@
import org.apiaddicts.apitools.dosonarapi.api.OpenApiCheck;
+import apiaddicts.sonar.openapi.utils.ExternalRefHttpServer;
+
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -77,6 +79,7 @@ private void verify(String file, boolean isV2, boolean isV3, boolean isV31) {
@BeforeClass
public static void beforeClass() {
+ ExternalRefHttpServer.start();
I18nContext.setLang("en");
if (repository != null) return;
OpenAPICustomRulesDefinition rulesDefinition = new OpenAPICustomRulesDefinition();
diff --git a/src/test/java/apiaddicts/sonar/openapi/utils/ExternalRefHttpServer.java b/src/test/java/apiaddicts/sonar/openapi/utils/ExternalRefHttpServer.java
new file mode 100644
index 00000000..13f28492
--- /dev/null
+++ b/src/test/java/apiaddicts/sonar/openapi/utils/ExternalRefHttpServer.java
@@ -0,0 +1,59 @@
+package apiaddicts.sonar.openapi.utils;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpServer;
+
+public final class ExternalRefHttpServer {
+
+ public static final int PORT = 18089;
+ public static final String BASE_URL = "http://localhost:" + PORT;
+
+ private static final Path FIXTURES = Paths.get("src", "test", "resources", "externalRef").toAbsolutePath().normalize();
+
+ private static HttpServer server;
+
+ private ExternalRefHttpServer() {
+ }
+
+ public static synchronized void start() {
+ if (server != null) {
+ return;
+ }
+ try {
+ HttpServer s = HttpServer.create(new InetSocketAddress("127.0.0.1", PORT), 0);
+ s.createContext("/", ExternalRefHttpServer::handle);
+ s.setExecutor(null);
+ s.start();
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> s.stop(0)));
+ server = s;
+ } catch (IOException e) {
+ throw new IllegalStateException("Could not start external ref HTTP server on port " + PORT, e);
+ }
+ }
+
+ private static void handle(HttpExchange exchange) throws IOException {
+ try {
+ String name = exchange.getRequestURI().getPath().replaceFirst("^/+", "");
+ Path file = FIXTURES.resolve(name).normalize();
+ if (!file.startsWith(FIXTURES) || !Files.isRegularFile(file)) {
+ exchange.sendResponseHeaders(404, -1);
+ return;
+ }
+ byte[] body = Files.readAllBytes(file);
+ exchange.getResponseHeaders().set("Content-Type", "application/yaml; charset=utf-8");
+ exchange.sendResponseHeaders(200, body.length);
+ try (OutputStream os = exchange.getResponseBody()) {
+ os.write(body);
+ }
+ } finally {
+ exchange.close();
+ }
+ }
+}
diff --git a/src/test/resources/checks/v2/examples/OAR031/externalref.yaml b/src/test/resources/checks/v2/examples/OAR031/externalref.yaml
index fc5c3de3..8b2b3bc2 100644
--- a/src/test/resources/checks/v2/examples/OAR031/externalref.yaml
+++ b/src/test/resources/checks/v2/examples/OAR031/externalref.yaml
@@ -18,7 +18,7 @@ paths:
$ref: '#/definitions/User'
400:
$ref: >- # Noncompliant {{OAR031: Responses must have one or more examples defined}}
- https://raw.githubusercontent.com/apiaddicts/sonaropenapi-rules/refs/heads/master/src/test/resources/externalRef/OAR031.yaml#/components/responses/server_error_response
+ http://localhost:18089/OAR031.yaml#/components/responses/server_error_response
/users/{userId}:
get:
parameters:
diff --git a/src/test/resources/checks/v3/examples/OAR031/externalref.yaml b/src/test/resources/checks/v3/examples/OAR031/externalref.yaml
index 0b1ec46d..05e57ddb 100644
--- a/src/test/resources/checks/v3/examples/OAR031/externalref.yaml
+++ b/src/test/resources/checks/v3/examples/OAR031/externalref.yaml
@@ -24,7 +24,7 @@ paths:
$ref: '#/components/schemas/User'
'400':
$ref: >- # Noncompliant {{OAR031: Responses must have one or more examples defined}}
- https://raw.githubusercontent.com/apiaddicts/sonaropenapi-rules/refs/heads/master/src/test/resources/externalRef/OAR031.yaml#/components/responses/server_error_response
+ http://localhost:18089/OAR031.yaml#/components/responses/server_error_response
/users/{userId}:
get:
summary: Get a user by ID
@@ -48,7 +48,7 @@ paths:
$ref: '#/components/schemas/User'
'400':
$ref: >- # Noncompliant {{OAR031: Responses must have one or more examples defined}}
- https://raw.githubusercontent.com/apiaddicts/sonaropenapi-rules/refs/heads/master/src/test/resources/externalRef/OAR031.yaml#/components/responses/server_error_response
+ http://localhost:18089/OAR031.yaml#/components/responses/server_error_response
components:
schemas:
User:
diff --git a/src/test/resources/checks/v3/examples/OAR094/externalref.yaml b/src/test/resources/checks/v3/examples/OAR094/externalref.yaml
index d750e57a..bb80ad35 100644
--- a/src/test/resources/checks/v3/examples/OAR094/externalref.yaml
+++ b/src/test/resources/checks/v3/examples/OAR094/externalref.yaml
@@ -36,7 +36,7 @@ paths:
application/json:
schema:
$ref: >- # Noncompliant {{OAR094: It is recommended to use examples instead of example as some tools like microcks use this section of the definition}}
- https://raw.githubusercontent.com/apiaddicts/sonaropenapi-rules/refs/heads/master/src/test/resources/externalRef/OAR094.yaml#/components/schemas/Pet
+ http://localhost:18089/OAR094.yaml#/components/schemas/Pet
'400':
description: Bad Request.
headers:
@@ -48,7 +48,7 @@ paths:
application/json:
schema:
$ref: >- # Noncompliant {{OAR094: It is recommended to use examples instead of example as some tools like microcks use this section of the definition}}
- https://raw.githubusercontent.com/apiaddicts/sonaropenapi-rules/refs/heads/master/src/test/resources/externalRef/OAR094.yaml#/components/schemas/ErrorMessage
+ http://localhost:18089/OAR094.yaml#/components/schemas/ErrorMessage
example: # Noncompliant {{OAR094: It is recommended to use examples instead of example as some tools like microcks use this section of the definition}}
error:
status: '400'
diff --git a/src/test/resources/checks/v3/format/OAR068/externalref.yaml b/src/test/resources/checks/v3/format/OAR068/externalref.yaml
index 5035f52c..94a9abb5 100644
--- a/src/test/resources/checks/v3/format/OAR068/externalref.yaml
+++ b/src/test/resources/checks/v3/format/OAR068/externalref.yaml
@@ -36,7 +36,7 @@ paths:
application/json:
schema: # Noncompliant {{OAR068: RequestBody and Responses schema property names must be compliant with the PascalCase naming convention}}
$ref: >-
- https://raw.githubusercontent.com/apiaddicts/sonaropenapi-rules/refs/heads/master/src/test/resources/externalRef/OAR068.yaml#/components/schemas/datosUsuario
+ http://localhost:18089/OAR068.yaml#/components/schemas/datosUsuario
'400':
description: Bad Request.
headers:
@@ -48,7 +48,7 @@ paths:
application/json:
schema: # Noncompliant {{OAR068: RequestBody and Responses schema property names must be compliant with the PascalCase naming convention}}
$ref: >-
- https://raw.githubusercontent.com/apiaddicts/sonaropenapi-rules/refs/heads/master/src/test/resources/externalRef/OAR086.yaml#/components/schemas/ErrorMessage
+ http://localhost:18089/OAR086.yaml#/components/schemas/ErrorMessage
example:
error:
status: '400'
diff --git a/src/test/resources/checks/v3/format/OAR086/external-refexample.yaml b/src/test/resources/checks/v3/format/OAR086/external-refexample.yaml
index 942a056b..6eca289b 100644
--- a/src/test/resources/checks/v3/format/OAR086/external-refexample.yaml
+++ b/src/test/resources/checks/v3/format/OAR086/external-refexample.yaml
@@ -36,7 +36,7 @@ paths:
application/json:
schema:
$ref: >- # Noncompliant {{OAR086: Descriptions must begin with a capital letter, end with a period and not be empty}}
- https://raw.githubusercontent.com/apiaddicts/sonaropenapi-rules/refs/heads/master/src/test/resources/externalRef/OAR086.yaml#/components/schemas/datosUsuario
+ http://localhost:18089/OAR086.yaml#/components/schemas/datosUsuario
'400':
description: Bad Request.
headers:
@@ -48,7 +48,7 @@ paths:
application/json:
schema:
$ref: >- # Noncompliant {{OAR086: Descriptions must begin with a capital letter, end with a period and not be empty}}
- https://raw.githubusercontent.com/apiaddicts/sonaropenapi-rules/refs/heads/master/src/test/resources/externalRef/OAR086.yaml#/components/schemas/ErrorMessage
+ http://localhost:18089/OAR086.yaml#/components/schemas/ErrorMessage
example:
error:
status: '400'