Browse Source

Bugfixes for plans.

pull/355/head
Sebastian Stehle 7 years ago
parent
commit
62a85aed23
  1. 17
      src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs
  2. 2
      src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs
  3. 24
      src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEmailEventConsumer.cs
  4. 2
      src/Squidex.Domain.Apps.Events/Apps/AppContributorAssigned.cs
  5. 6
      src/Squidex/appsettings.json
  6. 22
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs
  7. 20
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs
  8. 120
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InvitationEmailEventConsumerTests.cs

17
src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs

@ -67,12 +67,12 @@ namespace Squidex.Domain.Apps.Entities.Apps
Create(c);
});
case AssignContributor assigneContributor:
return UpdateReturnAsync(assigneContributor, async c =>
case AssignContributor assignContributor:
return UpdateReturnAsync(assignContributor, async c =>
{
await GuardAppContributors.CanAssign(Snapshot.Contributors, c, userResolver, appPlansProvider.GetPlan(Snapshot.Plan?.PlanId), Snapshot.Roles);
await GuardAppContributors.CanAssign(Snapshot.Contributors, Snapshot.Roles, c, userResolver, GetPlan());
AssignContributor(c);
AssignContributor(c, !Snapshot.Contributors.ContainsKey(assignContributor.ContributorId));
return EntityCreatedResult.Create(c.ContributorId, Version);
});
@ -218,6 +218,11 @@ namespace Squidex.Domain.Apps.Entities.Apps
}
}
private IAppLimitsPlan GetPlan()
{
return appPlansProvider.GetPlan(Snapshot.Plan?.PlanId);
}
public void Create(CreateApp command)
{
var appId = NamedId.Of(command.AppId, command.Name);
@ -261,9 +266,9 @@ namespace Squidex.Domain.Apps.Entities.Apps
RaiseEvent(SimpleMapper.Map(command, new AppLanguageUpdated()));
}
public void AssignContributor(AssignContributor command)
public void AssignContributor(AssignContributor command, bool isNew)
{
RaiseEvent(SimpleMapper.Map(command, new AppContributorAssigned()));
RaiseEvent(SimpleMapper.Map(command, new AppContributorAssigned { IsNew = isNew }));
}
public void RemoveContributor(RemoveContributor command)

2
src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs

