Browse Source

Fixes to app plan management

pull/906/head
Sebastian 4 years ago
parent
commit
3a6bce5178
  1. 105
      backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs
  2. 8
      backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/IAppPlanBillingManager.cs
  3. 13
      backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/IChangePlanResult.cs
  4. 20
      backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/NoopAppPlanBillingManager.cs
  5. 13
      backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/PlanChangeAsyncResult.cs
  6. 4
      backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/PlanChangedResult.cs
  7. 23
      backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/RedirectToCheckoutResult.cs
  8. 4
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs
  9. 16
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/TagsExtensions.cs
  10. 4
      backend/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs
  11. 63
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs
  12. 22
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Plans/NoopAppPlanBillingManagerTests.cs
  13. 15
      backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerManagerTests.cs
  14. 23
      backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerProcessorTests.cs
  15. 20
      backend/tests/Squidex.Infrastructure.Tests/TestHelpers/TestState.cs

105
backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs

@ -118,7 +118,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
case AssignContributor assignContributor: case AssignContributor assignContributor:
return UpdateReturnAsync(assignContributor, async (c, ct) => return UpdateReturnAsync(assignContributor, async (c, ct) =>
{ {
await GuardAppContributors.CanAssign(c, Snapshot, UserResolver(), GetPlan()); await GuardAppContributors.CanAssign(c, Snapshot, Users(), GetPlan());
AssignContributor(c, !Snapshot.Contributors.ContainsKey(assignContributor.ContributorId)); AssignContributor(c, !Snapshot.Contributors.ContainsKey(assignContributor.ContributorId));
@ -255,46 +255,71 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
return Snapshot; return Snapshot;
}, ct); }, ct);
case ChangePlan changePlan:
return UpdateReturnAsync(changePlan, async (c, ct) =>
{
GuardApp.CanChangePlan(c, Snapshot, AppPlansProvider());
if (c.FromCallback)
{
ChangePlan(c);
return null;
}
else
{
var result = await AppPlanBillingManager().ChangePlanAsync(c.Actor.Identifier, Snapshot.NamedId(), c.PlanId, c.Referer, default);
switch (result)
{
case PlanChangedResult:
ChangePlan(c);
break;
}
return result;
}
}, ct);
case DeleteApp delete: case DeleteApp delete:
return UpdateAsync(delete, async (c, ct) => return UpdateAsync(delete, async (c, ct) =>
{ {
await AppPlanBillingManager().ChangePlanAsync(c.Actor.Identifier, Snapshot.NamedId(), null, null, ct); await Billing().SubscribeAsync(c.Actor.Identifier, Snapshot.NamedId(), null, null, ct);
DeleteApp(c); DeleteApp(c);
}, ct); }, ct);
case ChangePlan changePlan:
return ChangePlanAsync(changePlan, ct);
default: default:
ThrowHelper.NotSupportedException(); ThrowHelper.NotSupportedException();
return default!; return default!;
} }
} }
private async Task<CommandResult> ChangePlanAsync(ChangePlan changePlan,
CancellationToken ct)
{
var userId = changePlan.Actor.Identifier;
var result = await UpdateReturnAsync(changePlan, async (c, ct) =>
{
GuardApp.CanChangePlan(c, Snapshot, Plans());
if (string.Equals(GetFreePlan()?.Id, c.PlanId, StringComparison.Ordinal))
{
ResetPlan(c);
return new PlanChangedResult(c.PlanId, true, null);
}
if (!c.FromCallback)
{
var redirectUri = await Billing().MustRedirectToPortalAsync(userId, Snapshot.NamedId(), c.PlanId, c.Referer, default);
if (redirectUri != null)
{
return new PlanChangedResult(c.PlanId, false, redirectUri);
}
}
ChangePlan(c);
return new PlanChangedResult(c.PlanId);
});
if (changePlan.FromCallback)
{
return result;
}
if (result.Payload is PlanChangedResult { Unsubscribed: true, RedirectUri: null })
{
await Billing().UnsubscribeAsync(userId, Snapshot.NamedId(), changePlan.Referer, default);
}
else if (result.Payload is PlanChangedResult { RedirectUri: null })
{
await Billing().SubscribeAsync(userId, Snapshot.NamedId(), changePlan.PlanId, changePlan.Referer);
}
return result;
}
private void Create(CreateApp command) private void Create(CreateApp command)
{ {
var appId = NamedId.Of(command.AppId, command.Name); var appId = NamedId.Of(command.AppId, command.Name);
@ -321,14 +346,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
private void ChangePlan(ChangePlan command) private void ChangePlan(ChangePlan command)
{ {
if (string.Equals(GetFreePlan()?.Id, command.PlanId, StringComparison.Ordinal)) Raise(command, new AppPlanChanged());
{ }
Raise(command, new AppPlanReset());
} private void ResetPlan(ChangePlan command)
else {
{ Raise(command, new AppPlanReset());
Raise(command, new AppPlanChanged());
}
} }
private void Update(UpdateApp command) private void Update(UpdateApp command)
@ -455,29 +478,29 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
return new AppSettingsUpdated { Settings = serviceProvider.GetRequiredService<InitialSettings>().Settings }; return new AppSettingsUpdated { Settings = serviceProvider.GetRequiredService<InitialSettings>().Settings };
} }
private IAppPlansProvider AppPlansProvider() private IAppPlansProvider Plans()
{ {
return serviceProvider.GetRequiredService<IAppPlansProvider>(); return serviceProvider.GetRequiredService<IAppPlansProvider>();
} }
private IAppPlanBillingManager AppPlanBillingManager() private IAppPlanBillingManager Billing()
{ {
return serviceProvider.GetRequiredService<IAppPlanBillingManager>(); return serviceProvider.GetRequiredService<IAppPlanBillingManager>();
} }
private IUserResolver UserResolver() private IUserResolver Users()
{ {
return serviceProvider.GetRequiredService<IUserResolver>(); return serviceProvider.GetRequiredService<IUserResolver>();
} }
private IAppLimitsPlan GetFreePlan() private IAppLimitsPlan GetFreePlan()
{ {
return AppPlansProvider().GetFreePlan(); return Plans().GetFreePlan();
} }
private IAppLimitsPlan GetPlan() private IAppLimitsPlan GetPlan()
{ {
return AppPlansProvider().GetPlanForApp(Snapshot).Plan; return Plans().GetPlanForApp(Snapshot).Plan;
} }
} }
} }

