diff --git a/src/Squidex.Write/Apps/AppDomainObject.cs b/src/Squidex.Write/Apps/AppDomainObject.cs index bfe30af4a..b991a0c8a 100644 --- a/src/Squidex.Write/Apps/AppDomainObject.cs +++ b/src/Squidex.Write/Apps/AppDomainObject.cs @@ -221,9 +221,10 @@ namespace Squidex.Write.Apps public AppDomainObject ChangePlan(ChangePlan command) { - Guard.NotNull(command, nameof(command)); + Guard.Valid(command, nameof(command), () => "Cannot change plan"); - ThrowIfOtherUser(command);; + ThrowIfNotCreated(); + ThrowIfOtherUser(command); RaiseEvent(SimpleMapper.Map(command, new AppPlanChanged())); @@ -254,7 +255,12 @@ namespace Squidex.Write.Apps { if (!string.IsNullOrWhiteSpace(command.PlanId) && planOwner != null && !planOwner.Equals(command.Actor)) { - throw new DomainException("Plan can only be changed from current user."); + throw new ValidationException("Plan can only be changed from current user."); + } + + if (string.Equals(command.PlanId, planId, StringComparison.OrdinalIgnoreCase)) + { + throw new ValidationException("App has already this plan."); } } diff --git a/src/Squidex.Write/Apps/Commands/ChangePlan.cs b/src/Squidex.Write/Apps/Commands/ChangePlan.cs index ed452eb0d..f9539b402 100644 --- a/src/Squidex.Write/Apps/Commands/ChangePlan.cs +++ b/src/Squidex.Write/Apps/Commands/ChangePlan.cs @@ -6,10 +6,21 @@ // All rights reserved. // ========================================================================== +using System.Collections.Generic; +using Squidex.Infrastructure; + namespace Squidex.Write.Apps.Commands { - public sealed class ChangePlan : AppAggregateCommand + public sealed class ChangePlan : AppAggregateCommand, IValidatable { public string PlanId { get; set; } + + public void Validate(IList errors) + { + if (string.IsNullOrWhiteSpace(PlanId)) + { + errors.Add(new ValidationError("PlanId is not defined", nameof(PlanId))); + } + } } } diff --git a/src/Squidex.Write/Assets/AssetDomainObject.cs b/src/Squidex.Write/Assets/AssetDomainObject.cs index a3a7a375b..3408a8823 100644 --- a/src/Squidex.Write/Assets/AssetDomainObject.cs +++ b/src/Squidex.Write/Assets/AssetDomainObject.cs @@ -31,11 +31,6 @@ namespace Squidex.Write.Assets get { return isDeleted; } } - public string FileName - { - get { return fileName; } - } - public long FileVersion { get { return fileVersion; } diff --git a/src/Squidex/app/features/administration/pages/users/users-page.component.scss b/src/Squidex/app/features/administration/pages/users/users-page.component.scss index cff4ad6ef..fbb752506 100644 --- a/src/Squidex/app/features/administration/pages/users/users-page.component.scss +++ b/src/Squidex/app/features/administration/pages/users/users-page.component.scss @@ -1,7 +1,2 @@ @import '_vars'; -@import '_mixins'; - -.user-email { - font-style: italic; - font-size: .8rem; -} \ No newline at end of file +@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.scss b/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.scss index cff4ad6ef..fbb752506 100644 --- a/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.scss +++ b/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.scss @@ -1,7 +1,2 @@ @import '_vars'; -@import '_mixins'; - -.user-email { - font-style: italic; - font-size: .8rem; -} \ No newline at end of file +@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/theme/_common.scss b/src/Squidex/app/theme/_common.scss index 6735bb90a..774dcea15 100644 --- a/src/Squidex/app/theme/_common.scss +++ b/src/Squidex/app/theme/_common.scss @@ -25,6 +25,12 @@ body { color: $color-theme-blue-dark; } +// Common style for user email. +.user-email { + font-style: italic; + font-size: .8rem; +} + // // Profile picture in circle // diff --git a/tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs b/tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs index ce884bd2f..1b57ee9a7 100644 --- a/tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs +++ b/tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs @@ -99,7 +99,7 @@ namespace Squidex.Write.Apps [Fact] public async Task AssignContributor_throw_exception_if_reached_max_contributor_size() { - appLimitsProvider.Setup(x => x.GetPlan("free")).Returns(new ConfigAppLimitsPlan { MaxContributors = 2 }); + appLimitsProvider.Setup(x => x.GetPlan(null)).Returns(new ConfigAppLimitsPlan { MaxContributors = 2 }); CreateApp() .AssignContributor(CreateCommand(new AssignContributor { ContributorId = "1" })) @@ -133,7 +133,7 @@ namespace Squidex.Write.Apps [Fact] public async Task AssignContributor_should_assign_if_user_found() { - appLimitsProvider.Setup(x => x.GetPlan("free")).Returns(new ConfigAppLimitsPlan { MaxContributors = -1 }); + appLimitsProvider.Setup(x => x.GetPlan(null)).Returns(new ConfigAppLimitsPlan { MaxContributors = -1 }); CreateApp(); @@ -182,6 +182,40 @@ namespace Squidex.Write.Apps context.Result>().IdOrValue.ShouldBeEquivalentTo(new AppClient(clientName, clientSecret)); } + [Fact] + public async Task ChangePlan_should_throw_if_plan_not_found() + { + appLimitsProvider.Setup(x => x.IsConfiguredPlan("my-plan")).Returns(false); + + CreateApp() + .AttachClient(CreateCommand(new AttachClient { Id = clientName }), clientSecret); + + var context = CreateContextForCommand(new ChangePlan { PlanId = "my-plan" }); + + await TestUpdate(app, async _ => + { + await Assert.ThrowsAsync(() => sut.HandleAsync(context)); + }, false); + } + + [Fact] + public async Task ChangePlan_should_update_domain_object() + { + appLimitsProvider.Setup(x => x.IsConfiguredPlan("my-plan")).Returns(true); + + CreateApp() + .AttachClient(CreateCommand(new AttachClient { Id = clientName }), clientSecret); + + var context = CreateContextForCommand(new ChangePlan { PlanId = "my-plan" }); + + await TestUpdate(app, async _ => + { + await sut.HandleAsync(context); + }); + + appPlansBillingManager.Verify(x => x.ChangePlanAsync(User.Identifier, app.Id, app.Name, "my-plan"), Times.Once()); + } + [Fact] public async Task RenameClient_should_update_domain_object() { diff --git a/tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs b/tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs index e09d898b1..7c0b272d1 100644 --- a/tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs +++ b/tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs @@ -28,6 +28,7 @@ namespace Squidex.Write.Apps private readonly string clientSecret = Guid.NewGuid().ToString(); private readonly string clientId = "client"; private readonly string clientNewName = "My Client"; + private readonly string planId = "premium"; public AppDomainObjectTests() { @@ -69,6 +70,62 @@ namespace Squidex.Write.Apps ); } + [Fact] + public void ChangePlan_should_throw_exception_if_not_created() + { + Assert.Throws(() => + { + sut.ChangePlan(CreateCommand(new ChangePlan { PlanId = planId })); + }); + } + + [Fact] + public void ChangePlan_should_throw_exception_if_command_is_not_valid() + { + Assert.Throws(() => + { + sut.ChangePlan(CreateCommand(new ChangePlan())); + }); + } + + [Fact] + public void ChangePlan_should_throw_exception_if_plan_configured_from_other_user() + { + CreateApp(); + + sut.ChangePlan(CreateCommand(new ChangePlan { PlanId = "other-plan", Actor = new RefToken("User", "other") })); + + Assert.Throws(() => + { + sut.ChangePlan(CreateCommand(new ChangePlan { PlanId = planId })); + }); + } + + [Fact] + public void ChangePlan_should_throw_exception_if_same_plan() + { + CreateApp(); + sut.ChangePlan(CreateCommand(new ChangePlan { PlanId = planId })); + + Assert.Throws(() => + { + sut.ChangePlan(CreateCommand(new ChangePlan { PlanId = planId })); + }); + } + + [Fact] + public void ChangePlan_should_create_events() + { + CreateApp(); + + sut.ChangePlan(CreateCommand(new ChangePlan { PlanId = planId })); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateEvent(new AppPlanChanged { PlanId = planId }) + ); + } + [Fact] public void AssignContributor_should_throw_exception_if_not_created() { @@ -102,6 +159,7 @@ namespace Squidex.Write.Apps public void AssignContributor_should_throw_exception_if_user_already_contributor() { CreateApp(); + sut.AssignContributor(CreateCommand(new AssignContributor { ContributorId = contributorId, Permission = PermissionLevel.Editor })); Assert.Throws(() => diff --git a/tests/Squidex.Write.Tests/TestHelpers/HandlerTestBase.cs b/tests/Squidex.Write.Tests/TestHelpers/HandlerTestBase.cs index bc958663a..643ba2d35 100644 --- a/tests/Squidex.Write.Tests/TestHelpers/HandlerTestBase.cs +++ b/tests/Squidex.Write.Tests/TestHelpers/HandlerTestBase.cs @@ -101,24 +101,27 @@ namespace Squidex.Write.TestHelpers if (!handler.IsUpdated && shouldUpdate) { - throw new InvalidOperationException("Create not called"); + throw new InvalidOperationException("Update not called"); } } protected TCommand CreateCommand(TCommand command) where TCommand : SquidexCommand { - command.Actor = User; + if (command.Actor == null) + { + command.Actor = User; + } var appCommand = command as AppCommand; - if (appCommand != null) + if (appCommand != null && appCommand.AppId == null) { appCommand.AppId = AppNamedId; } var schemaCommand = command as SchemaCommand; - if (schemaCommand != null) + if (schemaCommand != null && schemaCommand.SchemaId == null) { schemaCommand.SchemaId = SchemaNamedId; }