mirror of https://github.com/Squidex/squidex.git
30 changed files with 1426 additions and 19 deletions
@ -0,0 +1,32 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using NodaTime; |
|||
using Squidex.Infrastructure; |
|||
using System; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Comments |
|||
{ |
|||
public sealed class Comment |
|||
{ |
|||
public Guid Id { get; } |
|||
|
|||
public Instant Time { get; } |
|||
|
|||
public RefToken User { get; } |
|||
|
|||
public string Text { get; } |
|||
|
|||
public Comment(Guid id, Instant time, RefToken user, string text) |
|||
{ |
|||
Id = id; |
|||
Time = time; |
|||
Text = text; |
|||
User = user; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Comments.Commands |
|||
{ |
|||
public abstract class CommentsCommand : SquidexCommand, IAggregateCommand, IAppCommand |
|||
{ |
|||
public Guid CommentsId { get; set; } |
|||
|
|||
public NamedId<Guid> AppId { get; set; } |
|||
|
|||
Guid IAggregateCommand.AggregateId |
|||
{ |
|||
get { return CommentsId; } |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Comments.Commands |
|||
{ |
|||
public sealed class CreateComment : CommentsCommand |
|||
{ |
|||
public Guid CommentId { get; } = Guid.NewGuid(); |
|||
|
|||
public string Text { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Comments.Commands |
|||
{ |
|||
public sealed class DeleteComment : CommentsCommand |
|||
{ |
|||
public Guid CommentId { get; set; } |
|||
|
|||
public string Text { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Comments.Commands |
|||
{ |
|||
public sealed class UpdateComment : CommentsCommand |
|||
{ |
|||
public Guid CommentId { get; set; } |
|||
|
|||
public string Text { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,127 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Entities.Comments.Commands; |
|||
using Squidex.Domain.Apps.Entities.Comments.Guards; |
|||
using Squidex.Domain.Apps.Entities.Comments.State; |
|||
using Squidex.Domain.Apps.Events.Comments; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.Reflection; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Comments |
|||
{ |
|||
public sealed class CommentsGrain : DomainObjectGrainBase<CommentsState>, ICommentGrain |
|||
{ |
|||
private readonly IStore<Guid> store; |
|||
private readonly List<Envelope<CommentsEvent>> events = new List<Envelope<CommentsEvent>>(); |
|||
private CommentsState snapshot = new CommentsState { Version = EtagVersion.Empty }; |
|||
private IPersistence persistence; |
|||
|
|||
public override CommentsState Snapshot |
|||
{ |
|||
get { return snapshot; } |
|||
} |
|||
|
|||
public CommentsGrain(IStore<Guid> store, ISemanticLog log) |
|||
: base(log) |
|||
{ |
|||
Guard.NotNull(store, nameof(store)); |
|||
|
|||
this.store = store; |
|||
} |
|||
|
|||
protected override void ApplyEvent(Envelope<IEvent> @event) |
|||
{ |
|||
snapshot = new CommentsState { Version = snapshot.Version + 1 }; |
|||
|
|||
events.Add(@event.To<CommentsEvent>()); |
|||
} |
|||
|
|||
protected override void RestorePreviousSnapshot(CommentsState previousSnapshot, long previousVersion) |
|||
{ |
|||
snapshot = previousSnapshot; |
|||
} |
|||
|
|||
protected override Task ReadAsync(Type type, Guid id) |
|||
{ |
|||
persistence = store.WithEventSourcing<Guid>(GetType(), id, ApplyEvent); |
|||
|
|||
return persistence.ReadAsync(); |
|||
} |
|||
|
|||
protected override async Task WriteAsync(Envelope<IEvent>[] events, long previousVersion) |
|||
{ |
|||
if (events.Length > 0) |
|||
{ |
|||
await persistence.WriteEventsAsync(events); |
|||
await persistence.WriteSnapshotAsync(Snapshot); |
|||
} |
|||
} |
|||
|
|||
protected override Task<object> ExecuteAsync(IAggregateCommand command) |
|||
{ |
|||
switch (command) |
|||
{ |
|||
case CreateComment createComment: |
|||
return UpsertAsync(createComment, c => |
|||
{ |
|||
GuardComments.CanCreate(c); |
|||
|
|||
Create(c); |
|||
|
|||
return EntityCreatedResult.Create(createComment.CommentId, Version); |
|||
}); |
|||
|
|||
case UpdateComment updateComment: |
|||
return UpsertAsync(updateComment, c => |
|||
{ |
|||
GuardComments.CanUpdate(events, c); |
|||
|
|||
Update(c); |
|||
}); |
|||
|
|||
case DeleteComment deleteComment: |
|||
return UpsertAsync(deleteComment, c => |
|||
{ |
|||
GuardComments.CanDelete(events, c); |
|||
|
|||
Delete(c); |
|||
}); |
|||
|
|||
default: |
|||
throw new NotSupportedException(); |
|||
} |
|||
} |
|||
|
|||
public void Create(CreateComment command) |
|||
{ |
|||
RaiseEvent(SimpleMapper.Map(command, new CommentCreated())); |
|||
} |
|||
|
|||
public void Update(UpdateComment command) |
|||
{ |
|||
RaiseEvent(SimpleMapper.Map(command, new CommentUpdated())); |
|||
} |
|||
|
|||
public void Delete(DeleteComment command) |
|||
{ |
|||
RaiseEvent(SimpleMapper.Map(command, new CommentDeleted())); |
|||
} |
|||
|
|||
public Task<CommentsResult> GetCommentsAsync(long version = EtagVersion.Any) |
|||
{ |
|||
return Task.FromResult(CommentsResult.FromEvents(events, Version, (int)version)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,96 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Squidex.Domain.Apps.Core.Comments; |
|||
using Squidex.Domain.Apps.Events.Comments; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Comments |
|||
{ |
|||
public sealed class CommentsResult |
|||
{ |
|||
public List<Comment> CreatedComments { get; set; } = new List<Comment>(); |
|||
|
|||
public List<Comment> UpdatedComments { get; set; } = new List<Comment>(); |
|||
|
|||
public List<Guid> DeletedComments { get; set; } = new List<Guid>(); |
|||
|
|||
public long Version { get; set; } |
|||
|
|||
public static CommentsResult FromEvents(IEnumerable<Envelope<CommentsEvent>> events, long currentVersion, int lastVersion) |
|||
{ |
|||
var result = new CommentsResult { Version = currentVersion }; |
|||
|
|||
foreach (var @event in events.Skip(lastVersion < 0 ? 0 : lastVersion + 1)) |
|||
{ |
|||
switch (@event.Payload) |
|||
{ |
|||
case CommentDeleted deleted: |
|||
{ |
|||
var id = deleted.CommentId; |
|||
|
|||
if (result.CreatedComments.Any(x => x.Id == id)) |
|||
{ |
|||
result.CreatedComments.RemoveAll(x => x.Id == id); |
|||
} |
|||
else if (result.UpdatedComments.Any(x => x.Id == id)) |
|||
{ |
|||
result.UpdatedComments.RemoveAll(x => x.Id == id); |
|||
result.DeletedComments.Add(id); |
|||
} |
|||
else |
|||
{ |
|||
result.DeletedComments.Add(id); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case CommentCreated created: |
|||
{ |
|||
var comment = new Comment( |
|||
created.CommentId, |
|||
@event.Headers.Timestamp(), |
|||
@event.Payload.Actor, |
|||
created.Text); |
|||
|
|||
result.CreatedComments.Add(comment); |
|||
break; |
|||
} |
|||
|
|||
case CommentUpdated updated: |
|||
{ |
|||
var id = updated.CommentId; |
|||
|
|||
var comment = new Comment( |
|||
id, |
|||
@event.Headers.Timestamp(), |
|||
@event.Payload.Actor, |
|||
updated.Text); |
|||
|
|||
if (result.CreatedComments.Any(x => x.Id == id)) |
|||
{ |
|||
result.CreatedComments.RemoveAll(x => x.Id == id); |
|||
result.CreatedComments.Add(comment); |
|||
} |
|||
else |
|||
{ |
|||
result.UpdatedComments.Add(comment); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Squidex.Domain.Apps.Entities.Comments.Commands; |
|||
using Squidex.Domain.Apps.Events.Comments; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Comments.Guards |
|||
{ |
|||
public static class GuardComments |
|||
{ |
|||
public static void CanCreate(CreateComment command) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
Validate.It(() => "Cannot create comment.", e => |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(command.Text)) |
|||
{ |
|||
e("Text is required.", nameof(command.Text)); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public static void CanUpdate(List<Envelope<CommentsEvent>> events, UpdateComment command) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
var comment = FindComment(events, command.CommentId); |
|||
|
|||
if (!comment.Payload.Actor.Equals(command.Actor)) |
|||
{ |
|||
throw new DomainException("Comment is created by another actor."); |
|||
} |
|||
|
|||
Validate.It(() => "Cannot update comment.", e => |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(command.Text)) |
|||
{ |
|||
e("Text is required.", nameof(command.Text)); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public static void CanDelete(List<Envelope<CommentsEvent>> events, DeleteComment command) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
var comment = FindComment(events, command.CommentId); |
|||
|
|||
if (!comment.Payload.Actor.Equals(command.Actor)) |
|||
{ |
|||
throw new DomainException("Comment is created by another actor."); |
|||
} |
|||
} |
|||
|
|||
private static Envelope<CommentCreated> FindComment(List<Envelope<CommentsEvent>> events, Guid commentId) |
|||
{ |
|||
Envelope<CommentCreated> result = null; |
|||
|
|||
foreach (var @event in events) |
|||
{ |
|||
if (@event.Payload is CommentCreated created && created.CommentId == commentId) |
|||
{ |
|||
result = @event.To<CommentCreated>(); |
|||
} |
|||
else if (@event.Payload is CommentDeleted deleted && deleted.CommentId == commentId) |
|||
{ |
|||
result = null; |
|||
} |
|||
} |
|||
|
|||
if (result == null) |
|||
{ |
|||
throw new DomainObjectNotFoundException(commentId.ToString(), "Comments", typeof(CommentsGrain)); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Comments |
|||
{ |
|||
public interface ICommentGrain : IDomainObjectGrain |
|||
{ |
|||
Task<CommentsResult> GetCommentsAsync(long version = EtagVersion.Any); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Comments.State |
|||
{ |
|||
public sealed class CommentsState : DomainObjectState<CommentsState> |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Comments |
|||
{ |
|||
[EventType(nameof(CommentCreated))] |
|||
public sealed class CommentCreated : CommentsEvent |
|||
{ |
|||
public Guid CommentId { get; set; } |
|||
|
|||
public string Text { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Comments |
|||
{ |
|||
[EventType(nameof(CommentCreated))] |
|||
public sealed class CommentDeleted : CommentsEvent |
|||
{ |
|||
public Guid CommentId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Comments |
|||
{ |
|||
[EventType(nameof(CommentUpdated))] |
|||
public sealed class CommentUpdated : CommentsEvent |
|||
{ |
|||
public Guid CommentId { get; set; } |
|||
|
|||
public string Text { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Comments |
|||
{ |
|||
public abstract class CommentsEvent : AppEvent |
|||
{ |
|||
public Guid CommentsId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Orleans; |
|||
using Squidex.Areas.Api.Controllers.Comments.Models; |
|||
using Squidex.Domain.Apps.Entities.Comments; |
|||
using Squidex.Domain.Apps.Entities.Comments.Commands; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Pipeline; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Comments |
|||
{ |
|||
/// <summary>
|
|||
/// Manages comments for any kind of resource.
|
|||
/// </summary>
|
|||
[ApiExceptionFilter] |
|||
[ApiExplorerSettings(GroupName = nameof(Languages))] |
|||
public sealed class CommentsController : ApiController |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public CommentsController(ICommandBus commandBus, IGrainFactory grainFactory) |
|||
: base(commandBus) |
|||
{ |
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get all comments.
|
|||
/// </summary>
|
|||
/// <param name="commentsId">The id of the comments.</param>
|
|||
/// <returns>
|
|||
/// 200 => All comments returned.
|
|||
/// </returns>
|
|||
[HttpGet] |
|||
[Route("comments/{commentsId}")] |
|||
[ProducesResponseType(typeof(CommentsDto), 200)] |
|||
[ApiCosts(0)] |
|||
public async Task<IActionResult> GetComments(Guid commentsId) |
|||
{ |
|||
if (!int.TryParse(Request.Headers["X-Since"], out var version)) |
|||
{ |
|||
version = -1; |
|||
} |
|||
|
|||
var result = await grainFactory.GetGrain<ICommentGrain>(commentsId).GetCommentsAsync(version); |
|||
var response = CommentsDto.FromResult(result); |
|||
|
|||
Response.Headers["ETag"] = response.Version.ToString(); |
|||
|
|||
return Ok(response); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Create a new comment.
|
|||
/// </summary>
|
|||
/// <param name="commentsId">The id of the comments.</param>
|
|||
/// <param name="request">The comment object that needs to created.</param>
|
|||
/// <returns>
|
|||
/// 201 => Comment created.
|
|||
/// 400 => Comment is not valid.
|
|||
/// </returns>
|
|||
[HttpPost] |
|||
[Route("comments/{commentdsId}")] |
|||
[ProducesResponseType(typeof(EntityCreatedDto), 201)] |
|||
[ProducesResponseType(typeof(ErrorDto), 400)] |
|||
[ApiCosts(0)] |
|||
public async Task<IActionResult> PostComment(Guid commentsId, [FromBody] UpsertCommentDto request) |
|||
{ |
|||
var command = request.ToCreateCommand(commentsId); |
|||
var context = await CommandBus.PublishAsync(command); |
|||
|
|||
var response = CommentDto.FromCommand(command); |
|||
|
|||
return CreatedAtAction(nameof(GetComments), new { commentsId }, response); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Updates the comment.
|
|||
/// </summary>
|
|||
/// <param name="commentsId">The id of the comments.</param>
|
|||
/// <param name="commentId">The id of the comment.</param>
|
|||
/// <param name="request">The comment object that needs to updated.</param>
|
|||
/// <returns>
|
|||
/// 204 => Comment updated.
|
|||
/// 400 => Comment text not valid.
|
|||
/// 404 => Comment not found.
|
|||
/// </returns>
|
|||
[MustBeAppReader] |
|||
[HttpPut] |
|||
[Route("comments/{commentdsId}/{commentId}")] |
|||
[ProducesResponseType(typeof(ErrorDto), 400)] |
|||
[ApiCosts(0)] |
|||
public async Task<IActionResult> PutComment(Guid commentsId, Guid commentId, [FromBody] UpsertCommentDto request) |
|||
{ |
|||
await CommandBus.PublishAsync(request.ToUpdateComment(commentsId, commentId)); |
|||
|
|||
return NoContent(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Deletes the comment.
|
|||
/// </summary>
|
|||
/// <param name="commentsId">The id of the comments.</param>
|
|||
/// <param name="commentId">The id of the comment.</param>
|
|||
/// <returns>
|
|||
/// 204 => Comment deleted.
|
|||
/// 404 => Comment not found.
|
|||
/// </returns>
|
|||
[HttpDelete] |
|||
[Route("comments/{commentdsId}/{commentId}")] |
|||
[ProducesResponseType(typeof(ErrorDto), 400)] |
|||
[ApiCosts(0)] |
|||
public async Task<IActionResult> DeleteComment(Guid commentsId, Guid commentId) |
|||
{ |
|||
await CommandBus.PublishAsync(new DeleteComment { CommentsId = commentsId, CommentId = commentId }); |
|||
|
|||
return NoContent(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using NodaTime; |
|||
using Squidex.Domain.Apps.Core.Comments; |
|||
using Squidex.Domain.Apps.Entities.Comments.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Comments.Models |
|||
{ |
|||
public sealed class CommentDto |
|||
{ |
|||
/// <summary>
|
|||
/// The id of the comment.
|
|||
/// </summary>
|
|||
public Guid Id { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The time when the comment was created or updated last.
|
|||
/// </summary>
|
|||
[Required] |
|||
public Instant Time { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The user who created or updated the comment.
|
|||
/// </summary>
|
|||
[Required] |
|||
public RefToken User { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The text of the comment.
|
|||
/// </summary>
|
|||
[Required] |
|||
public string Text { get; set; } |
|||
|
|||
public static CommentDto FromComment(Comment comment) |
|||
{ |
|||
return SimpleMapper.Map(comment, new CommentDto()); |
|||
} |
|||
|
|||
public static CommentDto FromCommand(CreateComment command) |
|||
{ |
|||
return SimpleMapper.Map(command, new CommentDto { User = command.Actor, Time = SystemClock.Instance.GetCurrentInstant() }); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Squidex.Domain.Apps.Entities.Comments; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Comments.Models |
|||
{ |
|||
public sealed class CommentsDto |
|||
{ |
|||
/// <summary>
|
|||
/// The created comments including the updates.
|
|||
/// </summary>
|
|||
public List<CommentDto> CreatedComments { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The updates comments since the last version.
|
|||
/// </summary>
|
|||
public List<CommentDto> UpdatedComments { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The deleted comments since the last version.
|
|||
/// </summary>
|
|||
public List<Guid> DeletedComments { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The current version.
|
|||
/// </summary>
|
|||
public long Version { get; set; } |
|||
|
|||
public static CommentsDto FromResult(CommentsResult result) |
|||
{ |
|||
return new CommentsDto |
|||
{ |
|||
CreatedComments = result.CreatedComments.Select(CommentDto.FromComment).ToList(), |
|||
UpdatedComments = result.UpdatedComments.Select(CommentDto.FromComment).ToList(), |
|||
DeletedComments = result.DeletedComments, |
|||
Version = result.Version |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Squidex.Domain.Apps.Entities.Comments.Commands; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Comments.Models |
|||
{ |
|||
public sealed class UpsertCommentDto |
|||
{ |
|||
/// <summary>
|
|||
/// The comment text.
|
|||
/// </summary>
|
|||
[Required] |
|||
public string Text { get; set; } |
|||
|
|||
public CreateComment ToCreateCommand(Guid commentsId) |
|||
{ |
|||
return SimpleMapper.Map(this, new CreateComment { CommentsId = commentsId }); |
|||
} |
|||
|
|||
public UpdateComment ToUpdateComment(Guid commentsId, Guid commentId) |
|||
{ |
|||
return SimpleMapper.Map(this, new UpdateComment { CommentsId = commentsId, CommentId = commentId }); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; |
|||
import { inject, TestBed } from '@angular/core/testing'; |
|||
|
|||
import { |
|||
ApiUrlConfig, |
|||
CommentDto, |
|||
CommentsDto, |
|||
CommentsService, |
|||
DateTime, |
|||
UpsertCommentDto, |
|||
Version |
|||
} from './../'; |
|||
|
|||
describe('CommentsService', () => { |
|||
const user = 'me'; |
|||
|
|||
beforeEach(() => { |
|||
TestBed.configureTestingModule({ |
|||
imports: [ |
|||
HttpClientTestingModule |
|||
], |
|||
providers: [ |
|||
CommentsService, |
|||
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') } |
|||
] |
|||
}); |
|||
}); |
|||
|
|||
afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => { |
|||
httpMock.verify(); |
|||
})); |
|||
|
|||
it('should make get request to get comments', |
|||
inject([CommentsService, HttpTestingController], (commentsService: CommentsService, httpMock: HttpTestingController) => { |
|||
|
|||
let comments: CommentsDto; |
|||
|
|||
commentsService.getComments('my-comments', new Version('123')).subscribe(result => { |
|||
comments = result; |
|||
}); |
|||
|
|||
const req = httpMock.expectOne('http://service/p/api/comments/my-comments'); |
|||
|
|||
expect(req.request.method).toEqual('GET'); |
|||
expect(req.request.headers.get('X-Since')).toBe('123'); |
|||
|
|||
req.flush({ |
|||
createdComments: [{ |
|||
id: '123', |
|||
text: 'text1', |
|||
time: '2016-10-12T10:10', |
|||
user: user |
|||
}], |
|||
updatedComments: [{ |
|||
id: '456', |
|||
text: 'text2', |
|||
time: '2017-11-12T12:12', |
|||
user: user |
|||
}], |
|||
deletedComments: ['789'], |
|||
version: '9' |
|||
}); |
|||
|
|||
expect(comments!).toEqual( |
|||
new CommentsDto( |
|||
[ |
|||
new CommentDto('123', DateTime.parseISO_UTC('2016-12-12T10:10'), 'text1', user) |
|||
], [ |
|||
new CommentDto('456', DateTime.parseISO_UTC('2017-11-12T12:12'), 'text2', user) |
|||
], [ |
|||
'789' |
|||
], |
|||
new Version('9')) |
|||
); |
|||
})); |
|||
|
|||
it('should make post request to create comment', |
|||
inject([CommentsService, HttpTestingController], (commentsService: CommentsService, httpMock: HttpTestingController) => { |
|||
|
|||
let comment: CommentDto; |
|||
|
|||
commentsService.postComment('my-comments', new UpsertCommentDto('text1')).subscribe(result => { |
|||
comment = <CommentDto>result; |
|||
}); |
|||
|
|||
const req = httpMock.expectOne('http://service/p/api/comments/my-comments'); |
|||
|
|||
expect(req.request.method).toEqual('POST'); |
|||
expect(req.request.headers.get('If-Match')).toBeNull(); |
|||
|
|||
req.flush({ |
|||
id: '456', |
|||
text: 'text2', |
|||
time: '2017-11-12T12:12', |
|||
user: user |
|||
}); |
|||
|
|||
expect(comment!).toEqual(new CommentDto('123', DateTime.parseISO_UTC('2016-12-12T10:10'), 'text1', user)); |
|||
})); |
|||
|
|||
it('should make put request to replace comment content', |
|||
inject([CommentsService, HttpTestingController], (commentsService: CommentsService, httpMock: HttpTestingController) => { |
|||
|
|||
commentsService.putComment('my-comments', '123', new UpsertCommentDto('text1')).subscribe(); |
|||
|
|||
const req = httpMock.expectOne('http://service/p/api/comments/my-comments/123'); |
|||
|
|||
expect(req.request.method).toEqual('PUT'); |
|||
expect(req.request.headers.get('If-Match')).toBeNull(); |
|||
|
|||
req.flush({}); |
|||
})); |
|||
|
|||
it('should make delete request to delete comment', |
|||
inject([CommentsService, HttpTestingController], (commentsService: CommentsService, httpMock: HttpTestingController) => { |
|||
|
|||
commentsService.deleteComment('my-comments', '123').subscribe(); |
|||
|
|||
const req = httpMock.expectOne('http://service/p/api/comments/my-comments/123'); |
|||
|
|||
expect(req.request.method).toEqual('DELETE'); |
|||
expect(req.request.headers.get('If-Match')).toBeNull(); |
|||
|
|||
req.flush({}); |
|||
})); |
|||
}); |
|||
@ -0,0 +1,125 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { HttpClient } from '@angular/common/http'; |
|||
import { Injectable } from '@angular/core'; |
|||
import { Observable } from 'rxjs'; |
|||
import { map } from 'rxjs/operators'; |
|||
|
|||
import { |
|||
ApiUrlConfig, |
|||
DateTime, |
|||
Model, |
|||
pretifyError, |
|||
Version |
|||
} from '@app/framework'; |
|||
|
|||
export class CommentsDto extends Model { |
|||
constructor( |
|||
public readonly createdComments: CommentDto[], |
|||
public readonly updatedComments: CommentDto[], |
|||
public readonly deletedComments: string[], |
|||
public readonly version: Version |
|||
) { |
|||
super(); |
|||
} |
|||
} |
|||
|
|||
export class CommentDto extends Model { |
|||
constructor( |
|||
public readonly id: string, |
|||
public readonly time: DateTime, |
|||
public readonly text: string, |
|||
public readonly user: string |
|||
) { |
|||
super(); |
|||
} |
|||
} |
|||
|
|||
export class UpsertCommentDto { |
|||
constructor( |
|||
public readonly text: string |
|||
) { |
|||
} |
|||
} |
|||
|
|||
@Injectable() |
|||
export class CommentsService { |
|||
constructor( |
|||
private readonly http: HttpClient, |
|||
private readonly apiUrl: ApiUrlConfig |
|||
) { |
|||
} |
|||
|
|||
public getComments(commentsId: string, version: Version): Observable<CommentsDto> { |
|||
const url = this.apiUrl.buildUrl(`api/comments/${commentsId}`); |
|||
|
|||
return this.http.get(url, { headers: { 'Since': version.value } }).pipe( |
|||
map(response => { |
|||
const body: any = response; |
|||
|
|||
return new CommentsDto( |
|||
body.createdComments.map((item: any) => { |
|||
return new CommentDto( |
|||
item.id, |
|||
DateTime.parseISO_UTC(item.time), |
|||
item.text, |
|||
item.user); |
|||
}), |
|||
body.updatedComments.map((item: any) => { |
|||
return new CommentDto( |
|||
item.id, |
|||
DateTime.parseISO_UTC(item.time), |
|||
item.text, |
|||
item.user); |
|||
}), |
|||
body.deletedComments, |
|||
new Version(body.version) |
|||
); |
|||
}), |
|||
pretifyError('Failed to load comments.')); |
|||
} |
|||
|
|||
public postComment(commentsId: string, dto: UpsertCommentDto): Observable<CommentDto> { |
|||
const url = this.apiUrl.buildUrl(`api/comments/${commentsId}`); |
|||
|
|||
return this.http.post(url, dto).pipe( |
|||
map(response => { |
|||
const body: any = response; |
|||
|
|||
return new CommentDto( |
|||
body.id, |
|||
DateTime.parseISO_UTC(body.time), |
|||
body.text, |
|||
body.user); |
|||
}), |
|||
pretifyError('Failed to create comment.')); |
|||
} |
|||
|
|||
public putComment(commentsId: string, commentId: string, dto: UpsertCommentDto): Observable<any> { |
|||
const url = this.apiUrl.buildUrl(`api/comments/${commentsId}/${commentId}`); |
|||
|
|||
return this.http.put(url, dto).pipe( |
|||
map(response => { |
|||
const body: any = response; |
|||
|
|||
return new CommentDto( |
|||
body.id, |
|||
DateTime.parseISO_UTC(body.time), |
|||
body.text, |
|||
body.user); |
|||
}), |
|||
pretifyError('Failed to update comment.')); |
|||
} |
|||
|
|||
public deleteComment(commentsId: string, commentId: string): Observable<any> { |
|||
const url = this.apiUrl.buildUrl(`api/comments/${commentsId}/${commentId}`); |
|||
|
|||
return this.http.delete(url).pipe( |
|||
pretifyError('Failed to delete comment.')); |
|||
} |
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using FluentAssertions; |
|||
using NodaTime; |
|||
using Squidex.Domain.Apps.Core.Comments; |
|||
using Squidex.Domain.Apps.Entities.Comments.Commands; |
|||
using Squidex.Domain.Apps.Entities.Comments.State; |
|||
using Squidex.Domain.Apps.Entities.TestHelpers; |
|||
using Squidex.Domain.Apps.Events.Comments; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Log; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Comments |
|||
{ |
|||
public class CommentsGrainTests : HandlerTestBase<CommentsGrain, CommentsState> |
|||
{ |
|||
private readonly IAppProvider appProvider = A.Fake<IAppProvider>(); |
|||
private readonly Guid commentsId = Guid.NewGuid(); |
|||
private readonly CommentsGrain sut; |
|||
|
|||
protected override Guid Id |
|||
{ |
|||
get { return commentsId; } |
|||
} |
|||
|
|||
public CommentsGrainTests() |
|||
{ |
|||
sut = new CommentsGrain(Store, A.Dummy<ISemanticLog>()); |
|||
sut.OnActivateAsync(Id).Wait(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Create_should_create_events() |
|||
{ |
|||
var command = new CreateComment { Text = "text1" }; |
|||
|
|||
var result = await sut.ExecuteAsync(CreateCommentsCommand(command)); |
|||
|
|||
result.ShouldBeEquivalent(EntityCreatedResult.Create(command.CommentId, 0)); |
|||
|
|||
sut.GetCommentsAsync(0).Result.Should().BeEquivalentTo(new CommentsResult { Version = 0 }); |
|||
sut.GetCommentsAsync(-1).Result.Should().BeEquivalentTo(new CommentsResult |
|||
{ |
|||
CreatedComments = new List<Comment> |
|||
{ |
|||
new Comment(command.CommentId, LastEvents.ElementAt(0).Headers.Timestamp(), command.Actor, "text1") |
|||
}, |
|||
Version = 0 |
|||
}); |
|||
|
|||
LastEvents |
|||
.ShouldHaveSameEvents( |
|||
CreateCommentsEvent(new CommentCreated { CommentId = command.CommentId, Text = command.Text }) |
|||
); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Update_should_create_events_and_update_state() |
|||
{ |
|||
var createCommand = new CreateComment { Text = "text1" }; |
|||
var updateCommand = new UpdateComment { Text = "text2", CommentId = createCommand.CommentId }; |
|||
|
|||
await sut.ExecuteAsync(CreateCommentsCommand(createCommand)); |
|||
|
|||
var result = await sut.ExecuteAsync(CreateCommentsCommand(updateCommand)); |
|||
|
|||
result.ShouldBeEquivalent(new EntitySavedResult(1)); |
|||
|
|||
sut.GetCommentsAsync(-1).Result.Should().BeEquivalentTo(new CommentsResult |
|||
{ |
|||
CreatedComments = new List<Comment> |
|||
{ |
|||
new Comment(createCommand.CommentId, LastEvents.ElementAt(0).Headers.Timestamp(), createCommand.Actor, "text2") |
|||
}, |
|||
Version = 1 |
|||
}); |
|||
|
|||
sut.GetCommentsAsync(0).Result.Should().BeEquivalentTo(new CommentsResult |
|||
{ |
|||
UpdatedComments = new List<Comment> |
|||
{ |
|||
new Comment(createCommand.CommentId, LastEvents.ElementAt(0).Headers.Timestamp(), createCommand.Actor, "text2") |
|||
}, |
|||
Version = 1 |
|||
}); |
|||
|
|||
LastEvents |
|||
.ShouldHaveSameEvents( |
|||
CreateCommentsEvent(new CommentUpdated { CommentId = createCommand.CommentId, Text = updateCommand.Text }) |
|||
); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Delete_should_create_events_and_update_state() |
|||
{ |
|||
var createCommand = new CreateComment { Text = "text1" }; |
|||
var updateCommand = new UpdateComment { Text = "text2", CommentId = createCommand.CommentId }; |
|||
var deleteCommand = new DeleteComment { CommentId = createCommand.CommentId }; |
|||
|
|||
await sut.ExecuteAsync(CreateCommentsCommand(createCommand)); |
|||
await sut.ExecuteAsync(CreateCommentsCommand(updateCommand)); |
|||
|
|||
var result = await sut.ExecuteAsync(CreateCommentsCommand(deleteCommand)); |
|||
|
|||
result.ShouldBeEquivalent(new EntitySavedResult(2)); |
|||
|
|||
sut.GetCommentsAsync(-1).Result.Should().BeEquivalentTo(new CommentsResult { Version = 2 }); |
|||
sut.GetCommentsAsync(0).Result.Should().BeEquivalentTo(new CommentsResult |
|||
{ |
|||
DeletedComments = new List<Guid> |
|||
{ |
|||
deleteCommand.CommentId |
|||
}, |
|||
Version = 2 |
|||
}); |
|||
sut.GetCommentsAsync(1).Result.Should().BeEquivalentTo(new CommentsResult |
|||
{ |
|||
DeletedComments = new List<Guid> |
|||
{ |
|||
deleteCommand.CommentId |
|||
}, |
|||
Version = 2 |
|||
}); |
|||
|
|||
LastEvents |
|||
.ShouldHaveSameEvents( |
|||
CreateCommentsEvent(new CommentDeleted { CommentId = createCommand.CommentId }) |
|||
); |
|||
} |
|||
|
|||
private Task ExecuteDeleteAsync() |
|||
{ |
|||
return sut.ExecuteAsync(CreateCommentsCommand(new DeleteComment())); |
|||
} |
|||
|
|||
protected T CreateCommentsEvent<T>(T @event) where T : CommentsEvent |
|||
{ |
|||
@event.CommentsId = commentsId; |
|||
|
|||
return CreateEvent(@event); |
|||
} |
|||
|
|||
protected T CreateCommentsCommand<T>(T command) where T : CommentsCommand |
|||
{ |
|||
command.CommentsId = commentsId; |
|||
|
|||
return CreateCommand(command); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,164 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Squidex.Domain.Apps.Entities.Comments.Commands; |
|||
using Squidex.Domain.Apps.Entities.TestHelpers; |
|||
using Squidex.Domain.Apps.Events.Comments; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Comments.Guards |
|||
{ |
|||
public class GuardCommentsTests |
|||
{ |
|||
private readonly RefToken user1 = new RefToken(RefTokenType.Subject, "1"); |
|||
private readonly RefToken user2 = new RefToken(RefTokenType.Subject, "2"); |
|||
|
|||
[Fact] |
|||
public void CanCreate_should_throw_exception_if_text_not_defined() |
|||
{ |
|||
var command = new CreateComment(); |
|||
|
|||
ValidationAssert.Throws(() => GuardComments.CanCreate(command), |
|||
new ValidationError("Text is required.", "Text")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanCreate_should_not_throw_exception_if_text_defined() |
|||
{ |
|||
var command = new CreateComment { Text = "text" }; |
|||
|
|||
GuardComments.CanCreate(command); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanUpdate_should_throw_exception_if_text_not_defined() |
|||
{ |
|||
var commentId = Guid.NewGuid(); |
|||
var command = new UpdateComment { CommentId = commentId, Actor = user1 }; |
|||
|
|||
var events = new List<Envelope<CommentsEvent>> |
|||
{ |
|||
Envelope.Create(new CommentCreated { CommentId = commentId, Actor = user1 }).To<CommentsEvent>() |
|||
}; |
|||
|
|||
ValidationAssert.Throws(() => GuardComments.CanUpdate(events, command), |
|||
new ValidationError("Text is required.", "Text")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanUpdate_should_throw_exception_if_comment_from_another_user() |
|||
{ |
|||
var commentId = Guid.NewGuid(); |
|||
var command = new UpdateComment { CommentId = commentId, Actor = user2, Text = "text2" }; |
|||
|
|||
var events = new List<Envelope<CommentsEvent>> |
|||
{ |
|||
Envelope.Create(new CommentCreated { CommentId = commentId, Actor = user1 }).To<CommentsEvent>() |
|||
}; |
|||
|
|||
Assert.Throws<DomainException>(() => GuardComments.CanUpdate(events, command)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanUpdate_should_throw_exception_if_comment_not_found() |
|||
{ |
|||
var commentId = Guid.NewGuid(); |
|||
var command = new UpdateComment { CommentId = commentId, Actor = user1 }; |
|||
|
|||
var events = new List<Envelope<CommentsEvent>>(); |
|||
|
|||
Assert.Throws<DomainObjectNotFoundException>(() => GuardComments.CanUpdate(events, command)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanUpdate_should_throw_exception_if_comment_deleted_found() |
|||
{ |
|||
var commentId = Guid.NewGuid(); |
|||
var command = new UpdateComment { CommentId = commentId, Actor = user1 }; |
|||
|
|||
var events = new List<Envelope<CommentsEvent>> |
|||
{ |
|||
Envelope.Create(new CommentCreated { CommentId = commentId, Actor = user1 }).To<CommentsEvent>(), |
|||
Envelope.Create(new CommentDeleted { CommentId = commentId }).To<CommentsEvent>(), |
|||
}; |
|||
|
|||
Assert.Throws<DomainObjectNotFoundException>(() => GuardComments.CanUpdate(events, command)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanUpdate_should_not_throw_exception_if_comment_from_same_user() |
|||
{ |
|||
var commentId = Guid.NewGuid(); |
|||
var command = new UpdateComment { CommentId = commentId, Actor = user1, Text = "text2" }; |
|||
|
|||
var events = new List<Envelope<CommentsEvent>> |
|||
{ |
|||
Envelope.Create(new CommentCreated { CommentId = commentId, Actor = user1 }).To<CommentsEvent>() |
|||
}; |
|||
|
|||
GuardComments.CanUpdate(events, command); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanDelete_should_throw_exception_if_comment_from_another_user() |
|||
{ |
|||
var commentId = Guid.NewGuid(); |
|||
var command = new DeleteComment { CommentId = commentId, Actor = user2 }; |
|||
|
|||
var events = new List<Envelope<CommentsEvent>> |
|||
{ |
|||
Envelope.Create(new CommentCreated { CommentId = commentId, Actor = user1 }).To<CommentsEvent>() |
|||
}; |
|||
|
|||
Assert.Throws<DomainException>(() => GuardComments.CanDelete(events, command)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanDelete_should_throw_exception_if_comment_not_found() |
|||
{ |
|||
var commentId = Guid.NewGuid(); |
|||
var command = new DeleteComment { CommentId = commentId, Actor = user1 }; |
|||
|
|||
var events = new List<Envelope<CommentsEvent>>(); |
|||
|
|||
Assert.Throws<DomainObjectNotFoundException>(() => GuardComments.CanDelete(events, command)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanDelete_should_throw_exception_if_comment_deleted() |
|||
{ |
|||
var commentId = Guid.NewGuid(); |
|||
var command = new DeleteComment { CommentId = commentId, Actor = user1 }; |
|||
|
|||
var events = new List<Envelope<CommentsEvent>> |
|||
{ |
|||
Envelope.Create(new CommentCreated { CommentId = commentId, Actor = user1 }).To<CommentsEvent>(), |
|||
Envelope.Create(new CommentDeleted { CommentId = commentId }).To<CommentsEvent>(), |
|||
}; |
|||
|
|||
Assert.Throws<DomainObjectNotFoundException>(() => GuardComments.CanDelete(events, command)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanDelete_should_not_throw_exception_if_comment_from_same_user() |
|||
{ |
|||
var commentId = Guid.NewGuid(); |
|||
var command = new DeleteComment { CommentId = commentId, Actor = user1 }; |
|||
|
|||
var events = new List<Envelope<CommentsEvent>> |
|||
{ |
|||
Envelope.Create(new CommentCreated { CommentId = commentId, Actor = user1 }).To<CommentsEvent>() |
|||
}; |
|||
|
|||
GuardComments.CanDelete(events, command); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue