diff --git a/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/controller/RulesController.java b/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/controller/RulesController.java index 4ac9c4026..fee293156 100644 --- a/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/controller/RulesController.java +++ b/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/controller/RulesController.java @@ -17,6 +17,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @@ -57,27 +58,38 @@ public class RulesController implements RulesResource { @Override @PreAuthorize("hasAuthority('" + WRITE_RULES + "')") - public ResponseEntity upload(@RequestBody RulesUploadRequestModel rules) { + public ResponseEntity upload(@RequestBody RulesUploadRequestModel rules) { RulesUploadRequest rulesUploadRequest = RulesUploadRequest.fromModel(rules); - DroolsSyntaxValidationResponse droolsSyntaxValidationResponse = DroolsSyntaxValidationResponse.builder() - .build(); + DroolsSyntaxValidationResponse droolsSyntaxValidationResponse = DroolsSyntaxValidationResponse.builder().build(); try { DroolsSyntaxValidation droolsSyntaxValidation = rulesValidationService.validateRules(rulesUploadRequest.getRuleFileType(), rulesUploadRequest.getRules()); var rulesSyntaxWarningMessages = droolsSyntaxValidation.getDroolsSyntaxDeprecatedWarnings() .stream() - .map(warningMessage -> RuleSyntaxWarningMessage.builder().line(warningMessage.getLine()).column(warningMessage.getColumn()).message(warningMessage.getMessage()) + .map(warningMessage -> RuleSyntaxWarningMessage.builder() + .line(warningMessage.getLine()) + .column(warningMessage.getColumn()) + .message(warningMessage.getMessage()) .build()) .collect(Collectors.toList()); droolsSyntaxValidationResponse.setRulesSyntaxWarningMessages(rulesSyntaxWarningMessages); if (!droolsSyntaxValidation.isCompiled()) { var rulesSyntaxErrorMessages = droolsSyntaxValidation.getDroolsSyntaxErrorMessages() .stream() - .map(errorMessage -> RuleSyntaxErrorMessage.builder().line(errorMessage.getLine()).column(errorMessage.getColumn()).message(errorMessage.getMessage()) + .map(errorMessage -> RuleSyntaxErrorMessage.builder() + .line(errorMessage.getLine()) + .column(errorMessage.getColumn()) + .message(errorMessage.getMessage()) .build()) .toList(); droolsSyntaxValidationResponse.setRulesSyntaxErrorMessages(rulesSyntaxErrorMessages); - return new ResponseEntity<>(droolsSyntaxValidationResponse, HttpStatus.BAD_REQUEST); + if (!rules.isDryRun()) { + return new ResponseEntity<>(droolsSyntaxValidationResponse, HttpStatus.BAD_REQUEST); + } + } + + if (rules.isDryRun()) { + return ResponseEntity.ok(droolsSyntaxValidationResponse); } } catch (FeignException e) { if (e.status() == HttpStatus.BAD_REQUEST.value()) { @@ -117,20 +129,23 @@ public class RulesController implements RulesResource { @Override @PreAuthorize("hasAuthority('" + WRITE_RULES + "')") - public ResponseEntity uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestPart(name = "file") MultipartFile file) { + public ResponseEntity uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, + @RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun, + @RequestPart(name = "file") MultipartFile file) { - return uploadFile(dossierTemplateId, RuleFileType.ENTITY, file); + return uploadFile(dossierTemplateId, RuleFileType.ENTITY, dryRun, file); } @Override @PreAuthorize("hasAuthority('" + WRITE_RULES + "')") - public ResponseEntity uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, - @PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType, - @RequestPart(name = "file") MultipartFile file) { + public ResponseEntity uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, + @PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType, + @RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun, + @RequestPart(name = "file") MultipartFile file) { try { - return upload(new RulesUploadRequestModel(new String(file.getBytes(), StandardCharsets.UTF_8), dossierTemplateId, ruleFileType)); + return upload(new RulesUploadRequestModel(new String(file.getBytes(), StandardCharsets.UTF_8), dossierTemplateId, ruleFileType, dryRun)); } catch (IOException e) { throw new FileUploadException("Could not upload file.", e); } diff --git a/persistence-service-v1/persistence-service-external-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/external/resource/RulesResource.java b/persistence-service-v1/persistence-service-external-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/external/resource/RulesResource.java index b10c5ec5a..4b2ae0616 100644 --- a/persistence-service-v1/persistence-service-external-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/external/resource/RulesResource.java +++ b/persistence-service-v1/persistence-service-external-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/external/resource/RulesResource.java @@ -7,12 +7,14 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.multipart.MultipartFile; import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType; +import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsSyntaxValidationResponse; import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesResponse; import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequestModel; @@ -33,6 +35,8 @@ public interface RulesResource { String RULE_FILE_TYPE_PARAMETER_NAME = "ruleFileType"; String RULE_FILE_TYPE_PATH_VARIABLE = "/{ruleFileType}"; + String DRY_RUN_PARAMETER = "dryRun"; + /** * Upload rules to be used by redaction service. @@ -42,8 +46,8 @@ public interface RulesResource { @ResponseStatus(value = HttpStatus.NO_CONTENT) @PostMapping(value = RULES_PATH, consumes = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Takes object containing string or rules as argument, which will be used by the redaction service.") - @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Rules upload successful."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified.")}) - ResponseEntity upload(@RequestBody RulesUploadRequestModel rules); + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful or rules validation done."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified.")}) + ResponseEntity upload(@RequestBody RulesUploadRequestModel rules); @ResponseBody @@ -70,17 +74,20 @@ public interface RulesResource { @ResponseStatus(value = HttpStatus.NO_CONTENT) @PostMapping(value = RULES_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE + UPLOAD_PATH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @Operation(summary = "Takes object containing string or rules as argument, which will be used by the redaction service.") - @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Rules upload successful."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified.")}) - ResponseEntity uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file); + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful or rules validation done."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified.")}) + ResponseEntity uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, + @RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun, + @Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file); @ResponseStatus(value = HttpStatus.NO_CONTENT) @PostMapping(value = RULES_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE + RULE_FILE_TYPE_PATH_VARIABLE + UPLOAD_PATH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @Operation(summary = "Takes object containing string or rules as argument, which will be used by the redaction service.") - @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Rules upload successful."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified.")}) - ResponseEntity uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, - @PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType, - @Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file); + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful or rules validation done"), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified.")}) + ResponseEntity uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, + @PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType, + @RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun, + @Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file); @ResponseBody diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/RulesTest.java b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/RulesTest.java index b0553d4b4..016dcb2e8 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/RulesTest.java +++ b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/RulesTest.java @@ -1,18 +1,31 @@ package com.iqser.red.service.peristence.v1.server.integration.tests; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import org.apache.http.HttpStatus; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import com.iqser.red.service.peristence.v1.server.integration.client.RulesClient; import com.iqser.red.service.peristence.v1.server.integration.client.VersionClient; import com.iqser.red.service.peristence.v1.server.integration.service.DossierTemplateTesterAndProvider; import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest; import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType; +import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsSyntaxValidationResponse; import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequestModel; +import com.iqser.red.service.redaction.v1.model.DroolsSyntaxDeprecatedWarnings; +import com.iqser.red.service.redaction.v1.model.DroolsSyntaxErrorMessage; +import com.iqser.red.service.redaction.v1.model.DroolsSyntaxValidation; +import com.iqser.red.service.redaction.v1.model.RuleValidationModel; + +import feign.FeignException; +import software.amazon.awssdk.http.HttpStatusCode; public class RulesTest extends AbstractPersistenceServerServiceTest { @@ -31,16 +44,104 @@ public class RulesTest extends AbstractPersistenceServerServiceTest { var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate(); - rulesClient.upload(new RulesUploadRequestModel("lorem ipsum", dossierTemplate.getId(), RuleFileType.ENTITY)); + var response = rulesClient.upload(new RulesUploadRequestModel("lorem ipsum", dossierTemplate.getId(), RuleFileType.ENTITY, false)); + assertThat(response.getStatusCode().value()).isEqualTo(HttpStatus.SC_OK); assertThat(versionClient.getVersions(List.of(dossierTemplate.getId())) - .get(dossierTemplate.getId()) - .getRulesVersion()).isEqualTo(3); //1. beim Anlegen des DossierTemplates; 2. bei provideTestTemplate(), damit es ACTIVE ist + .get(dossierTemplate.getId()).getRulesVersion()).isEqualTo(3); //1. beim Anlegen des DossierTemplates; 2. bei provideTestTemplate(), damit es ACTIVE ist assertThat(rulesClient.download(dossierTemplate.getId()).getRules()).isEqualTo("lorem ipsum"); - rulesClient.upload(new RulesUploadRequestModel("lorem ipsum dolor sit amet", dossierTemplate.getId(), RuleFileType.ENTITY)); - assertThat(versionClient.getVersions(List.of(dossierTemplate.getId())).get(dossierTemplate.getId()).getRulesVersion()).isEqualTo(4); + response = rulesClient.upload(new RulesUploadRequestModel("lorem ipsum dolor sit amet", dossierTemplate.getId(), RuleFileType.ENTITY, false)); + assertThat(response.getStatusCode().value()).isEqualTo(HttpStatusCode.OK); + assertThat(versionClient.getVersions(List.of(dossierTemplate.getId())) + .get(dossierTemplate.getId()).getRulesVersion()).isEqualTo(4); assertThat(rulesClient.download(dossierTemplate.getId()).getRules()).isEqualTo("lorem ipsum dolor sit amet"); } + + @Test + public void testRulesWithRunDrySetTrue() { + + var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate(); + + List warningMessages = new ArrayList<>(); + warningMessages.add(DroolsSyntaxDeprecatedWarnings.builder().line(1).column(0).message("this function is deprecated").build()); + List errorMessages = new ArrayList<>(); + errorMessages.add(DroolsSyntaxErrorMessage.builder().line(1).column(0).message("error message").build()); + var request = new RulesUploadRequestModel("lorem ipsum", dossierTemplate.getId(), RuleFileType.ENTITY, true); + // case 1: dry-run true, no error messages just warning messages + when(redactionClient.testRules(new RuleValidationModel(request.getRuleFileType().name(), request.getRules()))).thenReturn(DroolsSyntaxValidation.builder() + .droolsSyntaxErrorMessages(Collections.emptyList()) + .droolsSyntaxDeprecatedWarnings( + warningMessages) + .build()); + + ResponseEntity response = rulesClient.upload(request); + assertThat(response.getStatusCode().value()).isEqualTo(HttpStatus.SC_OK); + assertThat(response.getBody().getRulesSyntaxWarningMessages()).isNotEmpty(); + assertThat(response.getBody().getRulesSyntaxErrorMessages()).isEmpty(); + + assertThat(versionClient.getVersions(List.of(dossierTemplate.getId())) + .get(dossierTemplate.getId()).getRulesVersion()).isEqualTo(2); //1. beim Anlegen des DossierTemplates; 2. bei provideTestTemplate() + + // case 2: dry-run true, error messages and warning messages + when(redactionClient.testRules(new RuleValidationModel(request.getRuleFileType().name(), request.getRules()))).thenReturn(DroolsSyntaxValidation.builder() + .droolsSyntaxErrorMessages(errorMessages) + .droolsSyntaxDeprecatedWarnings( + warningMessages) + .build()); + + response = rulesClient.upload(request); + assertThat(response.getStatusCode().value()).isEqualTo(HttpStatus.SC_OK); + assertThat(response.getBody().getRulesSyntaxWarningMessages()).isNotEmpty(); + assertThat(response.getBody().getRulesSyntaxErrorMessages()).isNotEmpty(); + + assertThat(versionClient.getVersions(List.of(dossierTemplate.getId())) + .get(dossierTemplate.getId()).getRulesVersion()).isEqualTo(2); //1. beim Anlegen des DossierTemplates; 2. bei provideTestTemplate() + + } + + + @Test + public void testRulesWithRunDrySetFalse() { + + var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate(); + + List warningMessages = new ArrayList<>(); + warningMessages.add(DroolsSyntaxDeprecatedWarnings.builder().line(1).column(0).message("this function is deprecated").build()); + List errorMessages = new ArrayList<>(); + errorMessages.add(DroolsSyntaxErrorMessage.builder().line(1).column(0).message("error message").build()); + var request = new RulesUploadRequestModel("lorem ipsum", dossierTemplate.getId(), RuleFileType.ENTITY, false); + // case 1: dry-run false, error messages and warning messages + when(redactionClient.testRules(new RuleValidationModel(request.getRuleFileType().name(), request.getRules()))).thenReturn(DroolsSyntaxValidation.builder() + .droolsSyntaxErrorMessages(errorMessages) + .droolsSyntaxDeprecatedWarnings( + warningMessages) + .build()); + try { + rulesClient.upload(request); + } catch(FeignException e) { + assertThat(e.status()).isEqualTo(HttpStatus.SC_BAD_REQUEST); + } + + assertThat(versionClient.getVersions(List.of(dossierTemplate.getId())) + .get(dossierTemplate.getId()).getRulesVersion()).isEqualTo(2); //1. beim Anlegen des DossierTemplates; 2. bei provideTestTemplate() + + + // case 2: dry-run false, no error messages just warning messages + when(redactionClient.testRules(new RuleValidationModel(request.getRuleFileType().name(), request.getRules()))).thenReturn(DroolsSyntaxValidation.builder() + .droolsSyntaxErrorMessages(Collections.emptyList()) + .droolsSyntaxDeprecatedWarnings( + warningMessages) + .build()); + + ResponseEntity response = rulesClient.upload(request); + assertThat(response.getStatusCode().value()).isEqualTo(HttpStatus.SC_OK); + assertThat(response.getBody().getRulesSyntaxWarningMessages()).isNotEmpty(); + assertThat(response.getBody().getRulesSyntaxErrorMessages()).isEmpty(); + + assertThat(versionClient.getVersions(List.of(dossierTemplate.getId())) + .get(dossierTemplate.getId()).getRulesVersion()).isEqualTo(3); //1. beim Anlegen des DossierTemplates; 2. bei provideTestTemplate() + } + } diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/AbstractPersistenceServerServiceTest.java b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/AbstractPersistenceServerServiceTest.java index 9a38145a1..382c285a2 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/AbstractPersistenceServerServiceTest.java +++ b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/AbstractPersistenceServerServiceTest.java @@ -162,7 +162,7 @@ public abstract class AbstractPersistenceServerServiceTest { @Autowired protected ApplicationConfigRepository applicationConfigRepository; @MockBean - private RedactionClient redactionClient; + protected RedactionClient redactionClient; @Autowired protected PrometheusMeterRegistry prometheusMeterRegistry; @MockBean @@ -247,7 +247,7 @@ public abstract class AbstractPersistenceServerServiceTest { when(amqpAdmin.getQueueInfo(Mockito.any())).thenReturn(null); when(entityLogService.getEntityLog(Mockito.any(), Mockito.any())).thenReturn(new EntityLog(1, 1, Lists.newArrayList(), null, 0, 0, 0, 0)); when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyBoolean())).thenReturn(new EntityLog(1, 1, Lists.newArrayList(), null, 0, 0, 0, 0)); - when(redactionClient.testRules(Mockito.any())).thenReturn(DroolsSyntaxValidation.builder().droolsSyntaxErrorMessages(Collections.emptyList()).build()); + when(redactionClient.testRules(Mockito.any())).thenReturn(DroolsSyntaxValidation.builder().droolsSyntaxErrorMessages(Collections.emptyList()).droolsSyntaxDeprecatedWarnings(Collections.emptyList()).build()); } diff --git a/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/dossiertemplate/rules/RulesUploadRequestModel.java b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/dossiertemplate/rules/RulesUploadRequestModel.java index 82eff8e88..a8b3133e2 100644 --- a/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/dossiertemplate/rules/RulesUploadRequestModel.java +++ b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/dossiertemplate/rules/RulesUploadRequestModel.java @@ -25,4 +25,16 @@ public class RulesUploadRequestModel { @Schema(description = "The Rule file type of these rules", allowableValues = {"ENTITY", "COMPONENT"}) private RuleFileType ruleFileType = RuleFileType.ENTITY; + @Schema(description = "The dry-run parameter if set to true the file is not saved (only the validation results are returned", defaultValue = "false") + private boolean dryRun; + + + public RulesUploadRequestModel(@NonNull String rules, @NonNull String dossierTemplateId, RuleFileType ruleFileType) { + + this.rules = rules; + this.dossierTemplateId = dossierTemplateId; + this.ruleFileType = ruleFileType; + this.dryRun = false; + } + }