8
backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/IAppPlanBillingManager.cs

@ -13,7 +13,13 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans
{ {
bool HasPortal { get; } bool HasPortal { get; }
Task<IChangePlanResult> ChangePlanAsync(string userId, NamedId<DomainId> appId, string? planId, string? referer, Task<Uri?> MustRedirectToPortalAsync(string userId, NamedId<DomainId> appId, string? planId, string? referer,
CancellationToken ct = default);
Task SubscribeAsync(string userId, NamedId<DomainId> appId, string? planId, string? referer,
CancellationToken ct = default);
Task UnsubscribeAsync(string userId, NamedId<DomainId> appId, string? referer,
CancellationToken ct = default); CancellationToken ct = default);
Task<string> GetPortalLinkAsync(string userId, Task<string> GetPortalLinkAsync(string userId,

13
backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/IChangePlanResult.cs

@ -1,13 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities.Apps.Plans
{
public interface IChangePlanResult
{
}
}

20
backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/NoopAppPlanBillingManager.cs

@ -16,16 +16,28 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans
get => false; get => false;
} }
public Task<IChangePlanResult> ChangePlanAsync(string userId, NamedId<DomainId> appId, string? planId, string? referer, public Task<string> GetPortalLinkAsync(string userId,
CancellationToken ct = default) CancellationToken ct = default)
{ {
return Task.FromResult<IChangePlanResult>(new PlanChangedResult()); return Task.FromResult(string.Empty);
} }
public Task<string> GetPortalLinkAsync(string userId, public Task<Uri?> MustRedirectToPortalAsync(string userId, NamedId<DomainId> appId, string? planId, string? referer,
CancellationToken ct = default) CancellationToken ct = default)
{ {
return Task.FromResult(string.Empty); return Task.FromResult<Uri?>(null);
}
public Task SubscribeAsync(string userId, NamedId<DomainId> appId, string? planId, string? referer,
CancellationToken ct = default)
{
return Task.CompletedTask;
}
public Task UnsubscribeAsync(string userId, NamedId<DomainId> appId, string? referer,
CancellationToken ct = default)
{
return Task.CompletedTask;
} }
} }
} }

