From 3a6bce5178414c43078cef664f067df77541b267 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 28 Jul 2022 14:25:38 +0200 Subject: [PATCH] Fixes to app plan management --- .../Apps/DomainObject/AppDomainObject.cs | 105 +++++++++++------- .../Apps/Plans/IAppPlanBillingManager.cs | 8 +- .../Apps/Plans/IChangePlanResult.cs | 13 --- .../Apps/Plans/NoopAppPlanBillingManager.cs | 20 +++- .../Apps/Plans/PlanChangeAsyncResult.cs | 13 --- .../Apps/Plans/PlanChangedResult.cs | 4 +- .../Apps/Plans/RedirectToCheckoutResult.cs | 23 ---- .../Assets/DomainObject/AssetDomainObject.cs | 4 +- .../DomainObject/Guards/TagsExtensions.cs | 16 ++- .../Controllers/Plans/AppPlansController.cs | 4 +- .../Apps/DomainObject/AppDomainObjectTests.cs | 63 +++++++---- .../Plans/NoopAppPlanBillingManagerTests.cs | 22 +++- .../Consume/EventConsumerManagerTests.cs | 15 ++- .../Consume/EventConsumerProcessorTests.cs | 23 ++++ .../TestHelpers/TestState.cs | 20 ++-- 15 files changed, 207 insertions(+), 146 deletions(-) delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/IChangePlanResult.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/PlanChangeAsyncResult.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/RedirectToCheckoutResult.cs diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs index ec6799875..7294e3786 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs @@ -118,7 +118,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject case AssignContributor assignContributor: 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)); @@ -255,46 +255,71 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject return Snapshot; }, 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: 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); }, ct); + case ChangePlan changePlan: + return ChangePlanAsync(changePlan, ct); + default: ThrowHelper.NotSupportedException(); return default!; } } + private async Task 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) { var appId = NamedId.Of(command.AppId, command.Name); @@ -321,14 +346,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject private void ChangePlan(ChangePlan command) { - if (string.Equals(GetFreePlan()?.Id, command.PlanId, StringComparison.Ordinal)) - { - Raise(command, new AppPlanReset()); - } - else - { - Raise(command, new AppPlanChanged()); - } + Raise(command, new AppPlanChanged()); + } + + private void ResetPlan(ChangePlan command) + { + Raise(command, new AppPlanReset()); } private void Update(UpdateApp command) @@ -455,29 +478,29 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject return new AppSettingsUpdated { Settings = serviceProvider.GetRequiredService().Settings }; } - private IAppPlansProvider AppPlansProvider() + private IAppPlansProvider Plans() { return serviceProvider.GetRequiredService(); } - private IAppPlanBillingManager AppPlanBillingManager() + private IAppPlanBillingManager Billing() { return serviceProvider.GetRequiredService(); } - private IUserResolver UserResolver() + private IUserResolver Users() { return serviceProvider.GetRequiredService(); } private IAppLimitsPlan GetFreePlan() { - return AppPlansProvider().GetFreePlan(); + return Plans().GetFreePlan(); } private IAppLimitsPlan GetPlan() { - return AppPlansProvider().GetPlanForApp(Snapshot).Plan; + return Plans().GetPlanForApp(Snapshot).Plan; } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/IAppPlanBillingManager.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/IAppPlanBillingManager.cs index 9f9fb5571..306ec6de8 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/IAppPlanBillingManager.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/IAppPlanBillingManager.cs @@ -13,7 +13,13 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans { bool HasPortal { get; } - Task ChangePlanAsync(string userId, NamedId appId, string? planId, string? referer, + Task MustRedirectToPortalAsync(string userId, NamedId appId, string? planId, string? referer, + CancellationToken ct = default); + + Task SubscribeAsync(string userId, NamedId appId, string? planId, string? referer, + CancellationToken ct = default); + + Task UnsubscribeAsync(string userId, NamedId appId, string? referer, CancellationToken ct = default); Task GetPortalLinkAsync(string userId, diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/IChangePlanResult.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/IChangePlanResult.cs deleted file mode 100644 index 3f0db643c..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/IChangePlanResult.cs +++ /dev/null @@ -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 - { - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/NoopAppPlanBillingManager.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/NoopAppPlanBillingManager.cs index ccd571397..8063166fb 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/NoopAppPlanBillingManager.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/NoopAppPlanBillingManager.cs @@ -16,16 +16,28 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans get => false; } - public Task ChangePlanAsync(string userId, NamedId appId, string? planId, string? referer, + public Task GetPortalLinkAsync(string userId, CancellationToken ct = default) { - return Task.FromResult(new PlanChangedResult()); + return Task.FromResult(string.Empty); } - public Task GetPortalLinkAsync(string userId, + public Task MustRedirectToPortalAsync(string userId, NamedId appId, string? planId, string? referer, CancellationToken ct = default) { - return Task.FromResult(string.Empty); + return Task.FromResult(null); + } + + public Task SubscribeAsync(string userId, NamedId appId, string? planId, string? referer, + CancellationToken ct = default) + { + return Task.CompletedTask; + } + + public Task UnsubscribeAsync(string userId, NamedId appId, string? referer, + CancellationToken ct = default) + { + return Task.CompletedTask; } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/PlanChangeAsyncResult.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/PlanChangeAsyncResult.cs deleted file mode 100644 index 6715dc0d5..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/PlanChangeAsyncResult.cs +++ /dev/null @@ -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 - { - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/PlanChangedResult.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/PlanChangedResult.cs index df5d47d86..bbe78e3d3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/PlanChangedResult.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/PlanChangedResult.cs @@ -5,9 +5,11 @@ // 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 { - public sealed class PlanChangedResult : IChangePlanResult + public sealed record PlanChangedResult(string PlanId, bool Unsubscribed = false, Uri? RedirectUri = null) { } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/RedirectToCheckoutResult.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/RedirectToCheckoutResult.cs deleted file mode 100644 index c6b84bf60..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/RedirectToCheckoutResult.cs +++ /dev/null @@ -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; - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs index ea4022cec..790bb10fc 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs +++ b/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) { - create.Tags = await operation.NormalizeTagsAsync(create.Tags); + create.Tags = await operation.GetTagIdsAsync(create.Tags); } Create(create); @@ -181,7 +181,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject if (annotate.Tags != null) { - annotate.Tags = await operation.NormalizeTagsAsync(annotate.Tags); + annotate.Tags = await operation.GetTagIdsAsync(annotate.Tags); } Annotate(annotate); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/TagsExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/TagsExtensions.cs index 840976eb4..7ddc2abc2 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/TagsExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/TagsExtensions.cs @@ -6,18 +6,26 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Tags; +using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards { public static class TagsExtensions { - public static async Task> NormalizeTagsAsync(this AssetOperation operation, HashSet names) + public static async Task> GetTagIdsAsync(this AssetOperation operation, HashSet? names) { - var tagService = operation.Resolve(); + var result = new HashSet(names?.Count ?? 0); - var normalized = await tagService.GetTagIdsAsync(operation.App.Id, TagGroups.Assets, names); + if (names != null) + { + var tagService = operation.Resolve(); - return new HashSet(normalized.Values); + var normalized = await tagService.GetTagIdsAsync(operation.App.Id, TagGroups.Assets, names); + + result.AddRange(normalized.Values); + } + + return result; } } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs b/backend/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs index c26f3271b..4c53faab4 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs @@ -81,9 +81,9 @@ namespace Squidex.Areas.Api.Controllers.Plans 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 }); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs index c6c72c14b..37bcbbcb0 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs +++ b/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)) .Returns(new ConfigAppLimitsPlan { Id = planIdPaid, MaxContributors = 30 }); + A.CallTo(() => appPlansBillingManager.MustRedirectToPortalAsync(Actor.Identifier, AppNamedId, A._, A._, default)) + .Returns(Task.FromResult(null)); + // Create a non-empty setting, otherwise the event is not raised as it does not change the domain object. initialSettings = new InitialSettings { @@ -217,14 +220,14 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject { var command = new ChangePlan { PlanId = planIdPaid }; - A.CallTo(() => appPlansBillingManager.ChangePlanAsync(Actor.Identifier, AppNamedId, planIdPaid, command.Referer, default)) - .Returns(new PlanChangedResult()); + A.CallTo(() => appPlansBillingManager.MustRedirectToPortalAsync(Actor.Identifier, AppNamedId, planIdPaid, command.Referer, default)) + .Returns(Task.FromResult(null)); await ExecuteCreateAsync(); var result = await PublishIdempotentAsync(command); - Assert.True(result is PlanChangedResult); + result.ShouldBeEquivalent(new PlanChangedResult(planIdPaid)); Assert.Equal(planIdPaid, sut.Snapshot.Plan!.PlanId); @@ -232,6 +235,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject .ShouldHaveSameEvents( 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] @@ -243,7 +252,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject var result = await PublishIdempotentAsync(command); - result.ShouldBeEquivalent(None.Value); + result.ShouldBeEquivalent(new PlanChangedResult(planIdPaid)); Assert.Equal(planIdPaid, sut.Snapshot.Plan!.PlanId); @@ -252,7 +261,10 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject CreateEvent(new AppPlanChanged { PlanId = planIdPaid }) ); - A.CallTo(() => appPlansBillingManager.ChangePlanAsync(A._, A>._, A._, A._, A._)) + A.CallTo(() => appPlansBillingManager.MustRedirectToPortalAsync(A._, A>._, A._, A._, default)) + .MustNotHaveHappened(); + + A.CallTo(() => appPlansBillingManager.SubscribeAsync(A._, A>._, A._, A._, A._)) .MustNotHaveHappened(); } @@ -261,15 +273,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject { 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 ExecuteChangePlanAsync(); var result = await PublishIdempotentAsync(command); - result.ShouldBeEquivalent(None.Value); + result.ShouldBeEquivalent(new PlanChangedResult(planIdFree, true)); Assert.Null(sut.Snapshot.Plan); @@ -278,7 +287,10 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject CreateEvent(new AppPlanReset()) ); - A.CallTo(() => appPlansBillingManager.ChangePlanAsync(A._, A>._, planIdFree, A._, A._)) + A.CallTo(() => appPlansBillingManager.MustRedirectToPortalAsync(A._, A>._, A._, A._, default)) + .MustHaveHappenedOnceExactly(); + + A.CallTo(() => appPlansBillingManager.UnsubscribeAsync(A._, A>._, A._, A._)) .MustNotHaveHappened(); } @@ -287,18 +299,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject { 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 ExecuteChangePlanAsync(); var result = await PublishIdempotentAsync(command); - Assert.True(result is PlanChangedResult); + result.ShouldBeEquivalent(new PlanChangedResult(planIdFree, true)); Assert.Null(sut.Snapshot.Plan); @@ -306,6 +312,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject .ShouldHaveSameEvents( CreateEvent(new AppPlanReset()) ); + + A.CallTo(() => appPlansBillingManager.MustRedirectToPortalAsync(Actor.Identifier, AppNamedId, planIdPaid, command.Referer, default)) + .MustHaveHappenedOnceExactly(); + + A.CallTo(() => appPlansBillingManager.UnsubscribeAsync(A._, A>._, A._, A._)) + .MustHaveHappened(); } [Fact] @@ -313,14 +325,14 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject { var command = new ChangePlan { PlanId = planIdPaid }; - A.CallTo(() => appPlansBillingManager.ChangePlanAsync(Actor.Identifier, AppNamedId, planIdPaid, command.Referer, default)) - .Returns(new RedirectToCheckoutResult(new Uri("http://squidex.io"))); + A.CallTo(() => appPlansBillingManager.MustRedirectToPortalAsync(Actor.Identifier, AppNamedId, planIdPaid, command.Referer, default)) + .Returns(new Uri("http://squidex.io")); await ExecuteCreateAsync(); 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); } @@ -334,9 +346,14 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject 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._, default)) + A.CallTo(() => appPlansBillingManager.SubscribeAsync(Actor.Identifier, AppNamedId, planIdPaid, A._, default)) .MustNotHaveHappened(); } @@ -651,7 +668,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject CreateEvent(new AppDeleted()) ); - A.CallTo(() => appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, AppNamedId, null, A._, default)) + A.CallTo(() => appPlansBillingManager.SubscribeAsync(command.Actor.Identifier, AppNamedId, null, A._, default)) .MustHaveHappened(); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Plans/NoopAppPlanBillingManagerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Plans/NoopAppPlanBillingManagerTests.cs index b9439762f..a8f9cacf9 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Plans/NoopAppPlanBillingManagerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Plans/NoopAppPlanBillingManagerTests.cs @@ -20,15 +20,31 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans } [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] 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); } } } diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerManagerTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerManagerTests.cs index c4a76a177..9544d47e9 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerManagerTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerManagerTests.cs @@ -83,12 +83,13 @@ namespace Squidex.Infrastructure.EventSourcing.Consume [Fact] public async Task Should_publish_event_on_start() { - var testState = new TestState(DomainId.Create(consumerName1), persistenceFactory) + var testState = new TestState(consumerName1, persistenceFactory) { Snapshot = new EventConsumerState { Position = "42" - } + }, + Version = 0 }; var response = await sut.StartAsync(consumerName1, default); @@ -102,12 +103,13 @@ namespace Squidex.Infrastructure.EventSourcing.Consume [Fact] public async Task Should_publish_event_on_stop() { - var testState = new TestState(DomainId.Create(consumerName1), persistenceFactory) + var testState = new TestState(consumerName1, persistenceFactory) { Snapshot = new EventConsumerState { Position = "42" - } + }, + Version = 0 }; var response = await sut.StopAsync(consumerName1, default); @@ -121,12 +123,13 @@ namespace Squidex.Infrastructure.EventSourcing.Consume [Fact] public async Task Should_publish_event_on_reset() { - var testState = new TestState(DomainId.Create(consumerName1), persistenceFactory) + var testState = new TestState(consumerName1, persistenceFactory) { Snapshot = new EventConsumerState { Position = "42" - } + }, + Version = 0 }; var response = await sut.ResetAsync(consumerName1, default); diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerProcessorTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerProcessorTests.cs index 33b2e54ab..9492b4759 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerProcessorTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerProcessorTests.cs @@ -71,6 +71,9 @@ namespace Squidex.Infrastructure.EventSourcing.Consume A.CallTo(() => eventConsumer.Name) .Returns(consumerName); + A.CallTo(() => eventConsumer.CanClear) + .Returns(true); + A.CallTo(() => eventConsumer.Handles(A._)) .Returns(true); @@ -205,6 +208,26 @@ namespace Squidex.Infrastructure.EventSourcing.Consume .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] public async Task Should_invoke_and_update_position_if_event_received() { diff --git a/backend/tests/Squidex.Infrastructure.Tests/TestHelpers/TestState.cs b/backend/tests/Squidex.Infrastructure.Tests/TestHelpers/TestState.cs index ba5124d89..6cc935ac8 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/TestHelpers/TestState.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/TestHelpers/TestState.cs @@ -33,16 +33,6 @@ namespace Squidex.Infrastructure.TestHelpers { } - public void AddEvent(Envelope @event) - { - events.Add(@event); - } - - public void AddEvent(IEvent @event) - { - events.Add(Envelope.Create(@event)); - } - public TestState(DomainId id, IPersistenceFactory? persistenceFactory = null) { Id = id; @@ -115,5 +105,15 @@ namespace Squidex.Infrastructure.TestHelpers Snapshot = new T(); }); } + + public void AddEvent(Envelope @event) + { + events.Add(@event); + } + + public void AddEvent(IEvent @event) + { + events.Add(Envelope.Create(@event)); + } } }