From 122195c1e22c6708ae98bd9c1fe00bef920bd1eb Mon Sep 17 00:00:00 2001 From: Kevin Snyder Date: Sun, 21 Jun 2026 07:33:09 -0400 Subject: [PATCH] Added Controllers for K6, Grafana and Javadoc --- README.md | 18 ++ java-server/MIGRATION_STATUS.md | 3 + java-server/README.md | 13 +- .../controllers/AZPipelineController.java | 26 ++ .../controllers/AzureController.java | 23 ++ .../controllers/BrowserStackController.java | 28 +++ .../controllers/BusinessController.java | 23 ++ .../controllers/CosmosDBController.java | 26 ++ .../controllers/DataFactoryController.java | 26 ++ .../controllers/DatabricksController.java | 32 +++ .../controllers/DataverseController.java | 26 ++ .../controllers/FHIRController.java | 30 +++ .../controllers/FileController.java | 26 ++ .../controllers/GrafanaController.java | 228 ++++++++++++++++++ .../controllers/JMeterController.java | 26 ++ .../controllers/JavadocController.java | 146 +++++++++++ .../controllers/JenkinsController.java | 29 +++ .../controllers/K6Controller.java | 152 ++++++++++++ .../controllers/LogAnalyticsController.java | 29 +++ .../controllers/MSDController.java | 26 ++ .../controllers/MiscController.java | 29 +++ .../controllers/OSController.java | 27 +++ .../controllers/OfficeController.java | 23 ++ .../controllers/OracleController.java | 23 ++ .../controllers/PostmanController.java | 26 ++ .../controllers/PowerBIController.java | 33 +++ .../controllers/SQLServerController.java | 23 ++ .../controllers/SequenceController.java | 20 ++ .../controllers/SnowflakeController.java | 27 +++ .../controllers/SonarQubeController.java | 27 +++ .../controllers/StorageAccountController.java | 26 ++ .../controllers/StringController.java | 20 ++ .../controllers/SynapseController.java | 26 ++ .../controllers/WaitController.java | 23 ++ .../core/DefaultRegistryFactory.java | 6 + .../AdminToggleIntegrationTest.java | 11 + .../GrafanaControllerIntegrationTest.java | 87 +++++++ .../JavadocControllerIntegrationTest.java | 66 +++++ .../K6ControllerIntegrationTest.java | 63 +++++ 39 files changed, 1521 insertions(+), 1 deletion(-) create mode 100644 java-server/src/main/java/com/restprovider/controllers/GrafanaController.java create mode 100644 java-server/src/main/java/com/restprovider/controllers/JavadocController.java create mode 100644 java-server/src/main/java/com/restprovider/controllers/K6Controller.java create mode 100644 java-server/src/test/java/com/restprovider/integration/GrafanaControllerIntegrationTest.java create mode 100644 java-server/src/test/java/com/restprovider/integration/JavadocControllerIntegrationTest.java create mode 100644 java-server/src/test/java/com/restprovider/integration/K6ControllerIntegrationTest.java diff --git a/README.md b/README.md index 4c9771d..92e6489 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ RestProvider/ │ │ ├── java/com/restprovider/ │ │ │ ├── App.java # Application entry point │ │ │ ├── controllers/ # One class per integration controller +│ │ │ │ └── JavadocController.java # Generates and serves Javadocs │ │ │ ├── core/ # Server bootstrap, registry, dispatcher │ │ │ ├── domain/ # DTOs and service layer (azure, business, common, security) │ │ │ └── util/ # Shared utilities (JsonUtil) @@ -34,6 +35,7 @@ RestProvider/ │ └── test/ │ └── java/com/restprovider/ │ └── integration/ # Integration tests per controller +│ └── JavadocControllerIntegrationTest.java ├── data_files/ # Sample properties and response templates │ ├── variable.properties │ ├── sample_var.properties @@ -72,6 +74,7 @@ All controllers currently shipped in the Java server are implemented natively. | BrowserStack | Complete | | Jenkins | Complete | | JMeter | Complete | +| K6 | Complete | | Postman | Complete | | SonarQube | Complete | @@ -82,6 +85,7 @@ All controllers currently shipped in the Java server are implemented natively. | MSD | Complete | | Oracle | Complete | | PowerBI | Complete | +| Grafana | Complete | | Snowflake | Complete | | SQLServer | Complete | @@ -95,6 +99,7 @@ All controllers currently shipped in the Java server are implemented natively. | Misc | Complete | | Office | Complete | | OS | Complete | +| Javadoc | Complete | | Sequence | Complete | | String | Complete | | Wait | Complete | @@ -183,6 +188,7 @@ Representative routes: 4. System and utility: - `GET /api/business/health` - `GET /api/file/exists`, `POST /api/file/upload/local` + - `GET /api/javadoc/generate`, `GET /api/javadoc/docs` - `GET /api/loganalytics/message`, `GET /api/loganalytics/message/startswith` - `GET /api/misc/check/vpn`, `GET /api/misc/account/names` - `POST /api/os/session/schedule`, `GET /api/os/env/variable` @@ -192,6 +198,18 @@ Representative routes: Route details and controller-specific aliases are documented in [java-server/README.md](java-server/README.md). +## Generated API Docs + +Generated Javadocs are published by the Java server at: + +- `GET /api/javadoc/docs` (or `http://localhost:8080/api/javadoc/docs` when running locally) + +Important: + +1. The `Javadoc` controller is disabled by default at startup. +2. Enable it before using documentation routes: + - `PUT /admin/controllers/javadoc/enabled?value=true` + ## Enable or Disable Controllers The Java server includes one central control method in [java-server/src/main/java/com/restprovider/core/ControllerRegistry.java](java-server/src/main/java/com/restprovider/core/ControllerRegistry.java): diff --git a/java-server/MIGRATION_STATUS.md b/java-server/MIGRATION_STATUS.md index cc81281..3cc170a 100644 --- a/java-server/MIGRATION_STATUS.md +++ b/java-server/MIGRATION_STATUS.md @@ -19,8 +19,11 @@ This file tracks the Java implementation status for each controller. | Dataverse | 4 | Native | Native Dataverse route set implemented via SQL CLI with GET and POST query support, route aliases, runtime credential overrides, and required-field validation | | FHIR | 4 | Native | Authenticated FHIR GET/POST/PUT/DELETE operations implemented via native HTTP with route-based resource paths, query aliases, token override support, and required-field validation | | File | 31 | Native | Full file route set implemented including sorting, archive, sftp, local transfer, diff, and template-copy flows, with common routes accepting query-param aliases and required-file validation | +| Grafana | 4 | Native | Native authenticated Grafana GET, POST, PUT, and DELETE pass-through implemented with request alias routes, runtime token and base-URL overrides, and required-field validation | | Jenkins | 4 | Native | Native Jenkins GET/POST/PUT/DELETE pass-through with auth implemented | +| Javadoc | 3 | Native | Native documentation generation and publish endpoints for building Javadocs and serving generated docs through HTTP | | JMeter | 3 | Native | Native process-based jmeter version/server/script-run endpoints | +| K6 | 2 | Native | Native process-based k6 version and script-run endpoints | | LogAnalytics | 3 | Native | Native log analytics message matching routes implemented with route aliases plus query/header aliases for run, workspace, and expected-message inputs | | Misc | 15 | Native | Native misc route set implemented for VPN, heartbeat, time, process, variables, random, and credential flows, with tested routes accepting query/header aliases and required-field validation | | MSD | 1 | Native | Native sqlcmd-backed MSD query execution implemented | diff --git a/java-server/README.md b/java-server/README.md index d91db2c..d91ff13 100644 --- a/java-server/README.md +++ b/java-server/README.md @@ -4,7 +4,7 @@ Java implementation of RestProvider controllers using Apache HttpComponents (Htt ## What this contains -- 31 Java controllers mirroring the existing controller names. +- 32 Java controllers mirroring the existing controller names. - Apache HTTP server bootstrap using `ServerBootstrap`. - Native controller implementations for every route-bearing domain. - DTO and service layer split for cleaner domain boundaries. @@ -83,6 +83,14 @@ Java implementation of RestProvider controllers using Apache HttpComponents (Htt - `GET /api/jmeter/servers` (alias: `/api/jmeter/server/list`) - `GET|POST /api/jmeter` (alias: `/api/jmeter/run`) - Script execution accepts aliases for script and output settings (`scriptName`, `resultFile`, `jmeterLogFile`) +- Javadoc routes: + - `GET /api/javadoc/generate` (aliases: `/api/javadoc`, `/api/javadoc/build`) to generate project Javadocs via Maven + - `GET /api/javadoc/docs` serves generated `target/reports/apidocs/index.html` + - `GET /api/javadoc/docs/{relativePath}` serves generated documentation assets and pages +- K6 routes (headers or query params supported): + - `GET /api/k6/version` (alias: `/api/k6/info/version`) + - `GET|POST /api/k6` (alias: `/api/k6/run`) + - Script execution accepts aliases for script and output settings (`scriptName`, `resultFile`) - LogAnalytics routes (headers or query params supported): - `GET /api/loganalytics/message` - `GET /api/loganalytics/message/startswith` (alias: `/api/loganalytics/message/starts-with`) @@ -110,6 +118,9 @@ Java implementation of RestProvider controllers using Apache HttpComponents (Htt - PowerBI routes (headers or query params supported): - `GET|POST|PUT|DELETE /api/powerbi` (alias: `/api/powerbi/request`) - Supports aliases for request path, organization, API version, base URL, and access token override +- Grafana routes (headers or query params supported): + - `GET|POST|PUT|DELETE /api/grafana` (alias: `/api/grafana/request`) + - Supports aliases for request path, API version, base URL, and API token override - Snowflake routes (headers or query params supported): - `GET|POST /api/snowflake/token` (alias: `/api/snowflake/oauth/token`) - Supports aliases for token endpoint, scope, client credentials, and runtime user/password overrides diff --git a/java-server/src/main/java/com/restprovider/controllers/AZPipelineController.java b/java-server/src/main/java/com/restprovider/controllers/AZPipelineController.java index 09db850..5d70151 100644 --- a/java-server/src/main/java/com/restprovider/controllers/AZPipelineController.java +++ b/java-server/src/main/java/com/restprovider/controllers/AZPipelineController.java @@ -18,6 +18,12 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the AZPipeline integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class AZPipelineController extends BaseController { private static final Pattern RESULT_PATTERN = Pattern.compile("\\\"result\\\"\\s*:\\s*\\\"([^\\\"]*)\\\""); private static final Pattern STATUS_PATTERN = Pattern.compile("\\\"status\\\"\\s*:\\s*\\\"([^\\\"]*)\\\""); @@ -25,16 +31,31 @@ public class AZPipelineController extends BaseController { private final PasscodeValidator passcodeValidator; private final CommandRunner commandRunner; + /** + * Creates a controller with default runtime dependencies. + */ public AZPipelineController() { this(new EnvPasscodeValidator(), ProcessUtil::run); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public AZPipelineController(PasscodeValidator passcodeValidator, CommandRunner commandRunner) { super("AZPipeline"); this.passcodeValidator = passcodeValidator; this.commandRunner = commandRunner; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -287,9 +308,14 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface CommandRunner { String run(String command, String args); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/AzureController.java b/java-server/src/main/java/com/restprovider/controllers/AzureController.java index 561e822..99e30ef 100644 --- a/java-server/src/main/java/com/restprovider/controllers/AzureController.java +++ b/java-server/src/main/java/com/restprovider/controllers/AzureController.java @@ -22,15 +22,27 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the Azure integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class AzureController implements BridgeController { private static final Logger logger = LogManager.getLogger(AzureController.class); private final AzureService azureService; private final PasscodeValidator passcodeValidator; + /** + * Creates a controller with default runtime dependencies. + */ public AzureController() { this(new AzureCliService(new ShellProcessExecutionService()), new EnvPasscodeValidator()); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public AzureController(AzureService azureService, PasscodeValidator passcodeValidator) { this.azureService = azureService; this.passcodeValidator = passcodeValidator; @@ -41,6 +53,15 @@ public String getName() { return "Azure"; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -121,3 +142,5 @@ private void respondWithResult(ClassicHttpResponse response, AzureCommandRespons ContentType.APPLICATION_JSON)); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/BrowserStackController.java b/java-server/src/main/java/com/restprovider/controllers/BrowserStackController.java index 6cf9589..711f0ba 100644 --- a/java-server/src/main/java/com/restprovider/controllers/BrowserStackController.java +++ b/java-server/src/main/java/com/restprovider/controllers/BrowserStackController.java @@ -21,20 +21,41 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the BrowserStack integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class BrowserStackController extends BaseController { private final PasscodeValidator passcodeValidator; private final HttpInvoker httpInvoker; + /** + * Creates a controller with default runtime dependencies. + */ public BrowserStackController() { this(new EnvPasscodeValidator(), BrowserStackController::invoke); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public BrowserStackController(PasscodeValidator passcodeValidator, HttpInvoker httpInvoker) { super("BrowserStack"); this.passcodeValidator = passcodeValidator; this.httpInvoker = httpInvoker; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -154,6 +175,7 @@ public void handle(ClassicHttpRequest request, ClassicHttpResponse response, Str private String call(ClassicHttpRequest request, Map query, String endpoint) { String user = defaultValue(readValue(request, query, "browserstackUser", "username", "user"), env("browserstack_user")); String key = defaultValue(readValue(request, query, "browserstackAccessKey", "accessKey", "key"), env("browserstack_accesskey")); + // Outbound REST call to the target service endpoint. return httpInvoker.call("GET", endpoint, user, key); } @@ -252,6 +274,7 @@ private static String invoke(String method, String endpoint, String user, String .header("Authorization", "Basic " + auth) .method(method, HttpRequest.BodyPublishers.noBody()) .build(); + // Outbound REST call to the target service endpoint. HttpResponse res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString()); return res.body(); } catch (Exception ex) { @@ -264,9 +287,14 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface HttpInvoker { String call(String method, String endpoint, String user, String accessKey); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/BusinessController.java b/java-server/src/main/java/com/restprovider/controllers/BusinessController.java index 141a34f..dd55380 100644 --- a/java-server/src/main/java/com/restprovider/controllers/BusinessController.java +++ b/java-server/src/main/java/com/restprovider/controllers/BusinessController.java @@ -15,14 +15,26 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the Business integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class BusinessController implements BridgeController { private static final Logger logger = LogManager.getLogger(BusinessController.class); private final BusinessService businessService; + /** + * Creates a controller with default runtime dependencies. + */ public BusinessController() { this(new DefaultBusinessService()); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public BusinessController(BusinessService businessService) { this.businessService = businessService; } @@ -32,6 +44,15 @@ public String getName() { return "Business"; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -75,3 +96,5 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/CosmosDBController.java b/java-server/src/main/java/com/restprovider/controllers/CosmosDBController.java index d4fccb5..2cfbc6e 100644 --- a/java-server/src/main/java/com/restprovider/controllers/CosmosDBController.java +++ b/java-server/src/main/java/com/restprovider/controllers/CosmosDBController.java @@ -24,22 +24,43 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the CosmosDB integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class CosmosDBController extends BaseController { private static final Pattern NAME_PATTERN = Pattern.compile("\\\"name\\\"\\s*:\\s*\\\"([^\\\"]+)\\\""); private final PasscodeValidator passcodeValidator; private final CommandRunner commandRunner; + /** + * Creates a controller with default runtime dependencies. + */ public CosmosDBController() { this(new EnvPasscodeValidator(), ProcessUtil::run); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public CosmosDBController(PasscodeValidator passcodeValidator, CommandRunner commandRunner) { super("CosmosDB"); this.passcodeValidator = passcodeValidator; this.commandRunner = commandRunner; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -422,6 +443,9 @@ private static void respondText(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.TEXT_PLAIN)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface CommandRunner { String run(String command, String args); @@ -448,3 +472,5 @@ private MatchContext(Path outputPath, List map) { } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/DataFactoryController.java b/java-server/src/main/java/com/restprovider/controllers/DataFactoryController.java index 1636512..64f39e3 100644 --- a/java-server/src/main/java/com/restprovider/controllers/DataFactoryController.java +++ b/java-server/src/main/java/com/restprovider/controllers/DataFactoryController.java @@ -21,6 +21,12 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the DataFactory integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class DataFactoryController extends BaseController { private static final Pattern STATUS_PATTERN = Pattern.compile("\\\"status\\\"\\s*:\\s*\\\"([^\\\"]*)\\\""); private static final Pattern MESSAGE_PATTERN = Pattern.compile("\\\"message\\\"\\s*:\\s*\\\"([^\\\"]*)\\\""); @@ -28,16 +34,31 @@ public class DataFactoryController extends BaseController { private final PasscodeValidator passcodeValidator; private final CommandRunner commandRunner; + /** + * Creates a controller with default runtime dependencies. + */ public DataFactoryController() { this(new EnvPasscodeValidator(), ProcessUtil::run); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public DataFactoryController(PasscodeValidator passcodeValidator, CommandRunner commandRunner) { super("DataFactory"); this.passcodeValidator = passcodeValidator; this.commandRunner = commandRunner; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -304,9 +325,14 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface CommandRunner { String run(String command, String args); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/DatabricksController.java b/java-server/src/main/java/com/restprovider/controllers/DatabricksController.java index 1138e10..b83b25b 100644 --- a/java-server/src/main/java/com/restprovider/controllers/DatabricksController.java +++ b/java-server/src/main/java/com/restprovider/controllers/DatabricksController.java @@ -22,15 +22,27 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the Databricks integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class DatabricksController extends BaseController { private final PasscodeValidator passcodeValidator; private final CommandRunner commandRunner; private final HttpInvoker httpInvoker; + /** + * Creates a controller with default runtime dependencies. + */ public DatabricksController() { this(new EnvPasscodeValidator(), ProcessUtil::run, DatabricksController::invokeHttp); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public DatabricksController(PasscodeValidator passcodeValidator, CommandRunner commandRunner, HttpInvoker httpInvoker) { @@ -40,6 +52,15 @@ public DatabricksController(PasscodeValidator passcodeValidator, this.httpInvoker = httpInvoker; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -84,6 +105,7 @@ public void handle(ClassicHttpRequest request, ClassicHttpResponse response, Str + "\"statement\":\"" + JsonUtil.escape(sql) + "\"" + "}"; String endpoint = "https://" + host + "/api/2.0/sql/statements/"; + // Outbound REST call to the target service endpoint. String result = httpInvoker.call("POST", endpoint, token, payload); Files.writeString(outputPath, result, StandardCharsets.UTF_8); @@ -128,6 +150,7 @@ public void handle(ClassicHttpRequest request, ClassicHttpResponse response, Str return; } String endpoint = "https://" + host + "/api/2.0/sql/warehouses/" + warehouse + "/start"; + // Outbound REST call to the target service endpoint. String result = httpInvoker.call("POST", endpoint, token, ""); respondJson(response, HttpStatus.SC_OK, "{\"result\":\"" + JsonUtil.escape(result) + "\"}"); return; @@ -251,6 +274,7 @@ private static String invokeHttp(String method, String endpoint, String bearerTo } else { req = builder.method(method, HttpRequest.BodyPublishers.ofString(body)).build(); } + // Outbound REST call to the target service endpoint. HttpResponse res = client.send(req, HttpResponse.BodyHandlers.ofString()); return res.body(); } catch (Exception ex) { @@ -263,14 +287,22 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface CommandRunner { String run(String command, String args); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface HttpInvoker { String call(String method, String endpoint, String bearerToken, String body); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/DataverseController.java b/java-server/src/main/java/com/restprovider/controllers/DataverseController.java index c5d5620..e10a2a9 100644 --- a/java-server/src/main/java/com/restprovider/controllers/DataverseController.java +++ b/java-server/src/main/java/com/restprovider/controllers/DataverseController.java @@ -19,20 +19,41 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the Dataverse integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class DataverseController extends BaseController { private final PasscodeValidator passcodeValidator; private final CommandRunner commandRunner; + /** + * Creates a controller with default runtime dependencies. + */ public DataverseController() { this(new EnvPasscodeValidator(), ProcessUtil::run); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public DataverseController(PasscodeValidator passcodeValidator, CommandRunner commandRunner) { super("Dataverse"); this.passcodeValidator = passcodeValidator; this.commandRunner = commandRunner; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -221,9 +242,14 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface CommandRunner { String run(String command, String args); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/FHIRController.java b/java-server/src/main/java/com/restprovider/controllers/FHIRController.java index 336a142..ca88e77 100644 --- a/java-server/src/main/java/com/restprovider/controllers/FHIRController.java +++ b/java-server/src/main/java/com/restprovider/controllers/FHIRController.java @@ -19,16 +19,28 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the FHIR integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class FHIRController extends BaseController { private final PasscodeValidator passcodeValidator; private final CommandRunner commandRunner; private final HttpInvoker httpInvoker; private String cachedPat; + /** + * Creates a controller with default runtime dependencies. + */ public FHIRController() { this(new EnvPasscodeValidator(), ProcessUtil::run, FHIRController::invokeHttp); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public FHIRController(PasscodeValidator passcodeValidator, CommandRunner commandRunner, HttpInvoker httpInvoker) { super("FHIR"); this.passcodeValidator = passcodeValidator; @@ -37,6 +49,15 @@ public FHIRController(PasscodeValidator passcodeValidator, CommandRunner command this.cachedPat = ""; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -199,6 +220,7 @@ private static String invokeHttp(String method, String endpoint, String token, S } else { req = builder.method(method, HttpRequest.BodyPublishers.ofString(payload)).build(); } + // Outbound REST call to the target service endpoint. HttpResponse res = client.send(req, HttpResponse.BodyHandlers.ofString()); return res.body(); } catch (Exception ex) { @@ -211,14 +233,22 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface CommandRunner { String run(String command, String args); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface HttpInvoker { String invoke(String method, String endpoint, String bearerToken, String payload, String contentType); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/FileController.java b/java-server/src/main/java/com/restprovider/controllers/FileController.java index 351106d..8e50149 100644 --- a/java-server/src/main/java/com/restprovider/controllers/FileController.java +++ b/java-server/src/main/java/com/restprovider/controllers/FileController.java @@ -28,20 +28,41 @@ import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the File integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class FileController extends BaseController { private final PasscodeValidator passcodeValidator; private final CommandRunner commandRunner; + /** + * Creates a controller with default runtime dependencies. + */ public FileController() { this(new EnvPasscodeValidator(), ProcessUtil::run); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public FileController(PasscodeValidator passcodeValidator, CommandRunner commandRunner) { super("File"); this.passcodeValidator = passcodeValidator; this.commandRunner = commandRunner; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -743,9 +764,14 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface CommandRunner { String run(String command, String args); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/GrafanaController.java b/java-server/src/main/java/com/restprovider/controllers/GrafanaController.java new file mode 100644 index 0000000..ed1fa69 --- /dev/null +++ b/java-server/src/main/java/com/restprovider/controllers/GrafanaController.java @@ -0,0 +1,228 @@ +package com.restprovider.controllers; + +import com.restprovider.core.BaseController; +import com.restprovider.core.HttpRequestUtil; +import com.restprovider.domain.security.EnvPasscodeValidator; +import com.restprovider.domain.security.PasscodeValidator; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.io.entity.StringEntity; + +/** + * Controller for the Grafana integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ +public class GrafanaController extends BaseController { + private static final String GRAFANA_ROOT = "https://grafana.com"; + + private final PasscodeValidator passcodeValidator; + private final TokenProvider tokenProvider; + private final HttpInvoker httpInvoker; + + /** + * Creates a controller with default runtime dependencies. + */ + public GrafanaController() { + this(new EnvPasscodeValidator(), GrafanaController::resolveApiToken, GrafanaController::invokeGrafana); + } + + /** + * Creates a controller with injected dependencies for testability and customization. + */ + public GrafanaController(PasscodeValidator passcodeValidator, + TokenProvider tokenProvider, + HttpInvoker httpInvoker) { + super("Grafana"); + this.passcodeValidator = passcodeValidator; + this.tokenProvider = tokenProvider; + this.httpInvoker = httpInvoker; + } + + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ + @Override + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) + throws IOException, HttpException { + logger.debug("Handling request controller={} method={} subPath={}", getName(), request.getMethod(), subPath); + String route = normalizeRoute(subPath); + String method = request.getMethod().toUpperCase(); + Map query = HttpRequestUtil.queryParams(HttpRequestUtil.requestUri(request)); + + if (!("".equals(route) || "request".equalsIgnoreCase(route))) { + super.handle(request, response, subPath); + return; + } + + if (!("GET".equals(method) || "POST".equals(method) || "PUT".equals(method) || "DELETE".equals(method))) { + super.handle(request, response, subPath); + return; + } + + String passCode = readValue(request, query, "passCode", "passcode"); + if (!passcodeValidator.isValid(passCode)) { + logger.warn("Passcode validation failed for controller={} method={}", getName(), request.getMethod()); + respondText(response, HttpStatus.SC_OK, "Passcode failure"); + return; + } + + String projectName = defaultValue(readValue(request, query, "projectName", "project"), "grafana"); + String grafanaRequest = readValue(request, query, "grafanaRequest", "request", "path"); + String apiVersion = defaultValue(readValue(request, query, "apiVersion", "version"), "1"); + String baseUrl = defaultValue(readValue(request, query, "grafanaBaseUrl", "baseUrl"), GRAFANA_ROOT); + + if (!require(response, "grafanaRequest", grafanaRequest)) { + return; + } + + String token = defaultValue(readValue(request, query, "grafanaApiToken", "token", "bearerToken"), tokenProvider.getToken()); + if (!require(response, "grafanaApiToken", token)) { + return; + } + + String endpoint = stripTrailingSlash(baseUrl) + "/api/v" + apiVersion + "/" + stripSlashes(grafanaRequest); + + Path tempFolder = Path.of(System.getProperty("user.dir"), "data_files", "temp", projectName); + Files.createDirectories(tempFolder); + String outputFile = defaultValue(readValue(request, query, "outputFile"), "grafana_get_response.txt"); + Path outputPath = tempFolder.resolve(outputFile); + if (Files.exists(outputPath)) { + Files.delete(outputPath); + } + + // Outbound REST call to the target service endpoint. + String responseBody = httpInvoker.call(method, endpoint, token); + Files.writeString(outputPath, responseBody, StandardCharsets.UTF_8); + respondText(response, HttpStatus.SC_OK, "Grafana Response written to " + outputPath); + } + + private static String normalizeRoute(String subPath) { + String route = subPath == null ? "" : subPath; + if (route.startsWith("grafana/")) { + route = route.substring("grafana/".length()); + } + if ("grafana".equalsIgnoreCase(route)) { + return ""; + } + return route; + } + + private static String readValue(ClassicHttpRequest request, Map query, String... keys) { + for (String key : keys) { + String headerValue = HttpRequestUtil.headerValue(request, key); + if (headerValue != null && !headerValue.isBlank()) { + return headerValue; + } + } + for (String key : keys) { + for (Map.Entry entry : query.entrySet()) { + if (entry.getKey() != null && entry.getKey().equalsIgnoreCase(key)) { + String value = entry.getValue(); + if (value != null && !value.isBlank()) { + return value; + } + } + } + } + return ""; + } + + private static boolean require(ClassicHttpResponse response, String field, String value) { + if (value != null && !value.isBlank()) { + return true; + } + respondText(response, HttpStatus.SC_BAD_REQUEST, "Missing required parameter: " + field); + return false; + } + + private static String defaultValue(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } + + private static String stripTrailingSlash(String value) { + String v = value == null ? "" : value.trim(); + while (v.endsWith("/")) { + v = v.substring(0, v.length() - 1); + } + return v; + } + + private static String stripSlashes(String value) { + String v = value == null ? "" : value.trim(); + while (v.startsWith("/")) { + v = v.substring(1); + } + while (v.endsWith("/")) { + v = v.substring(0, v.length() - 1); + } + return v; + } + + private static String resolveApiToken() { + return env("grafanaApiToken"); + } + + private static String invokeGrafana(String method, String endpoint, String token) { + try { + HttpClient client = HttpClient.newHttpClient(); + HttpRequest req = HttpRequest.newBuilder(URI.create(endpoint)) + .header("Authorization", "Bearer " + token) + .method(method, HttpRequest.BodyPublishers.noBody()) + .build(); + // Outbound REST call to the target service endpoint. + HttpResponse res = client.send(req, HttpResponse.BodyHandlers.ofString()); + return res.body(); + } catch (Exception ex) { + return "Grafana request failed: " + ex.getMessage(); + } + } + + private static String env(String name) { + String value = System.getenv(name); + return value == null ? "" : value; + } + + private static void respondText(ClassicHttpResponse response, int code, String body) { + response.setCode(code); + response.setEntity(new StringEntity(body, ContentType.TEXT_PLAIN)); + } + + /** + * Functional contract used to abstract external operations for this controller. + */ + @FunctionalInterface + public interface TokenProvider { + String getToken(); + } + + /** + * Functional contract used to abstract external operations for this controller. + */ + @FunctionalInterface + public interface HttpInvoker { + String call(String method, String endpoint, String bearerToken); + } +} + + + diff --git a/java-server/src/main/java/com/restprovider/controllers/JMeterController.java b/java-server/src/main/java/com/restprovider/controllers/JMeterController.java index c2d10ee..f43d9be 100644 --- a/java-server/src/main/java/com/restprovider/controllers/JMeterController.java +++ b/java-server/src/main/java/com/restprovider/controllers/JMeterController.java @@ -14,18 +14,39 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the JMeter integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class JMeterController extends BaseController { private final CommandRunner commandRunner; + /** + * Creates a controller with default runtime dependencies. + */ public JMeterController() { this(ProcessUtil::run); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public JMeterController(CommandRunner commandRunner) { super("JMeter"); this.commandRunner = commandRunner; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -131,8 +152,13 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface CommandRunner { String run(String command, String args); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/JavadocController.java b/java-server/src/main/java/com/restprovider/controllers/JavadocController.java new file mode 100644 index 0000000..799ca01 --- /dev/null +++ b/java-server/src/main/java/com/restprovider/controllers/JavadocController.java @@ -0,0 +1,146 @@ +package com.restprovider.controllers; + +import com.restprovider.core.BaseController; +import com.restprovider.core.ProcessUtil; +import com.restprovider.util.JsonUtil; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.io.entity.ByteArrayEntity; +import org.apache.hc.core5.http.io.entity.StringEntity; + +/** + * Controller for generating and publishing project Javadocs. + * + *

