Skip to content

Commit

Permalink
Implemented field type update.
Browse files Browse the repository at this point in the history
  • Loading branch information
Utar94 committed Jul 22, 2024
1 parent 0ecd107 commit 2c7e4dd
Show file tree
Hide file tree
Showing 6 changed files with 296 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using FluentValidation;
using Logitar.Cms.Contracts.FieldTypes;
using Logitar.Cms.Core.FieldTypes.Properties;
using Logitar.Cms.Core.FieldTypes.Validators;
using Logitar.Identity.Contracts.Settings;
using Logitar.Identity.Domain.Shared;
using MediatR;

namespace Logitar.Cms.Core.FieldTypes.Commands;

internal class UpdateFieldTypeCommandHandler : IRequestHandler<UpdateFieldTypeCommand, FieldType?>
{
private readonly IFieldTypeQuerier _fieldTypeQuerier;
private readonly IFieldTypeRepository _fieldTypeRepository;
private readonly ISender _sender;

public UpdateFieldTypeCommandHandler(IFieldTypeQuerier fieldTypeQuerier, IFieldTypeRepository fieldTypeRepository, ISender sender)
{
_fieldTypeQuerier = fieldTypeQuerier;
_fieldTypeRepository = fieldTypeRepository;
_sender = sender;
}

public async Task<FieldType?> Handle(UpdateFieldTypeCommand command, CancellationToken cancellationToken)
{
FieldTypeId id = new(command.Id);
FieldTypeAggregate? fieldType = await _fieldTypeRepository.LoadAsync(id, cancellationToken);
if (fieldType == null)
{
return null;
}

IUniqueNameSettings uniqueNameSettings = FieldTypeAggregate.UniqueNameSettings;

UpdateFieldTypePayload payload = command.Payload;
new UpdateFieldTypeValidator(uniqueNameSettings, fieldType.DataType).ValidateAndThrow(payload);

if (!string.IsNullOrWhiteSpace(payload.UniqueName))
{
fieldType.UniqueName = new UniqueNameUnit(uniqueNameSettings, payload.UniqueName);
}
if (payload.DisplayName != null)
{
fieldType.DisplayName = DisplayNameUnit.TryCreate(payload.DisplayName.Value);
}
if (payload.Description != null)
{
fieldType.Description = DescriptionUnit.TryCreate(payload.Description.Value);
}
fieldType.Update(command.ActorId);

if (payload.BooleanProperties != null)
{
fieldType.SetProperties(new ReadOnlyBooleanProperties(payload.BooleanProperties), command.ActorId);
}
if (payload.DateTimeProperties != null)
{
fieldType.SetProperties(new ReadOnlyDateTimeProperties(payload.DateTimeProperties), command.ActorId);
}
if (payload.NumberProperties != null)
{
fieldType.SetProperties(new ReadOnlyNumberProperties(payload.NumberProperties), command.ActorId);
}
if (payload.StringProperties != null)
{
fieldType.SetProperties(new ReadOnlyStringProperties(payload.StringProperties), command.ActorId);
}
if (payload.TextProperties != null)
{
fieldType.SetProperties(new ReadOnlyTextProperties(payload.TextProperties), command.ActorId);
}

await _sender.Send(new SaveFieldTypeCommand(fieldType), cancellationToken);

return await _fieldTypeQuerier.ReadAsync(fieldType, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using FluentValidation;
using Logitar.Cms.Contracts.FieldTypes;
using Logitar.Identity.Contracts.Settings;
using Logitar.Identity.Domain.Shared;

namespace Logitar.Cms.Core.FieldTypes.Validators;

public class UpdateFieldTypeValidator : AbstractValidator<UpdateFieldTypePayload>
{
public UpdateFieldTypeValidator(IUniqueNameSettings uniqueNameSettings, DataType dataType)
{
When(x => !string.IsNullOrWhiteSpace(x.UniqueName), () => RuleFor(x => x.UniqueName!).SetValidator(new UniqueNameValidator(uniqueNameSettings)));
When(x => !string.IsNullOrWhiteSpace(x.DisplayName?.Value), () => RuleFor(x => x.DisplayName!.Value!).SetValidator(new DisplayNameValidator()));
When(x => !string.IsNullOrWhiteSpace(x.Description?.Value), () => RuleFor(x => x.Description!.Value!).SetValidator(new DescriptionValidator()));

switch (dataType)
{
case DataType.Boolean:
When(x => x.BooleanProperties != null, () => RuleFor(x => x.BooleanProperties!).SetValidator(new BooleanPropertiesValidator()));
RuleFor(x => x.DateTimeProperties).Null();
RuleFor(x => x.NumberProperties).Null();
RuleFor(x => x.StringProperties).Null();
RuleFor(x => x.TextProperties).Null();
break;
case DataType.DateTime:
RuleFor(x => x.BooleanProperties).Null();
When(x => x.DateTimeProperties != null, () => RuleFor(x => x.DateTimeProperties!).SetValidator(new DateTimePropertiesValidator()));
RuleFor(x => x.NumberProperties).Null();
RuleFor(x => x.StringProperties).Null();
RuleFor(x => x.TextProperties).Null();
break;
case DataType.Number:
RuleFor(x => x.BooleanProperties).Null();
RuleFor(x => x.DateTimeProperties).Null();
When(x => x.NumberProperties != null, () => RuleFor(x => x.NumberProperties!).SetValidator(new NumberPropertiesValidator()));
RuleFor(x => x.StringProperties).Null();
RuleFor(x => x.TextProperties).Null();
break;
case DataType.String:
RuleFor(x => x.BooleanProperties).Null();
RuleFor(x => x.DateTimeProperties).Null();
RuleFor(x => x.NumberProperties).Null();
When(x => x.StringProperties != null, () => RuleFor(x => x.StringProperties!).SetValidator(new StringPropertiesValidator()));
RuleFor(x => x.TextProperties).Null();
break;
case DataType.Text:
RuleFor(x => x.BooleanProperties).Null();
RuleFor(x => x.DateTimeProperties).Null();
RuleFor(x => x.NumberProperties).Null();
RuleFor(x => x.StringProperties).Null();
When(x => x.TextProperties != null, () => RuleFor(x => x.TextProperties!).SetValidator(new TextPropertiesValidator()));
break;
default:
throw new DataTypeNotSupportedException(dataType);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,11 @@ public async Task<ActionResult<SearchResults<FieldType>>> SearchAsync([FromQuery
SearchResults<FieldType> fieldTypes = await _pipeline.ExecuteAsync(new SearchFieldTypesQuery(parameters.ToPayload()), cancellationToken);
return Ok(fieldTypes);
}

[HttpPatch("{id}")]
public async Task<ActionResult<FieldType>> UpdateAsync(Guid id, [FromBody] UpdateFieldTypePayload payload, CancellationToken cancellationToken)
{
FieldType? fieldType = await _pipeline.ExecuteAsync(new UpdateFieldTypeCommand(id, payload), cancellationToken);
return fieldType == null ? NotFound() : Ok(fieldType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Logitar.Cms.Contracts;
using Logitar.Cms.Contracts.FieldTypes;
using Logitar.Cms.Contracts.FieldTypes.Properties;
using Logitar.Cms.Core.FieldTypes.Properties;
using Logitar.Identity.Domain.Shared;
using Microsoft.Extensions.DependencyInjection;

namespace Logitar.Cms.Core.FieldTypes.Commands;

[Trait(Traits.Category, Categories.Integration)]
public class UpdateFieldTypeCommandHandlerTests : IntegrationTests
{
private readonly IFieldTypeRepository _fieldTypeRepository;

private readonly FieldTypeAggregate _fieldType;

public UpdateFieldTypeCommandHandlerTests() : base()
{
_fieldTypeRepository = ServiceProvider.GetRequiredService<IFieldTypeRepository>();

_fieldType = new(new UniqueNameUnit(FieldTypeAggregate.UniqueNameSettings, "ArticleTitle"), new ReadOnlyStringProperties());
}

public override async Task InitializeAsync()
{
await base.InitializeAsync();

await _fieldTypeRepository.SaveAsync(_fieldType);
}

[Fact(DisplayName = "It should update an existing field type.")]
public async Task It_should_update_an_existing_field_type()
{
UpdateFieldTypePayload payload = new()
{
DisplayName = new Change<string>(" Article Title "),
StringProperties = new StringProperties
{
MinimumLength = 1,
MaximumLength = 100
}
};
UpdateFieldTypeCommand command = new(_fieldType.Id.ToGuid(), payload);
FieldType? fieldType = await Pipeline.ExecuteAsync(command);
Assert.NotNull(fieldType);

Assert.Equal(_fieldType.Id.ToGuid(), fieldType.Id);
Assert.Equal(4, fieldType.Version);
Assert.Equal(Contracts.Actors.Actor.System, fieldType.CreatedBy);
Assert.Equal(_fieldType.CreatedOn.AsUniversalTime(), fieldType.CreatedOn);
Assert.Equal(Actor, fieldType.UpdatedBy);
Assert.True(fieldType.CreatedOn < fieldType.UpdatedOn);

Assert.Equal(_fieldType.UniqueName.Value, fieldType.UniqueName);
Assert.NotNull(payload.DisplayName.Value);
Assert.Equal(payload.DisplayName.Value.Trim(), fieldType.DisplayName);
Assert.Null(fieldType.Description);
Assert.Equal(DataType.String, fieldType.DataType);
Assert.Null(fieldType.BooleanProperties);
Assert.Null(fieldType.DateTimeProperties);
Assert.Null(fieldType.NumberProperties);
Assert.Equal(payload.StringProperties, fieldType.StringProperties);
Assert.Null(fieldType.TextProperties);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public ReplaceFieldTypeCommandHandlerTests()
_handler = new(_fieldTypeQuerier.Object, _fieldTypeRepository.Object, _sender.Object);
}

[Fact(DisplayName = "It should replace a new Boolean field type.")]
public async Task It_should_replace_a_new_Boolean_field_type()
[Fact(DisplayName = "It should replace an existing Boolean field type.")]
public async Task It_should_replace_an_existing_Boolean_field_type()
{
FieldTypeAggregate fieldType = new(new UniqueNameUnit(FieldTypeAggregate.UniqueNameSettings, "Featured"), new ReadOnlyBooleanProperties());
_fieldTypeRepository.Setup(x => x.LoadAsync(fieldType.Id, _cancellationToken)).ReturnsAsync(fieldType);
Expand All @@ -49,8 +49,8 @@ public async Task It_should_replace_a_new_Boolean_field_type()
), _cancellationToken), Times.Once());
}

[Fact(DisplayName = "It should replace a new DateTime field type.")]
public async Task It_should_replace_a_new_DateTime_field_type()
[Fact(DisplayName = "It should replace an existing DateTime field type.")]
public async Task It_should_replace_an_existing_DateTime_field_type()
{
FieldTypeAggregate fieldType = new(new UniqueNameUnit(FieldTypeAggregate.UniqueNameSettings, "Published"), new ReadOnlyDateTimeProperties());
_fieldTypeRepository.Setup(x => x.LoadAsync(fieldType.Id, _cancellationToken)).ReturnsAsync(fieldType);
Expand All @@ -74,8 +74,8 @@ public async Task It_should_replace_a_new_DateTime_field_type()
), _cancellationToken), Times.Once());
}

[Fact(DisplayName = "It should replace a new Number field type.")]
public async Task It_should_replace_a_new_Number_field_type()
[Fact(DisplayName = "It should replace an existing Number field type.")]
public async Task It_should_replace_an_existing_Number_field_type()
{
FieldTypeAggregate fieldType = new(new UniqueNameUnit(FieldTypeAggregate.UniqueNameSettings, "WordCount"), new ReadOnlyNumberProperties());
_fieldTypeRepository.Setup(x => x.LoadAsync(fieldType.Id, _cancellationToken)).ReturnsAsync(fieldType);
Expand All @@ -99,8 +99,8 @@ public async Task It_should_replace_a_new_Number_field_type()
), _cancellationToken), Times.Once());
}

[Fact(DisplayName = "It should replace a new String field type.")]
public async Task It_should_replace_a_new_String_field_type()
[Fact(DisplayName = "It should replace an existing String field type.")]
public async Task It_should_replace_an_existing_String_field_type()
{
FieldTypeAggregate fieldType = new(new UniqueNameUnit(FieldTypeAggregate.UniqueNameSettings, "BlogTitle"), new ReadOnlyStringProperties());
_fieldTypeRepository.Setup(x => x.LoadAsync(fieldType.Id, _cancellationToken)).ReturnsAsync(fieldType);
Expand All @@ -124,8 +124,8 @@ public async Task It_should_replace_a_new_String_field_type()
), _cancellationToken), Times.Once());
}