13
backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/PlanChangeAsyncResult.cs

@ -1,13 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities.Apps.Plans
{
public sealed class PlanChangeAsyncResult : IChangePlanResult
{
}
}

4
backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/PlanChangedResult.cs

@ -5,9 +5,11 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Entities.Apps.Plans namespace Squidex.Domain.Apps.Entities.Apps.Plans
{ {
public sealed class PlanChangedResult : IChangePlanResult public sealed record PlanChangedResult(string PlanId, bool Unsubscribed = false, Uri? RedirectUri = null)
{ {
} }
} }

23
backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/RedirectToCheckoutResult.cs

@ -1,23 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps.Plans
{
public sealed class RedirectToCheckoutResult : IChangePlanResult
{
public Uri Url { get; }
public RedirectToCheckoutResult(Uri url)
{
Guard.NotNull(url);
Url = url;
}
}
}

4
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs

@ -166,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
if (create.Tags != null) if (create.Tags != null)
{ {
create.Tags = await operation.NormalizeTagsAsync(create.Tags); create.Tags = await operation.GetTagIdsAsync(create.Tags);
} }
Create(create); Create(create);
@ -181,7 +181,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
if (annotate.Tags != null) if (annotate.Tags != null)
{ {
annotate.Tags = await operation.NormalizeTagsAsync(annotate.Tags); annotate.Tags = await operation.GetTagIdsAsync(annotate.Tags);
} }
Annotate(annotate); Annotate(annotate);

16
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/TagsExtensions.cs

@ -6,18 +6,26 @@
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Core.Tags;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards
{ {
public static class TagsExtensions public static class TagsExtensions
{ {
public static async Task<HashSet<string>> NormalizeTagsAsync(this AssetOperation operation, HashSet<string> names) public static async Task<HashSet<string>> GetTagIdsAsync(this AssetOperation operation, HashSet<string>? names)
{ {
var tagService = operation.Resolve<ITagService>(); var result = new HashSet<string>(names?.Count ?? 0);
var normalized = await tagService.GetTagIdsAsync(operation.App.Id, TagGroups.Assets, names); if (names != null)
{
var tagService = operation.Resolve<ITagService>();
return new HashSet<string>(normalized.Values); var normalized = await tagService.GetTagIdsAsync(operation.App.Id, TagGroups.Assets, names);
result.AddRange(normalized.Values);
}
return result;
} }
} }
} }

4
backend/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs

@ -81,9 +81,9 @@ namespace Squidex.Areas.Api.Controllers.Plans
string? redirectUri = null; string? redirectUri = null;
if (context.PlainResult is RedirectToCheckoutResult result) if (context.PlainResult is PlanChangedResult result)
{ {
redirectUri = result.Url.ToString(); redirectUri = result.RedirectUri?.ToString();
} }
return Ok(new PlanChangedDto { RedirectUri = redirectUri }); return Ok(new PlanChangedDto { RedirectUri = redirectUri });

63
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs

@ -59,6 +59,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
A.CallTo(() => appPlansProvider.GetPlan(planIdPaid)) A.CallTo(() => appPlansProvider.GetPlan(planIdPaid))
.Returns(new ConfigAppLimitsPlan { Id = planIdPaid, MaxContributors = 30 }); .Returns(new ConfigAppLimitsPlan { Id = planIdPaid, MaxContributors = 30 });
A.CallTo(() => appPlansBillingManager.MustRedirectToPortalAsync(Actor.Identifier, AppNamedId, A<string>._, A<string>._, default))
.Returns(Task.FromResult<Uri?>(null));
// Create a non-empty setting, otherwise the event is not raised as it does not change the domain object. // Create a non-empty setting, otherwise the event is not raised as it does not change the domain object.
initialSettings = new InitialSettings initialSettings = new InitialSettings
{ {
@ -217,14 +220,14 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
{ {
var command = new ChangePlan { PlanId = planIdPaid }; var command = new ChangePlan { PlanId = planIdPaid };
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(Actor.Identifier, AppNamedId, planIdPaid, command.Referer, default)) A.CallTo(() => appPlansBillingManager.MustRedirectToPortalAsync(Actor.Identifier, AppNamedId, planIdPaid, command.Referer, default))
.Returns(new PlanChangedResult()); .Returns(Task.FromResult<Uri?>(null));
await ExecuteCreateAsync(); await ExecuteCreateAsync();
var result = await PublishIdempotentAsync(command); var result = await PublishIdempotentAsync(command);
Assert.True(result is PlanChangedResult); result.ShouldBeEquivalent(new PlanChangedResult(planIdPaid));
Assert.Equal(planIdPaid, sut.Snapshot.Plan!.PlanId); Assert.Equal(planIdPaid, sut.Snapshot.Plan!.PlanId);
@ -232,6 +235,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
CreateEvent(new AppPlanChanged { PlanId = planIdPaid }) CreateEvent(new AppPlanChanged { PlanId = planIdPaid })
); );
A.CallTo(() => appPlansBillingManager.MustRedirectToPortalAsync(Actor.Identifier, AppNamedId, planIdPaid, command.Referer, default))
.MustHaveHappened();
A.CallTo(() => appPlansBillingManager.SubscribeAsync(Actor.Identifier, AppNamedId, planIdPaid, command.Referer, default))
.MustHaveHappened();
} }
[Fact] [Fact]
@ -243,7 +252,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
var result = await PublishIdempotentAsync(command); var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent(None.Value); result.ShouldBeEquivalent(new PlanChangedResult(planIdPaid));
Assert.Equal(planIdPaid, sut.Snapshot.Plan!.PlanId); Assert.Equal(planIdPaid, sut.Snapshot.Plan!.PlanId);
@ -252,7 +261,10 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
CreateEvent(new AppPlanChanged { PlanId = planIdPaid }) CreateEvent(new AppPlanChanged { PlanId = planIdPaid })
); );
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(A<string>._, A<NamedId<DomainId>>._, A<string?>._, A<string?>._, A<CancellationToken>._)) A.CallTo(() => appPlansBillingManager.MustRedirectToPortalAsync(A<string>._, A<NamedId<DomainId>>._, A<string?>._, A<string?>._, default))
.MustNotHaveHappened();
A.CallTo(() => appPlansBillingManager.SubscribeAsync(A<string>._, A<NamedId<DomainId>>._, A<string?>._, A<string?>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -261,15 +273,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
{ {
var command = new ChangePlan { PlanId = planIdFree, FromCallback = true }; var command = new ChangePlan { PlanId = planIdFree, FromCallback = true };
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(Actor.Identifier, AppNamedId, planIdPaid, command.Referer, default))
.Returns(new PlanChangedResult());
await ExecuteCreateAsync(); await ExecuteCreateAsync();
await ExecuteChangePlanAsync(); await ExecuteChangePlanAsync();
var result = await PublishIdempotentAsync(command); var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent(None.Value); result.ShouldBeEquivalent(new PlanChangedResult(planIdFree, true));
Assert.Null(sut.Snapshot.Plan); Assert.Null(sut.Snapshot.Plan);
@ -278,7 +287,10 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
CreateEvent(new AppPlanReset()) CreateEvent(new AppPlanReset())
); );
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(A<string>._, A<NamedId<DomainId>>._, planIdFree, A<string?>._, A<CancellationToken>._)) A.CallTo(() => appPlansBillingManager.MustRedirectToPortalAsync(A<string>._, A<NamedId<DomainId>>._, A<string?>._, A<string?>._, default))
.MustHaveHappenedOnceExactly();
A.CallTo(() => appPlansBillingManager.UnsubscribeAsync(A<string>._, A<NamedId<DomainId>>._, A<string?>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -287,18 +299,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
{ {
var command = new ChangePlan { PlanId = planIdFree }; var command = new ChangePlan { PlanId = planIdFree };
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(Actor.Identifier, AppNamedId, planIdPaid, command.Referer, default))
.Returns(new PlanChangedResult());
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(Actor.Identifier, AppNamedId, planIdFree, command.Referer, default))
.Returns(new PlanChangedResult());
await ExecuteCreateAsync(); await ExecuteCreateAsync();
await ExecuteChangePlanAsync(); await ExecuteChangePlanAsync();
var result = await PublishIdempotentAsync(command); var result = await PublishIdempotentAsync(command);
Assert.True(result is PlanChangedResult); result.ShouldBeEquivalent(new PlanChangedResult(planIdFree, true));
Assert.Null(sut.Snapshot.Plan); Assert.Null(sut.Snapshot.Plan);
@ -306,6 +312,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
CreateEvent(new AppPlanReset()) CreateEvent(new AppPlanReset())
); );
A.CallTo(() => appPlansBillingManager.MustRedirectToPortalAsync(Actor.Identifier, AppNamedId, planIdPaid, command.Referer, default))
.MustHaveHappenedOnceExactly();
A.CallTo(() => appPlansBillingManager.UnsubscribeAsync(A<string>._, A<NamedId<DomainId>>._, A<string?>._, A<CancellationToken>._))
.MustHaveHappened();
} }
[Fact] [Fact]
@ -313,14 +325,14 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
{ {
var command = new ChangePlan { PlanId = planIdPaid }; var command = new ChangePlan { PlanId = planIdPaid };
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(Actor.Identifier, AppNamedId, planIdPaid, command.Referer, default)) A.CallTo(() => appPlansBillingManager.MustRedirectToPortalAsync(Actor.Identifier, AppNamedId, planIdPaid, command.Referer, default))
.Returns(new RedirectToCheckoutResult(new Uri("http://squidex.io"))); .Returns(new Uri("http://squidex.io"));
await ExecuteCreateAsync(); await ExecuteCreateAsync();
var result = await PublishIdempotentAsync(command); var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent(new RedirectToCheckoutResult(new Uri("http://squidex.io"))); result.ShouldBeEquivalent(new PlanChangedResult(planIdPaid, false, new Uri("http://squidex.io")));
Assert.Null(sut.Snapshot.Plan); Assert.Null(sut.Snapshot.Plan);
} }
@ -334,9 +346,14 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
var result = await PublishIdempotentAsync(command); var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent(None.Value); result.ShouldBeEquivalent(new PlanChangedResult(planIdPaid));
Assert.Equal(planIdPaid, sut.Snapshot.Plan?.PlanId);
A.CallTo(() => appPlansBillingManager.MustRedirectToPortalAsync(Actor.Identifier, AppNamedId, planIdPaid, command.Referer, default))
.MustNotHaveHappened();
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(Actor.Identifier, AppNamedId, planIdPaid, A<string?>._, default)) A.CallTo(() => appPlansBillingManager.SubscribeAsync(Actor.Identifier, AppNamedId, planIdPaid, A<string?>._, default))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -651,7 +668,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
CreateEvent(new AppDeleted()) CreateEvent(new AppDeleted())
); );
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, AppNamedId, null, A<string?>._, default)) A.CallTo(() => appPlansBillingManager.SubscribeAsync(command.Actor.Identifier, AppNamedId, null, A<string?>._, default))
.MustHaveHappened(); .MustHaveHappened();
} }