This controller exposes endpoints to generate API documentation using Maven and + * serve the generated files from the running HTTP server.

+ */ +public class JavadocController extends BaseController { + private final CommandRunner commandRunner; + private final Path docsRoot; + + /** + * Creates a controller with default runtime dependencies. + */ + public JavadocController() { + this(ProcessUtil::run, defaultDocsRoot()); + } + + /** + * Creates a controller with injected dependencies for testability and customization. + */ + public JavadocController(CommandRunner commandRunner, Path docsRoot) { + super("Javadoc"); + this.commandRunner = commandRunner; + this.docsRoot = docsRoot; + } + + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ + @Override + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) + throws IOException, HttpException { + logger.debug("Handling request controller={} method={} subPath={}", getName(), request.getMethod(), subPath); + String route = normalizeRoute(subPath); + String method = request.getMethod(); + + if ("GET".equalsIgnoreCase(method) + && ("".equals(route) || "generate".equalsIgnoreCase(route) || "build".equalsIgnoreCase(route))) { + String output = commandRunner.run("mvn", "-q -DskipTests javadoc:javadoc"); + Path index = docsRoot.resolve("index.html"); + if (Files.exists(index)) { + respondJson(response, HttpStatus.SC_OK, + "{\"message\":\"Javadocs generated\"," + + "\"docsEndpoint\":\"/api/javadoc/docs\"," + + "\"result\":\"" + JsonUtil.escape(output) + "\"}"); + } else { + respondJson(response, HttpStatus.SC_INTERNAL_SERVER_ERROR, + "{\"error\":\"Javadoc generation failed\"," + + "\"result\":\"" + JsonUtil.escape(output) + "\"}"); + } + return; + } + + if ("GET".equalsIgnoreCase(method) && ("docs".equalsIgnoreCase(route) || route.startsWith("docs/"))) { + String relativePath = "docs".equalsIgnoreCase(route) ? "index.html" : route.substring("docs/".length()); + Path normalizedRoot = docsRoot.normalize(); + Path resolved = normalizedRoot.resolve(relativePath).normalize(); + if (!resolved.startsWith(normalizedRoot)) { + respondJson(response, HttpStatus.SC_BAD_REQUEST, "{\"error\":\"Invalid documentation path\"}"); + return; + } + if (!Files.exists(resolved) || Files.isDirectory(resolved)) { + respondJson(response, HttpStatus.SC_NOT_FOUND, "{\"error\":\"Documentation file not found\"}"); + return; + } + + ContentType contentType = resolveContentType(resolved); + byte[] body = Files.readAllBytes(resolved); + response.setCode(HttpStatus.SC_OK); + response.setEntity(new ByteArrayEntity(body, contentType)); + return; + } + + super.handle(request, response, subPath); + } + + private static String normalizeRoute(String subPath) { + String route = subPath == null ? "" : subPath; + if (route.startsWith("javadoc/")) { + route = route.substring("javadoc/".length()); + } + if ("javadoc".equalsIgnoreCase(route)) { + return ""; + } + return route; + } + + private static Path defaultDocsRoot() { + return Path.of(System.getProperty("user.dir"), "target", "reports", "apidocs"); + } + + private static ContentType resolveContentType(Path file) { + String name = file.getFileName().toString().toLowerCase(); + if (name.endsWith(".html") || name.endsWith(".htm")) { + return ContentType.TEXT_HTML; + } + if (name.endsWith(".css")) { + return ContentType.parse("text/css"); + } + if (name.endsWith(".js")) { + return ContentType.parse("application/javascript"); + } + if (name.endsWith(".json")) { + return ContentType.APPLICATION_JSON; + } + if (name.endsWith(".txt")) { + return ContentType.TEXT_PLAIN; + } + return ContentType.APPLICATION_OCTET_STREAM; + } + + private static void respondJson(ClassicHttpResponse response, int code, String body) { + response.setCode(code); + response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); + } + + /** + * Functional contract used to abstract external operations for this controller. + */ + @FunctionalInterface + public interface CommandRunner { + String run(String command, String args); + } +} diff --git a/java-server/src/main/java/com/restprovider/controllers/JenkinsController.java b/java-server/src/main/java/com/restprovider/controllers/JenkinsController.java index 240a9b7..8d09c81 100644 --- a/java-server/src/main/java/com/restprovider/controllers/JenkinsController.java +++ b/java-server/src/main/java/com/restprovider/controllers/JenkinsController.java @@ -20,20 +20,41 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the Jenkins integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class JenkinsController extends BaseController { private final PasscodeValidator passcodeValidator; private final HttpInvoker httpInvoker; + /** + * Creates a controller with default runtime dependencies. + */ public JenkinsController() { this(new EnvPasscodeValidator(), JenkinsController::invoke); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public JenkinsController(PasscodeValidator passcodeValidator, HttpInvoker httpInvoker) { super("Jenkins"); this.passcodeValidator = passcodeValidator; this.httpInvoker = httpInvoker; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -76,6 +97,7 @@ public void handle(ClassicHttpRequest request, ClassicHttpResponse response, Str String endpointBase = baseUrl.isBlank() ? "http://" + serverName + ":" + serverPort : stripTrailingSlash(baseUrl); String endpoint = endpointBase + "/" + stripLeadingSlash(subUri); + // Outbound REST call to the target service endpoint. String result = httpInvoker.call(method, endpoint, user, token); respondJson(response, HttpStatus.SC_OK, "{\"result\":\"" + JsonUtil.escape(result) + "\"}"); } @@ -163,6 +185,7 @@ private static String invoke(String method, String endpoint, String user, String .header("Authorization", "Basic " + creds) .method(method, HttpRequest.BodyPublishers.noBody()) .build(); + // Outbound REST call to the target service endpoint. HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); return response.body(); } catch (Exception ex) { @@ -175,9 +198,15 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface HttpInvoker { String call(String method, String endpoint, String user, String token); } } + + + diff --git a/java-server/src/main/java/com/restprovider/controllers/K6Controller.java b/java-server/src/main/java/com/restprovider/controllers/K6Controller.java new file mode 100644 index 0000000..8194d61 --- /dev/null +++ b/java-server/src/main/java/com/restprovider/controllers/K6Controller.java @@ -0,0 +1,152 @@ +package com.restprovider.controllers; + +import com.restprovider.core.BaseController; +import com.restprovider.core.HttpRequestUtil; +import com.restprovider.core.ProcessUtil; +import com.restprovider.util.JsonUtil; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Map; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.io.entity.StringEntity; + +/** + * Controller for the K6 integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ +public class K6Controller extends BaseController { + private final CommandRunner commandRunner; + + /** + * Creates a controller with default runtime dependencies. + */ + public K6Controller() { + this(ProcessUtil::run); + } + + /** + * Creates a controller with injected dependencies for testability and customization. + */ + public K6Controller(CommandRunner commandRunner) { + super("K6"); + this.commandRunner = commandRunner; + } + + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ + @Override + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) + throws IOException, HttpException { + logger.debug("Handling request controller={} method={} subPath={}", getName(), request.getMethod(), subPath); + String route = normalizeRoute(subPath); + String method = request.getMethod(); + Map query = HttpRequestUtil.queryParams(HttpRequestUtil.requestUri(request)); + + if ("GET".equalsIgnoreCase(method) + && ("version".equalsIgnoreCase(route) || "info/version".equalsIgnoreCase(route))) { + String output = commandRunner.run("k6", "version"); + respondJson(response, HttpStatus.SC_OK, "{\"result\":\"" + JsonUtil.escape(output) + "\"}"); + return; + } + + if (("POST".equalsIgnoreCase(method) || "GET".equalsIgnoreCase(method)) + && ("".equals(route) || "run".equalsIgnoreCase(route))) { + String projectName = defaultValue(readValue(request, query, "projectName", "project"), "k6"); + String testcaseName = safeName(defaultValue(readValue(request, query, "testcaseName", "testcase", "testCase"), "run")); + String scriptName = readValue(request, query, "scriptName", "script", "testScript"); + String resultFile = defaultValue(readValue(request, query, "resultFile", "summaryFile", "result"), "summary.json"); + + if (!require(response, "scriptName", scriptName)) { + return; + } + + String rootDir = System.getProperty("user.dir"); + Path scriptPath = Path.of(rootDir, "data_files", scriptName); + Path tempFolder = Path.of(rootDir, "data_files", "temp", projectName); + String resolvedResultFile = tempFolder.resolve(testcaseName + "_" + safeName(resultFile)).toString(); + + String args = "run " + scriptPath + " --summary-export " + resolvedResultFile; + String output = commandRunner.run("k6", args); + respondJson(response, HttpStatus.SC_OK, "{\"result\":\"" + JsonUtil.escape(output) + "\"}"); + return; + } + + super.handle(request, response, subPath); + } + + private static String normalizeRoute(String subPath) { + String route = subPath == null ? "" : subPath; + if (route.startsWith("k6/")) { + route = route.substring("k6/".length()); + } + if ("k6".equalsIgnoreCase(route)) { + return ""; + } + return route; + } + + private static String safeName(String name) { + return (name == null ? "" : name).replace(" ", "_"); + } + + private static String readValue(ClassicHttpRequest request, Map query, String... keys) { + for (String key : keys) { + String headerValue = HttpRequestUtil.headerValue(request, key); + if (headerValue != null && !headerValue.isBlank()) { + return headerValue; + } + } + for (String key : keys) { + for (Map.Entry entry : query.entrySet()) { + if (entry.getKey() != null && entry.getKey().equalsIgnoreCase(key)) { + String value = entry.getValue(); + if (value != null && !value.isBlank()) { + return value; + } + } + } + } + return ""; + } + + private static String defaultValue(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } + + private static boolean require(ClassicHttpResponse response, String field, String value) { + if (value != null && !value.isBlank()) { + return true; + } + respondJson(response, HttpStatus.SC_BAD_REQUEST, + "{\"error\":\"" + JsonUtil.escape("Missing required parameter: " + field) + "\"}"); + return false; + } + + private static void respondJson(ClassicHttpResponse response, int code, String body) { + response.setCode(code); + response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); + } + + /** + * Functional contract used to abstract external operations for this controller. + */ + @FunctionalInterface + public interface CommandRunner { + String run(String command, String args); + } +} + + diff --git a/java-server/src/main/java/com/restprovider/controllers/LogAnalyticsController.java b/java-server/src/main/java/com/restprovider/controllers/LogAnalyticsController.java index 11c9463..f2e2540 100644 --- a/java-server/src/main/java/com/restprovider/controllers/LogAnalyticsController.java +++ b/java-server/src/main/java/com/restprovider/controllers/LogAnalyticsController.java @@ -18,16 +18,28 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the LogAnalytics integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class LogAnalyticsController extends BaseController { private final PasscodeValidator passcodeValidator; private final CommandRunner commandRunner; private final FileContentReader fileContentReader; + /** + * Creates a controller with default runtime dependencies. + */ public LogAnalyticsController() { this(new EnvPasscodeValidator(), ProcessUtil::run, path -> Files.readString(path, StandardCharsets.UTF_8)); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public LogAnalyticsController(PasscodeValidator passcodeValidator, CommandRunner commandRunner, FileContentReader fileContentReader) { @@ -37,6 +49,15 @@ public LogAnalyticsController(PasscodeValidator passcodeValidator, this.fileContentReader = fileContentReader; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -186,14 +207,22 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface CommandRunner { String run(String command, String args); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface FileContentReader { String read(Path filePath) throws IOException; } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/MSDController.java b/java-server/src/main/java/com/restprovider/controllers/MSDController.java index c62e791..8f06cdc 100644 --- a/java-server/src/main/java/com/restprovider/controllers/MSDController.java +++ b/java-server/src/main/java/com/restprovider/controllers/MSDController.java @@ -17,20 +17,41 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the MSD integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class MSDController extends BaseController { private final PasscodeValidator passcodeValidator; private final CommandRunner commandRunner; + /** + * Creates a controller with default runtime dependencies. + */ public MSDController() { this(new EnvPasscodeValidator(), ProcessUtil::run); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public MSDController(PasscodeValidator passcodeValidator, CommandRunner commandRunner) { super("MSD"); this.passcodeValidator = passcodeValidator; this.commandRunner = commandRunner; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -146,9 +167,14 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface CommandRunner { String run(String command, String args); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/MiscController.java b/java-server/src/main/java/com/restprovider/controllers/MiscController.java index 994d1cd..16127f0 100644 --- a/java-server/src/main/java/com/restprovider/controllers/MiscController.java +++ b/java-server/src/main/java/com/restprovider/controllers/MiscController.java @@ -28,6 +28,12 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the Misc integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class MiscController extends BaseController { private static final DateTimeFormatter RANDOM_DATE_FORMAT = DateTimeFormatter.ofPattern("MM/dd/yyyy"); private static final String SAFE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @@ -38,14 +44,23 @@ public class MiscController extends BaseController { private final Random random; private final Map variableData; + /** + * Creates a controller with default runtime dependencies. + */ public MiscController() { this(new EnvPasscodeValidator(), ProcessUtil::run, new Random(), new ConcurrentHashMap<>()); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public MiscController(PasscodeValidator passcodeValidator, CommandRunner commandRunner) { this(passcodeValidator, commandRunner, new Random(), new ConcurrentHashMap<>()); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public MiscController(PasscodeValidator passcodeValidator, CommandRunner commandRunner, Random random, @@ -57,6 +72,15 @@ public MiscController(PasscodeValidator passcodeValidator, this.variableData = variableData; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -393,9 +417,14 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface CommandRunner { String run(String command, String args); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/OSController.java b/java-server/src/main/java/com/restprovider/controllers/OSController.java index 769ebea..88a5780 100644 --- a/java-server/src/main/java/com/restprovider/controllers/OSController.java +++ b/java-server/src/main/java/com/restprovider/controllers/OSController.java @@ -29,20 +29,41 @@ import org.apache.hc.core5.http.io.entity.StringEntity; import com.restprovider.core.BaseController; +/** + * Controller for the OS integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class OSController extends BaseController { private final PasscodeValidator passcodeValidator; private final CommandRunner commandRunner; + /** + * Creates a controller with default runtime dependencies. + */ public OSController() { this(new EnvPasscodeValidator(), ProcessUtil::run); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public OSController(PasscodeValidator passcodeValidator, CommandRunner commandRunner) { super("OS"); this.passcodeValidator = passcodeValidator; this.commandRunner = commandRunner; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -81,6 +102,7 @@ public void handle(ClassicHttpRequest request, ClassicHttpResponse response, Str try { HttpClient client = HttpClient.newHttpClient(); HttpRequest req = HttpRequest.newBuilder(URI.create("https://ipinfo.io/ip")).GET().build(); + // Outbound REST call to the target service endpoint. HttpResponse res = client.send(req, HttpResponse.BodyHandlers.ofString()); respondJson(response, HttpStatus.SC_OK, "{\"Content\":\"" + JsonUtil.escape(res.body().trim()) + "\"}"); @@ -375,9 +397,14 @@ private static void respondJson(ClassicHttpResponse response, int code, String j response.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface CommandRunner { String run(String command, String args); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/OfficeController.java b/java-server/src/main/java/com/restprovider/controllers/OfficeController.java index 17abba1..d888b6a 100644 --- a/java-server/src/main/java/com/restprovider/controllers/OfficeController.java +++ b/java-server/src/main/java/com/restprovider/controllers/OfficeController.java @@ -24,18 +24,39 @@ import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; +/** + * Controller for the Office integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class OfficeController extends BaseController { private final PasscodeValidator passcodeValidator; + /** + * Creates a controller with default runtime dependencies. + */ public OfficeController() { this(new EnvPasscodeValidator()); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public OfficeController(PasscodeValidator passcodeValidator) { super("Office"); this.passcodeValidator = passcodeValidator; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -220,3 +241,5 @@ private static void respondText(ClassicHttpResponse response, int code, String b } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/OracleController.java b/java-server/src/main/java/com/restprovider/controllers/OracleController.java index 49f1dcd..88d0d5b 100644 --- a/java-server/src/main/java/com/restprovider/controllers/OracleController.java +++ b/java-server/src/main/java/com/restprovider/controllers/OracleController.java @@ -21,18 +21,39 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the Oracle integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class OracleController extends BaseController { private final PasscodeValidator passcodeValidator; + /** + * Creates a controller with default runtime dependencies. + */ public OracleController() { this(new EnvPasscodeValidator()); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public OracleController(PasscodeValidator passcodeValidator) { super("Oracle"); this.passcodeValidator = passcodeValidator; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -268,3 +289,5 @@ private static void respondJson(ClassicHttpResponse response, int code, String b } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/PostmanController.java b/java-server/src/main/java/com/restprovider/controllers/PostmanController.java index e2063fd..ce3e5d9 100644 --- a/java-server/src/main/java/com/restprovider/controllers/PostmanController.java +++ b/java-server/src/main/java/com/restprovider/controllers/PostmanController.java @@ -16,20 +16,41 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the Postman integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class PostmanController extends BaseController { private final PasscodeValidator passcodeValidator; private final CommandRunner commandRunner; + /** + * Creates a controller with default runtime dependencies. + */ public PostmanController() { this(new EnvPasscodeValidator(), ProcessUtil::run); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public PostmanController(PasscodeValidator passcodeValidator, CommandRunner commandRunner) { super("Postman"); this.passcodeValidator = passcodeValidator; this.commandRunner = commandRunner; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -164,8 +185,13 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface CommandRunner { String run(String command, String args); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/PowerBIController.java b/java-server/src/main/java/com/restprovider/controllers/PowerBIController.java index 190d972..9cff91b 100644 --- a/java-server/src/main/java/com/restprovider/controllers/PowerBIController.java +++ b/java-server/src/main/java/com/restprovider/controllers/PowerBIController.java @@ -21,6 +21,12 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the PowerBI integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class PowerBIController extends BaseController { private static final String POWER_BI_ROOT = "https://api.powerbi.com"; @@ -28,10 +34,16 @@ public class PowerBIController extends BaseController { private final TokenProvider tokenProvider; private final HttpInvoker httpInvoker; + /** + * Creates a controller with default runtime dependencies. + */ public PowerBIController() { this(new EnvPasscodeValidator(), PowerBIController::resolveAccessToken, PowerBIController::invokePowerBi); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public PowerBIController(PasscodeValidator passcodeValidator, TokenProvider tokenProvider, HttpInvoker httpInvoker) { @@ -41,6 +53,15 @@ public PowerBIController(PasscodeValidator passcodeValidator, this.httpInvoker = httpInvoker; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -91,6 +112,7 @@ public void handle(ClassicHttpRequest request, ClassicHttpResponse response, Str Files.delete(outputPath); } + // Outbound REST call to the target service endpoint. String responseBody = httpInvoker.call(method, endpoint, token); Files.writeString(outputPath, responseBody, StandardCharsets.UTF_8); respondText(response, HttpStatus.SC_OK, "PowerBI Response written to " + outputPath); @@ -183,6 +205,7 @@ private static String resolveAccessToken() { .header("Content-Type", "application/x-www-form-urlencoded") .POST(HttpRequest.BodyPublishers.ofString(form)) .build(); + // Outbound REST call to the target service endpoint. HttpResponse res = client.send(req, HttpResponse.BodyHandlers.ofString()); return extractAccessToken(res.body()); } catch (Exception ex) { @@ -197,6 +220,7 @@ private static String invokePowerBi(String method, String endpoint, String token .header("Authorization", "Bearer " + token) .method(method, HttpRequest.BodyPublishers.noBody()) .build(); + // Outbound REST call to the target service endpoint. HttpResponse res = client.send(req, HttpResponse.BodyHandlers.ofString()); return res.body(); } catch (Exception ex) { @@ -229,14 +253,23 @@ private static void respondText(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.TEXT_PLAIN)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface TokenProvider { String getToken(); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface HttpInvoker { String call(String method, String endpoint, String bearerToken); } } + + + diff --git a/java-server/src/main/java/com/restprovider/controllers/SQLServerController.java b/java-server/src/main/java/com/restprovider/controllers/SQLServerController.java index 2c638b3..3e74bd5 100644 --- a/java-server/src/main/java/com/restprovider/controllers/SQLServerController.java +++ b/java-server/src/main/java/com/restprovider/controllers/SQLServerController.java @@ -22,18 +22,39 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the SQLServer integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class SQLServerController extends BaseController { private final PasscodeValidator passcodeValidator; + /** + * Creates a controller with default runtime dependencies. + */ public SQLServerController() { this(new EnvPasscodeValidator()); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public SQLServerController(PasscodeValidator passcodeValidator) { super("SQLServer"); this.passcodeValidator = passcodeValidator; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -241,3 +262,5 @@ private static void respondJson(ClassicHttpResponse response, int code, String b } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/SequenceController.java b/java-server/src/main/java/com/restprovider/controllers/SequenceController.java index 48f5b70..55c9c60 100644 --- a/java-server/src/main/java/com/restprovider/controllers/SequenceController.java +++ b/java-server/src/main/java/com/restprovider/controllers/SequenceController.java @@ -26,11 +26,29 @@ import org.w3c.dom.Element; import org.xml.sax.InputSource; +/** + * Controller for the Sequence integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class SequenceController extends BaseController { + /** + * Creates a controller with default runtime dependencies. + */ public SequenceController() { super("Sequence"); } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -234,3 +252,5 @@ private static void respondText(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.TEXT_PLAIN)); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/SnowflakeController.java b/java-server/src/main/java/com/restprovider/controllers/SnowflakeController.java index 0bed150..3c824a5 100644 --- a/java-server/src/main/java/com/restprovider/controllers/SnowflakeController.java +++ b/java-server/src/main/java/com/restprovider/controllers/SnowflakeController.java @@ -21,20 +21,41 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the Snowflake integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class SnowflakeController extends BaseController { private final PasscodeValidator passcodeValidator; private final HttpExecutor httpExecutor; + /** + * Creates a controller with default runtime dependencies. + */ public SnowflakeController() { this(new EnvPasscodeValidator(), SnowflakeController::executeHttp); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public SnowflakeController(PasscodeValidator passcodeValidator, HttpExecutor httpExecutor) { super("Snowflake"); this.passcodeValidator = passcodeValidator; this.httpExecutor = httpExecutor; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -144,6 +165,7 @@ private static String executeHttp(String endpoint, Map form) { .header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); + // Outbound REST call to the target service endpoint. HttpResponse res = client.send(req, HttpResponse.BodyHandlers.ofString()); return res.body(); } catch (Exception ex) { @@ -169,9 +191,14 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface HttpExecutor { String postForm(String endpoint, Map form); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/SonarQubeController.java b/java-server/src/main/java/com/restprovider/controllers/SonarQubeController.java index d3288e6..a5eae81 100644 --- a/java-server/src/main/java/com/restprovider/controllers/SonarQubeController.java +++ b/java-server/src/main/java/com/restprovider/controllers/SonarQubeController.java @@ -18,20 +18,41 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the SonarQube integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class SonarQubeController extends BaseController { private final PasscodeValidator passcodeValidator; private final HttpExecutor httpExecutor; + /** + * Creates a controller with default runtime dependencies. + */ public SonarQubeController() { this(new EnvPasscodeValidator(), SonarQubeController::executeGraphQL); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public SonarQubeController(PasscodeValidator passcodeValidator, HttpExecutor httpExecutor) { super("SonarQube"); this.passcodeValidator = passcodeValidator; this.httpExecutor = httpExecutor; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -152,6 +173,7 @@ private static String executeGraphQL(String endpoint, String token, String query .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); + // Outbound REST call to the target service endpoint. HttpResponse res = client.send(req, HttpResponse.BodyHandlers.ofString()); return res.body(); } catch (Exception ex) { @@ -164,9 +186,14 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface HttpExecutor { String postGraphQL(String endpoint, String apiToken, String query); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/StorageAccountController.java b/java-server/src/main/java/com/restprovider/controllers/StorageAccountController.java index 49858a8..c29521f 100644 --- a/java-server/src/main/java/com/restprovider/controllers/StorageAccountController.java +++ b/java-server/src/main/java/com/restprovider/controllers/StorageAccountController.java @@ -19,20 +19,41 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the StorageAccount integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class StorageAccountController extends BaseController { private final PasscodeValidator passcodeValidator; private final CommandRunner commandRunner; + /** + * Creates a controller with default runtime dependencies. + */ public StorageAccountController() { this(new EnvPasscodeValidator(), ProcessUtil::run); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public StorageAccountController(PasscodeValidator passcodeValidator, CommandRunner commandRunner) { super("StorageAccount"); this.passcodeValidator = passcodeValidator; this.commandRunner = commandRunner; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -408,9 +429,14 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface CommandRunner { String run(String command, String args); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/StringController.java b/java-server/src/main/java/com/restprovider/controllers/StringController.java index 7e5f2a2..ceb730d 100644 --- a/java-server/src/main/java/com/restprovider/controllers/StringController.java +++ b/java-server/src/main/java/com/restprovider/controllers/StringController.java @@ -23,11 +23,29 @@ import org.apache.hc.core5.http.io.entity.StringEntity; import com.restprovider.core.BaseController; +/** + * Controller for the String integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class StringController extends BaseController { + /** + * Creates a controller with default runtime dependencies. + */ public StringController() { super("String"); } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -319,3 +337,5 @@ private static String encryptString(String plainText, String passphrase) { } } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/SynapseController.java b/java-server/src/main/java/com/restprovider/controllers/SynapseController.java index 99715c5..f508f35 100644 --- a/java-server/src/main/java/com/restprovider/controllers/SynapseController.java +++ b/java-server/src/main/java/com/restprovider/controllers/SynapseController.java @@ -18,20 +18,41 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the Synapse integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class SynapseController extends BaseController { private final PasscodeValidator passcodeValidator; private final CommandRunner commandRunner; + /** + * Creates a controller with default runtime dependencies. + */ public SynapseController() { this(new EnvPasscodeValidator(), ProcessUtil::run); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public SynapseController(PasscodeValidator passcodeValidator, CommandRunner commandRunner) { super("Synapse"); this.passcodeValidator = passcodeValidator; this.commandRunner = commandRunner; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -146,9 +167,14 @@ private static void respondJson(ClassicHttpResponse response, int code, String b response.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); } + /** + * Functional contract used to abstract external operations for this controller. + */ @FunctionalInterface public interface CommandRunner { String run(String command, String args); } } + + diff --git a/java-server/src/main/java/com/restprovider/controllers/WaitController.java b/java-server/src/main/java/com/restprovider/controllers/WaitController.java index 98df571..fe311fc 100644 --- a/java-server/src/main/java/com/restprovider/controllers/WaitController.java +++ b/java-server/src/main/java/com/restprovider/controllers/WaitController.java @@ -20,18 +20,39 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.StringEntity; +/** + * Controller for the Wait integration endpoints. + * + *

This class maps controller routes, validates request input aliases, and + * returns API responses aligned with RestProvider automation behavior.

+ */ public class WaitController extends BaseController { private final PasscodeValidator passcodeValidator; + /** + * Creates a controller with default runtime dependencies. + */ public WaitController() { this(new EnvPasscodeValidator()); } + /** + * Creates a controller with injected dependencies for testability and customization. + */ public WaitController(PasscodeValidator passcodeValidator) { super("Wait"); this.passcodeValidator = passcodeValidator; } + /** + * Handles incoming HTTP requests for this controller's route surface. + * + * @param request inbound HTTP request + * @param response outbound HTTP response + * @param subPath controller-specific route segment after /api/{controller}/ + * @throws IOException when I/O work fails + * @throws HttpException when request handling fails at HTTP protocol level + */ @Override public void handle(ClassicHttpRequest request, ClassicHttpResponse response, String subPath) throws IOException, HttpException { @@ -290,3 +311,5 @@ private WaitResult(String result, int statusCode) { } } + + diff --git a/java-server/src/main/java/com/restprovider/core/DefaultRegistryFactory.java b/java-server/src/main/java/com/restprovider/core/DefaultRegistryFactory.java index 84c011e..78bd8a0 100644 --- a/java-server/src/main/java/com/restprovider/core/DefaultRegistryFactory.java +++ b/java-server/src/main/java/com/restprovider/core/DefaultRegistryFactory.java @@ -10,8 +10,11 @@ import com.restprovider.controllers.DataverseController; import com.restprovider.controllers.FHIRController; import com.restprovider.controllers.FileController; +import com.restprovider.controllers.GrafanaController; import com.restprovider.controllers.JMeterController; +import com.restprovider.controllers.JavadocController; import com.restprovider.controllers.JenkinsController; +import com.restprovider.controllers.K6Controller; import com.restprovider.controllers.LogAnalyticsController; import com.restprovider.controllers.MSDController; import com.restprovider.controllers.MiscController; @@ -45,8 +48,11 @@ public static ControllerRegistry createDefaultRegistry() { registry.register(new DataverseController()); registry.register(new FHIRController()); registry.register(new FileController()); + registry.register(new GrafanaController()); + registry.register(new JavadocController()); registry.register(new JenkinsController()); registry.register(new JMeterController()); + registry.register(new K6Controller()); registry.register(new LogAnalyticsController()); registry.register(new MiscController()); registry.register(new MSDController()); diff --git a/java-server/src/test/java/com/restprovider/integration/AdminToggleIntegrationTest.java b/java-server/src/test/java/com/restprovider/integration/AdminToggleIntegrationTest.java index 6c5017f..6766ce4 100644 --- a/java-server/src/test/java/com/restprovider/integration/AdminToggleIntegrationTest.java +++ b/java-server/src/test/java/com/restprovider/integration/AdminToggleIntegrationTest.java @@ -59,4 +59,15 @@ void shouldDisableAndEnableControllerViaAdminRoute() throws Exception { dispatcher.handle(new BasicClassicHttpRequest("GET", "/api/string/echo"), afterEnable, TestHttpContexts.newContext()); Assertions.assertEquals(200, afterEnable.getCode()); } + + @Test + void shouldListAllControllersDisabledByDefault() throws Exception { + BasicClassicHttpResponse response = new BasicClassicHttpResponse(200); + dispatcher.handle(new BasicClassicHttpRequest("GET", "/admin/controllers"), response, TestHttpContexts.newContext()); + + String body = TestResponseUtil.body(response); + Assertions.assertEquals(200, response.getCode()); + Assertions.assertTrue(body.contains("\"name\":\"k6\"")); + Assertions.assertFalse(body.contains("\"enabled\":true")); + } } diff --git a/java-server/src/test/java/com/restprovider/integration/GrafanaControllerIntegrationTest.java b/java-server/src/test/java/com/restprovider/integration/GrafanaControllerIntegrationTest.java new file mode 100644 index 0000000..ce95ad7 --- /dev/null +++ b/java-server/src/test/java/com/restprovider/integration/GrafanaControllerIntegrationTest.java @@ -0,0 +1,87 @@ +package com.restprovider.integration; + +import com.restprovider.controllers.GrafanaController; +import com.restprovider.core.ControllerDispatcher; +import com.restprovider.core.ControllerRegistry; +import com.restprovider.domain.security.PasscodeValidator; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import org.apache.hc.core5.http.message.BasicClassicHttpRequest; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class GrafanaControllerIntegrationTest { + private ControllerDispatcher dispatcher; + + @BeforeEach + void setup() { + PasscodeValidator validator = passCode -> "valid-passcode".equals(passCode); + GrafanaController.TokenProvider tokenProvider = () -> "token-1"; + GrafanaController.HttpInvoker invoker = (method, endpoint, token) -> "{\"status\":\"ok\"}"; + + ControllerRegistry registry = new ControllerRegistry(); + registry.register(new GrafanaController(validator, tokenProvider, invoker)); + registry.setControllerEnabled("Grafana", true); + dispatcher = new ControllerDispatcher(registry); + } + + @Test + void shouldReturnPasscodeFailureWhenMissingPasscode() throws Exception { + BasicClassicHttpRequest request = new BasicClassicHttpRequest("GET", "/api/grafana"); + BasicClassicHttpResponse response = new BasicClassicHttpResponse(200); + + dispatcher.handle(request, response, TestHttpContexts.newContext()); + + Assertions.assertEquals(200, response.getCode()); + Assertions.assertEquals("Passcode failure", TestResponseUtil.body(response)); + } + + @Test + void shouldWriteOutputWhenPasscodeValid() throws Exception { + String project = "grafana_" + System.nanoTime(); + BasicClassicHttpRequest request = new BasicClassicHttpRequest("GET", "/api/grafana"); + request.addHeader("passCode", "valid-passcode"); + request.addHeader("projectName", project); + request.addHeader("grafanaRequest", "dashboards/home"); + request.addHeader("apiVersion", "1"); + BasicClassicHttpResponse response = new BasicClassicHttpResponse(200); + + dispatcher.handle(request, response, TestHttpContexts.newContext()); + + Assertions.assertEquals(200, response.getCode()); + Path output = Path.of(System.getProperty("user.dir"), "data_files", "temp", project, "grafana_get_response.txt"); + Assertions.assertTrue(Files.exists(output)); + Assertions.assertTrue(Files.readString(output, StandardCharsets.UTF_8).contains("status")); + } + + @Test + void shouldSupportRequestAliasAndQueryString() throws Exception { + String project = "grafana_" + System.nanoTime(); + BasicClassicHttpRequest request = new BasicClassicHttpRequest( + "GET", + "/api/grafana/request?passCode=valid-passcode&project=" + project + + "&request=dashboards/home&version=1&token=abc-token"); + BasicClassicHttpResponse response = new BasicClassicHttpResponse(200); + + dispatcher.handle(request, response, TestHttpContexts.newContext()); + + Assertions.assertEquals(200, response.getCode()); + Path output = Path.of(System.getProperty("user.dir"), "data_files", "temp", project, "grafana_get_response.txt"); + Assertions.assertTrue(Files.exists(output)); + } + + @Test + void shouldReturnBadRequestWhenRequiredFieldsMissing() throws Exception { + BasicClassicHttpRequest request = new BasicClassicHttpRequest("GET", "/api/grafana"); + request.addHeader("passCode", "valid-passcode"); + BasicClassicHttpResponse response = new BasicClassicHttpResponse(200); + + dispatcher.handle(request, response, TestHttpContexts.newContext()); + + Assertions.assertEquals(400, response.getCode()); + Assertions.assertTrue(TestResponseUtil.body(response).contains("Missing required parameter")); + } +} diff --git a/java-server/src/test/java/com/restprovider/integration/JavadocControllerIntegrationTest.java b/java-server/src/test/java/com/restprovider/integration/JavadocControllerIntegrationTest.java new file mode 100644 index 0000000..a7bf76e --- /dev/null +++ b/java-server/src/test/java/com/restprovider/integration/JavadocControllerIntegrationTest.java @@ -0,0 +1,66 @@ +package com.restprovider.integration; + +import com.restprovider.controllers.JavadocController; +import com.restprovider.core.ControllerDispatcher; +import com.restprovider.core.ControllerRegistry; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import org.apache.hc.core5.http.message.BasicClassicHttpRequest; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class JavadocControllerIntegrationTest { + private ControllerDispatcher dispatcher; + private Path docsRoot; + + @BeforeEach + void setup() throws Exception { + docsRoot = Files.createTempDirectory("javadocs_"); + Files.createDirectories(docsRoot); + Files.writeString(docsRoot.resolve("index.html"), "API Docs", StandardCharsets.UTF_8); + + JavadocController.CommandRunner runner = (command, args) -> "javadocs generated"; + ControllerRegistry registry = new ControllerRegistry(); + registry.register(new JavadocController(runner, docsRoot)); + registry.setControllerEnabled("Javadoc", true); + dispatcher = new ControllerDispatcher(registry); + } + + @Test + void shouldGenerateJavadocs() throws Exception { + BasicClassicHttpRequest request = new BasicClassicHttpRequest("GET", "/api/javadoc/generate"); + BasicClassicHttpResponse response = new BasicClassicHttpResponse(200); + + dispatcher.handle(request, response, TestHttpContexts.newContext()); + + Assertions.assertEquals(200, response.getCode()); + String body = TestResponseUtil.body(response); + Assertions.assertTrue(body.contains("Javadocs generated")); + Assertions.assertTrue(body.contains("/api/javadoc/docs")); + } + + @Test + void shouldServeJavadocsIndex() throws Exception { + BasicClassicHttpRequest request = new BasicClassicHttpRequest("GET", "/api/javadoc/docs"); + BasicClassicHttpResponse response = new BasicClassicHttpResponse(200); + + dispatcher.handle(request, response, TestHttpContexts.newContext()); + + Assertions.assertEquals(200, response.getCode()); + Assertions.assertTrue(TestResponseUtil.body(response).contains("API Docs")); + } + + @Test + void shouldRejectPathTraversal() throws Exception { + BasicClassicHttpRequest request = new BasicClassicHttpRequest("GET", "/api/javadoc/docs/../secret.txt"); + BasicClassicHttpResponse response = new BasicClassicHttpResponse(200); + + dispatcher.handle(request, response, TestHttpContexts.newContext()); + + Assertions.assertEquals(400, response.getCode()); + Assertions.assertTrue(TestResponseUtil.body(response).contains("Invalid documentation path")); + } +} diff --git a/java-server/src/test/java/com/restprovider/integration/K6ControllerIntegrationTest.java b/java-server/src/test/java/com/restprovider/integration/K6ControllerIntegrationTest.java new file mode 100644 index 0000000..01a66d2 --- /dev/null +++ b/java-server/src/test/java/com/restprovider/integration/K6ControllerIntegrationTest.java @@ -0,0 +1,63 @@ +package com.restprovider.integration; + +import com.restprovider.controllers.K6Controller; +import com.restprovider.core.ControllerDispatcher; +import com.restprovider.core.ControllerRegistry; +import org.apache.hc.core5.http.message.BasicClassicHttpRequest; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class K6ControllerIntegrationTest { + private ControllerDispatcher dispatcher; + + @BeforeEach + void setup() { + K6Controller.CommandRunner runner = (command, args) -> { + if ("version".equals(args)) { + return "k6 v0.52.0"; + } + return "run ok"; + }; + ControllerRegistry registry = new ControllerRegistry(); + registry.register(new K6Controller(runner)); + registry.setControllerEnabled("K6", true); + dispatcher = new ControllerDispatcher(registry); + } + + @Test + void shouldReturnK6Version() throws Exception { + BasicClassicHttpRequest request = new BasicClassicHttpRequest("GET", "/api/k6/version"); + BasicClassicHttpResponse response = new BasicClassicHttpResponse(200); + + dispatcher.handle(request, response, TestHttpContexts.newContext()); + + Assertions.assertEquals(200, response.getCode()); + Assertions.assertTrue(TestResponseUtil.body(response).contains("k6 v0.52.0")); + } + + @Test + void shouldSupportRunAliasWithQueryParameters() throws Exception { + BasicClassicHttpRequest request = new BasicClassicHttpRequest( + "GET", + "/api/k6/run?scriptName=test.js&projectName=proj&testcaseName=tc1"); + BasicClassicHttpResponse response = new BasicClassicHttpResponse(200); + + dispatcher.handle(request, response, TestHttpContexts.newContext()); + + Assertions.assertEquals(200, response.getCode()); + Assertions.assertTrue(TestResponseUtil.body(response).contains("run ok")); + } + + @Test + void shouldReturnBadRequestWhenScriptMissing() throws Exception { + BasicClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/api/k6"); + BasicClassicHttpResponse response = new BasicClassicHttpResponse(200); + + dispatcher.handle(request, response, TestHttpContexts.newContext()); + + Assertions.assertEquals(400, response.getCode()); + Assertions.assertTrue(TestResponseUtil.body(response).contains("Missing required parameter")); + } +}