@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
public static class GuardAppContributors
{
public static Task CanAssign(AppContributors contributors, AssignContributor command, IUserResolver users, IAppLimitsPlan plan, Roles roles)
public static Task CanAssign(AppContributors contributors, Roles roles, AssignContributor command, IUserResolver users, IAppLimitsPlan plan)
{
Guard.NotNull(command, nameof(command));

24
src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEmailEventConsumer.cs

@ -5,7 +5,9 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using NodaTime;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
@ -17,6 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
{
public sealed class InvitationEmailEventConsumer : IEventConsumer
{
private static readonly Duration MaxAge = Duration.FromDays(2);
private readonly IInvitationEmailSender emailSender;
private readonly IUserResolver userResolver;
private readonly ISemanticLog log;
@ -60,8 +63,27 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
return;
}
if (@event.Payload is AppContributorAssigned appContributorAssigned && appContributorAssigned.Actor.IsSubject)
if (@event.Headers.EventStreamNumber() <= 1)
{
return;
}
var now = SystemClock.Instance.GetCurrentInstant();
var timestamp = @event.Headers.Timestamp();
if (now - timestamp > MaxAge)
{
return;
}
if (@event.Payload is AppContributorAssigned appContributorAssigned)
{
if (!appContributorAssigned.Actor.IsSubject || !appContributorAssigned.IsNew)
{
return;
}
var assignerId = appContributorAssigned.Actor.Identifier;
var assigneeId = appContributorAssigned.ContributorId;

2
src/Squidex.Domain.Apps.Events/Apps/AppContributorAssigned.cs

@ -17,5 +17,7 @@ namespace Squidex.Domain.Apps.Events.Apps
public string Role { get; set; }
public bool IsCreated { get; set; }
public bool IsNew { get; set; }
}
}

6
src/Squidex/appsettings.json

@ -104,15 +104,15 @@
/*
* The email body when a new user is added as contributor.
*/
"newUserBody": "Welcome to Squidex\r\nDear User,\r\n\r\n{{var:assigner_name}} ($ASSIGNER_EMAIL) has invited you to join Project (also called an App) {{var:app_name}} at Squidex Headless CMS. Login with your Github, Google or Microsoft credentials to create a new user account and start editing content now.\r\n\r\nThank you very much,\r\nThe Squidex Team\r\n\r\n<<Start now!>> [https://cloud.squidex.io]",
"newUserBody": "Welcome to Squidex\r\nDear User,\r\n\r\n{{$ASSIGNER_NAME}} ($ASSIGNER_EMAIL) has invited you to join Project (also called an App) {{var:app_name}} at Squidex Headless CMS. Login with your Github, Google or Microsoft credentials to create a new user account and start editing content now.\r\n\r\nThank you very much,\r\nThe Squidex Team\r\n\r\n<<Start now!>> [$UI_URL]",
/*
* The email subject when an existing user is added as contributor.
*/
"existingUserSubject": "[Squidex CMS] You have been invited to App $APP_NAME",
"existingUserSubject": "[Squidex CMS] You have been invited to join App $APP_NAME",
/*
* The email body when an existing user is added as contributor.
*/
"existingUserBody": "You have been invited to join an App at Squidex CMS\n\nWelcome to Squidex\n$ASSIGNER_NAME ($ASSIGNER_EMAIL) has invited you to join app $APP_NAME at Squidex Headless CMS.\nLogin or reload the Management UI to see the app.\n\nThank you very much,\nThe Squidex Team\n<<Start now!>> [$UI_URL]"
"existingUserBody": "Dear User,\r\n\r\n{{$ASSIGNER_NAME}} ($ASSIGNER_EMAIL) has invited you to join App {{var:app_name}} at Squidex Headless CMS.\r\n\r\nLogin or reload the Management UI to see the App\r\n\r\nThank you very much,\r\nThe Squidex Team\r\n\r\n<<Start now!>> [$UI_URL]"
}
},

22
tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs

@ -171,7 +171,27 @@ namespace Squidex.Domain.Apps.Entities.Apps
LastEvents
.ShouldHaveSameEvents(
CreateEvent(new AppContributorAssigned { ContributorId = contributorId, Role = Role.Editor })
CreateEvent(new AppContributorAssigned { ContributorId = contributorId, Role = Role.Editor, IsNew = true })
);
}
[Fact]
public async Task AssignContributor_should_create_update_events_and_update_state()
{
var command = new AssignContributor { ContributorId = contributorId, Role = Role.Owner };
await ExecuteCreateAsync();
await ExecuteAssignContributorAsync();
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(EntityCreatedResult.Create(contributorId, 6));
Assert.Equal(Role.Owner, sut.Snapshot.Contributors[contributorId]);
LastEvents
.ShouldHaveSameEvents(
CreateEvent(new AppContributorAssigned { ContributorId = contributorId, Role = Role.Owner })
);
}

20
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs

