diff --git a/app/core/src/main/java/stirling/software/SPDF/model/ApiEndpoint.java b/app/core/src/main/java/stirling/software/SPDF/model/ApiEndpoint.java index 79a910300..04d2bb1fa 100644 --- a/app/core/src/main/java/stirling/software/SPDF/model/ApiEndpoint.java +++ b/app/core/src/main/java/stirling/software/SPDF/model/ApiEndpoint.java @@ -25,8 +25,9 @@ public class ApiEndpoint { } public boolean areParametersValid(Map providedParams) { - for (String requiredParam : parameters.keySet()) { - if (!providedParams.containsKey(requiredParam)) { + for (Map.Entry entry : parameters.entrySet()) { + boolean isRequired = entry.getValue().path("required").asBoolean(false); + if (isRequired && !providedParams.containsKey(entry.getKey())) { return false; } } diff --git a/app/core/src/test/java/stirling/software/SPDF/model/ApiEndpointTest.java b/app/core/src/test/java/stirling/software/SPDF/model/ApiEndpointTest.java index 85e1587a5..a9293f116 100644 --- a/app/core/src/test/java/stirling/software/SPDF/model/ApiEndpointTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/model/ApiEndpointTest.java @@ -18,6 +18,11 @@ class ApiEndpointTest { private final ObjectMapper mapper = JsonMapper.builder().build(); private JsonNode postNodeWithParams(String description, String... names) { + return postNodeWithParams(description, true, names); + } + + private JsonNode postNodeWithParams( + String description, boolean required, String... names) { ObjectNode post = mapper.createObjectNode(); post.put("description", description); ArrayNode params = mapper.createArrayNode(); @@ -26,6 +31,7 @@ class ApiEndpointTest { if (n != null) { p.put("name", n); } + p.put("required", required); params.add(p); } post.set("parameters", params); @@ -92,6 +98,75 @@ class ApiEndpointTest { assertTrue(endpoint.areParametersValid(Map.of("", 42))); } + @Test + void optional_parameters_can_be_omitted() { + JsonNode post = postNodeWithParams("desc", false, "fileOrder"); + ApiEndpoint endpoint = new ApiEndpoint("merge", post); + + assertTrue( + endpoint.areParametersValid(Map.of()), + "Should be valid when optional param is omitted"); + } + + @Test + void mixed_required_and_optional_validates_only_required() { + ObjectNode post = mapper.createObjectNode(); + post.put("description", "merge pdfs"); + ArrayNode params = mapper.createArrayNode(); + + ObjectNode required = mapper.createObjectNode(); + required.put("name", "sortType"); + required.put("required", true); + params.add(required); + + ObjectNode optional = mapper.createObjectNode(); + optional.put("name", "fileOrder"); + optional.put("required", false); + params.add(optional); + + post.set("parameters", params); + ApiEndpoint endpoint = new ApiEndpoint("/api/v1/general/merge-pdfs", post); + + Map provided = new HashMap<>(); + provided.put("sortType", "byFileName"); + + assertTrue( + endpoint.areParametersValid(provided), + "Should pass when required params present and optional omitted"); + + provided.put("fileOrder", "0,1,2"); + assertTrue( + endpoint.areParametersValid(provided), + "Should also pass when optional param is provided"); + } + + @Test + void missing_required_param_with_optional_present_still_fails() { + ObjectNode post = mapper.createObjectNode(); + post.put("description", "desc"); + ArrayNode params = mapper.createArrayNode(); + + ObjectNode required = mapper.createObjectNode(); + required.put("name", "file"); + required.put("required", true); + params.add(required); + + ObjectNode optional = mapper.createObjectNode(); + optional.put("name", "fileOrder"); + optional.put("required", false); + params.add(optional); + + post.set("parameters", params); + ApiEndpoint endpoint = new ApiEndpoint("x", post); + + Map provided = new HashMap<>(); + provided.put("fileOrder", "0,1"); + + assertFalse( + endpoint.areParametersValid(provided), + "Should fail when required param is missing even if optional is present"); + } + @Test void toString_contains_name_and_parameter_names() { JsonNode post = postNodeWithParams("desc", "file", "mode"); diff --git a/app/core/src/test/java/stirling/software/SPDF/service/ApiDocServiceTest.java b/app/core/src/test/java/stirling/software/SPDF/service/ApiDocServiceTest.java index baa6d3f2f..cf176c042 100644 --- a/app/core/src/test/java/stirling/software/SPDF/service/ApiDocServiceTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/service/ApiDocServiceTest.java @@ -74,15 +74,37 @@ class ApiDocServiceTest { @Test void isValidOperationChecksRequiredParameters() throws Exception { String json = - "{\"description\": \"desc\", \"parameters\": [{\"name\":\"param1\"}, {\"name\":\"param2\"}]}"; + "{\"description\": \"desc\", \"parameters\": [{\"name\":\"param1\", \"required\": true}, {\"name\":\"param2\", \"required\": true}]}"; JsonNode postNode = mapper.readTree(json); ApiEndpoint endpoint = new ApiEndpoint("/op", postNode); setApiDocumentation(Map.of("/op", endpoint)); setApiDocsJsonRootNode(); + // All required params provided - valid assertTrue(apiDocService.isValidOperation("/op", Map.of("param1", "a", "param2", "b"))); + // Missing required param2 - invalid assertFalse(apiDocService.isValidOperation("/op", Map.of("param1", "a"))); + // Missing required param1 - invalid + assertFalse(apiDocService.isValidOperation("/op", Map.of("param2", "b"))); + } + + @Test + void isValidOperationAllowsOptionalParameters() throws Exception { + String json = + "{\"description\": \"desc\", \"parameters\": [{\"name\":\"param1\", \"required\": false}, {\"name\":\"param2\", \"required\": false}]}"; + JsonNode postNode = mapper.readTree(json); + ApiEndpoint endpoint = new ApiEndpoint("/op", postNode); + + setApiDocumentation(Map.of("/op", endpoint)); + setApiDocsJsonRootNode(); + + // All optional params provided - valid + assertTrue(apiDocService.isValidOperation("/op", Map.of("param1", "a", "param2", "b"))); + // Only one optional param provided - valid + assertTrue(apiDocService.isValidOperation("/op", Map.of("param1", "a"))); + // No optional params provided - valid + assertTrue(apiDocService.isValidOperation("/op", Map.of())); } @Test