diff --git a/src/PostalRegistry/PostalInformation/Commands/UpdatePostalNames.cs b/src/PostalRegistry/PostalInformation/Commands/UpdatePostalNames.cs new file mode 100644 index 00000000..6df87e5a --- /dev/null +++ b/src/PostalRegistry/PostalInformation/Commands/UpdatePostalNames.cs @@ -0,0 +1,47 @@ +namespace PostalRegistry.PostalInformation.Commands +{ + using System; + using System.Collections.Generic; + using Be.Vlaanderen.Basisregisters.Generators.Guid; + using Be.Vlaanderen.Basisregisters.GrAr.Provenance; + using Be.Vlaanderen.Basisregisters.Utilities; + + public sealed class UpdatePostalNames : IHasCommandProvenance + { + private static readonly Guid Namespace = new Guid("6af54580-27cc-4b16-a6c9-d14eee229ed8"); + + public PostalCode PostalCode { get; } + + public IReadOnlyCollection PostalNamesToAdd { get; } + public IReadOnlyCollection PostalNamesToRemove { get; } + public Provenance Provenance { get; } + + public UpdatePostalNames( + PostalCode postalCode, + IReadOnlyCollection postalNamesToAdd, + IReadOnlyCollection postalNamesToRemove, + Provenance provenance) + { + PostalCode = postalCode; + PostalNamesToAdd = postalNamesToAdd; + PostalNamesToRemove = postalNamesToRemove; + Provenance = provenance; + } + + public Guid CreateCommandId() + => Deterministic.Create(Namespace, $"UpdatePostalNames-{ToString()}"); + + public override string? ToString() + => ToStringBuilder.ToString(IdentityFields()); + + private IEnumerable IdentityFields() + { + yield return PostalCode; + + foreach (var field in Provenance.GetIdentityFields()) + { + yield return field; + } + } + } +} diff --git a/src/PostalRegistry/PostalInformation/Exceptions/PostalNameAlreadyExistsException.cs b/src/PostalRegistry/PostalInformation/Exceptions/PostalNameAlreadyExistsException.cs new file mode 100644 index 00000000..3b4c8569 --- /dev/null +++ b/src/PostalRegistry/PostalInformation/Exceptions/PostalNameAlreadyExistsException.cs @@ -0,0 +1,20 @@ +namespace PostalRegistry.PostalInformation.Exceptions +{ + using System; + using System.Runtime.Serialization; + + [Serializable] + public sealed class PostalNameAlreadyExistsException : PostalRegistryException + { + public PostalName PostalName { get; } + + public PostalNameAlreadyExistsException(PostalName postalName) + { + PostalName = postalName; + } + + private PostalNameAlreadyExistsException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + } +} diff --git a/src/PostalRegistry/PostalInformation/PostalInformation.cs b/src/PostalRegistry/PostalInformation/PostalInformation.cs index c2e7ea47..2b1491e7 100755 --- a/src/PostalRegistry/PostalInformation/PostalInformation.cs +++ b/src/PostalRegistry/PostalInformation/PostalInformation.cs @@ -93,5 +93,21 @@ public void RelinkMunicipality(NisCode newNisCode) ApplyChange(new MunicipalityWasRelinked(PostalCode, newNisCode, NisCode!)); } + + public void UpdatePostalNames( + IReadOnlyCollection postalNamesToAdd, + IReadOnlyCollection postalNamesToRemove) + { + foreach (var postalName in postalNamesToRemove.Where(_postalNames.Contains)) + ApplyChange(new PostalInformationPostalNameWasRemoved(PostalCode, postalName)); + + foreach (var postalName in postalNamesToAdd) + { + if(_postalNames.Contains(postalName)) + throw new PostalNameAlreadyExistsException(postalName); + + ApplyChange(new PostalInformationPostalNameWasAdded(PostalCode, postalName)); + } + } } } diff --git a/src/PostalRegistry/PostalInformation/PostalInformationCommandHandlerModule.cs b/src/PostalRegistry/PostalInformation/PostalInformationCommandHandlerModule.cs index 1989ae06..1ec9fd13 100755 --- a/src/PostalRegistry/PostalInformation/PostalInformationCommandHandlerModule.cs +++ b/src/PostalRegistry/PostalInformation/PostalInformationCommandHandlerModule.cs @@ -82,6 +82,17 @@ public PostalInformationCommandHandlerModule( postalInformation.RelinkMunicipality(message.Command.NewNisCode); }); + + For() + .AddSqlStreamStore(getStreamStore, getUnitOfWork, eventMapping, eventSerializer) + .AddProvenance(getUnitOfWork, postalInformationProvenanceFactory) + .Handle(async (message, ct) => + { + var postalCode = new PostalCode(message.Command.PostalCode); + var postalInformation = await getPostalInformationSet().GetAsync(postalCode, ct); + + postalInformation.UpdatePostalNames(message.Command.PostalNamesToAdd, message.Command.PostalNamesToRemove); + }); } } } diff --git a/src/PostalRegistry/PostalInformation/PostalInformationState.cs b/src/PostalRegistry/PostalInformation/PostalInformationState.cs index ddec7b12..e1c8a258 100755 --- a/src/PostalRegistry/PostalInformation/PostalInformationState.cs +++ b/src/PostalRegistry/PostalInformation/PostalInformationState.cs @@ -8,11 +8,11 @@ namespace PostalRegistry.PostalInformation public partial class PostalInformation { - public PostalCode PostalCode { get; private set; } private PostalInformationStatus? _status; - private readonly List _postalNames = new List(); + public PostalCode PostalCode { get; private set; } + public IReadOnlyCollection PostalNames => _postalNames.AsReadOnly(); public NisCode? NisCode { get; set; } public Modification LastModification { get; private set; } diff --git a/test/PostalRegistry.Tests/AggregateTests/WhenUpdatingPostalNames/GivenNoPostalInformation.cs b/test/PostalRegistry.Tests/AggregateTests/WhenUpdatingPostalNames/GivenNoPostalInformation.cs new file mode 100644 index 00000000..0e0ec699 --- /dev/null +++ b/test/PostalRegistry.Tests/AggregateTests/WhenUpdatingPostalNames/GivenNoPostalInformation.cs @@ -0,0 +1,35 @@ +namespace PostalRegistry.Tests.AggregateTests.WhenUpdatingPostalNames +{ + using AutoFixture; + using Be.Vlaanderen.Basisregisters.AggregateSource; + using Be.Vlaanderen.Basisregisters.AggregateSource.Testing; + using global::AutoFixture; + using PostalInformation; + using PostalInformation.Commands; + using Xunit; + using Xunit.Abstractions; + + public class GivenNoPostalInformation : PostalRegistryTest + { + private readonly Fixture _fixture; + + public GivenNoPostalInformation(ITestOutputHelper testOutputHelper) : base(testOutputHelper) + { + _fixture = new Fixture(); + _fixture.Customize(new WithFixedPostalCode()); + _fixture.Customize(new WithIntegerNisCode()); + } + + [Fact] + public void ThenAggregateNotFoundExceptionIsThrown() + { + var command = _fixture.Create(); + + Assert( + new Scenario() + .Given() + .When(command) + .Throws(new AggregateNotFoundException(command.PostalCode.ToString(), typeof(PostalInformation)))); + } + } +} diff --git a/test/PostalRegistry.Tests/AggregateTests/WhenUpdatingPostalNames/GivenPostalInformation.cs b/test/PostalRegistry.Tests/AggregateTests/WhenUpdatingPostalNames/GivenPostalInformation.cs new file mode 100644 index 00000000..0e993266 --- /dev/null +++ b/test/PostalRegistry.Tests/AggregateTests/WhenUpdatingPostalNames/GivenPostalInformation.cs @@ -0,0 +1,98 @@ +namespace PostalRegistry.Tests.AggregateTests.WhenUpdatingPostalNames +{ + using System.Collections.Generic; + using AutoFixture; + using Be.Vlaanderen.Basisregisters.AggregateSource; + using Be.Vlaanderen.Basisregisters.AggregateSource.Testing; + using Be.Vlaanderen.Basisregisters.GrAr.Provenance; + using Builders; + using FluentAssertions; + using global::AutoFixture; + using PostalInformation; + using PostalInformation.Commands; + using PostalInformation.Events; + using PostalInformation.Exceptions; + using Xunit; + using Xunit.Abstractions; + + public class GivenPostalInformation : PostalRegistryTest + { + private readonly Fixture _fixture; + + public GivenPostalInformation(ITestOutputHelper testOutputHelper) : base(testOutputHelper) + { + _fixture = new Fixture(); + _fixture.Customize(new WithFixedPostalCode()); + _fixture.Customize(new WithIntegerNisCode()); + _fixture.Customize(new InfrastructureCustomization()); + } + + [Fact] + public void GivenNameAlreadyExists_ThenPostalNameAlreadyExistsExceptionIsThrown() + { + var named = _fixture.Create(); + var postalName = new PostalName(named.Name, named.Language); + var command = new UpdatePostalNames(_fixture.Create(), + [postalName], + new List(), + _fixture.Create()); + + Assert( + new Scenario() + .Given( + command.PostalCode, + _fixture.Create(), + named) + .When(command) + .Throws(new PostalNameAlreadyExistsException(postalName))); + } + + [Fact] + public void ThenPostalNamesAreUpdated() + { + var named = _fixture.Create(); + var nameToAdd = new PostalName(_fixture.Create(), Language.Dutch); + var command = new UpdatePostalNames(_fixture.Create(), + [nameToAdd], + [new PostalName(named.Name, named.Language)], + _fixture.Create()); + + Assert( + new Scenario() + .Given( + command.PostalCode, + _fixture.Create(), + named) + .When(command) + .Then(new Fact(command.PostalCode, + new PostalInformationPostalNameWasRemoved(command.PostalCode, new PostalName(named.Name, named.Language))), + new Fact(command.PostalCode, + new PostalInformationPostalNameWasAdded(command.PostalCode, nameToAdd)))); + } + + [Fact] + public void StateCheck() + { + // Arrange + var postalInformationPostalNameWasAdded = _fixture.Create(); + var postalInformationPostalNameWasRemoved = new PostalInformationPostalNameWasRemovedBuilder(_fixture) + .WithName(postalInformationPostalNameWasAdded.Name, postalInformationPostalNameWasAdded.Language) + .Build(); + var postalInformationPostalNameWasAdded2 = _fixture.Create(); + + // Act + var sut = PostalInformation.Factory(); + sut.Initialize([ + _fixture.Create(), + _fixture.Create(), + postalInformationPostalNameWasAdded, + postalInformationPostalNameWasRemoved, + postalInformationPostalNameWasAdded2 + ]); + + // Assert + sut.PostalNames.Count.Should().Be(1); + sut.PostalNames.Should().Contain(x => x.Name == postalInformationPostalNameWasAdded2.Name && x.Language == postalInformationPostalNameWasAdded2.Language); + } + } +}