@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AssignContributor();
return ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_0, command, users, appPlan, roles),
return ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_0, roles, command, users, appPlan),
new ValidationError("Contributor id is required.", "ContributorId"));
}
@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AssignContributor { ContributorId = "1", Role = "Invalid" };
return ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_0, command, users, appPlan, roles),
return ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_0, roles, command, users, appPlan),
new ValidationError("Role is not a valid value.", "Role"));
}
@ -75,7 +75,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var contributors_1 = contributors_0.Assign("1", Role.Owner);
return ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_1, command, users, appPlan, roles),
return ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_1, roles, command, users, appPlan),
new ValidationError("Contributor has already this role.", "Role"));
}
@ -84,7 +84,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AssignContributor { ContributorId = "notfound", Role = Role.Owner };
return Assert.ThrowsAsync<DomainObjectNotFoundException>(() => GuardAppContributors.CanAssign(contributors_0, command, users, appPlan, roles));
return Assert.ThrowsAsync<DomainObjectNotFoundException>(() => GuardAppContributors.CanAssign(contributors_0, roles, command, users, appPlan));
}
[Fact]
@ -92,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AssignContributor { ContributorId = "3", Role = Role.Editor, Actor = new RefToken("user", "3") };
return Assert.ThrowsAsync<DomainForbiddenException>(() => GuardAppContributors.CanAssign(contributors_0, command, users, appPlan, roles));
return Assert.ThrowsAsync<DomainForbiddenException>(() => GuardAppContributors.CanAssign(contributors_0, roles, command, users, appPlan));
}
[Fact]
@ -106,7 +106,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var contributors_1 = contributors_0.Assign("1", Role.Owner);
var contributors_2 = contributors_1.Assign("2", Role.Editor);
return ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_2, command, users, appPlan, roles),
return ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_2, roles, command, users, appPlan),
new ValidationError("You have reached the maximum number of contributors for your plan."));
}
@ -115,7 +115,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AssignContributor { ContributorId = "1@email.com" };
await GuardAppContributors.CanAssign(contributors_0, command, users, appPlan, roles);
await GuardAppContributors.CanAssign(contributors_0, roles, command, users, appPlan);
Assert.Equal("1", command.ContributorId);
}
@ -125,7 +125,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AssignContributor { ContributorId = "1" };
return GuardAppContributors.CanAssign(contributors_0, command, users, appPlan, roles);
return GuardAppContributors.CanAssign(contributors_0, roles, command, users, appPlan);
}
[Fact]
@ -135,7 +135,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var contributors_1 = contributors_0.Assign("1", Role.Editor);
return GuardAppContributors.CanAssign(contributors_1, command, users, appPlan, roles);
return GuardAppContributors.CanAssign(contributors_1, roles, command, users, appPlan);
}
[Fact]
@ -149,7 +149,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var contributors_1 = contributors_0.Assign("1", Role.Editor);
var contributors_2 = contributors_1.Assign("2", Role.Editor);
return GuardAppContributors.CanAssign(contributors_2, command, users, appPlan, roles);
return GuardAppContributors.CanAssign(contributors_2, roles, command, users, appPlan);
}
[Fact]

120
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InvitationEmailEventConsumerTests.cs

