diff --git a/.github/action.yml b/.github/action.yml index fe3f9eaf..8e12e356 100644 --- a/.github/action.yml +++ b/.github/action.yml @@ -20,13 +20,13 @@ runs: run: | cd ${{ github.workspace }}/${{ inputs.path }} - if dotnet nuget list source | grep -q $NugetSourceName; then - echo "Removing existing nuget source: $NugetSourceName" - dotnet nuget remove source $NugetSourceName + if dotnet nuget list source | grep -q ${{ inputs.nuget-source-name }}; then + echo "Removing existing nuget source: '${{ inputs.nuget-source-name }}'" + dotnet nuget remove source '${{ inputs.nuget-source-name }}' else echo "Nuget source $NugetSourceName does not exist. Skipping removal." fi dotnet nuget add source --username ${{ inputs.owner }} --password ${{ inputs.github-token }} --store-password-in-clear-text --name ${{ inputs.nuget-source-name }} "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" dotnet nuget list source - shell: bash \ No newline at end of file + shell: bash diff --git a/.github/workflows/chapter-4-contracts-workflow.yml b/.github/workflows/chapter-4-contracts-workflow.yml index 292cd6c0..bca3828d 100644 --- a/.github/workflows/chapter-4-contracts-workflow.yml +++ b/.github/workflows/chapter-4-contracts-workflow.yml @@ -29,7 +29,7 @@ jobs: with: dotnet-version: 8.0.x - name: Add Evolutionary Architecture Nuget Source - uses: evolutionary-architecture/evolutionary-architecture-by-example/.github@main + uses: evolutionary-architecture/evolutionary-architecture-by-example/.github@feature/add_signature_value_object with: github-token: ${{ secrets.GITHUB_TOKEN }} owner: ${{ github.repository_owner }} @@ -54,7 +54,7 @@ jobs: with: dotnet-version: 8.0.x - name: Add Evolutionary Architecture Nuget Source - uses: evolutionary-architecture/evolutionary-architecture-by-example/.github@main + uses: evolutionary-architecture/evolutionary-architecture-by-example/.github@feature/add_signature_value_object with: github-token: ${{ secrets.GITHUB_TOKEN }} owner: ${{ github.repository_owner }} diff --git a/Chapter-2-modules-separation/Src/Fitnet/appsettings.json b/Chapter-2-modules-separation/Src/Fitnet/appsettings.json index b0a47787..eade704d 100644 --- a/Chapter-2-modules-separation/Src/Fitnet/appsettings.json +++ b/Chapter-2-modules-separation/Src/Fitnet/appsettings.json @@ -32,4 +32,4 @@ } } } -} \ No newline at end of file +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api.UnitTests/Fitnet.Contracts.Api.UnitTests.csproj b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api.UnitTests/Fitnet.Contracts.Api.UnitTests.csproj index 9394de56..64dc823b 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api.UnitTests/Fitnet.Contracts.Api.UnitTests.csproj +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api.UnitTests/Fitnet.Contracts.Api.UnitTests.csproj @@ -2,6 +2,7 @@ + all @@ -25,6 +26,7 @@ + diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api.UnitTests/SignContract/SignContractRequestValidatorTests.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api.UnitTests/SignContract/SignContractRequestValidatorTests.cs index 1017d9ce..074c9237 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api.UnitTests/SignContract/SignContractRequestValidatorTests.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api.UnitTests/SignContract/SignContractRequestValidatorTests.cs @@ -5,6 +5,8 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.UnitTests.SignContract; public sealed class SignContractRequestValidatorTests { + private const string ValidSignature = "John Doe"; + private const int SignatureCharacterLimit = 100; private readonly SignContractRequestValidator _validator = new(); private readonly DateTimeOffset _fakeNow = new Faker().Date.RecentOffset(); @@ -12,7 +14,7 @@ public sealed class SignContractRequestValidatorTests internal void Given_sign_contract_request_validation_When_request_is_valid_Then_result_should_have_no_errors() { // Arrange - var request = new SignContractRequest(_fakeNow); + var request = new SignContractRequest(_fakeNow, ValidSignature); // Act var result = _validator.TestValidate(request); @@ -25,7 +27,7 @@ internal void Given_sign_contract_request_validation_When_request_is_valid_Then_ internal void Given_sign_contract_request_validation_When_signed_at_not_provided_Then_result_should_have_error() { // Arrange - var request = new SignContractRequest(default); + var request = new SignContractRequest(default, ValidSignature); // Act var result = _validator.TestValidate(request); @@ -33,4 +35,21 @@ internal void Given_sign_contract_request_validation_When_signed_at_not_provided // Assert result.ShouldHaveValidationErrorFor(signContractRequest => signContractRequest.SignedAt); } + + + [Fact] + internal void Given_sign_contract_request_validation_When_signature_is_to_long_Then_result_should_have_error() + { + // Arrange + var tooLongSignature = GenerateTooLongSignature(); + var request = new SignContractRequest(default, tooLongSignature); + + // Act + var result = _validator.TestValidate(request); + + // Assert + result.ShouldHaveValidationErrorFor(signContractRequest => signContractRequest.SignedAt); + } + + private static string GenerateTooLongSignature() => new('a', SignatureCharacterLimit + 1); } diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api.UnitTests/SignContract/Signatures/SignatureTests.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api.UnitTests/SignContract/Signatures/SignatureTests.cs new file mode 100644 index 00000000..7f402099 --- /dev/null +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api.UnitTests/SignContract/Signatures/SignatureTests.cs @@ -0,0 +1,41 @@ +namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.UnitTests.SignContract.Signatures; +using EvolutionaryArchitecture.Fitnet.Contracts.Core.SignContract.Signatures; + +using Core.SignContract.Signatures.Exceptions; +using FluentAssertions; + +public sealed class SignatureTests +{ + [Theory] + [InlineData("John Doe")] + [InlineData("Kamil Baczek")] + [InlineData("Maciej Jedrzejewski")] + [InlineData("John David Smith")] + internal void Given_create_signature_When_signature_is_valid_Then_should_not_throw(string value) + { + // Arrange + var now = DateTimeOffset.Now; + + // Act + var signature = Signature.From(now, value); + + // Assert + signature.Value.Should().Be(value); + signature.Date.Should().Be(now); + } + + [Theory] + [InlineData("invalidSignature!")] + [InlineData("invalid@Signature")] + internal void Given_create_signature_When_signature_has_forbidden_characters_Then_should_throw_exception(string value) + { + // Arrange + var now = DateTimeOffset.Now; + + // Act + Action act = () => Signature.From(now, value); + + // Assert + act.Should().Throw(); + } +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/PrepareContract/PrepareContractEndpoint.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/PrepareContract/PrepareContractEndpoint.cs index 61b272a1..92f6fcd7 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/PrepareContract/PrepareContractEndpoint.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/PrepareContract/PrepareContractEndpoint.cs @@ -11,8 +11,9 @@ internal static class PrepareContractEndpoint { internal static void MapPrepareContract(this IEndpointRouteBuilder app) => app.MapPost(ContractsApiPaths.Prepare, - async Task (PrepareContractRequest request, IContractsModule contractsModule, CancellationToken cancellationToken) => - await contractsModule.ExecuteCommandAsync(request.ToCommand(), cancellationToken).Match( + async Task (PrepareContractRequest request, IContractsModule contractsModule, CancellationToken cancellationToken) => + await contractsModule.ExecuteCommandAsync(request.ToCommand(), cancellationToken) + .Match( contractId => Results.Created(ContractsApiPaths.GetPreparedContractPath(contractId), (object?)contractId), errors => errors.ToProblem())) .ValidateRequest() diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/SignContract/SignContractEndpoint.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/SignContract/SignContractEndpoint.cs index 181dfcb9..64ed579d 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/SignContract/SignContractEndpoint.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/SignContract/SignContractEndpoint.cs @@ -9,7 +9,8 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.SignContract; internal static class SignContractEndpoint { - internal static void MapSignContract(this IEndpointRouteBuilder app) => app.MapPatch(ContractsApiPaths.Sign, async ( + internal static void MapSignContract(this IEndpointRouteBuilder app) => app.MapPatch(ContractsApiPaths.Sign, + async Task ( Guid id, SignContractRequest request, IContractsModule contractsModule, CancellationToken cancellationToken) => diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/SignContract/SignContractRequest.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/SignContract/SignContractRequest.cs index 3ebb1ad9..cb44693e 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/SignContract/SignContractRequest.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/SignContract/SignContractRequest.cs @@ -2,8 +2,8 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.SignContract; using EvolutionaryArchitecture.Fitnet.Contracts.Application.SignContract; -internal sealed record SignContractRequest(DateTimeOffset SignedAt) +internal sealed record SignContractRequest(DateTimeOffset SignedAt, string Signature) { internal SignContractCommand ToCommand(Guid id) => - new(id, SignedAt); + new(id, Signature, SignedAt); } diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/SignContract/SignContractRequestValidator.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/SignContract/SignContractRequestValidator.cs index 87bc8a68..753a3454 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/SignContract/SignContractRequestValidator.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/SignContract/SignContractRequestValidator.cs @@ -4,6 +4,15 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.SignContract; internal sealed class SignContractRequestValidator : AbstractValidator { - public SignContractRequestValidator() => RuleFor(signContractRequest => signContractRequest.SignedAt) - .NotEmpty(); + private const int SignatureMaximumLength = 100; + + public SignContractRequestValidator() + { + RuleFor(signContractRequest => signContractRequest.Signature) + .NotEmpty() + .MaximumLength(SignatureMaximumLength); + + RuleFor(signContractRequest => signContractRequest.SignedAt) + .NotEmpty(); + } } diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/SignContract/SignContractCommand.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/SignContract/SignContractCommand.cs index 3a85dde0..0ccda282 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/SignContract/SignContractCommand.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/SignContract/SignContractCommand.cs @@ -1,3 +1,3 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Application.SignContract; -public sealed record SignContractCommand(Guid Id, DateTimeOffset SignedAt) : ICommand>; +public sealed record SignContractCommand(Guid Id, string Signature, DateTimeOffset SignedAt) : ICommand>; diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/SignContract/SignContractCommandHandler.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/SignContract/SignContractCommandHandler.cs index dc42c02c..21ee5ebe 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/SignContract/SignContractCommandHandler.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/SignContract/SignContractCommandHandler.cs @@ -1,5 +1,6 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Application.SignContract; +using Core.SignContract.Signatures; using IntegrationEvents; using MassTransit; @@ -12,14 +13,14 @@ internal sealed class SignContractCommandHandler( { public async Task> Handle(SignContractCommand command, CancellationToken cancellationToken) => await contractsRepository.GetByIdAsync(command.Id, cancellationToken) - .ThenAsync(async contract => await contract.Sign(command.SignedAt, timeProvider.GetUtcNow()) + .ThenAsync(async contract => await contract.Sign(Signature.From(command.SignedAt, command.Signature), timeProvider.GetUtcNow()) .ThenAsync(async bindingContract => { await bindingContractsRepository.AddAsync(bindingContract, cancellationToken); await contractsRepository.CommitAsync(cancellationToken); var @event = ContractSignedEvent.Create(contract.Id.Value, contract.CustomerId, - contract.SignedAt!.Value, + contract.Signature!.Date, contract.ExpiringAt!.Value); await publishEndpoint.Publish(@event, cancellationToken); diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/Builders/SignContractBuilder.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/Builders/SignContractBuilder.cs index 4db12d2d..e9d4bd32 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/Builders/SignContractBuilder.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/Builders/SignContractBuilder.cs @@ -1,5 +1,7 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.Common.Builders; +using Core.SignContract.Signatures; + internal sealed class SignContractBuilder(Contract parentBuilder) { private DateTimeOffset _signDay; @@ -15,7 +17,8 @@ public SignContractBuilder SignedOn(DateTimeOffset signDay, DateTimeOffset fakeT private BindingContract Build() { - var bindingContract = parentBuilder.Sign(_signDay, _fakeToday); + var signature = Signature.From(_signDay, "John Doe"); + var bindingContract = parentBuilder.Sign(signature, _fakeToday); return bindingContract.Value; } diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/SignContractTests.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/SignContractTests.cs index 19e3ebf3..abb4bdb0 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/SignContractTests.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/SignContractTests.cs @@ -3,9 +3,12 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.SignContract; using Common; using Common.Builders; using Core.SignContract; +using Core.SignContract.Signatures; public sealed class SignContractTests { + private const string SignatureValue = "John Doe"; + [Theory] [ClassData(typeof(SignContractTestData))] internal void Given_sign_contract_Then_expiration_date_is_set_to_contract_duration_from_now( @@ -18,9 +21,10 @@ internal void Given_sign_contract_Then_expiration_date_is_set_to_contract_durati Contract contract = ContractBuilder .Prepared() .PreparedAt(preparedAt); + var signature = Signature.From(signedAt, SignatureValue); // Act - var signResult = contract.Sign(signedAt, fakeNow); + var signResult = contract.Sign(signature, fakeNow); // Assert var @event = signResult.Value.GetPublishedEvent(); @@ -36,9 +40,10 @@ internal void Given_sign_contract_Then_contracts_becomes_binding_contract() // Arrange Contract contract = ContractBuilder .Prepared(); + var signature = Signature.From(SignedAt, SignatureValue); // Act - var signResult = contract.Sign(SignedAt, FakeNow); + var signResult = contract.Sign(signature, FakeNow); // Assert var @event = signResult.Value.GetPublishedEvent(); diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/SignedContractBuilder.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/SignedContractBuilder.cs index 3e7df05c..7454aca5 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/SignedContractBuilder.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/SignedContractBuilder.cs @@ -1,9 +1,11 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.SignContract; using Common; +using Core.SignContract.Signatures; internal sealed class SignedContractBuilder(Contract parentBuilder) { + private const string SignatureValue = "John Doe"; private DateTimeOffset? _signDay; private DateTimeOffset? _fakeToday; @@ -19,7 +21,8 @@ private BindingContract Build() { var signDay = _signDay ?? FakeContractDates.SignDay; var fakeToday = _fakeToday ?? FakeContractDates.SignDay; - var bindingContract = parentBuilder.Sign(signDay, fakeToday).Value; + var signature = Signature.From(signDay, SignatureValue); + var bindingContract = parentBuilder.Sign(signature, fakeToday).Value; return bindingContract; } diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Contract.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Contract.cs index 472a8a1d..334f26ba 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Contract.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Contract.cs @@ -5,6 +5,7 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Core; using PrepareContract; using PrepareContract.BusinessRules; using SignContract.BusinessRules; +using SignContract.Signatures; public sealed class Contract : Entity { @@ -17,10 +18,10 @@ public sealed class Contract : Entity public DateTimeOffset PreparedAt { get; init; } public TimeSpan Duration { get; init; } - public DateTimeOffset? SignedAt { get; private set; } + public Signature? Signature { get; private set; } public DateTimeOffset? ExpiringAt { get; private set; } - public bool IsSigned => SignedAt.HasValue; + public bool IsSigned => Signature is not null; // EF needs this constructor to create non-primitive types private Contract() { } @@ -51,13 +52,13 @@ public static ErrorOr Prepare( new PreviousContractHasToBeSignedRule(isPreviousContractSigned)) .Then(_ => new Contract(customerId, preparedAt, StandardDuration)); - public ErrorOr Sign(DateTimeOffset signedAt, DateTimeOffset now) => + public ErrorOr Sign(Signature signature, DateTimeOffset now) => BusinessRuleValidator.Validate( new ContractMustNotBeAlreadySignedRule(IsSigned), - new ContractCanOnlyBeSignedWithin30DaysFromPreparationRule(PreparedAt, signedAt)) + new ContractCanOnlyBeSignedWithin30DaysFromPreparationRule(PreparedAt, signature.Date)) .Then(_ => { - SignedAt = signedAt; + Signature = signature; ExpiringAt = now.Add(Duration); var bindingContract = BindingContract.Start(Id, CustomerId, Duration, now, ExpiringAt.Value); diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/GlobalUsings.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/GlobalUsings.cs index 6f3d204b..ab59266a 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/GlobalUsings.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/GlobalUsings.cs @@ -1 +1,3 @@ -global using ErrorOr; +global using System.Text.RegularExpressions; +global using ErrorOr; +global using EvolutionaryArchitecture.Fitnet.Contracts.Core.SignContract.Signatures.Exceptions; diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/SignContract/Signatures/Exceptions/SignatureNotValidException.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/SignContract/Signatures/Exceptions/SignatureNotValidException.cs new file mode 100644 index 00000000..58a02ff1 --- /dev/null +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/SignContract/Signatures/Exceptions/SignatureNotValidException.cs @@ -0,0 +1,3 @@ +namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.SignContract.Signatures.Exceptions; + +public sealed class SignatureNotValidException(string signature) : InvalidOperationException($"Signature: '{signature}' contains invalid characters."); diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/SignContract/Signatures/Signature.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/SignContract/Signatures/Signature.cs new file mode 100644 index 00000000..9620bbaa --- /dev/null +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/SignContract/Signatures/Signature.cs @@ -0,0 +1,25 @@ +namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.SignContract.Signatures; + +public sealed partial class Signature +{ + private static readonly Regex SignaturePattern = SignatureAllowedCharacters(); + public DateTimeOffset Date { get; } + public string Value { get; } + + private Signature(DateTimeOffset date, string value) + { + Date = date; + if (!SignaturePattern.IsMatch(value)) + { + throw new SignatureNotValidException(value); + } + + Value = value; + } + + public static Signature From(DateTimeOffset signedAt, string signature) => + new(signedAt, signature); + + [GeneratedRegex(@"^[A-Za-z\s]+$")] + private static partial Regex SignatureAllowedCharacters(); +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/ContractEntityConfiguration.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/ContractEntityConfiguration.cs index 94b2b2d2..cb41b866 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/ContractEntityConfiguration.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/ContractEntityConfiguration.cs @@ -1,6 +1,7 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Database.Configurations; using Core; +using Core.SignContract.Signatures; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -17,6 +18,10 @@ public void Configure(EntityTypeBuilder builder) value => new ContractId(value)) .ValueGeneratedOnAdd(); builder.Property(contract => contract.PreparedAt).IsRequired(); - builder.Property(contract => contract.SignedAt).IsRequired(false); + builder.OwnsOne("Signature", signatureBuilder => + { + signatureBuilder.Property(signature => signature.Date).IsRequired(); + signatureBuilder.Property(signature => signature.Value).IsRequired().HasMaxLength(100); + }); } } diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20241026165318_AddSignature.Designer.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20241026165318_AddSignature.Designer.cs new file mode 100644 index 00000000..757fb0c4 --- /dev/null +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20241026165318_AddSignature.Designer.cs @@ -0,0 +1,133 @@ +// +using System; +using EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Database.Migrations +{ + [DbContext(typeof(ContractsPersistence))] + [Migration("20241026165318_AddSignature")] + partial class AddSignature + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Contracts") + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.BindingContract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BindingFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("ContractId") + .HasColumnType("uuid"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Duration") + .HasColumnType("interval"); + + b.Property("ExpiringAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TerminatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("BindingContracts", "Contracts"); + }); + + modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.Contract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Duration") + .HasColumnType("interval"); + + b.Property("ExpiringAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PreparedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Contracts", "Contracts"); + }); + + modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.BindingContract", b => + { + b.OwnsMany("EvolutionaryArchitecture.Fitnet.Contracts.Core.Annex", "AttachedAnnexes", b1 => + { + b1.Property("BindingContractId") + .HasColumnType("uuid"); + + b1.Property("Id") + .HasColumnType("uuid"); + + b1.Property("ValidFrom") + .HasColumnType("timestamp with time zone"); + + b1.HasKey("BindingContractId", "Id"); + + b1.ToTable("Annexes", "Contracts"); + + b1.WithOwner() + .HasForeignKey("BindingContractId"); + }); + + b.Navigation("AttachedAnnexes"); + }); + + modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.Contract", b => + { + b.OwnsOne("EvolutionaryArchitecture.Fitnet.Contracts.Core.SignContract.Signatures.Signature", "Signature", b1 => + { + b1.Property("ContractId") + .HasColumnType("uuid"); + + b1.Property("Date") + .HasColumnType("timestamp with time zone"); + + b1.Property("Value") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b1.HasKey("ContractId"); + + b1.ToTable("Contracts", "Contracts"); + + b1.WithOwner() + .HasForeignKey("ContractId"); + }); + + b.Navigation("Signature"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20241026165318_AddSignature.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20241026165318_AddSignature.cs new file mode 100644 index 00000000..f5f85dd2 --- /dev/null +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20241026165318_AddSignature.cs @@ -0,0 +1,41 @@ +#nullable disable + +namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Database.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; + +/// +public partial class AddSignature : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "SignedAt", + schema: "Contracts", + table: "Contracts", + newName: "Signature_Date"); + + migrationBuilder.AddColumn( + name: "Signature_Value", + schema: "Contracts", + table: "Contracts", + type: "character varying(100)", + maxLength: 100, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Signature_Value", + schema: "Contracts", + table: "Contracts"); + + migrationBuilder.RenameColumn( + name: "Signature_Date", + schema: "Contracts", + table: "Contracts", + newName: "SignedAt"); + } +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/ContractsPersistenceModelSnapshot.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/ContractsPersistenceModelSnapshot.cs index 6d62f3ea..0fecf2a7 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/ContractsPersistenceModelSnapshot.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/ContractsPersistenceModelSnapshot.cs @@ -8,100 +8,122 @@ #nullable disable -namespace EvolutionaryArchitecture.Fitnet.Migrations +namespace EvolutionaryArchitecture.Fitnet.Migrations; + +[DbContext(typeof(ContractsPersistence))] +partial class ContractsPersistenceModelSnapshot : ModelSnapshot { - [DbContext(typeof(ContractsPersistence))] - partial class ContractsPersistenceModelSnapshot : ModelSnapshot + protected override void BuildModel(ModelBuilder modelBuilder) { - protected override void BuildModel(ModelBuilder modelBuilder) - { #pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Contracts") - .HasAnnotation("ProductVersion", "8.0.4") - .HasAnnotation("Relational:MaxIdentifierLength", 63); + modelBuilder + .HasDefaultSchema("Contracts") + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.BindingContract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BindingFrom") + .HasColumnType("timestamp with time zone"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + b.Property("ContractId") + .HasColumnType("uuid"); - modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.BindingContract", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); + b.Property("CustomerId") + .HasColumnType("uuid"); - b.Property("BindingFrom") - .HasColumnType("timestamp with time zone"); + b.Property("Duration") + .HasColumnType("interval"); - b.Property("ContractId") - .HasColumnType("uuid"); + b.Property("ExpiringAt") + .HasColumnType("timestamp with time zone"); - b.Property("CustomerId") - .HasColumnType("uuid"); + b.Property("TerminatedAt") + .HasColumnType("timestamp with time zone"); - b.Property("Duration") - .HasColumnType("interval"); + b.HasKey("Id"); - b.Property("ExpiringAt") - .HasColumnType("timestamp with time zone"); + b.ToTable("BindingContracts", "Contracts"); + }); - b.Property("TerminatedAt") - .HasColumnType("timestamp with time zone"); + modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.Contract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Duration") + .HasColumnType("interval"); - b.HasKey("Id"); + b.Property("ExpiringAt") + .HasColumnType("timestamp with time zone"); - b.ToTable("BindingContracts", "Contracts"); - }); + b.Property("PreparedAt") + .HasColumnType("timestamp with time zone"); - modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.Contract", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); + b.HasKey("Id"); - b.Property("CustomerId") - .HasColumnType("uuid"); + b.ToTable("Contracts", "Contracts"); + }); - b.Property("Duration") - .HasColumnType("interval"); + modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.BindingContract", b => + { + b.OwnsMany("EvolutionaryArchitecture.Fitnet.Contracts.Core.Annex", "AttachedAnnexes", b1 => + { + b1.Property("BindingContractId") + .HasColumnType("uuid"); + + b1.Property("Id") + .HasColumnType("uuid"); - b.Property("ExpiringAt") - .HasColumnType("timestamp with time zone"); + b1.Property("ValidFrom") + .HasColumnType("timestamp with time zone"); - b.Property("PreparedAt") - .HasColumnType("timestamp with time zone"); + b1.HasKey("BindingContractId", "Id"); - b.Property("SignedAt") - .HasColumnType("timestamp with time zone"); + b1.ToTable("Annexes", "Contracts"); - b.HasKey("Id"); + b1.WithOwner() + .HasForeignKey("BindingContractId"); + }); - b.ToTable("Contracts", "Contracts"); - }); + b.Navigation("AttachedAnnexes"); + }); - modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.BindingContract", b => - { - b.OwnsMany("EvolutionaryArchitecture.Fitnet.Contracts.Core.Annex", "AttachedAnnexes", b1 => - { - b1.Property("BindingContractId") - .HasColumnType("uuid"); + modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.Contract", b => + { + b.OwnsOne("EvolutionaryArchitecture.Fitnet.Contracts.Core.SignContract.Signatures.Signature", "Signature", b1 => + { + b1.Property("ContractId") + .HasColumnType("uuid"); - b1.Property("Id") - .HasColumnType("uuid"); + b1.Property("Date") + .HasColumnType("timestamp with time zone"); - b1.Property("ValidFrom") - .HasColumnType("timestamp with time zone"); + b1.Property("Value") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); - b1.HasKey("BindingContractId", "Id"); + b1.HasKey("ContractId"); - b1.ToTable("Annexes", "Contracts"); + b1.ToTable("Contracts", "Contracts"); - b1.WithOwner() - .HasForeignKey("BindingContractId"); - }); + b1.WithOwner() + .HasForeignKey("ContractId"); + }); - b.Navigation("AttachedAnnexes"); - }); + b.Navigation("Signature"); + }); #pragma warning restore 612, 618 - } } } diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Fitnet.Contracts.Infrastructure.csproj b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Fitnet.Contracts.Infrastructure.csproj index b28bf201..9ed8cf44 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Fitnet.Contracts.Infrastructure.csproj +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Fitnet.Contracts.Infrastructure.csproj @@ -10,9 +10,10 @@ + - + diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/GlobalUsings.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/GlobalUsings.cs index 1bfe64b6..34ac5da1 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/GlobalUsings.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/GlobalUsings.cs @@ -2,3 +2,4 @@ global using System.Net.Http.Json; global using Bogus; global using FluentAssertions; +global using Microsoft.AspNetCore.Mvc; diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/PrepareContract/PrepareContractTests.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/PrepareContract/PrepareContractTests.cs index 6319f1ba..422474ed 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/PrepareContract/PrepareContractTests.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/PrepareContract/PrepareContractTests.cs @@ -3,10 +3,9 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.IntegrationTests.PrepareCont using EvolutionaryArchitecture.Fitnet.Common.IntegrationTestsToolbox.TestEngine.Database; using Api; using Api.PrepareContract; -using Fitnet.Common.IntegrationTestsToolbox.TestEngine; -using Fitnet.Common.IntegrationTestsToolbox.TestEngine.Configuration; -using Fitnet.Common.IntegrationTestsToolbox.TestEngine.EventBus; -using Microsoft.AspNetCore.Mvc; +using Common.IntegrationTestsToolbox.TestEngine; +using Common.IntegrationTestsToolbox.TestEngine.Configuration; +using Common.IntegrationTestsToolbox.TestEngine.EventBus; public sealed class PrepareContractTests(FitnetWebApplicationFactory applicationInMemoryFactory, DatabaseContainer database) : IClassFixture>, diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/SignContract/SignContractRequestParameters.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/SignContract/SignContractRequestParameters.cs index d668a118..f2008f2c 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/SignContract/SignContractRequestParameters.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/SignContract/SignContractRequestParameters.cs @@ -2,17 +2,17 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.IntegrationTests.SignContrac using Api; -internal record SignContractRequestParameters(string Url, DateTimeOffset SignedAt) +internal record SignContractRequestParameters(string Url, DateTimeOffset SignedAt, string Signature) { - internal static SignContractRequestParameters GetValid(Guid id) => - new(BuildUrl(id), GetValidSignedAtDate()); + internal static SignContractRequestParameters GetValid(Guid id, string signature = "John Doe") => + new(BuildUrl(id), GetValidSignedAtDate(), signature); internal static SignContractRequestParameters GetWithNotExistingContractId() => - new(BuildUrl(Guid.NewGuid()), GetValidSignedAtDate()); + new(BuildUrl(Guid.NewGuid()), GetValidSignedAtDate(), "John Doe"); internal static SignContractRequestParameters GetWithInvalidSignedAtDate(Guid id) => - new(BuildUrl(id), DateTimeOffset.Now.AddDays(31).ToUniversalTime()); + new(BuildUrl(id), DateTimeOffset.Now.AddDays(31).ToUniversalTime(), "John Doe"); private static string BuildUrl(Guid id) => ContractsApiPaths.Sign.Replace("{id}", id.ToString()); private static DateTimeOffset GetValidSignedAtDate() => DateTimeOffset.Now.AddDays(1).ToUniversalTime(); -} \ No newline at end of file +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/SignContract/SignContractTestExtensions.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/SignContract/SignContractTestExtensions.cs index b4ddef5c..bb34028f 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/SignContract/SignContractTestExtensions.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/SignContract/SignContractTestExtensions.cs @@ -7,7 +7,7 @@ internal static class SignContractTestExtensions internal static async Task SignContractAsync(this HttpClient httpClient, Guid contractId) { var requestParameters = SignContractRequestParameters.GetValid(contractId); - var signContractRequest = new SignContractRequest(requestParameters.SignedAt); + var signContractRequest = new SignContractRequest(requestParameters.SignedAt, requestParameters.Signature); var response = await httpClient.PatchAsJsonAsync(requestParameters.Url, signContractRequest); response.EnsureSuccessStatusCode(); diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/SignContract/SignContractTests.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/SignContract/SignContractTests.cs index d673843d..69e1d8cd 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/SignContract/SignContractTests.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/SignContract/SignContractTests.cs @@ -1,11 +1,10 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.IntegrationTests.SignContract; using Api.SignContract; -using EvolutionaryArchitecture.Fitnet.Common.IntegrationTestsToolbox.TestEngine.Database; -using EvolutionaryArchitecture.Fitnet.Common.Api.ErrorHandling; using Common.IntegrationTestsToolbox.TestEngine; using Common.IntegrationTestsToolbox.TestEngine.Configuration; using Common.IntegrationTestsToolbox.TestEngine.EventBus; +using EvolutionaryArchitecture.Fitnet.Common.IntegrationTestsToolbox.TestEngine.Database; using PrepareContract; public sealed class SignContractTests(FitnetWebApplicationFactory applicationInMemoryFactory, @@ -22,7 +21,7 @@ internal async Task Given_valid_contract_signature_request_Then_should_return_ok // Arrange var preparedContractId = await _applicationHttpClient.PrepareContractAsync(); var requestParameters = SignContractRequestParameters.GetValid(preparedContractId); - var signContractRequest = new SignContractRequest(requestParameters.SignedAt); + var signContractRequest = new SignContractRequest(requestParameters.SignedAt, requestParameters.Signature); // Act var signContractResponse = @@ -37,7 +36,7 @@ internal async Task Given_contract_signature_request_with_not_existing_id_Then_s { // Arrange var requestParameters = SignContractRequestParameters.GetWithNotExistingContractId(); - var signContractRequest = new SignContractRequest(requestParameters.SignedAt); + var signContractRequest = new SignContractRequest(requestParameters.SignedAt, requestParameters.Signature); // Act var signContractResponse = @@ -55,7 +54,7 @@ internal async Task var preparedContractId = await _applicationHttpClient.PrepareContractAsync(); var requestParameters = SignContractRequestParameters.GetWithInvalidSignedAtDate(preparedContractId); - var signContractRequest = new SignContractRequest(requestParameters.SignedAt); + var signContractRequest = new SignContractRequest(requestParameters.SignedAt, requestParameters.Signature); // Act var signContractResponse = @@ -64,9 +63,9 @@ internal async Task // Assert signContractResponse.Should().HaveStatusCode(HttpStatusCode.Conflict); - var responseMessage = await signContractResponse.Content.ReadFromJsonAsync(); - responseMessage?.StatusCode.Should().Be((int)HttpStatusCode.Conflict); - responseMessage?.Message.Should() - .Be("Contract can not be signed because more than 30 days have passed from the contract preparation"); + var responseMessage = await signContractResponse.Content.ReadFromJsonAsync(); + responseMessage?.Status.Should().Be((int)HttpStatusCode.Conflict); + responseMessage?.Detail.Should() + .Be("Contract can only be signed within 30 days from preparation"); } } diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/TerminateBindingContract/TerminateBindingContractTests.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/TerminateBindingContract/TerminateBindingContractTests.cs index f17b27de..dd9c8066 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/TerminateBindingContract/TerminateBindingContractTests.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/TerminateBindingContract/TerminateBindingContractTests.cs @@ -1,10 +1,10 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.IntegrationTests.TerminateBindingContract; -using Common.IntegrationTestsToolbox.TestEngine.Time; using EvolutionaryArchitecture.Fitnet.Common.IntegrationTestsToolbox.TestEngine.Database; using Common.IntegrationTestsToolbox.TestEngine; using Common.IntegrationTestsToolbox.TestEngine.Configuration; using Common.IntegrationTestsToolbox.TestEngine.EventBus; +using Common.IntegrationTestsToolbox.TestEngine.Time; using PrepareContract; using SignContract;