diff --git a/backend/src/main/java/de/bund/digitalservice/ris/norms/application/port/input/ApplyPassiveModificationsUseCase.java b/backend/src/main/java/de/bund/digitalservice/ris/norms/application/port/input/ApplyPassiveModificationsUseCase.java deleted file mode 100644 index a34e4adb8..000000000 --- a/backend/src/main/java/de/bund/digitalservice/ris/norms/application/port/input/ApplyPassiveModificationsUseCase.java +++ /dev/null @@ -1,33 +0,0 @@ -package de.bund.digitalservice.ris.norms.application.port.input; - -import de.bund.digitalservice.ris.norms.domain.entity.Regelungstext; -import java.time.Instant; -import java.util.Set; - -/** - * Interface representing the use case for applying passive changes to a {@link Regelungstext} up until a - * given date. - */ -public interface ApplyPassiveModificationsUseCase { - /** - * Applies the passive modifications of the regelungstext. Only applies "aenderungsbefehl-ersetzen". - * - * @param query The query contains the regelungstext and date until which passive modifications are applied - * @return A {@link Regelungstext} with its passive changes applied that are in effect before the date - */ - Regelungstext applyPassiveModifications(Query query); - - /** - * A record representing the query for applying the passive modifications of the regelungstext. - * - * @param regelungstext The regelungstext which contains the passive modifications. - * @param date The date until which the passive modifications are applied. - * @param customRegelungstexte A set of regelungstexte. When looking for regelungstexte these regelungstexte are used instead of the - * persisted once. - */ - record Query(Regelungstext regelungstext, Instant date, Set customRegelungstexte) { - public Query(Regelungstext regelungstext, Instant date) { - this(regelungstext, date, Set.of()); - } - } -} diff --git a/backend/src/main/java/de/bund/digitalservice/ris/norms/application/service/ArticleService.java b/backend/src/main/java/de/bund/digitalservice/ris/norms/application/service/ArticleService.java index 1a6fcfc06..f8ecd2416 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/norms/application/service/ArticleService.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/norms/application/service/ArticleService.java @@ -24,16 +24,13 @@ public class ArticleService LoadSpecificArticlesXmlFromDokumentUseCase { LoadRegelungstextPort loadRegelungstextPort; - TimeMachineService timeMachineService; XsltTransformationService xsltTransformationService; public ArticleService( LoadRegelungstextPort loadRegelungstextPort, - TimeMachineService timeMachineService, XsltTransformationService xsltTransformationService ) { this.loadRegelungstextPort = loadRegelungstextPort; - this.timeMachineService = timeMachineService; this.xsltTransformationService = xsltTransformationService; } diff --git a/backend/src/main/java/de/bund/digitalservice/ris/norms/application/service/ReleaseService.java b/backend/src/main/java/de/bund/digitalservice/ris/norms/application/service/ReleaseService.java index a5787d274..5e79a7706 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/norms/application/service/ReleaseService.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/norms/application/service/ReleaseService.java @@ -17,11 +17,9 @@ import de.bund.digitalservice.ris.norms.utils.XmlMapper; import java.time.Instant; import java.time.LocalDate; -import java.time.ZoneId; import java.util.HashSet; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -36,7 +34,6 @@ public class ReleaseService implements ReleaseAnnouncementUseCase { private final LoadAnnouncementByNormEliUseCase loadAnnouncementByNormEliUseCase; private final UpdateOrSaveNormPort updateOrSaveNormPort; private final NormService normService; - private final TimeMachineService timeMachineService; private final CreateNewVersionOfNormService createNewVersionOfNormService; private final DeleteNormPort deleteNormPort; private final SaveReleaseToAnnouncementPort saveReleaseToAnnouncementPort; @@ -47,7 +44,6 @@ public ReleaseService( LoadAnnouncementByNormEliUseCase loadAnnouncementByNormEliUseCase, UpdateOrSaveNormPort updateOrSaveNormPort, NormService normService, - TimeMachineService timeMachineService, CreateNewVersionOfNormService createNewVersionOfNormService, DeleteNormPort deleteNormPort, SaveReleaseToAnnouncementPort saveReleaseToAnnouncementPort, @@ -57,7 +53,6 @@ public ReleaseService( this.loadAnnouncementByNormEliUseCase = loadAnnouncementByNormEliUseCase; this.updateOrSaveNormPort = updateOrSaveNormPort; this.normService = normService; - this.timeMachineService = timeMachineService; this.createNewVersionOfNormService = createNewVersionOfNormService; this.deleteNormPort = deleteNormPort; this.saveReleaseToAnnouncementPort = saveReleaseToAnnouncementPort; @@ -164,21 +159,6 @@ public Announcement releaseAnnouncement(ReleaseAnnouncementUseCase.Query query) allVersionsOfAllNormsToPublish.add(result.newManifestationOfOldExpression()); latestNormExpression = result.newExpression(); - - latestNormExpression.setRegelungstexte( - latestNormExpression - .getRegelungstexte() - .stream() - .map(regelungstext -> - timeMachineService.applyPassiveModifications( - new ApplyPassiveModificationsUseCase.Query( - regelungstext, - date.atStartOfDay(ZoneId.systemDefault()).toInstant() - ) - ) - ) - .collect(Collectors.toSet()) - ); } allVersionsOfAllNormsToPublish.add(latestNormExpression); diff --git a/backend/src/main/java/de/bund/digitalservice/ris/norms/application/service/TimeMachineService.java b/backend/src/main/java/de/bund/digitalservice/ris/norms/application/service/TimeMachineService.java deleted file mode 100644 index 65cbe5e33..000000000 --- a/backend/src/main/java/de/bund/digitalservice/ris/norms/application/service/TimeMachineService.java +++ /dev/null @@ -1,283 +0,0 @@ -package de.bund.digitalservice.ris.norms.application.service; - -import de.bund.digitalservice.ris.norms.application.exception.NormNotFoundException; -import de.bund.digitalservice.ris.norms.application.exception.ValidationException; -import de.bund.digitalservice.ris.norms.application.port.input.ApplyPassiveModificationsUseCase; -import de.bund.digitalservice.ris.norms.application.port.output.LoadNormPort; -import de.bund.digitalservice.ris.norms.domain.entity.*; -import de.bund.digitalservice.ris.norms.utils.EidConsistencyGuardian; -import de.bund.digitalservice.ris.norms.utils.NodeParser; -import de.bund.digitalservice.ris.norms.utils.exceptions.MandatoryNodeNotFoundException; -import java.time.Duration; -import java.time.Instant; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.tuple.Pair; -import org.springframework.stereotype.Service; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -/** - * Namespace for business Logics related to "time machine" functionality, i.e. to applying LDML.de - * "modifications" to LDML.de files. For details on LDML.de modifications, cf. LDML-details - */ -@Service -@Slf4j -public class TimeMachineService implements ApplyPassiveModificationsUseCase { - - private final LoadNormPort loadNormPort; - - public TimeMachineService(final LoadNormPort loadNormPort) { - this.loadNormPort = loadNormPort; - } - - /** - * Applies the passive modifications of the regelungstext. Only applies "aenderungsbefehl-ersetzen". - * - * @param query An ApplyPassiveModificationsUsecase.Query containing the norm and a date - * @return the regelungstext with the applied passive modifications that are in effect before the date - */ - public Regelungstext applyPassiveModifications(ApplyPassiveModificationsUseCase.Query query) { - var regelungstext = new Regelungstext(query.regelungstext()); // create a copy of the norm to work on - var date = query.date(); - var customRegelungstexts = query - .customRegelungstexte() - .stream() - .collect( - Collectors.toMap( - Regelungstext::getExpressionEli, - customRegelungstext -> customRegelungstext - ) - ); - - var actualDate = date.equals(Instant.MAX) ? Instant.MAX : date.plus(Duration.ofDays(1)); - - try { - regelungstext.getMeta(); - } catch (final MandatoryNodeNotFoundException e) { - return regelungstext; - } - - regelungstext - .getMeta() - .getAnalysis() - .map(analysis -> analysis.getPassiveModifications().stream()) - .orElse(Stream.empty()) - .filter((TextualMod passiveModification) -> { - final var startDate = passiveModification - .getForcePeriodEid() - .flatMap(regelungstext::getStartDateForTemporalGroup) - .map(dateString -> Instant.parse(dateString + "T00:00:00.000Z")); - - // when no start date exists we do not want to apply the mod - return startDate.isPresent() && startDate.get().isBefore(actualDate); - }) - .sorted( - Comparator.comparing((TextualMod passiveModification) -> - // We already filtered out empty Optionals, so safe retrieving directly - passiveModification - .getForcePeriodEid() - .flatMap(regelungstext::getStartDateForTemporalGroup) - .map(dateString -> Instant.parse(dateString + "T00:00:00.000Z")) - .orElse(Instant.EPOCH) // This is just a fallback - ) - ) - .flatMap((TextualMod passiveModification) -> { - var sourceEli = passiveModification - .getSourceHref() - .flatMap(Href::getExpressionEli) - .orElseThrow(() -> - new ValidationException( - ValidationException.ErrorType.SOURCE_HREF_IN_META_MOD_MISSING, - Pair.of(ValidationException.FieldName.EID, passiveModification.getEid()) - ) - ); - - Regelungstext amendingLaw; - if (customRegelungstexts.containsKey(sourceEli)) { - amendingLaw = customRegelungstexts.get(sourceEli); - } else { - amendingLaw = - loadNormPort - .loadNorm(new LoadNormPort.Command(sourceEli)) - .orElseThrow(() -> new NormNotFoundException(sourceEli.toString())) - .getRegelungstext1(); - } - - var sourceEid = passiveModification.getSourceHref().flatMap(Href::getEId); - return amendingLaw - .getMods() - .stream() - .filter(mod -> mod.getEid().equals(sourceEid.get())) - .map(mod -> new ModData(passiveModification, mod)); - }) - .forEach(modData -> applyMod(modData, regelungstext)); - - EidConsistencyGuardian.correctEids(regelungstext.getDocument()); - - return regelungstext; - } - - record ModData(TextualMod passiveModification, Mod mod) {} - - private void applyMod(final ModData modData, final Regelungstext targetZf0Norm) { - if ( - modData.passiveModification().getDestinationHref().isEmpty() || - modData.passiveModification().getDestinationHref().get().getEId().isEmpty() - ) { - return; - } - - final var targetEid = modData.passiveModification().getDestinationHref().get().getEId().get(); - final var targetNode = NodeParser.getElementFromExpression( - String.format("//*[@eId='%s']", targetEid), - targetZf0Norm.getDocument() - ); - - if (targetNode.isEmpty()) { - return; - } - - if (modData.mod().getSecondQuotedText().isPresent()) { - try { - applyQuotedText(modData, targetNode.get()); - } catch (IllegalArgumentException | IndexOutOfBoundsException exception) { - log.info( - "Could not apply quoted text mod (%s)".formatted(modData.mod().getEid()), - exception - ); - } - } else if (modData.mod().getQuotedStructure().isPresent()) { - applyQuotedStructure(modData, targetNode.get(), targetZf0Norm); - } - - if (targetZf0Norm.getMeta().getAnalysis().isPresent()) { - targetZf0Norm - .getMeta() - .getAnalysis() - .get() - .deletePassiveModification(modData.passiveModification()); - } - } - - private void applyQuotedText(final ModData modData, Node targetNode) - throws IllegalArgumentException, IndexOutOfBoundsException { - final Mod mod = modData.mod(); - final Node secondQuotedTextNode = mod - .getSecondQuotedText() - .orElseThrow(() -> new IllegalArgumentException("Second quoted text (new text) is empty.")); - final var targetHref = modData - .passiveModification() - .getDestinationHref() - .orElseThrow(() -> new IllegalArgumentException("Target href is empty.")); - final var characterRange = targetHref - .getCharacterRange() - .orElseThrow(() -> - new IllegalArgumentException( - "Destination has empty character range (%s)".formatted(targetHref) - ) - ); - final var oldText = mod - .getOldText() - .orElseThrow(() -> new IllegalArgumentException("Old text is empty.")); - - if (!Objects.equals(characterRange.findTextInNode(targetNode), oldText)) { - throw new IllegalArgumentException( - "Old text (%s) is not the same as the text of the character range (%s). Text of the node: %s".formatted( - oldText, - characterRange.findTextInNode(targetNode), - targetNode.getTextContent() - ) - ); - } - - final var nodesToBeReplaced = characterRange.getNodesInRange(targetNode); - - final var newChildFragment = targetNode.getOwnerDocument().createDocumentFragment(); - - // Import content of quotedText from amending law (which include akn:refs) using importNode - // because of different documents - NodeParser - .nodeListToList(secondQuotedTextNode.getChildNodes()) - .forEach(child -> { - final Node importedChild = targetNode.getOwnerDocument().importNode(child, true); - newChildFragment.appendChild(importedChild); - }); - - replaceNodes(nodesToBeReplaced, newChildFragment); - } - - private void applyQuotedStructure( - final ModData modData, - final Node targetNode, - final Regelungstext targetZf0Norm - ) { - final Mod mod = modData.mod(); - if (mod.getQuotedStructure().isEmpty()) return; - - Optional upToTargetNode = Optional.empty(); - if (modData.passiveModification().getDestinationUpTo().isPresent()) { - if (modData.passiveModification().getDestinationUpTo().get().getEId().isEmpty()) { - return; - } else { - final var upToTargetNodeEid = modData - .passiveModification() - .getDestinationUpTo() - .get() - .getEId() - .get(); - upToTargetNode = - NodeParser.getElementFromExpression( - String.format("//*[@eId='%s']", upToTargetNodeEid), - targetZf0Norm.getDocument() - ); - if (upToTargetNode.isEmpty()) { - return; - } - } - } - - final List newQuotedStructureContent = NodeParser.nodeListToList( - mod.getQuotedStructure().get().getChildNodes() - ); - - final Node newChildFragment = targetNode.getOwnerDocument().createDocumentFragment(); - newQuotedStructureContent.forEach(node -> { - Node importedChild = targetNode.getOwnerDocument().importNode(node, true); - newChildFragment.appendChild(importedChild); - }); - - // Collect nodes to be replaced - final List nodesToReplace = new ArrayList<>(); - nodesToReplace.add(targetNode); - Node currentNode = targetNode.getNextSibling(); - while (upToTargetNode.isPresent() && currentNode != null) { - nodesToReplace.add(currentNode); - if (currentNode == upToTargetNode.get()) { - break; - } - currentNode = currentNode.getNextSibling(); - } - - replaceNodes(nodesToReplace, newChildFragment); - } - - private void replaceNodes(final List targetNodes, final Node newNode) { - if (targetNodes.isEmpty()) { - return; - } - - final var parentNode = targetNodes.getFirst().getParentNode(); - - if (targetNodes.stream().anyMatch(node -> node.getParentNode() != parentNode)) { - throw new IllegalArgumentException("Not all target nodes are siblings."); - } - - final var firstNode = targetNodes.getFirst(); - parentNode.insertBefore(newNode, firstNode); - targetNodes.forEach(parentNode::removeChild); - } -} diff --git a/backend/src/test/java/de/bund/digitalservice/ris/norms/application/service/ArticleServiceTest.java b/backend/src/test/java/de/bund/digitalservice/ris/norms/application/service/ArticleServiceTest.java index 3c59a65a4..264856050 100644 --- a/backend/src/test/java/de/bund/digitalservice/ris/norms/application/service/ArticleServiceTest.java +++ b/backend/src/test/java/de/bund/digitalservice/ris/norms/application/service/ArticleServiceTest.java @@ -28,11 +28,9 @@ class ArticleServiceTest { final LoadNormPort loadNormPort = mock(LoadNormPort.class); final LoadRegelungstextPort loadRegelungstextPort = mock(LoadRegelungstextPort.class); - final TimeMachineService timeMachineService = mock(TimeMachineService.class); final XsltTransformationService xsltTransformationService = mock(XsltTransformationService.class); final ArticleService articleService = new ArticleService( loadRegelungstextPort, - timeMachineService, xsltTransformationService ); @@ -49,7 +47,6 @@ void returnResult() { var eid = "hauptteil-1_art-1"; when(loadRegelungstextPort.loadRegelungstext(new LoadRegelungstextPort.Command(eli))) .thenReturn(Optional.of(regelungstext)); - when(timeMachineService.applyPassiveModifications(any())).thenReturn(regelungstext); when(xsltTransformationService.transformLegalDocMlToHtml(any())).thenReturn("
"); // when diff --git a/backend/src/test/java/de/bund/digitalservice/ris/norms/application/service/ReleaseServiceTest.java b/backend/src/test/java/de/bund/digitalservice/ris/norms/application/service/ReleaseServiceTest.java index db7ee4493..8ee01ee35 100644 --- a/backend/src/test/java/de/bund/digitalservice/ris/norms/application/service/ReleaseServiceTest.java +++ b/backend/src/test/java/de/bund/digitalservice/ris/norms/application/service/ReleaseServiceTest.java @@ -32,7 +32,6 @@ class ReleaseServiceTest { ); private final UpdateOrSaveNormPort updateOrSaveNormPort = mock(UpdateOrSaveNormPort.class); private final NormService normService = mock(NormService.class); - private final TimeMachineService timeMachineService = mock(TimeMachineService.class); private final CreateNewVersionOfNormService createNewVersionOfNormService = mock( CreateNewVersionOfNormService.class ); @@ -48,7 +47,6 @@ class ReleaseServiceTest { loadAnnouncementByNormEliUseCase, updateOrSaveNormPort, normService, - timeMachineService, createNewVersionOfNormService, deleteNormPort, saveReleaseToAnnouncementPort, @@ -57,7 +55,7 @@ class ReleaseServiceTest { ); @Test - void itShouldReleaseAnAnnouncementWithoutTargetNorms() { + void itShouldReleaseAnAnnouncement() { // Given var norm = Fixtures.loadNormFromDisk("SimpleNorm.xml"); // these are just arbitrary norm files, it's not important what is in them just that they are all different. @@ -119,132 +117,6 @@ void itShouldReleaseAnAnnouncementWithoutTargetNorms() { .contains(manifestationOfNormToQueue); } - @Test - void itShouldReleaseAnAnnouncementWithTargetNorm() { - // Given - var amendingNorm = Fixtures.loadNormFromDisk("NormWithMods.xml"); - var targetNorm = Fixtures.loadNormFromDisk("NormWithPassiveModifications.xml"); - // these are just arbitrary norm files, it's not important what is in them just that they are all different. - var targetNormExpressionAtDateOne = Fixtures.loadNormFromDisk( - "NormWithAppliedQuotedStructure.xml" - ); - var manifestationOfAmendingNormToQueue = Fixtures.loadNormFromDisk( - "NormWithModsSameTarget.xml" - ); - var manifestationOfTargetNormToUseForCreatingExpressions = Fixtures.loadNormFromDisk( - "NormWithoutPassiveModificationsNoNextVersion.xml" - ); - var manifestationOfTargetNormToUseInTimeMachine = Fixtures.loadNormFromDisk( - "NormWithoutPassiveModificationsSameTarget.xml" - ); - var manifestationOfTargetNormToQueue = Fixtures.loadNormFromDisk( - "NormWithoutPassiveModsQuotedStructure.xml" - ); - var newNewestUnpublishedManifestationOfAmendingNorm = Fixtures.loadNormFromDisk( - "NormWithMultipleMods.xml" - ); - var newNewestUnpublishedManifestationOfTargetNorm = Fixtures.loadNormFromDisk( - "NormWithoutPassiveModsQuotedStructureAndUpTo.xml" - ); - - var announcement = Announcement.builder().eli(amendingNorm.getExpressionEli()).build(); - - when(loadAnnouncementByNormEliUseCase.loadAnnouncementByNormEli(any())) - .thenReturn(announcement); - when(normService.loadNorm(any())).thenReturn(amendingNorm).thenReturn(targetNorm); - when(createNewVersionOfNormService.createNewManifestation(amendingNorm)) - .thenReturn(manifestationOfAmendingNormToQueue); - when(createNewVersionOfNormService.createNewManifestation(targetNorm)) - .thenReturn(manifestationOfTargetNormToUseForCreatingExpressions); - when(deleteQueuedReleasesPort.deleteQueuedReleases(any())).thenReturn(List.of()); - when( - createNewVersionOfNormService.createNewExpression(any(), eq(LocalDate.parse("2017-03-23"))) - ) - .thenReturn( - new CreateNewVersionOfNormService.CreateNewExpressionResult( - targetNormExpressionAtDateOne, - manifestationOfTargetNormToUseInTimeMachine - ) - ); - when(timeMachineService.applyPassiveModifications(any())) - .thenReturn(manifestationOfTargetNormToQueue.getRegelungstext1()); - when( - createNewVersionOfNormService.createNewManifestation(any(), eq(LocalDate.now().plusDays(1))) - ) - .thenReturn(newNewestUnpublishedManifestationOfAmendingNorm) - .thenReturn(newNewestUnpublishedManifestationOfTargetNorm); - - // When - releaseService.releaseAnnouncement( - new ReleaseAnnouncementUseCase.Query(amendingNorm.getExpressionEli()) - ); - - // Then - // previously queued norms are deleted - verify(deleteQueuedReleasesPort, times(1)) - .deleteQueuedReleases(new DeleteQueuedReleasesPort.Command(announcement.getEli())); - - // results are validated - verify(ldmlDeValidator, times(1)) - .parseAndValidateRegelungstext( - XmlMapper.toString(manifestationOfAmendingNormToQueue.getRegelungstext1().getDocument()) - ); - verify(ldmlDeValidator, times(1)).validateSchematron(manifestationOfAmendingNormToQueue); - - // the queue norms are saved - verify(updateOrSaveNormPort, times(1)) - .updateOrSave(new UpdateOrSaveNormPort.Command(manifestationOfAmendingNormToQueue)); - assertThat(manifestationOfAmendingNormToQueue.getPublishState()) - .isEqualTo(NormPublishState.QUEUED_FOR_PUBLISH); - verify(updateOrSaveNormPort, times(1)) - .updateOrSave( - argThat(command -> - command.norm().getPublishState().equals(NormPublishState.QUEUED_FOR_PUBLISH) && - command.norm().equals(manifestationOfTargetNormToQueue) - ) - ); - verify(updateOrSaveNormPort, times(1)) - .updateOrSave(new UpdateOrSaveNormPort.Command(manifestationOfTargetNormToUseInTimeMachine)); - assertThat(manifestationOfTargetNormToUseInTimeMachine.getPublishState()) - .isEqualTo(NormPublishState.QUEUED_FOR_PUBLISH); - - // the old manifestations for editing are deleted (as they might no longer be the newest manifestation, that's why new once are created) - verify(deleteNormPort, times(1)) - .deleteNorm( - new DeleteNormPort.Command(amendingNorm.getManifestationEli(), NormPublishState.UNPUBLISHED) - ); - verify(deleteNormPort, times(1)) - .deleteNorm( - new DeleteNormPort.Command(targetNorm.getManifestationEli(), NormPublishState.UNPUBLISHED) - ); - - // new manifestations for further editing are saved - verify(updateOrSaveNormPort, times(1)) - .updateOrSave( - new UpdateOrSaveNormPort.Command(newNewestUnpublishedManifestationOfAmendingNorm) - ); - assertThat(newNewestUnpublishedManifestationOfAmendingNorm.getPublishState()) - .isEqualTo(NormPublishState.UNPUBLISHED); - verify(updateOrSaveNormPort, times(1)) - .updateOrSave( - new UpdateOrSaveNormPort.Command(newNewestUnpublishedManifestationOfTargetNorm) - ); - assertThat(newNewestUnpublishedManifestationOfTargetNorm.getPublishState()) - .isEqualTo(NormPublishState.UNPUBLISHED); - - // the announcement is updated - verify(saveReleaseToAnnouncementPort, times(1)).saveReleaseToAnnouncement(any()); - - assertThat(announcement.getReleases()).hasSize(1); - assertThat(announcement.getReleases().getFirst().getPublishedNorms()).hasSize(3); - assertThat(announcement.getReleases().getFirst().getPublishedNorms()) - .contains(manifestationOfAmendingNormToQueue); - assertThat(announcement.getReleases().getFirst().getPublishedNorms()) - .contains(manifestationOfTargetNormToQueue); - assertThat(announcement.getReleases().getFirst().getPublishedNorms()) - .contains(manifestationOfTargetNormToUseInTimeMachine); - } - @Test void itShouldUpdateTheReleasedByDocumentalistAtDate() { // Given diff --git a/backend/src/test/java/de/bund/digitalservice/ris/norms/application/service/TimeMachineServiceTest.java b/backend/src/test/java/de/bund/digitalservice/ris/norms/application/service/TimeMachineServiceTest.java deleted file mode 100644 index 0f49a0463..000000000 --- a/backend/src/test/java/de/bund/digitalservice/ris/norms/application/service/TimeMachineServiceTest.java +++ /dev/null @@ -1,409 +0,0 @@ -package de.bund.digitalservice.ris.norms.application.service; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -import de.bund.digitalservice.ris.norms.application.port.input.ApplyPassiveModificationsUseCase; -import de.bund.digitalservice.ris.norms.application.port.output.LoadNormPort; -import de.bund.digitalservice.ris.norms.domain.entity.Fixtures; -import de.bund.digitalservice.ris.norms.domain.entity.Regelungstext; -import de.bund.digitalservice.ris.norms.utils.NodeParser; -import de.bund.digitalservice.ris.norms.utils.XmlMapper; -import java.time.Instant; -import java.util.Optional; -import java.util.Set; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.w3c.dom.Node; -import org.xmlunit.builder.DiffBuilder; -import org.xmlunit.builder.Input; -import org.xmlunit.diff.Diff; - -class TimeMachineServiceTest { - - final LoadNormPort loadNormPort = mock(LoadNormPort.class); - - final TimeMachineService timeMachineService = new TimeMachineService(loadNormPort); - - @Nested - class applyPassiveModifications { - - @Test - void returnUnchangedIfNoPassiveModifications() { - // given - final var regelungstext = new Regelungstext( - XmlMapper.toDocument( - """ - - - - - - - - - - - - - - - - """ - ) - ); - - // when - Regelungstext result = timeMachineService.applyPassiveModifications( - new ApplyPassiveModificationsUseCase.Query(regelungstext, Instant.MAX) - ); - - // then - assertThat(result).isEqualTo(regelungstext); - } - - @Test - void applyOnePassiveModification() { - // given - final var regelungstext = Fixtures.loadRegelungstextFromDisk( - "NormWithPassiveModifications.xml" - ); - - final var amendingLaw = Fixtures.loadNormFromDisk("NormWithMods.xml"); - - when(loadNormPort.loadNorm(any())).thenReturn(Optional.of(amendingLaw)); - - // when - Regelungstext result = timeMachineService.applyPassiveModifications( - new ApplyPassiveModificationsUseCase.Query(regelungstext, Instant.MAX) - ); - - // then - var changedNodeValue = NodeParser.getValueFromExpression( - "//*[@eId=\"hauptteil-1_art-1_abs-1_untergl-1_listenelem-2_inhalt-1_text-1\"]", - result.getDocument() - ); - assertThat(changedNodeValue).isPresent(); - assertThat(changedNodeValue.get()) - .isEqualToIgnoringWhitespace( - "entgegen § 9 Absatz 1 Satz 2, Absatz 2 oder 3 Kennezichen eines verbotenen Vereins oder einer Ersatzorganisation verwendet," - ); - // passive mod is deleted - assertThat(result.getElementByEId("meta-1_analysis-1_pasmod-1_textualmod-1")).isEmpty(); - } - - @Test - void applyPassiveModificationsInCorrectOrder() { - // given - final var regelungstext = Fixtures.loadRegelungstextFromDisk( - "NormWithMultiplePassiveModifications.xml" - ); - - final var amendingLaw = Fixtures.loadNormFromDisk("NormWithMultipleMods.xml"); - - when(loadNormPort.loadNorm(any())).thenReturn(Optional.of(amendingLaw)); - - // when - Regelungstext result = timeMachineService.applyPassiveModifications( - new ApplyPassiveModificationsUseCase.Query(regelungstext, Instant.MAX) - ); - - // then - var changedNodeValue = NodeParser.getValueFromExpression( - "//*[@eId=\"hauptteil-1_art-1_abs-1_untergl-1_listenelem-2_inhalt-1_text-1\"]", - result.getDocument() - ); - assertThat(changedNodeValue).isPresent(); - assertThat(changedNodeValue.get()) - .isEqualToIgnoringWhitespace( - "entgegen § 9 Absatz 1 Satz 2, Absatz 2, 3 oder 4 Kennezichen eines verbotenen Vereins oder einer Ersatzorganisation verwendet," - ); - // passive mods are deleted - assertThat(result.getElementByEId("meta-1_analysis-1_pasmod-1_textualmod-1")).isEmpty(); - assertThat(result.getElementByEId("meta-1_analysis-1_pasmod-1_textualmod-2")).isEmpty(); - } - - @Test - void applyPassiveModificationsDoesNotModifyTheParameter() { - // given - final var norm = Fixtures.loadRegelungstextFromDisk( - "NormWithMultiplePassiveModifications.xml" - ); - - final var amendingLaw = Fixtures.loadNormFromDisk("NormWithMultipleMods.xml"); - - when(loadNormPort.loadNorm(any())).thenReturn(Optional.of(amendingLaw)); - - // when - Regelungstext result = timeMachineService.applyPassiveModifications( - new ApplyPassiveModificationsUseCase.Query(norm, Instant.MAX) - ); - - // then - assertThat(result).isNotEqualTo(norm); - assertThat(result.getElementByEId("meta-1_analysis-1_pasmod-1_textualmod-1")).isEmpty(); - assertThat(norm.getElementByEId("meta-1_analysis-1_pasmod-1_textualmod-2")).isPresent(); - } - - @Test - void applyPassiveModificationsWhereTargetNodeEqualsNodeToChange() { - // given - final var regelungstext = Fixtures.loadRegelungstextFromDisk( - "NormWithPassiveModsWhereTargetNodeEqualsNodeToChange.xml" - ); - - final var amendingLaw = Fixtures.loadNormFromDisk( - "NormWithModsWhereTargetNodeEqualsNodeToChange.xml" - ); - when(loadNormPort.loadNorm(any())).thenReturn(Optional.of(amendingLaw)); - - // when - Regelungstext result = timeMachineService.applyPassiveModifications( - new ApplyPassiveModificationsUseCase.Query(regelungstext, Instant.MAX) - ); - - // then - var changedNodeValue = NodeParser.getValueFromExpression( - "//*[@eId=\"hauptteil-1_abschnitt-1_art-10_abs-3_inhalt-1_text-1\"]", - result.getDocument() - ); - assertThat(changedNodeValue).isPresent(); - assertThat(changedNodeValue.get()) - .isEqualToIgnoringWhitespace( - """ - Das Bundesamt für Verfassungsschutz trifft für die gemeinsamen Dateien die technischen und organisatorischen Maßnahmen - entsprechend § - 64 des Bundesdatenschutzgesetzes. Es hat bei jedem Zugriff für Zwecke der Datenschutzkontrolle den Zeitpunkt, die - Angaben, die die - Feststellung der abgefragten Datensätze ermöglichen, sowie die abfragende Stelle zu protokollieren. Die Auswertung der - Protokolldaten - ist nach dem Stand der Technik zu gewährleisten. Die protokollierten Daten dürfen nur für Zwecke der - Datenschutzkontrolle, der - Datensicherung oder zur Sicherstellung eines ordnungsgemäßen Betriebs der Datenverarbeitungsanlage verwendet werden. - Die - Protokolldaten sind nach Ablauf von fünf Jahren zu löschen. - """ - ); - } - - @Test - void applyPassiveModificationsBeforeDate() { - // given - final var regelungstext = Fixtures.loadRegelungstextFromDisk( - "NormWithMultiplePassiveModifications.xml" - ); - final var amendingLaw = Fixtures.loadNormFromDisk("NormWithMultipleMods.xml"); - - when(loadNormPort.loadNorm(any())).thenReturn(Optional.of(amendingLaw)); - - // when - Regelungstext result = timeMachineService.applyPassiveModifications( - new ApplyPassiveModificationsUseCase.Query( - regelungstext, - Instant.parse("2017-03-01T00:00:00.000Z") - ) - ); - - // then - var changedNodeValue = NodeParser.getValueFromExpression( - "//*[@eId=\"hauptteil-1_art-1_abs-1_untergl-1_listenelem-2_inhalt-1_text-1\"]", - result.getDocument() - ); - assertThat(changedNodeValue).isPresent(); - assertThat(changedNodeValue.get()) - .isEqualToIgnoringWhitespace( - "entgegen § 9 Absatz 1 Satz 2, Absatz 2 oder 3 Kennezichen eines verbotenen Vereins oder einer Ersatzorganisation verwendet," - ); - } - - @Test - void applyOnePassiveModificationWithCustomNorm() { - // given - final var regelungstext = Fixtures.loadRegelungstextFromDisk( - "NormWithPassiveModifications.xml" - ); - - final var amendingLawRegelungstext = Fixtures.loadRegelungstextFromDisk("NormWithMods.xml"); - - // when - Regelungstext result = timeMachineService.applyPassiveModifications( - new ApplyPassiveModificationsUseCase.Query( - regelungstext, - Instant.MAX, - Set.of(amendingLawRegelungstext) - ) - ); - - // then - var changedNodeValue = NodeParser.getValueFromExpression( - "//*[@eId=\"hauptteil-1_art-1_abs-1_untergl-1_listenelem-2_inhalt-1_text-1\"]", - result.getDocument() - ); - assertThat(changedNodeValue).isPresent(); - assertThat(changedNodeValue.get()) - .isEqualToIgnoringWhitespace( - "entgegen § 9 Absatz 1 Satz 2, Absatz 2 oder 3 Kennezichen eines verbotenen Vereins oder einer Ersatzorganisation verwendet," - ); - } - - @Test - void doNotApplyPassiveModificationWithoutForcePeriod() { - // given - final var norm = Fixtures.loadRegelungstextFromDisk("NormWithPassiveModifications.xml"); - norm.deleteByEId("meta-1_analysis-1_pasmod-1_textualmod-1_gelzeitnachw-1"); - - final var amendingLaw = Fixtures.loadNormFromDisk("NormWithMods.xml"); - - when(loadNormPort.loadNorm(any())).thenReturn(Optional.of(amendingLaw)); - - // when - Regelungstext result = timeMachineService.applyPassiveModifications( - new ApplyPassiveModificationsUseCase.Query(norm, Instant.MAX) - ); - - // then - var changedNodeValue = NodeParser.getValueFromExpression( - "//*[@eId=\"hauptteil-1_art-1_abs-1_untergl-1_listenelem-2_inhalt-1_text-1\"]", - result.getDocument() - ); - assertThat(changedNodeValue).isPresent(); - assertThat(changedNodeValue.get()) - .isEqualToIgnoringWhitespace( - "entgegen § 9 Abs. 1 Satz 2, Abs. 2 Kennezichen eines verbotenen Vereins oder einer Ersatzorganisation verwendet," - ); - } - - @Test - void applyOnePassiveModificationQuotedStructure() { - // given - final var targetLawRegelungstext = Fixtures.loadRegelungstextFromDisk( - "NormWithPassiveModsQuotedStructure.xml" - ); - final var amendingLawNorm = Fixtures.loadNormFromDisk("NormWithQuotedStructureMods.xml"); - final var expectedResult = Fixtures.loadNormFromDisk("NormWithAppliedQuotedStructure.xml"); - - when(loadNormPort.loadNorm(any())).thenReturn(Optional.of(amendingLawNorm)); - - // when - Regelungstext result = timeMachineService.applyPassiveModifications( - new ApplyPassiveModificationsUseCase.Query(targetLawRegelungstext, Instant.MAX) - ); - - // then - final Diff diff = DiffBuilder - .compare(Input.from(result.getDocument())) - .withTest(Input.from(expectedResult.getDocument())) - .ignoreWhitespace() - .withNodeFilter(node -> !node.getNodeName().equals("akn:meta")) - .withAttributeFilter(attribute -> !attribute.getName().equals("GUID")) - .build(); - assertThat(diff.hasDifferences()).isFalse(); - } - - @Test - void applyOnePassiveModificationQuotedStructureWithUpTo() { - // given - final var targetLawNormRegelungstext = Fixtures.loadRegelungstextFromDisk( - "NormWithPassiveModsQuotedStructureAndUpTo.xml" - ); - final var amendingLawNorm = Fixtures.loadNormFromDisk( - "NormWithQuotedStructureModsAndUpTo.xml" - ); - final var expectedResult = Fixtures.loadNormFromDisk( - "NormWithAppliedQuotedStructureAndUpTo.xml" - ); - - when(loadNormPort.loadNorm(any())).thenReturn(Optional.of(amendingLawNorm)); - - // when - Regelungstext result = timeMachineService.applyPassiveModifications( - new ApplyPassiveModificationsUseCase.Query(targetLawNormRegelungstext, Instant.MAX) - ); - - // then - final Diff diff = DiffBuilder - .compare(Input.from(result.getDocument())) - .withTest(Input.from(expectedResult.getDocument())) - .ignoreWhitespace() - .withNodeFilter(node -> !node.getNodeName().equals("akn:meta")) - .build(); - assertThat(diff.hasDifferences()).isFalse(); - } - - @Test - void applyQuotedTextWithRefs() { - // given - final var norm = Fixtures.loadRegelungstextFromDisk("NormWithPassiveModifications.xml"); - - final var amendingLaw = Fixtures.loadNormFromDisk("NormWithQuotedTextModAndRefs.xml"); - - final Node expectedNode = XmlMapper.toElement( - """ - entgegen § 9 Absatz 1 Satz 2, Absatz 2 oder 3 - Kennezichen eines verbotenen Vereins oder einer Ersatzorganisation verwendet, - """ - ); - - when(loadNormPort.loadNorm(any())).thenReturn(Optional.of(amendingLaw)); - - // when - Regelungstext result = timeMachineService.applyPassiveModifications( - new ApplyPassiveModificationsUseCase.Query(norm, Instant.MAX) - ); - - // then - final Optional updatedNode = NodeParser.getNodeFromExpression( - "//*[@eId=\"hauptteil-1_art-1_abs-1_untergl-1_listenelem-2_inhalt-1_text-1\"]", - result.getDocument() - ); - assertThat(updatedNode).isPresent(); - final Diff diff = DiffBuilder - .compare(Input.from(updatedNode.get())) - .withTest(Input.from(expectedNode)) - .normalizeWhitespace() - .build(); - assertThat(diff.hasDifferences()).isFalse(); - } - - @Test - void applyQuotedTextWithRefsToNodeWithExistingRefs() { - // given - final var norm = Fixtures.loadRegelungstextFromDisk( - "NormWithPassiveModificationsAndExistingRef.xml" - ); - - final var amendingLaw = Fixtures.loadNormFromDisk("NormWithQuotedTextModAndRefs.xml"); - - when(loadNormPort.loadNorm(any())).thenReturn(Optional.of(amendingLaw)); - - final Node expectedNode = XmlMapper.toElement( - """ - entgegen § 9 Absatz 1 Satz 2, Absatz 2 oder 3 - Kennezichen eines verbotenen Vereins oder einer Ersatzorganisation verwendet, - - """ - ); - - // when - Regelungstext result = timeMachineService.applyPassiveModifications( - new ApplyPassiveModificationsUseCase.Query(norm, Instant.MAX) - ); - - // then - final Optional updatedNode = NodeParser.getNodeFromExpression( - "//*[@eId=\"hauptteil-1_art-1_abs-1_untergl-1_listenelem-2_inhalt-1_text-1\"]", - result.getDocument() - ); - assertThat(updatedNode).isPresent(); - final Diff diff = DiffBuilder - .compare(Input.from(updatedNode.get())) - .withTest(Input.from(expectedNode)) - .normalizeWhitespace() - .build(); - assertThat(diff.hasDifferences()).isFalse(); - } - } -}