diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs index eb771ce8e..1d6eeb05c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs @@ -276,7 +276,14 @@ namespace Squidex.Domain.Apps.Entities.Apps if (c.FromCallback) { - ChangePlan(c); + if (string.Equals(appPlansProvider.GetFreePlan()?.Id, c.PlanId)) + { + ResetPlan(c); + } + else + { + ChangePlan(c); + } return null; } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs index 277bc77b8..aa460c882 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs @@ -59,8 +59,14 @@ namespace Squidex.Domain.Apps.Entities.Apps A.CallTo(() => userResolver.FindByIdOrEmailAsync(contributorId)) .Returns(user); - A.CallTo(() => appPlansProvider.GetPlan(A.Ignored)) - .Returns(new ConfigAppLimitsPlan { MaxContributors = 10 }); + A.CallTo(() => appPlansProvider.GetFreePlan()) + .Returns(new ConfigAppLimitsPlan { Id = planIdFree, MaxContributors = 10 }); + + A.CallTo(() => appPlansProvider.GetPlan(planIdFree)) + .Returns(new ConfigAppLimitsPlan { Id = planIdFree, MaxContributors = 10 }); + + A.CallTo(() => appPlansProvider.GetPlan(planIdPaid)) + .Returns(new ConfigAppLimitsPlan { Id = planIdPaid, MaxContributors = 30 }); initialPatterns = new InitialPatterns { @@ -182,6 +188,54 @@ namespace Squidex.Domain.Apps.Entities.Apps ); } + [Fact] + public async Task ChangePlan_from_callback_should_create_events_and_update_state() + { + var command = new ChangePlan { PlanId = planIdPaid, FromCallback = true }; + + await ExecuteCreateAsync(); + + var result = await PublishIdempotentAsync(command); + + result.ShouldBeEquivalent2(new EntitySavedResult(4)); + + Assert.Equal(planIdPaid, sut.Snapshot.Plan!.PlanId); + + LastEvents + .ShouldHaveSameEvents( + CreateEvent(new AppPlanChanged { PlanId = planIdPaid }) + ); + + A.CallTo(() => appPlansBillingManager.ChangePlanAsync(A.Ignored, A>.Ignored, A.Ignored)) + .MustNotHaveHappened(); + } + + [Fact] + public async Task ChangePlan_from_callback_should_reset_plan_for_free_plan() + { + var command = new ChangePlan { PlanId = planIdFree, FromCallback = true }; + + A.CallTo(() => appPlansBillingManager.ChangePlanAsync(Actor.Identifier, AppNamedId, planIdPaid)) + .Returns(new PlanChangedResult()); + + await ExecuteCreateAsync(); + await ExecuteChangePlanAsync(); + + var result = await PublishIdempotentAsync(command); + + result.ShouldBeEquivalent2(new EntitySavedResult(5)); + + Assert.Null(sut.Snapshot.Plan); + + LastEvents + .ShouldHaveSameEvents( + CreateEvent(new AppPlanReset()) + ); + + A.CallTo(() => appPlansBillingManager.ChangePlanAsync(A.Ignored, A>.Ignored, planIdFree)) + .MustNotHaveHappened(); + } + [Fact] public async Task ChangePlan_should_reset_plan_for_reset_plan() {