[Fact(DisplayName = "It should replace a new Text field type.")]
public async Task It_should_replace_a_new_Text_field_type()
[Fact(DisplayName = "It should replace an existing Text field type.")]
public async Task It_should_replace_an_existing_Text_field_type()
{
FieldTypeAggregate fieldType = new(new UniqueNameUnit(FieldTypeAggregate.UniqueNameSettings, "BlogContent"), new ReadOnlyTextProperties());
_fieldTypeRepository.Setup(x => x.LoadAsync(fieldType.Id, _cancellationToken)).ReturnsAsync(fieldType);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using FluentValidation.Results;
using Logitar.Cms.Contracts;
using Logitar.Cms.Contracts.FieldTypes;
using Logitar.Cms.Contracts.FieldTypes.Properties;
using Logitar.Cms.Core.FieldTypes.Properties;
using Logitar.Identity.Domain.Shared;
using MediatR;
using Moq;

namespace Logitar.Cms.Core.FieldTypes.Commands;

[Trait(Traits.Category, Categories.Unit)]
public class UpdateFieldTypeCommandHandlerTests
{
private readonly CancellationToken _cancellationToken = default;

private readonly Mock<IFieldTypeQuerier> _fieldTypeQuerier = new();
private readonly Mock<IFieldTypeRepository> _fieldTypeRepository = new();
private readonly Mock<ISender> _sender = new();

private readonly UpdateFieldTypeCommandHandler _handler;

public UpdateFieldTypeCommandHandlerTests()
{
_handler = new(_fieldTypeQuerier.Object, _fieldTypeRepository.Object, _sender.Object);
}

[Fact(DisplayName = "It should return null when the field type is not found.")]
public async Task It_should_return_null_when_the_field_type_is_not_found()
{
UpdateFieldTypePayload payload = new();
UpdateFieldTypeCommand command = new(Guid.Empty, payload);
Assert.Null(await _handler.Handle(command, _cancellationToken));
}

[Fact(DisplayName = "It should throw ValidationException when the payload is not valid.")]
public async Task It_should_throw_ValidationException_when_the_payload_is_not_valid()
{
FieldTypeAggregate fieldType = new(new UniqueNameUnit(FieldTypeAggregate.UniqueNameSettings, "ArticleTitle"), new ReadOnlyStringProperties());
_fieldTypeRepository.Setup(x => x.LoadAsync(fieldType.Id, _cancellationToken)).ReturnsAsync(fieldType);

UpdateFieldTypePayload payload = new()
{
StringProperties = new StringProperties(),
TextProperties = new TextProperties()
};
UpdateFieldTypeCommand command = new(fieldType.Id.ToGuid(), payload);
var exception = await Assert.ThrowsAsync<FluentValidation.ValidationException>(async () => await _handler.Handle(command, _cancellationToken));
ValidationFailure error = Assert.Single(exception.Errors);
Assert.Equal("NullValidator", error.ErrorCode);
Assert.Equal("TextProperties", error.PropertyName);
}

[Fact(DisplayName = "It should update an existing field type.")]
public async Task It_should_update_an_existing_field_type()
{
FieldTypeAggregate fieldType = new(new UniqueNameUnit(FieldTypeAggregate.UniqueNameSettings, "ArticleTitle"), new ReadOnlyStringProperties());
_fieldTypeRepository.Setup(x => x.LoadAsync(fieldType.Id, _cancellationToken)).ReturnsAsync(fieldType);

UpdateFieldTypePayload payload = new()
{
DisplayName = new Change<string>(" Article Title "),
Description = new Change<string>(" "),
StringProperties = new StringProperties(minimumLength: 1, maximumLength: 100, pattern: null)
};
UpdateFieldTypeCommand command = new(fieldType.Id.ToGuid(), payload);
ActivityHelper.Contextualize(command);
await _handler.Handle(command, _cancellationToken);

_sender.Verify(x => x.Send(It.Is<SaveFieldTypeCommand>(y => y.FieldType.Id == fieldType.Id
&& y.FieldType.UniqueName == fieldType.UniqueName
&& y.FieldType.DisplayName != null && payload.DisplayName.Value != null && y.FieldType.DisplayName.Value == payload.DisplayName.Value.Trim()
&& y.FieldType.Description == null
&& y.FieldType.DataType == DataType.String
&& ((ReadOnlyStringProperties)y.FieldType.Properties).MinimumLength == payload.StringProperties.MinimumLength
&& ((ReadOnlyStringProperties)y.FieldType.Properties).MaximumLength == payload.StringProperties.MaximumLength
&& ((ReadOnlyStringProperties)y.FieldType.Properties).Pattern == payload.StringProperties.Pattern
), _cancellationToken), Times.Once());
}
}

0 comments on commit 2c7e4dd

Please sign in to comment.