22
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Plans/NoopAppPlanBillingManagerTests.cs

@ -20,15 +20,31 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans
} }
[Fact] [Fact]
public async Task Should_do_nothing_if_changing_plan() public async Task Should_do_nothing_if_subscribing()
{ {
await sut.ChangePlanAsync(null!, null!, null, null); await sut.SubscribeAsync(null!, null!, null, null);
}
[Fact]
public async Task Should_do_nothing_if_unsubscribing()
{
await sut.SubscribeAsync(null!, null!, null, null);
} }
[Fact] [Fact]
public async Task Should_not_return_portal_link() public async Task Should_not_return_portal_link()
{ {
Assert.Equal(string.Empty, await sut.GetPortalLinkAsync(null!)); var result = await sut.GetPortalLinkAsync(null!);
Assert.Empty(result);
}
[Fact]
public async Task Should_do_nothing_if_checking_for_redirect()
{
var result = await sut.MustRedirectToPortalAsync(null!, null!, null, null);
Assert.Null(result);
} }
} }
} }

15
backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerManagerTests.cs

@ -83,12 +83,13 @@ namespace Squidex.Infrastructure.EventSourcing.Consume
[Fact] [Fact]
public async Task Should_publish_event_on_start() public async Task Should_publish_event_on_start()
{ {
var testState = new TestState<EventConsumerState>(DomainId.Create(consumerName1), persistenceFactory) var testState = new TestState<EventConsumerState>(consumerName1, persistenceFactory)
{ {
Snapshot = new EventConsumerState Snapshot = new EventConsumerState
{ {
Position = "42" Position = "42"
} },
Version = 0
}; };
var response = await sut.StartAsync(consumerName1, default); var response = await sut.StartAsync(consumerName1, default);
@ -102,12 +103,13 @@ namespace Squidex.Infrastructure.EventSourcing.Consume
[Fact] [Fact]
public async Task Should_publish_event_on_stop() public async Task Should_publish_event_on_stop()
{ {
var testState = new TestState<EventConsumerState>(DomainId.Create(consumerName1), persistenceFactory) var testState = new TestState<EventConsumerState>(consumerName1, persistenceFactory)
{ {
Snapshot = new EventConsumerState Snapshot = new EventConsumerState
{ {
Position = "42" Position = "42"
} },
Version = 0
}; };
var response = await sut.StopAsync(consumerName1, default); var response = await sut.StopAsync(consumerName1, default);
@ -121,12 +123,13 @@ namespace Squidex.Infrastructure.EventSourcing.Consume
[Fact] [Fact]
public async Task Should_publish_event_on_reset() public async Task Should_publish_event_on_reset()
{ {
var testState = new TestState<EventConsumerState>(DomainId.Create(consumerName1), persistenceFactory) var testState = new TestState<EventConsumerState>(consumerName1, persistenceFactory)
{ {
Snapshot = new EventConsumerState Snapshot = new EventConsumerState
{ {
Position = "42" Position = "42"
} },
Version = 0
}; };
var response = await sut.ResetAsync(consumerName1, default); var response = await sut.ResetAsync(consumerName1, default);

23
backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerProcessorTests.cs

@ -71,6 +71,9 @@ namespace Squidex.Infrastructure.EventSourcing.Consume
A.CallTo(() => eventConsumer.Name) A.CallTo(() => eventConsumer.Name)
.Returns(consumerName); .Returns(consumerName);
A.CallTo(() => eventConsumer.CanClear)
.Returns(true);
A.CallTo(() => eventConsumer.Handles(A<StoredEvent>._)) A.CallTo(() => eventConsumer.Handles(A<StoredEvent>._))
.Returns(true); .Returns(true);
@ -205,6 +208,26 @@ namespace Squidex.Infrastructure.EventSourcing.Consume
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
} }
[Fact]
public async Task Should_not_reset_consumer_if_not_allowed()
{
A.CallTo(() => eventConsumer.CanClear)
.Returns(false);
await sut.InitializeAsync(default);
await sut.ActivateAsync();
await sut.StopAsync();
await sut.ResetAsync();
await sut.CompleteAsync();
AssertGrainState(isStopped: true, position: initialPosition);
A.CallTo(() => eventConsumer.ClearAsync())
.MustNotHaveHappened();
}
[Fact] [Fact]
public async Task Should_invoke_and_update_position_if_event_received() public async Task Should_invoke_and_update_position_if_event_received()
{ {

20
backend/tests/Squidex.Infrastructure.Tests/TestHelpers/TestState.cs

@ -33,16 +33,6 @@ namespace Squidex.Infrastructure.TestHelpers
{ {
} }
public void AddEvent(Envelope<IEvent> @event)
{
events.Add(@event);
}
public void AddEvent(IEvent @event)
{
events.Add(Envelope.Create(@event));
}
public TestState(DomainId id, IPersistenceFactory<T>? persistenceFactory = null) public TestState(DomainId id, IPersistenceFactory<T>? persistenceFactory = null)
{ {
Id = id; Id = id;
@ -115,5 +105,15 @@ namespace Squidex.Infrastructure.TestHelpers
Snapshot = new T(); Snapshot = new T();
}); });
} }
public void AddEvent(Envelope<IEvent> @event)
{
events.Add(@event);
}
public void AddEvent(IEvent @event)
{
events.Add(Envelope.Create(@event));
}
} }
} }

Loading…
Cancel
Save