From f4a3afc06301095b20493b2f9baf0e938b7a87f2 Mon Sep 17 00:00:00 2001 From: OZGCloud <ozgcloud@mgm-tp.com> Date: Mon, 10 Jun 2024 20:03:56 +0200 Subject: [PATCH] OZG-5822 validate nachricht text and subject --- .gitignore | 1 + alfa-client/.nvmrc | 2 +- .../de/ozgcloud/alfa/bescheid/Bescheid.java | 5 + .../alfa/bescheid/BescheidModelAssembler.java | 5 +- .../bescheid/BescheidNachrichtValidation.java | 4 + .../BescheidSendenCommandController.java | 47 +++++ .../BescheidSendenCommandValidator.java | 17 ++ .../bescheid/BescheidModelAssemblerTest.java | 11 +- .../BescheidSendenCommandControllerTest.java | 180 ++++++++++++++++++ 9 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/BescheidNachrichtValidation.java create mode 100644 alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/BescheidSendenCommandController.java create mode 100644 alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/BescheidSendenCommandValidator.java create mode 100644 alfa-service/src/test/java/de/ozgcloud/alfa/bescheid/BescheidSendenCommandControllerTest.java diff --git a/.gitignore b/.gitignore index 6a8fd526ec..a8d2e8e3d8 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ target/ .attach** .factorypath .vscode* +.nx \ No newline at end of file diff --git a/alfa-client/.nvmrc b/alfa-client/.nvmrc index 87ec8842b1..48b14e6b2b 100644 --- a/alfa-client/.nvmrc +++ b/alfa-client/.nvmrc @@ -1 +1 @@ -18.18.2 +20.14.0 diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/Bescheid.java b/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/Bescheid.java index 67cb7eb4c7..76d8324789 100644 --- a/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/Bescheid.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/Bescheid.java @@ -1,7 +1,10 @@ package de.ozgcloud.alfa.bescheid; +import static de.ozgcloud.alfa.common.ValidationMessageCodes.*; + import java.util.List; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -53,7 +56,9 @@ public class Bescheid implements CommandBody { @LinkedResource(controllerClass = BinaryFileController.class) private List<FileId> attachments; + @NotEmpty(message = FIELD_IS_EMPTY, groups = BescheidNachrichtValidation.class) private String nachrichtText; + @NotEmpty(message = FIELD_IS_EMPTY, groups = BescheidNachrichtValidation.class) private String nachrichtSubject; private SendBy sendBy; private BescheidStatus status; diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/BescheidModelAssembler.java b/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/BescheidModelAssembler.java index f8d3c4a12e..cef975602a 100644 --- a/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/BescheidModelAssembler.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/BescheidModelAssembler.java @@ -61,6 +61,9 @@ public class BescheidModelAssembler implements RepresentationModelAssembler<Besc var attachmentsLink = linkTo(methodOn(BescheidController.class).getAttachments(bescheid.getId(), bescheid.getVorgangId())); var createCommandLink = buildCreateCommandLink(bescheid); var vorgangWithEingang = vorgangController.getVorgang(bescheid.getVorgangId()); + var bescheidenUndSendenLink = linkTo( + methodOn(BescheidSendenCommandController.class).createCommand(vorgangWithEingang.getId(), bescheid.getId(), bescheid.getVersion(), + null)); return ModelBuilder.fromEntity(bescheid) .addLink(selfLink.withSelfRel()) @@ -75,7 +78,7 @@ public class BescheidModelAssembler implements RepresentationModelAssembler<Besc .addLink(createCommandLink.withRel(REL_CREATE_DOCUMENT)) .addLink(createCommandLink.withRel(REL_CREATE_DOCUMENT_FROM_FILE)) .ifMatch(() -> canSendMessageToAntragsteller(vorgangWithEingang)) - .addLink(createCommandLink.withRel(REL_BESCHEIDEN_UND_SENDEN)) + .addLink(bescheidenUndSendenLink.withRel(REL_BESCHEIDEN_UND_SENDEN)) .addLink(createCommandLink.withRel(REL_BESCHEIDEN)) .buildModel(); } diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/BescheidNachrichtValidation.java b/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/BescheidNachrichtValidation.java new file mode 100644 index 0000000000..13e50b003f --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/BescheidNachrichtValidation.java @@ -0,0 +1,4 @@ +package de.ozgcloud.alfa.bescheid; + +interface BescheidNachrichtValidation { +} diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/BescheidSendenCommandController.java b/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/BescheidSendenCommandController.java new file mode 100644 index 0000000000..443ade445b --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/BescheidSendenCommandController.java @@ -0,0 +1,47 @@ +package de.ozgcloud.alfa.bescheid; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*; + +import org.springframework.hateoas.EntityModel; +import org.springframework.http.ResponseEntity; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import de.ozgcloud.alfa.common.command.Command; +import de.ozgcloud.alfa.common.command.CommandController; +import de.ozgcloud.alfa.common.command.CommandService; +import de.ozgcloud.alfa.common.command.CreateCommand; +import de.ozgcloud.common.errorhandling.TechnicalException; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping(BescheidSendenCommandController.PATH) +@RequiredArgsConstructor +class BescheidSendenCommandController { + + static final String PATH = "/api/vorgangs/{vorgangId}/bescheids/relations/{relationId}/{relationVersion}/commands"; // NOSONAR + + private final BescheidService bescheidService; + private final CommandService commandService; + private final BescheidSendenCommandValidator bescheidSendenCommandValidator; + + @PostMapping + public ResponseEntity<EntityModel<Command>> createCommand(@PathVariable String vorgangId, @PathVariable String relationId, + @PathVariable long relationVersion, @RequestBody CreateCommand command) { + command = command.toBuilder().vorgangId(vorgangId).relationId(relationId).build(); + + validate(vorgangId, command); + + var created = commandService.createCommand(command, relationVersion); + return ResponseEntity.created(linkTo(CommandController.class).slash(created.getId()).toUri()).build(); + } + + void validate(String vorgangId, CreateCommand command) { + var bescheid = bescheidService.getBescheidDraft(vorgangId) + .orElseThrow(() -> new TechnicalException("Bescheid not found for vorgang id: " + vorgangId)); + bescheidSendenCommandValidator.validate(command.toBuilder().body(bescheid).build()); + } +} diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/BescheidSendenCommandValidator.java b/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/BescheidSendenCommandValidator.java new file mode 100644 index 0000000000..f304a0428a --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/bescheid/BescheidSendenCommandValidator.java @@ -0,0 +1,17 @@ +package de.ozgcloud.alfa.bescheid; + +import jakarta.validation.Valid; + +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +import de.ozgcloud.alfa.common.command.CreateCommand; + +@Validated(BescheidNachrichtValidation.class) +@Component +class BescheidSendenCommandValidator { + + public void validate(@Valid CreateCommand command) { + // noop + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/bescheid/BescheidModelAssemblerTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/bescheid/BescheidModelAssemblerTest.java index 4372f02417..c78910e742 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/bescheid/BescheidModelAssemblerTest.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/bescheid/BescheidModelAssemblerTest.java @@ -177,7 +177,7 @@ class BescheidModelAssemblerTest { assertThat(model.getLink(REL_BESCHEIDEN_UND_SENDEN)) .isPresent().get() - .extracting(Link::getHref).isEqualTo(createCommandLink()); + .extracting(Link::getHref).isEqualTo(bescheidenUndSendenLink()); } @Test @@ -225,6 +225,11 @@ class BescheidModelAssemblerTest { .expand(VorgangHeaderTestFactory.ID, BescheidTestFactory.ID, BescheidTestFactory.VERSION).toString(); } + private String bescheidenUndSendenLink() { + return new UriTemplate(BescheidSendenCommandController.PATH) + .expand(VorgangHeaderTestFactory.ID, BescheidTestFactory.ID, BescheidTestFactory.VERSION).toString(); + } + private EntityModel<Bescheid> callToModel() { return callToModel(bescheid); } @@ -240,6 +245,8 @@ class BescheidModelAssemblerTest { @Test void shouldCallToModel() { + when(vorgangController.getVorgang(VorgangHeaderTestFactory.ID)).thenReturn(VorgangWithEingangTestFactory.create()); + callMethod(); verify(assembler).toCollectionModel(List.of(bescheid)); @@ -257,6 +264,8 @@ class BescheidModelAssemblerTest { @Test void shouldHaveSelfLink() { + when(vorgangController.getVorgang(VorgangHeaderTestFactory.ID)).thenReturn(VorgangWithEingangTestFactory.create()); + var collectionModel = callMethod(); assertThat(collectionModel.getLinks()) diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/bescheid/BescheidSendenCommandControllerTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/bescheid/BescheidSendenCommandControllerTest.java new file mode 100644 index 0000000000..a6ee7a8966 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/bescheid/BescheidSendenCommandControllerTest.java @@ -0,0 +1,180 @@ +package de.ozgcloud.alfa.bescheid; + +import static de.ozgcloud.alfa.common.command.CommandController.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import de.ozgcloud.alfa.common.command.Command; +import de.ozgcloud.alfa.common.command.CommandOrder; +import de.ozgcloud.alfa.common.command.CommandService; +import de.ozgcloud.alfa.common.command.CommandTestFactory; +import de.ozgcloud.alfa.common.command.CreateCommand; +import de.ozgcloud.alfa.vorgang.VorgangHeaderTestFactory; +import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.common.test.TestUtils; + +class BescheidSendenCommandControllerTest { + + @Spy + @InjectMocks + private BescheidSendenCommandController controller; + + @Mock + private BescheidService bescheidService; + @Mock + private CommandService commandService; + + @Mock + private BescheidSendenCommandValidator bescheidSendenCommandValidator; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + + @Nested + class TestCreateCommand { + + private final Command command = CommandTestFactory.create(); + + @Captor + private ArgumentCaptor<CreateCommand> createCommandArgumentCaptor; + + @BeforeEach + void setUp() { + doNothing().when(controller).validate(eq(VorgangHeaderTestFactory.ID), any(CreateCommand.class)); + when(commandService.createCommand(any(CreateCommand.class), eq(BescheidTestFactory.VERSION))).thenReturn(command); + } + + @Test + void shouldValidate() throws Exception { + doRequest(); + + verify(controller).validate(eq(VorgangHeaderTestFactory.ID), any(CreateCommand.class)); + } + + @Test + void shouldSetVorgangIdOnCreateCommand() throws Exception { + doRequest(); + + verify(controller).validate(eq(VorgangHeaderTestFactory.ID), createCommandArgumentCaptor.capture()); + assertThat(createCommandArgumentCaptor.getValue().getVorgangId()).isEqualTo(VorgangHeaderTestFactory.ID); + } + + @Test + void shouldSetRelationIdOnCreateCommand() throws Exception { + doRequest(); + + verify(controller).validate(eq(VorgangHeaderTestFactory.ID), createCommandArgumentCaptor.capture()); + assertThat(createCommandArgumentCaptor.getValue().getRelationId()).isEqualTo(BescheidTestFactory.ID); + } + + @Test + void shouldCallCommandService() throws Exception { + doRequest(); + + verify(commandService).createCommand(any(CreateCommand.class), eq(BescheidTestFactory.VERSION)); + } + + @Test + void shouldReturnCreated() throws Exception { + doRequest().andExpect(status().isCreated()); + } + + @Test + void shouldReturnCommand() throws Exception { + doRequest() + .andExpect(header().stringValues("location", "http://localhost" + COMMANDS_PATH + "/" + CommandTestFactory.ID)); + } + + private ResultActions doRequest() throws Exception { + return mockMvc.perform( + post(BescheidSendenCommandController.PATH, + VorgangHeaderTestFactory.ID, + BescheidTestFactory.ID, + BescheidTestFactory.VERSION) + .content(createContent()) + .contentType(MediaType.APPLICATION_JSON)); + } + + private String createContent() { + return TestUtils.loadTextFile("jsonTemplates/command/createCommandWithBody.json.tmpl", + CommandOrder.SEND_BESCHEID.name(), + null); + } + + } + + @Nested + class TestValidate { + + private final CreateCommand createCommand = CommandTestFactory.createCreateCommand(); + private final Bescheid bescheid = BescheidTestFactory.create(); + + @Captor + private ArgumentCaptor<CreateCommand> commandArgumentCaptor; + + @Nested + class WhenBescheidExists { + + @BeforeEach + void setUp() { + when(bescheidService.getBescheidDraft(VorgangHeaderTestFactory.ID)).thenReturn(Optional.of(bescheid)); + doNothing().when(bescheidSendenCommandValidator).validate(any(CreateCommand.class)); + } + + @Test + void shouldGetBescheidDraft() { + controller.validate(VorgangHeaderTestFactory.ID, createCommand); + + verify(bescheidService).getBescheidDraft(VorgangHeaderTestFactory.ID); + } + + @Test + void shouldValidate() { + controller.validate(VorgangHeaderTestFactory.ID, createCommand); + + verify(bescheidSendenCommandValidator).validate(any(CreateCommand.class)); + } + + @Test + void shouldAddBescheidToCommand() { + controller.validate(VorgangHeaderTestFactory.ID, createCommand); + + verify(bescheidSendenCommandValidator).validate(commandArgumentCaptor.capture()); + assertThat(commandArgumentCaptor.getValue()).usingRecursiveComparison().isEqualTo(createCommand.toBuilder().body(bescheid).build()); + } + } + + @Nested + class WhenBescheidNotExists { + + @Test + void shouldThrowTechnicalException() { + when(bescheidService.getBescheidDraft(VorgangHeaderTestFactory.ID)).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> controller.validate(VorgangHeaderTestFactory.ID, createCommand)).isInstanceOf(TechnicalException.class); + } + } + } + +} \ No newline at end of file -- GitLab