@ -8,6 +8,7 @@
using System;
using System.Threading.Tasks;
using FakeItEasy;
using NodaTime;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
@ -34,88 +35,104 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
A.CallTo(() => emailSender.IsActive)
.Returns(true);
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assignerId))
.Returns(assigner);
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assigneeId))
.Returns(assignee);
sut = new InvitationEmailEventConsumer(emailSender, userResolver, log);
}
[Fact]
public async Task Should_ignore_contributors_assigned_by_clients()
public async Task Should_not_send_email_if_contributors_assigned_by_clients()
{
var @event = Envelope.Create(CreateEvent(RefTokenType.Client, true));
var @event = CreateEvent(RefTokenType.Client, true);
await sut.On(@event);
A.CallTo(() => userResolver.FindByIdOrEmailAsync(A<string>.Ignored))
.MustNotHaveHappened();
MustNotResolveUser();
MustNotSendEmail();
}
A.CallTo(() => emailSender.SendNewUserEmailAsync(A<IUser>.Ignored, A<IUser>.Ignored, appName))
.MustNotHaveHappened();
[Fact]
public async Task Should_not_send_email_for_initial_owner()
{
var @event = CreateEvent(RefTokenType.Subject, false, streamNumber: 1);
await sut.On(@event);
MustNotSendEmail();
}
[Fact]
public async Task Should_not_send_email_for_old_events()
{
var @event = CreateEvent(RefTokenType.Subject, true, instant: SystemClock.Instance.GetCurrentInstant().Minus(Duration.FromHours(50)));
await sut.On(@event);
MustNotResolveUser();
MustNotSendEmail();
}
[Fact]
public async Task Should_not_send_email_for_old_contributor()
{
var @event = CreateEvent(RefTokenType.Subject, true, isNewContributor: false);
await sut.On(@event);
MustNotResolveUser();
MustNotSendEmail();
}
[Fact]
public async Task Should_not_send_email_if_sender_not_active()
{
var @event = Envelope.Create(CreateEvent(RefTokenType.Subject, true));
var @event = CreateEvent(RefTokenType.Subject, true);
A.CallTo(() => emailSender.IsActive)
.Returns(false);
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assignerId))
.Returns(Task.FromResult<IUser>(null));
await sut.On(@event);
A.CallTo(() => userResolver.FindByIdOrEmailAsync(A<string>.Ignored))
.MustNotHaveHappened();
A.CallTo(() => emailSender.SendNewUserEmailAsync(A<IUser>.Ignored, A<IUser>.Ignored, appName))
.MustNotHaveHappened();
MustNotResolveUser();
MustNotSendEmail();
}
[Fact]
public async Task Should_not_send_email_if_assigner_not_found()
{
var @event = Envelope.Create(CreateEvent(RefTokenType.Subject, true));
var @event = CreateEvent(RefTokenType.Subject, true);
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assignerId))
.Returns(Task.FromResult<IUser>(null));
await sut.On(@event);
A.CallTo(() => emailSender.SendNewUserEmailAsync(A<IUser>.Ignored, A<IUser>.Ignored, appName))
.MustNotHaveHappened();
MustNotSendEmail();
MustLogWarning();
}
[Fact]
public async Task Should_not_send_email_if_assignee_not_found()
{
var @event = Envelope.Create(CreateEvent(RefTokenType.Subject, true));
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assignerId))
.Returns(assigner);
var @event = CreateEvent(RefTokenType.Subject, true);
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assigneeId))
.Returns(Task.FromResult<IUser>(null));
await sut.On(@event);
A.CallTo(() => emailSender.SendNewUserEmailAsync(A<IUser>.Ignored, A<IUser>.Ignored, appName))
.MustNotHaveHappened();
MustNotSendEmail();
MustLogWarning();
}
[Fact]
public async Task Should_send_email_for_new_user()
{
var @event = Envelope.Create(CreateEvent(RefTokenType.Subject, true));
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assignerId))
.Returns(assigner);
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assigneeId))
.Returns(assignee);
var @event = CreateEvent(RefTokenType.Subject, true);
await sut.On(@event);
@ -126,13 +143,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
[Fact]
public async Task Should_send_email_for_existing_user()
{
var @event = Envelope.Create(CreateEvent(RefTokenType.Subject, false));
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assignerId))
.Returns(assigner);
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assigneeId))
.Returns(assignee);
var @event = CreateEvent(RefTokenType.Subject, false);
await sut.On(@event);
@ -146,15 +157,38 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
.MustHaveHappened();
}
private IEvent CreateEvent(string assignerType, bool isNew)
private void MustNotResolveUser()
{
return new AppContributorAssigned
A.CallTo(() => userResolver.FindByIdOrEmailAsync(A<string>.Ignored))
.MustNotHaveHappened();
}
private void MustNotSendEmail()
{
A.CallTo(() => emailSender.SendNewUserEmailAsync(A<IUser>.Ignored, A<IUser>.Ignored, A<string>.Ignored))
.MustNotHaveHappened();
A.CallTo(() => emailSender.SendExistingUserEmailAsync(A<IUser>.Ignored, A<IUser>.Ignored, A<string>.Ignored))
.MustNotHaveHappened();
}
private Envelope<IEvent> CreateEvent(string assignerType, bool isNewUser, bool isNewContributor = true, Instant? instant = null, int streamNumber = 2)
{
var @event = new AppContributorAssigned
{
Actor = new RefToken(assignerType, assignerId),
AppId = new NamedId<Guid>(Guid.NewGuid(), appName),
ContributorId = assigneeId,
IsCreated = isNew
IsCreated = isNewUser,
IsNew = isNewContributor,
};
var envelope = Envelope.Create(@event);
envelope.SetTimestamp(instant ?? SystemClock.Instance.GetCurrentInstant());
envelope.SetEventStreamNumber(streamNumber);
return envelope;
}
}
}

Loading…
Cancel
Save