Browse Source

More progressive

pull/152/head
Sebastian Stehle 8 years ago
parent
commit
eb82bcc16c
  1. 29
      src/Squidex.Domain.Apps.Core.Model/Apps/AppPlan.cs
  2. 123
      src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs
  3. 59
      src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs
  4. 11
      src/Squidex.Domain.Apps.Write/Apps/Commands/AddLanguage.cs
  5. 17
      src/Squidex.Domain.Apps.Write/Apps/Commands/AssignContributor.cs
  6. 11
      src/Squidex.Domain.Apps.Write/Apps/Commands/AttachClient.cs
  7. 11
      src/Squidex.Domain.Apps.Write/Apps/Commands/ChangePlan.cs
  8. 12
      src/Squidex.Domain.Apps.Write/Apps/Commands/CreateApp.cs
  9. 13
      src/Squidex.Domain.Apps.Write/Apps/Commands/RemoveContributor.cs
  10. 11
      src/Squidex.Domain.Apps.Write/Apps/Commands/RemoveLanguage.cs
  11. 13
      src/Squidex.Domain.Apps.Write/Apps/Commands/RevokeClient.cs
  12. 22
      src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateClient.cs
  13. 10
      src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateLanguage.cs
  14. 65
      src/Squidex.Domain.Apps.Write/Apps/Guards/GuardApp.cs
  15. 82
      src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppContributors.cs
  16. 90
      src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppLanguages.cs
  17. 19
      src/Squidex.Domain.Apps.Write/Assets/AssetCommandMiddleware.cs
  18. 22
      src/Squidex.Domain.Apps.Write/Assets/AssetDomainObject.cs
  19. 13
      src/Squidex.Domain.Apps.Write/Assets/Commands/RenameAsset.cs
  20. 49
      src/Squidex.Domain.Apps.Write/Assets/Guards/GuardAsset.cs
  21. 2
      src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchemaField.cs
  22. 4
      src/Squidex.Domain.Apps.Write/Schemas/SchemaDomainObject.cs
  23. 6
      src/Squidex.Domain.Apps.Write/Webhooks/Guards/GuardWebhook.cs
  24. 243
      tests/Squidex.Domain.Apps.Write.Tests/Apps/AppCommandMiddlewareTests.cs
  25. 288
      tests/Squidex.Domain.Apps.Write.Tests/Apps/AppDomainObjectTests.cs
  26. 51
      tests/Squidex.Domain.Apps.Write.Tests/Apps/AppEventTests.cs
  27. 179
      tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppContributorsTests.cs
  28. 129
      tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppLanguagesTests.cs
  29. 119
      tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppTests.cs
  30. 139
      tests/Squidex.Domain.Apps.Write.Tests/Assets/AssetCommandMiddlewareTests.cs
  31. 213
      tests/Squidex.Domain.Apps.Write.Tests/Assets/AssetDomainObjectTests.cs
  32. 65
      tests/Squidex.Domain.Apps.Write.Tests/Assets/Guards/GuardAssetTests.cs
  33. 249
      tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandMiddlewareTests.cs
  34. 280
      tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentDomainObjectTests.cs
  35. 70
      tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentEventTests.cs
  36. 139
      tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentVersionLoaderTests.cs
  37. 27
      tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/JsonFieldPropertiesTests.cs
  38. 1
      tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/NumberFieldPropertiesTests.cs
  39. 1
      tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/StringFieldPropertiesTests.cs
  40. 20
      tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/GuardSchemaTests.cs
  41. 138
      tests/Squidex.Domain.Apps.Write.Tests/Webhooks/Guards/GuardWebhookTests.cs
  42. 115
      tests/Squidex.Domain.Apps.Write.Tests/Webhooks/WebhookCommandMiddlewareTests.cs
  43. 159
      tests/Squidex.Domain.Apps.Write.Tests/Webhooks/WebhookDomainObjectTests.cs

29
src/Squidex.Domain.Apps.Core.Model/Apps/AppPlan.cs

@ -0,0 +1,29 @@
// ==========================================================================
// AppPlan.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Apps
{
public sealed class AppPlan
{
public RefToken Owner { get; }
public string PlanId { get; }
public AppPlan(RefToken owner, string planId)
{
Guard.NotNull(owner, nameof(owner));
Guard.NotNullOrEmpty(planId, nameof(planId));
Owner = owner;
PlanId = planId;
}
}
}

123
src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs

@ -8,9 +8,9 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Read.Apps.Repositories;
using Squidex.Domain.Apps.Read.Apps.Services; using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Write.Apps.Commands; using Squidex.Domain.Apps.Write.Apps.Commands;
using Squidex.Domain.Apps.Write.Apps.Guards;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
@ -21,141 +21,130 @@ namespace Squidex.Domain.Apps.Write.Apps
public class AppCommandMiddleware : ICommandMiddleware public class AppCommandMiddleware : ICommandMiddleware
{ {
private readonly IAggregateHandler handler; private readonly IAggregateHandler handler;
private readonly IAppRepository appRepository; private readonly IAppProvider appProvider;
private readonly IAppPlansProvider appPlansProvider; private readonly IAppPlansProvider appPlansProvider;
private readonly IAppPlanBillingManager appPlansBillingManager; private readonly IAppPlanBillingManager appPlansBillingManager;
private readonly IUserResolver userResolver; private readonly IUserResolver userResolver;
public AppCommandMiddleware( public AppCommandMiddleware(
IAggregateHandler handler, IAggregateHandler handler,
IAppRepository appRepository, IAppProvider appProvider,
IAppPlansProvider appPlansProvider, IAppPlansProvider appPlansProvider,
IAppPlanBillingManager appPlansBillingManager, IAppPlanBillingManager appPlansBillingManager,
IUserResolver userResolver) IUserResolver userResolver)
{ {
Guard.NotNull(handler, nameof(handler)); Guard.NotNull(handler, nameof(handler));
Guard.NotNull(appRepository, nameof(appRepository)); Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(userResolver, nameof(userResolver)); Guard.NotNull(userResolver, nameof(userResolver));
Guard.NotNull(appPlansProvider, nameof(appPlansProvider)); Guard.NotNull(appPlansProvider, nameof(appPlansProvider));
Guard.NotNull(appPlansBillingManager, nameof(appPlansBillingManager)); Guard.NotNull(appPlansBillingManager, nameof(appPlansBillingManager));
this.handler = handler; this.handler = handler;
this.userResolver = userResolver; this.userResolver = userResolver;
this.appRepository = appRepository; this.appProvider = appProvider;
this.appPlansProvider = appPlansProvider; this.appPlansProvider = appPlansProvider;
this.appPlansBillingManager = appPlansBillingManager; this.appPlansBillingManager = appPlansBillingManager;
} }
protected async Task On(CreateApp command, CommandContext context) protected async Task On(CreateApp command, CommandContext context)
{ {
if (await appRepository.FindAppAsync(command.Name) != null) await handler.CreateAsync<AppDomainObject>(context, async a =>
{ {
var error = await GuardApp.CanCreate(command, appProvider);
new ValidationError($"An app with name '{command.Name}' already exists",
nameof(CreateApp.Name));
throw new ValidationException("Cannot create a new app.", error);
}
await handler.CreateAsync<AppDomainObject>(context, a =>
{
a.Create(command); a.Create(command);
context.Complete(EntityCreatedResult.Create(a.Id, a.Version)); context.Complete(EntityCreatedResult.Create(a.Id, a.Version));
}); });
} }
protected async Task On(AssignContributor command, CommandContext context) protected Task On(AttachClient command, CommandContext context)
{
if (await userResolver.FindByIdAsync(command.ContributorId) == null)
{ {
var error = return handler.UpdateAsync<AppDomainObject>(context, a => a.AttachClient(command));
new ValidationError("Cannot find contributor the contributor.",
nameof(AssignContributor.ContributorId));
throw new ValidationException("Cannot assign contributor to app.", error);
} }
await handler.UpdateAsync<AppDomainObject>(context, a => protected async Task On(AssignContributor command, CommandContext context)
{ {
var oldContributors = a.ContributorCount; await handler.UpdateAsync<AppDomainObject>(context, async a =>
var maxContributors = appPlansProvider.GetPlan(a.PlanId).MaxContributors;
a.AssignContributor(command);
if (maxContributors > 0 && a.ContributorCount > oldContributors && a.ContributorCount > maxContributors)
{ {
var error = new ValidationError("You have reached your max number of contributors."); await GuardAppContributors.CanAssign(a.Contributors, command, userResolver, appPlansProvider.GetPlan(a.Plan?.PlanId));
throw new ValidationException("Cannot assign contributor to app.", error); a.AssignContributor(command);
}
}); });
} }
protected Task On(ChangePlan command, CommandContext context) protected Task On(RemoveContributor command, CommandContext context)
{ {
if (!appPlansProvider.IsConfiguredPlan(command.PlanId)) return handler.UpdateAsync<AppDomainObject>(context, a =>
{ {
var error = GuardAppContributors.CanRemove(a.Contributors, command);
new ValidationError($"The plan '{command.PlanId}' does not exists",
nameof(CreateApp.Name));
throw new ValidationException("Cannot change plan.", error); a.RemoveContributor(command);
});
} }
return handler.UpdateAsync<AppDomainObject>(context, async a => protected Task On(UpdateClient command, CommandContext context)
{
if (command.FromCallback)
{ {
a.ChangePlan(command); return handler.UpdateAsync<AppDomainObject>(context, a => a.UpdateClient(command));
} }
else
{
var result = await appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, a.Id, a.Name, command.PlanId);
if (result is PlanChangedResult) protected Task On(RevokeClient command, CommandContext context)
{ {
a.ChangePlan(command); return handler.UpdateAsync<AppDomainObject>(context, a => a.RevokeClient(command));
} }
context.Complete(result); protected Task On(AddLanguage command, CommandContext context)
} {
return handler.UpdateAsync<AppDomainObject>(context, a =>
{
GuardAppLanguages.CanAdd(a.LanguagesConfig, command);
a.AddLanguage(command);
}); });
} }
protected Task On(AttachClient command, CommandContext context) protected Task On(RemoveLanguage command, CommandContext context)
{ {
return handler.UpdateAsync<AppDomainObject>(context, a => a.AttachClient(command)); return handler.UpdateAsync<AppDomainObject>(context, a =>
}
protected Task On(RemoveContributor command, CommandContext context)
{ {
return handler.UpdateAsync<AppDomainObject>(context, a => a.RemoveContributor(command)); GuardAppLanguages.CanRemove(a.LanguagesConfig, command);
a.RemoveLanguage(command);
});
} }
protected Task On(UpdateClient command, CommandContext context) protected Task On(UpdateLanguage command, CommandContext context)
{ {
return handler.UpdateAsync<AppDomainObject>(context, a => a.UpdateClient(command)); return handler.UpdateAsync<AppDomainObject>(context, a =>
{
GuardAppLanguages.CanUpdate(a.LanguagesConfig, command);
a.UpdateLanguage(command);
});
} }
protected Task On(RevokeClient command, CommandContext context) protected Task On(ChangePlan command, CommandContext context)
{ {
return handler.UpdateAsync<AppDomainObject>(context, a => a.RevokeClient(command)); return handler.UpdateAsync<AppDomainObject>(context, async a =>
} {
GuardApp.CanChangePlan(command, a.Plan, appPlansProvider);
protected Task On(AddLanguage command, CommandContext context) if (command.FromCallback)
{ {
return handler.UpdateAsync<AppDomainObject>(context, a => a.AddLanguage(command)); a.ChangePlan(command);
} }
else
{
var result = await appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, a.Id, a.Name, command.PlanId);
protected Task On(RemoveLanguage command, CommandContext context) if (result is PlanChangedResult)
{ {
return handler.UpdateAsync<AppDomainObject>(context, a => a.RemoveLanguage(command)); a.ChangePlan(command);
} }
protected Task On(UpdateLanguage command, CommandContext context) context.Complete(result);
{ }
return handler.UpdateAsync<AppDomainObject>(context, a => a.UpdateLanguage(command)); });
} }
public async Task HandleAsync(CommandContext context, Func<Task> next) public async Task HandleAsync(CommandContext context, Func<Task> next)

59
src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs

@ -27,23 +27,32 @@ namespace Squidex.Domain.Apps.Write.Apps
private readonly AppContributors contributors = new AppContributors(); private readonly AppContributors contributors = new AppContributors();
private readonly AppClients clients = new AppClients(); private readonly AppClients clients = new AppClients();
private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(DefaultLanguage); private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(DefaultLanguage);
private AppPlan plan;
private string name; private string name;
private string planId;
private RefToken planOwner;
public string Name public string Name
{ {
get { return name; } get { return name; }
} }
public string PlanId public AppPlan Plan
{ {
get { return planId; } get { return plan; }
} }
public int ContributorCount public AppClients Clients
{ {
get { return contributors.Contributors.Count; } get { return clients; }
}
public AppContributors Contributors
{
get { return contributors; }
}
public LanguagesConfig LanguagesConfig
{
get { return languagesConfig; }
} }
public AppDomainObject(Guid id, int version) public AppDomainObject(Guid id, int version)
@ -103,9 +112,7 @@ namespace Squidex.Domain.Apps.Write.Apps
protected void On(AppPlanChanged @event) protected void On(AppPlanChanged @event)
{ {
planId = @event.PlanId; plan = string.IsNullOrWhiteSpace(@event.PlanId) ? null : new AppPlan(@event.Actor, @event.PlanId);
planOwner = string.IsNullOrWhiteSpace(planId) ? null : @event.Actor;
} }
protected override void DispatchEvent(Envelope<IEvent> @event) protected override void DispatchEvent(Envelope<IEvent> @event)
@ -115,8 +122,6 @@ namespace Squidex.Domain.Apps.Write.Apps
public AppDomainObject Create(CreateApp command) public AppDomainObject Create(CreateApp command)
{ {
Guard.Valid(command, nameof(command), () => "Cannot create app");
ThrowIfCreated(); ThrowIfCreated();
var appId = new NamedId<Guid>(command.AppId, command.Name); var appId = new NamedId<Guid>(command.AppId, command.Name);
@ -131,8 +136,6 @@ namespace Squidex.Domain.Apps.Write.Apps
public AppDomainObject UpdateClient(UpdateClient command) public AppDomainObject UpdateClient(UpdateClient command)
{ {
Guard.Valid(command, nameof(command), () => "Cannot update client");
ThrowIfNotCreated(); ThrowIfNotCreated();
if (!string.IsNullOrWhiteSpace(command.Name)) if (!string.IsNullOrWhiteSpace(command.Name))
@ -150,8 +153,6 @@ namespace Squidex.Domain.Apps.Write.Apps
public AppDomainObject AssignContributor(AssignContributor command) public AppDomainObject AssignContributor(AssignContributor command)
{ {
Guard.Valid(command, nameof(command), () => "Cannot assign contributor");
ThrowIfNotCreated(); ThrowIfNotCreated();
RaiseEvent(SimpleMapper.Map(command, new AppContributorAssigned())); RaiseEvent(SimpleMapper.Map(command, new AppContributorAssigned()));
@ -161,8 +162,6 @@ namespace Squidex.Domain.Apps.Write.Apps
public AppDomainObject RemoveContributor(RemoveContributor command) public AppDomainObject RemoveContributor(RemoveContributor command)
{ {
Guard.Valid(command, nameof(command), () => "Cannot remove contributor");
ThrowIfNotCreated(); ThrowIfNotCreated();
RaiseEvent(SimpleMapper.Map(command, new AppContributorRemoved())); RaiseEvent(SimpleMapper.Map(command, new AppContributorRemoved()));
@ -172,8 +171,6 @@ namespace Squidex.Domain.Apps.Write.Apps
public AppDomainObject AttachClient(AttachClient command) public AppDomainObject AttachClient(AttachClient command)
{ {
Guard.Valid(command, nameof(command), () => "Cannot attach client");
ThrowIfNotCreated(); ThrowIfNotCreated();
RaiseEvent(SimpleMapper.Map(command, new AppClientAttached())); RaiseEvent(SimpleMapper.Map(command, new AppClientAttached()));
@ -183,8 +180,6 @@ namespace Squidex.Domain.Apps.Write.Apps
public AppDomainObject RevokeClient(RevokeClient command) public AppDomainObject RevokeClient(RevokeClient command)
{ {
Guard.Valid(command, nameof(command), () => "Cannot revoke client");
ThrowIfNotCreated(); ThrowIfNotCreated();
RaiseEvent(SimpleMapper.Map(command, new AppClientRevoked())); RaiseEvent(SimpleMapper.Map(command, new AppClientRevoked()));
@ -194,8 +189,6 @@ namespace Squidex.Domain.Apps.Write.Apps
public AppDomainObject AddLanguage(AddLanguage command) public AppDomainObject AddLanguage(AddLanguage command)
{ {
Guard.Valid(command, nameof(command), () => "Cannot add language");
ThrowIfNotCreated(); ThrowIfNotCreated();
RaiseEvent(SimpleMapper.Map(command, new AppLanguageAdded())); RaiseEvent(SimpleMapper.Map(command, new AppLanguageAdded()));
@ -205,8 +198,6 @@ namespace Squidex.Domain.Apps.Write.Apps
public AppDomainObject RemoveLanguage(RemoveLanguage command) public AppDomainObject RemoveLanguage(RemoveLanguage command)
{ {
Guard.Valid(command, nameof(command), () => "Cannot remove language");
ThrowIfNotCreated(); ThrowIfNotCreated();
RaiseEvent(SimpleMapper.Map(command, new AppLanguageRemoved())); RaiseEvent(SimpleMapper.Map(command, new AppLanguageRemoved()));
@ -216,8 +207,6 @@ namespace Squidex.Domain.Apps.Write.Apps
public AppDomainObject UpdateLanguage(UpdateLanguage command) public AppDomainObject UpdateLanguage(UpdateLanguage command)
{ {
Guard.Valid(command, nameof(command), () => "Cannot update language");
ThrowIfNotCreated(); ThrowIfNotCreated();
RaiseEvent(SimpleMapper.Map(command, new AppLanguageUpdated())); RaiseEvent(SimpleMapper.Map(command, new AppLanguageUpdated()));
@ -227,10 +216,7 @@ namespace Squidex.Domain.Apps.Write.Apps
public AppDomainObject ChangePlan(ChangePlan command) public AppDomainObject ChangePlan(ChangePlan command)
{ {
Guard.Valid(command, nameof(command), () => "Cannot change plan");
ThrowIfNotCreated(); ThrowIfNotCreated();
ThrowIfOtherUser(command);
RaiseEvent(SimpleMapper.Map(command, new AppPlanChanged())); RaiseEvent(SimpleMapper.Map(command, new AppPlanChanged()));
@ -257,19 +243,6 @@ namespace Squidex.Domain.Apps.Write.Apps
return new AppContributorAssigned { AppId = id, ContributorId = command.Actor.Identifier, Permission = AppContributorPermission.Owner }; return new AppContributorAssigned { AppId = id, ContributorId = command.Actor.Identifier, Permission = AppContributorPermission.Owner };
} }
private void ThrowIfOtherUser(ChangePlan command)
{
if (!string.IsNullOrWhiteSpace(command.PlanId) && planOwner != null && !planOwner.Equals(command.Actor))
{
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.");
}
}
private void ThrowIfNotCreated() private void ThrowIfNotCreated()
{ {
if (string.IsNullOrWhiteSpace(name)) if (string.IsNullOrWhiteSpace(name))

11
src/Squidex.Domain.Apps.Write/Apps/Commands/AddLanguage.cs

@ -6,21 +6,12 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Write.Apps.Commands
{ {
public sealed class AddLanguage : AppAggregateCommand, IValidatable public sealed class AddLanguage : AppAggregateCommand
{ {
public Language Language { get; set; } public Language Language { get; set; }
public void Validate(IList<ValidationError> errors)
{
if (Language == null)
{
errors.Add(new ValidationError("Language cannot be null.", nameof(Language)));
}
}
} }
} }

17
src/Squidex.Domain.Apps.Write/Apps/Commands/AssignContributor.cs

@ -6,29 +6,14 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Write.Apps.Commands
{ {
public sealed class AssignContributor : AppAggregateCommand, IValidatable public sealed class AssignContributor : AppAggregateCommand
{ {
public string ContributorId { get; set; } public string ContributorId { get; set; }
public AppContributorPermission Permission { get; set; } public AppContributorPermission Permission { get; set; }
public void Validate(IList<ValidationError> errors)
{
if (string.IsNullOrWhiteSpace(ContributorId))
{
errors.Add(new ValidationError("Contributor id not assigned.", nameof(ContributorId)));
}
if (!Permission.IsEnumValue())
{
errors.Add(new ValidationError("Permission is not valid.", nameof(Permission)));
}
}
} }
} }

11
src/Squidex.Domain.Apps.Write/Apps/Commands/AttachClient.cs

@ -6,23 +6,14 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Write.Apps.Commands
{ {
public sealed class AttachClient : AppAggregateCommand, IValidatable public sealed class AttachClient : AppAggregateCommand
{ {
public string Id { get; set; } public string Id { get; set; }
public string Secret { get; } = RandomHash.New(); public string Secret { get; } = RandomHash.New();
public void Validate(IList<ValidationError> errors)
{
if (!Id.IsSlug())
{
errors.Add(new ValidationError("Client id must be a valid slug.", nameof(Id)));
}
}
} }
} }

11
src/Squidex.Domain.Apps.Write/Apps/Commands/ChangePlan.cs

@ -6,23 +6,14 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Write.Apps.Commands
{ {
public sealed class ChangePlan : AppAggregateCommand, IValidatable public sealed class ChangePlan : AppAggregateCommand
{ {
public bool FromCallback { get; set; } public bool FromCallback { get; set; }
public string PlanId { get; set; } public string PlanId { get; set; }
public void Validate(IList<ValidationError> errors)
{
if (string.IsNullOrWhiteSpace(PlanId))
{
errors.Add(new ValidationError("PlanId is not defined.", nameof(PlanId)));
}
}
} }
} }

12
src/Squidex.Domain.Apps.Write/Apps/Commands/CreateApp.cs

@ -7,13 +7,11 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Commands;
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Write.Apps.Commands
{ {
public sealed class CreateApp : SquidexCommand, IValidatable, IAggregateCommand public sealed class CreateApp : SquidexCommand, IAggregateCommand
{ {
public string Name { get; set; } public string Name { get; set; }
@ -28,13 +26,5 @@ namespace Squidex.Domain.Apps.Write.Apps.Commands
{ {
AppId = Guid.NewGuid(); AppId = Guid.NewGuid();
} }
public void Validate(IList<ValidationError> errors)
{
if (!Name.IsSlug())
{
errors.Add(new ValidationError("Name must be a valid slug.", nameof(Name)));
}
}
} }
} }

13
src/Squidex.Domain.Apps.Write/Apps/Commands/RemoveContributor.cs

@ -6,21 +6,10 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Write.Apps.Commands
{ {
public sealed class RemoveContributor : AppAggregateCommand, IValidatable public sealed class RemoveContributor : AppAggregateCommand
{ {
public string ContributorId { get; set; } public string ContributorId { get; set; }
public void Validate(IList<ValidationError> errors)
{
if (string.IsNullOrWhiteSpace(ContributorId))
{
errors.Add(new ValidationError("Contributor id not assigned.", nameof(ContributorId)));
}
}
} }
} }

11
src/Squidex.Domain.Apps.Write/Apps/Commands/RemoveLanguage.cs

@ -6,21 +6,12 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Write.Apps.Commands
{ {
public sealed class RemoveLanguage : AppAggregateCommand, IValidatable public sealed class RemoveLanguage : AppAggregateCommand
{ {
public Language Language { get; set; } public Language Language { get; set; }
public void Validate(IList<ValidationError> errors)
{
if (Language == null)
{
errors.Add(new ValidationError("Language cannot be null.", nameof(Language)));
}
}
} }
} }

13
src/Squidex.Domain.Apps.Write/Apps/Commands/RevokeClient.cs

@ -6,21 +6,10 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Write.Apps.Commands
{ {
public sealed class RevokeClient : AppAggregateCommand, IValidatable public sealed class RevokeClient : AppAggregateCommand
{ {
public string Id { get; set; } public string Id { get; set; }
public void Validate(IList<ValidationError> errors)
{
if (!Id.IsSlug())
{
errors.Add(new ValidationError("Client id must be a valid slug.", nameof(Id)));
}
}
} }
} }

22
src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateClient.cs

@ -6,36 +6,16 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Write.Apps.Commands
{ {
public sealed class UpdateClient : AppAggregateCommand, IValidatable public sealed class UpdateClient : AppAggregateCommand
{ {
public string Id { get; set; } public string Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public AppClientPermission? Permission { get; set; } public AppClientPermission? Permission { get; set; }
public void Validate(IList<ValidationError> errors)
{
if (!Id.IsSlug())
{
errors.Add(new ValidationError("Client id must be a valid slug.", nameof(Id)));
}
if (string.IsNullOrWhiteSpace(Name) && Permission == null)
{
errors.Add(new ValidationError("Either name or permission must be defined.", nameof(Name), nameof(Permission)));
}
if (Permission.HasValue && !Permission.Value.IsEnumValue())
{
errors.Add(new ValidationError("Permission is not valid.", nameof(Permission)));
}
}
} }
} }

10
src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateLanguage.cs

@ -11,7 +11,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Write.Apps.Commands
{ {
public sealed class UpdateLanguage : AppAggregateCommand, IValidatable public sealed class UpdateLanguage : AppAggregateCommand
{ {
public Language Language { get; set; } public Language Language { get; set; }
@ -20,13 +20,5 @@ namespace Squidex.Domain.Apps.Write.Apps.Commands
public bool IsMaster { get; set; } public bool IsMaster { get; set; }
public List<Language> Fallback { get; set; } public List<Language> Fallback { get; set; }
public void Validate(IList<ValidationError> errors)
{
if (Language == null)
{
errors.Add(new ValidationError("Language cannot be null.", nameof(Language)));
}
}
} }
} }

65
src/Squidex.Domain.Apps.Write/Apps/Guards/GuardApp.cs

@ -0,0 +1,65 @@
// ==========================================================================
// GuardApp.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Write.Apps.Commands;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Guards
{
public static class GuardApp
{
public static Task CanCreate(CreateApp command, IAppProvider apps)
{
Guard.NotNull(command, nameof(command));
return Validate.It(() => "Cannot create app.", async error =>
{
if (await apps.FindAppByNameAsync(command.Name) != null)
{
error(new ValidationError($"An app with name '{command.Name}' already exists", nameof(command.Name)));
}
if (!command.Name.IsSlug())
{
error(new ValidationError("Name must be a valid slug.", nameof(command.Name)));
}
});
}
public static void CanChangePlan(ChangePlan command, AppPlan plan, IAppPlansProvider appPlans)
{
Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot change plan.", error =>
{
if (string.IsNullOrWhiteSpace(command.PlanId))
{
error(new ValidationError("PlanId is not defined.", nameof(command.PlanId)));
}
else if (appPlans.GetPlan(command.PlanId) == null)
{
error(new ValidationError("Plan id not available.", nameof(command.PlanId)));
}
if (!string.IsNullOrWhiteSpace(command.PlanId) && plan != null && !plan.Owner.Equals(command.Actor))
{
error(new ValidationError("Plan can only be changed from current user."));
}
if (string.Equals(command.PlanId, plan?.PlanId, StringComparison.OrdinalIgnoreCase))
{
error(new ValidationError("App has already this plan."));
}
});
}
}
}

82
src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppContributors.cs

@ -0,0 +1,82 @@
// ==========================================================================
// GuardApp.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Write.Apps.Commands;
using Squidex.Infrastructure;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Write.Apps.Guards
{
public static class GuardAppContributors
{
public static Task CanAssign(AppContributors contributors, AssignContributor command, IUserResolver users, IAppLimitsPlan plan)
{
Guard.NotNull(command, nameof(command));
return Validate.It(() => "Cannot assign contributor.", async error =>
{
if (!command.Permission.IsEnumValue())
{
error(new ValidationError("Permission is not valid.", nameof(command.Permission)));
}
if (string.IsNullOrWhiteSpace(command.ContributorId))
{
error(new ValidationError("Contributor id not assigned.", nameof(command.ContributorId)));
}
else
{
if (await users.FindByIdAsync(command.ContributorId) == null)
{
error(new ValidationError("Cannot find contributor id.", nameof(command.ContributorId)));
}
else if (contributors.Contributors.TryGetValue(command.ContributorId, out var existing))
{
if (existing == command.Permission)
{
error(new ValidationError("Contributor has already this permission.", nameof(command.Permission)));
}
}
else if (plan.MaxContributors == contributors.Contributors.Count)
{
error(new ValidationError("You have reached the maximum number of contributors for your plan."));
}
}
});
}
public static void CanRemove(AppContributors contributors, RemoveContributor command)
{
Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot remove contributor.", error =>
{
if (string.IsNullOrWhiteSpace(command.ContributorId))
{
error(new ValidationError("Contributor id not assigned.", nameof(command.ContributorId)));
}
var ownerIds = contributors.Contributors.Where(x => x.Value == AppContributorPermission.Owner).Select(x => x.Key).ToList();
if (ownerIds.Count == 1 && ownerIds.Contains(command.ContributorId))
{
error(new ValidationError("Cannot remove the only owner.", nameof(command.ContributorId)));
}
});
if (!contributors.Contributors.ContainsKey(command.ContributorId))
{
throw new DomainObjectNotFoundException(command.ContributorId, "Contributors", typeof(AppDomainObject));
}
}
}
}

90
src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppLanguages.cs

@ -0,0 +1,90 @@
// ==========================================================================
// GuardApp.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Write.Apps.Commands;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Guards
{
public static class GuardAppLanguages
{
public static void CanAdd(LanguagesConfig languages, AddLanguage command)
{
Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot add language.", error =>
{
if (command.Language == null)
{
error(new ValidationError("Language cannot be null.", nameof(command.Language)));
}
else if (languages.Contains(command.Language))
{
error(new ValidationError("Language already added.", nameof(command.Language)));
}
});
}
public static void CanRemove(LanguagesConfig languages, RemoveLanguage command)
{
Guard.NotNull(command, nameof(command));
var languageConfig = GetLanguageConfigOrThrow(languages, command.Language);
Validate.It(() => "Cannot remove language.", error =>
{
if (languages.Master == languageConfig)
{
error(new ValidationError("Language config is master.", nameof(command.Language)));
}
});
}
public static void CanUpdate(LanguagesConfig languages, UpdateLanguage command)
{
Guard.NotNull(command, nameof(command));
var languageConfig = GetLanguageConfigOrThrow(languages, command.Language);
Validate.It(() => "Cannot update language.", error =>
{
if ((languages.Master == languageConfig || command.IsMaster) && command.IsOptional)
{
error(new ValidationError("Cannot make master language optional.", nameof(command.IsMaster)));
}
if (command.Fallback != null)
{
foreach (var fallback in command.Fallback)
{
if (!languages.Contains(fallback))
{
error(new ValidationError($"Config does not contain fallback language {fallback}.", nameof(command.Fallback)));
}
}
}
});
}
private static LanguageConfig GetLanguageConfigOrThrow(LanguagesConfig languages, Language language)
{
if (language == null)
{
throw new DomainObjectNotFoundException(language, "Languages", typeof(AppDomainObject));
}
if (!languages.TryGetConfig(language, out var languageConfig))
{
throw new DomainObjectNotFoundException(language, "Languages", typeof(AppDomainObject));
}
return languageConfig;
}
}
}

19
src/Squidex.Domain.Apps.Write/Assets/AssetCommandMiddleware.cs

@ -9,6 +9,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Write.Assets.Commands; using Squidex.Domain.Apps.Write.Assets.Commands;
using Squidex.Domain.Apps.Write.Assets.Guards;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Commands;
@ -43,6 +44,8 @@ namespace Squidex.Domain.Apps.Write.Assets
{ {
var asset = await handler.CreateAsync<AssetDomainObject>(context, async a => var asset = await handler.CreateAsync<AssetDomainObject>(context, async a =>
{ {
GuardAsset.CanCreate(command);
a.Create(command); a.Create(command);
await assetStore.UploadTemporaryAsync(context.ContextId.ToString(), command.File.OpenRead()); await assetStore.UploadTemporaryAsync(context.ContextId.ToString(), command.File.OpenRead());
@ -66,6 +69,8 @@ namespace Squidex.Domain.Apps.Write.Assets
{ {
var asset = await handler.UpdateAsync<AssetDomainObject>(context, async a => var asset = await handler.UpdateAsync<AssetDomainObject>(context, async a =>
{ {
GuardAsset.CanUpdate(command);
a.Update(command); a.Update(command);
await assetStore.UploadTemporaryAsync(context.ContextId.ToString(), command.File.OpenRead()); await assetStore.UploadTemporaryAsync(context.ContextId.ToString(), command.File.OpenRead());
@ -83,12 +88,22 @@ namespace Squidex.Domain.Apps.Write.Assets
protected Task On(RenameAsset command, CommandContext context) protected Task On(RenameAsset command, CommandContext context)
{ {
return handler.UpdateAsync<AssetDomainObject>(context, a => a.Rename(command)); return handler.UpdateAsync<AssetDomainObject>(context, a =>
{
GuardAsset.CanRename(command, a.FileName);
a.Rename(command);
});
} }
protected Task On(DeleteAsset command, CommandContext context) protected Task On(DeleteAsset command, CommandContext context)
{ {
return handler.UpdateAsync<AssetDomainObject>(context, a => a.Delete(command)); return handler.UpdateAsync<AssetDomainObject>(context, a =>
{
GuardAsset.CanDelete(command);
a.Delete(command);
});
} }
public async Task HandleAsync(CommandContext context, Func<Task> next) public async Task HandleAsync(CommandContext context, Func<Task> next)

22
src/Squidex.Domain.Apps.Write/Assets/AssetDomainObject.cs

@ -34,6 +34,11 @@ namespace Squidex.Domain.Apps.Write.Assets
get { return fileVersion; } get { return fileVersion; }
} }
public string FileName
{
get { return fileName; }
}
public AssetDomainObject(Guid id, int version) public AssetDomainObject(Guid id, int version)
: base(id, version) : base(id, version)
{ {
@ -66,8 +71,6 @@ namespace Squidex.Domain.Apps.Write.Assets
public AssetDomainObject Create(CreateAsset command) public AssetDomainObject Create(CreateAsset command)
{ {
Guard.NotNull(command, nameof(command));
VerifyNotCreated(); VerifyNotCreated();
var @event = SimpleMapper.Map(command, new AssetCreated var @event = SimpleMapper.Map(command, new AssetCreated
@ -88,8 +91,6 @@ namespace Squidex.Domain.Apps.Write.Assets
public AssetDomainObject Update(UpdateAsset command) public AssetDomainObject Update(UpdateAsset command)
{ {
Guard.NotNull(command, nameof(command));
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
var @event = SimpleMapper.Map(command, new AssetUpdated var @event = SimpleMapper.Map(command, new AssetUpdated
@ -109,8 +110,6 @@ namespace Squidex.Domain.Apps.Write.Assets
public AssetDomainObject Delete(DeleteAsset command) public AssetDomainObject Delete(DeleteAsset command)
{ {
Guard.NotNull(command, nameof(command));
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
RaiseEvent(SimpleMapper.Map(command, new AssetDeleted { DeletedSize = totalSize })); RaiseEvent(SimpleMapper.Map(command, new AssetDeleted { DeletedSize = totalSize }));
@ -120,24 +119,13 @@ namespace Squidex.Domain.Apps.Write.Assets
public AssetDomainObject Rename(RenameAsset command) public AssetDomainObject Rename(RenameAsset command)
{ {
Guard.Valid(command, nameof(command), () => "Cannot rename asset.");
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
VerifyDifferentNames(command.FileName, () => "Cannot rename asset.");
RaiseEvent(SimpleMapper.Map(command, new AssetRenamed())); RaiseEvent(SimpleMapper.Map(command, new AssetRenamed()));
return this; return this;
} }
private void VerifyDifferentNames(string newName, Func<string> message)
{
if (string.Equals(fileName, newName))
{
throw new ValidationException(message(), new ValidationError("The asset already has this name.", "Name"));
}
}
private void VerifyNotCreated() private void VerifyNotCreated()
{ {
if (!string.IsNullOrWhiteSpace(fileName)) if (!string.IsNullOrWhiteSpace(fileName))

13
src/Squidex.Domain.Apps.Write/Assets/Commands/RenameAsset.cs

@ -6,21 +6,10 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Assets.Commands namespace Squidex.Domain.Apps.Write.Assets.Commands
{ {
public sealed class RenameAsset : AssetAggregateCommand, IValidatable public sealed class RenameAsset : AssetAggregateCommand
{ {
public string FileName { get; set; } public string FileName { get; set; }
public void Validate(IList<ValidationError> errors)
{
if (string.IsNullOrWhiteSpace(FileName))
{
errors.Add(new ValidationError("File name must not be null or empty.", nameof(FileName)));
}
}
} }
} }

49
src/Squidex.Domain.Apps.Write/Assets/Guards/GuardAsset.cs

@ -0,0 +1,49 @@
// ==========================================================================
// GuardAsset.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Domain.Apps.Write.Assets.Commands;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Assets.Guards
{
public static class GuardAsset
{
public static void CanRename(RenameAsset command, string oldName)
{
Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot rename asset.", error =>
{
if (string.IsNullOrWhiteSpace(command.FileName))
{
error(new ValidationError("Name must be defined.", nameof(command.FileName)));
}
if (string.Equals(command.FileName, oldName))
{
error(new ValidationError("Name is equal to old name.", nameof(command.FileName)));
}
});
}
public static void CanCreate(CreateAsset command)
{
Guard.NotNull(command, nameof(command));
}
public static void CanUpdate(UpdateAsset command)
{
Guard.NotNull(command, nameof(command));
}
public static void CanDelete(DeleteAsset command)
{
Guard.NotNull(command, nameof(command));
}
}
}

2
src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchemaField.cs

@ -151,7 +151,7 @@ namespace Squidex.Domain.Apps.Write.Schemas.Guards
{ {
if (!schema.FieldsById.TryGetValue(fieldId, out var field)) if (!schema.FieldsById.TryGetValue(fieldId, out var field))
{ {
throw new DomainObjectNotFoundException(fieldId.ToString(), "Fields", typeof(Field)); throw new DomainObjectNotFoundException(fieldId.ToString(), "Fields", typeof(Schema));
} }
return field; return field;

4
src/Squidex.Domain.Apps.Write/Schemas/SchemaDomainObject.cs

@ -276,10 +276,6 @@ namespace Squidex.Domain.Apps.Write.Schemas
{ {
@event.FieldId = new NamedId<long>(field.Id, field.Name); @event.FieldId = new NamedId<long>(field.Id, field.Name);
} }
else
{
throw new DomainObjectNotFoundException(fieldCommand.FieldId.ToString(), "Fields", typeof(Field));
}
RaiseEvent(@event); RaiseEvent(@event);
} }

6
src/Squidex.Domain.Apps.Write/Webhooks/Guards/GuardWebhook.cs

@ -43,11 +43,8 @@ namespace Squidex.Domain.Apps.Write.Webhooks.Guards
error(new ValidationError("Url must be specified and absolute.", nameof(command.Url))); error(new ValidationError("Url must be specified and absolute.", nameof(command.Url)));
} }
if (command.Schemas == null) if (command.Schemas != null)
{ {
error(new ValidationError("Schemas cannot be null.", nameof(command.Schemas)));
}
var schemaErrors = await Task.WhenAll( var schemaErrors = await Task.WhenAll(
command.Schemas.Select(async s => command.Schemas.Select(async s =>
await schemas.FindSchemaByIdAsync(s.SchemaId) == null await schemas.FindSchemaByIdAsync(s.SchemaId) == null
@ -60,4 +57,5 @@ namespace Squidex.Domain.Apps.Write.Webhooks.Guards
} }
} }
} }
}
} }

243
tests/Squidex.Domain.Apps.Write.Tests/Apps/AppCommandMiddlewareTests.cs

@ -0,0 +1,243 @@
// ==========================================================================
// AppCommandMiddlewareTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Read.Apps.Services.Implementations;
using Squidex.Domain.Apps.Write.Apps.Commands;
using Squidex.Domain.Apps.Write.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Shared.Users;
using Xunit;
namespace Squidex.Domain.Apps.Write.Apps
{
public class AppCommandMiddlewareTests : HandlerTestBase<AppDomainObject>
{
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IAppPlansProvider appPlansProvider = A.Fake<IAppPlansProvider>();
private readonly IAppPlanBillingManager appPlansBillingManager = A.Fake<IAppPlanBillingManager>();
private readonly IUserResolver userResolver = A.Fake<IUserResolver>();
private readonly AppCommandMiddleware sut;
private readonly AppDomainObject app;
private readonly Language language = Language.DE;
private readonly string contributorId = Guid.NewGuid().ToString();
private readonly string clientName = "client";
public AppCommandMiddlewareTests()
{
app = new AppDomainObject(AppId, -1);
A.CallTo(() => appProvider.FindAppByNameAsync(AppName))
.Returns((IAppEntity)null);
A.CallTo(() => userResolver.FindByIdAsync(contributorId))
.Returns(A.Fake<IUser>());
sut = new AppCommandMiddleware(Handler, appProvider, appPlansProvider, appPlansBillingManager, userResolver);
}
[Fact]
public async Task Create_should_create_domain_object()
{
var context = CreateContextForCommand(new CreateApp { Name = AppName, AppId = AppId });
await TestCreate(app, async _ =>
{
await sut.HandleAsync(context);
});
Assert.Equal(AppId, context.Result<EntityCreatedResult<Guid>>().IdOrValue);
}
[Fact]
public async Task AssignContributor_should_assign_if_user_found()
{
A.CallTo(() => appPlansProvider.GetPlan(null))
.Returns(new ConfigAppLimitsPlan { MaxContributors = -1 });
CreateApp();
var context = CreateContextForCommand(new AssignContributor { ContributorId = contributorId });
await TestUpdate(app, async _ =>
{
await sut.HandleAsync(context);
});
}
[Fact]
public async Task RemoveContributor_should_update_domain_object()
{
CreateApp()
.AssignContributor(CreateCommand(new AssignContributor { ContributorId = contributorId }));
var context = CreateContextForCommand(new RemoveContributor { ContributorId = contributorId });
await TestUpdate(app, async _ =>
{
await sut.HandleAsync(context);
});
}
[Fact]
public async Task AttachClient_should_update_domain_object()
{
CreateApp();
var context = CreateContextForCommand(new AttachClient { Id = clientName });
await TestUpdate(app, async _ =>
{
await sut.HandleAsync(context);
});
}
[Fact]
public async Task RenameClient_should_update_domain_object()
{
CreateApp()
.AttachClient(CreateCommand(new AttachClient { Id = clientName }));
var context = CreateContextForCommand(new UpdateClient { Id = clientName, Name = "New Name" });
await TestUpdate(app, async _ =>
{
await sut.HandleAsync(context);
});
}
[Fact]
public async Task RevokeClient_should_update_domain_object()
{
CreateApp()
.AttachClient(CreateCommand(new AttachClient { Id = clientName }));
var context = CreateContextForCommand(new RevokeClient { Id = clientName });
await TestUpdate(app, async _ =>
{
await sut.HandleAsync(context);
});
}
[Fact]
public async Task ChangePlan_should_update_domain_object()
{
A.CallTo(() => appPlansProvider.IsConfiguredPlan("my-plan"))
.Returns(true);
CreateApp();
var context = CreateContextForCommand(new ChangePlan { PlanId = "my-plan" });
await TestUpdate(app, async _ =>
{
await sut.HandleAsync(context);
});
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, app.Id, app.Name, "my-plan")).MustHaveHappened();
}
[Fact]
public async Task ChangePlan_should_not_make_update_for_redirect_result()
{
A.CallTo(() => appPlansProvider.IsConfiguredPlan("my-plan"))
.Returns(true);
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, app.Id, app.Name, "my-plan"))
.Returns(CreateRedirectResult());
CreateApp();
var context = CreateContextForCommand(new ChangePlan { PlanId = "my-plan" });
await TestUpdate(app, async _ =>
{
await sut.HandleAsync(context);
});
Assert.Null(app.Plan);
}
[Fact]
public async Task ChangePlan_should_not_call_billing_manager_for_callback()
{
A.CallTo(() => appPlansProvider.IsConfiguredPlan("my-plan"))
.Returns(true);
CreateApp();
var context = CreateContextForCommand(new ChangePlan { PlanId = "my-plan", FromCallback = true });
await TestUpdate(app, async _ =>
{
await sut.HandleAsync(context);
});
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, app.Id, app.Name, "my-plan")).MustNotHaveHappened();
}
[Fact]
public async Task AddLanguage_should_update_domain_object()
{
CreateApp();
var context = CreateContextForCommand(new AddLanguage { Language = language });
await TestUpdate(app, async _ =>
{
await sut.HandleAsync(context);
});
}
[Fact]
public async Task RemoveLanguage_should_update_domain_object()
{
CreateApp()
.AddLanguage(CreateCommand(new AddLanguage { Language = language }));
var context = CreateContextForCommand(new RemoveLanguage { Language = language });
await TestUpdate(app, async _ =>
{
await sut.HandleAsync(context);
});
}
[Fact]
public async Task UpdateLanguage_should_update_domain_object()
{
CreateApp()
.AddLanguage(CreateCommand(new AddLanguage { Language = language }));
var context = CreateContextForCommand(new UpdateLanguage { Language = language });
await TestUpdate(app, async _ =>
{
await sut.HandleAsync(context);
});
}
private AppDomainObject CreateApp()
{
app.Create(CreateCommand(new CreateApp { Name = AppName }));
return app;
}
private static Task<IChangePlanResult> CreateRedirectResult()
{
return Task.FromResult<IChangePlanResult>(new RedirectToCheckoutResult(new Uri("http://squidex.io")));
}
}
}

288
tests/Squidex.Domain.Apps.Write.Tests/Apps/AppDomainObjectTests.cs

@ -0,0 +1,288 @@
// ==========================================================================
// AppDomainObjectTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Domain.Apps.Write.Apps.Commands;
using Squidex.Domain.Apps.Write.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS;
using Xunit;
namespace Squidex.Domain.Apps.Write.Apps
{
public class AppDomainObjectTests : HandlerTestBase<AppDomainObject>
{
private readonly AppDomainObject sut;
private readonly string contributorId = Guid.NewGuid().ToString();
private readonly string clientId = "client";
private readonly string clientNewName = "My Client";
private readonly string planId = "premium";
public AppDomainObjectTests()
{
sut = new AppDomainObject(AppId, 0);
}
[Fact]
public void Create_should_throw_exception_if_created()
{
CreateApp();
Assert.Throws<DomainException>(() =>
{
sut.Create(CreateCommand(new CreateApp { Name = AppName }));
});
}
[Fact]
public void Create_should_specify_name_and_owner()
{
sut.Create(CreateCommand(new CreateApp { Name = AppName, Actor = User, AppId = AppId }));
Assert.Equal(AppName, sut.Name);
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateEvent(new AppCreated { Name = AppName }),
CreateEvent(new AppContributorAssigned { ContributorId = User.Identifier, Permission = AppContributorPermission.Owner }),
CreateEvent(new AppLanguageAdded { Language = Language.EN })
);
}
[Fact]
public void ChangePlan_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
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()
{
Assert.Throws<DomainException>(() =>
{
sut.AssignContributor(CreateCommand(new AssignContributor { ContributorId = contributorId }));
});
}
[Fact]
public void AssignContributor_should_create_events()
{
CreateApp();
sut.AssignContributor(CreateCommand(new AssignContributor { ContributorId = contributorId, Permission = AppContributorPermission.Editor }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateEvent(new AppContributorAssigned { ContributorId = contributorId, Permission = AppContributorPermission.Editor })
);
}
[Fact]
public void RemoveContributor_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.RemoveContributor(CreateCommand(new RemoveContributor { ContributorId = contributorId }));
});
}
[Fact]
public void RemoveContributor_should_create_events_and_remove_contributor()
{
CreateApp();
sut.AssignContributor(CreateCommand(new AssignContributor { ContributorId = contributorId, Permission = AppContributorPermission.Editor }));
sut.RemoveContributor(CreateCommand(new RemoveContributor { ContributorId = contributorId }));
sut.GetUncomittedEvents().Skip(1)
.ShouldHaveSameEvents(
CreateEvent(new AppContributorRemoved { ContributorId = contributorId })
);
}
[Fact]
public void AttachClient_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.AttachClient(CreateCommand(new AttachClient { Id = clientId }));
});
}
[Fact]
public void AttachClient_should_create_events()
{
var command = new AttachClient { Id = clientId };
CreateApp();
sut.AttachClient(CreateCommand(command));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateEvent(new AppClientAttached { Id = clientId, Secret = command.Secret })
);
}
[Fact]
public void RevokeClient_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.RevokeClient(CreateCommand(new RevokeClient { Id = "not-found" }));
});
}
[Fact]
public void RevokeClient_should_create_events()
{
CreateApp();
CreateClient();
sut.RevokeClient(CreateCommand(new RevokeClient { Id = clientId }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateEvent(new AppClientRevoked { Id = clientId })
);
}
[Fact]
public void UpdateClient_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.UpdateClient(CreateCommand(new UpdateClient { Id = "not-found", Name = clientNewName }));
});
}
[Fact]
public void UpdateClient_should_create_events()
{
CreateApp();
CreateClient();
sut.UpdateClient(CreateCommand(new UpdateClient { Id = clientId, Name = clientNewName, Permission = AppClientPermission.Developer }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateEvent(new AppClientRenamed { Id = clientId, Name = clientNewName }),
CreateEvent(new AppClientUpdated { Id = clientId, Permission = AppClientPermission.Developer })
);
}
[Fact]
public void AddLanguage_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.AddLanguage(CreateCommand(new AddLanguage { Language = Language.DE }));
});
}
[Fact]
public void AddLanguage_should_create_events()
{
CreateApp();
sut.AddLanguage(CreateCommand(new AddLanguage { Language = Language.DE }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateEvent(new AppLanguageAdded { Language = Language.DE })
);
}
[Fact]
public void RemoveLanguage_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.RemoveLanguage(CreateCommand(new RemoveLanguage { Language = Language.EN }));
});
}
[Fact]
public void RemoveLanguage_should_create_events()
{
CreateApp();
CreateLanguage(Language.DE);
sut.RemoveLanguage(CreateCommand(new RemoveLanguage { Language = Language.DE }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateEvent(new AppLanguageRemoved { Language = Language.DE })
);
}
[Fact]
public void UpdateLanguage_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.UpdateLanguage(CreateCommand(new UpdateLanguage { Language = Language.EN }));
});
}
[Fact]
public void UpdateLanguage_should_create_events()
{
CreateApp();
CreateLanguage(Language.DE);
sut.UpdateLanguage(CreateCommand(new UpdateLanguage { Language = Language.DE, Fallback = new List<Language> { Language.EN } }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateEvent(new AppLanguageUpdated { Language = Language.DE, Fallback = new List<Language> { Language.EN } })
);
}
private void CreateApp()
{
sut.Create(CreateCommand(new CreateApp { Name = AppName }));
((IAggregate)sut).ClearUncommittedEvents();
}
private void CreateClient()
{
sut.AttachClient(CreateCommand(new AttachClient { Id = clientId }));
((IAggregate)sut).ClearUncommittedEvents();
}
private void CreateLanguage(Language language)
{
sut.AddLanguage(CreateCommand(new AddLanguage { Language = language }));
((IAggregate)sut).ClearUncommittedEvents();
}
}
}

51
tests/Squidex.Domain.Apps.Write.Tests/Apps/AppEventTests.cs

@ -0,0 +1,51 @@
// ==========================================================================
// AppEventTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Domain.Apps.Events.Apps.Old;
using Squidex.Domain.Apps.Write.TestHelpers;
using Squidex.Infrastructure;
using Xunit;
#pragma warning disable CS0612 // Type or member is obsolete
namespace Squidex.Domain.Apps.Write.Apps
{
public class AppEventTests
{
private readonly RefToken actor = new RefToken("User", Guid.NewGuid().ToString());
private readonly NamedId<Guid> appId = new NamedId<Guid>(Guid.NewGuid(), "my-app");
[Fact]
public void Should_migrate_client_changed_as_reader_to_client_updated()
{
var source = CreateEvent(new AppClientChanged { IsReader = true });
source.Migrate().ShouldBeSameEvent(CreateEvent(new AppClientUpdated { Permission = AppClientPermission.Reader }));
}
[Fact]
public void Should_migrate_client_changed_as_writer_to_client_updated()
{
var source = CreateEvent(new AppClientChanged { IsReader = false });
source.Migrate().ShouldBeSameEvent(CreateEvent(new AppClientUpdated { Permission = AppClientPermission.Editor }));
}
private T CreateEvent<T>(T contentEvent) where T : AppEvent
{
contentEvent.Actor = actor;
contentEvent.AppId = appId;
return contentEvent;
}
}
}

179
tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppContributorsTests.cs

@ -0,0 +1,179 @@
// ==========================================================================
// GuardAppContributorsTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Write.Apps.Commands;
using Squidex.Infrastructure;
using Squidex.Shared.Users;
using Xunit;
namespace Squidex.Domain.Apps.Write.Apps.Guards
{
public class GuardAppContributorsTests
{
private readonly IUserResolver users = A.Fake<IUserResolver>();
private readonly IAppLimitsPlan appPlan = A.Fake<IAppLimitsPlan>();
public GuardAppContributorsTests()
{
A.CallTo(() => users.FindByIdAsync(A<string>.Ignored))
.Returns(A.Fake<IUser>());
A.CallTo(() => appPlan.MaxContributors)
.Returns(10);
}
[Fact]
public Task CanAssign_should_throw_exception_if_contributor_id_is_null()
{
var command = new AssignContributor();
var contributors = new AppContributors();
return Assert.ThrowsAsync<ValidationException>(() => GuardAppContributors.CanAssign(contributors, command, users, appPlan));
}
[Fact]
public Task CanAssign_should_throw_exception_if_permission_not_valid()
{
var command = new AssignContributor { ContributorId = "1", Permission = (AppContributorPermission)10 };
var contributors = new AppContributors();
return Assert.ThrowsAsync<ValidationException>(() => GuardAppContributors.CanAssign(contributors, command, users, appPlan));
}
[Fact]
public Task CanAssign_should_throw_exception_if_user_already_exists_with_same_permission()
{
var command = new AssignContributor { ContributorId = "1" };
var contributors = new AppContributors();
contributors.Assign("1", AppContributorPermission.Owner);
return Assert.ThrowsAsync<ValidationException>(() => GuardAppContributors.CanAssign(contributors, command, users, appPlan));
}
[Fact]
public Task CanAssign_should_throw_exception_if_user_not_found()
{
A.CallTo(() => users.FindByIdAsync(A<string>.Ignored))
.Returns(Task.FromResult<IUser>(null));
var command = new AssignContributor { ContributorId = "1", Permission = (AppContributorPermission)10 };
var contributors = new AppContributors();
return Assert.ThrowsAsync<ValidationException>(() => GuardAppContributors.CanAssign(contributors, command, users, appPlan));
}
[Fact]
public Task CanAssign_should_throw_exception_if_contributor_max_reached()
{
A.CallTo(() => appPlan.MaxContributors)
.Returns(2);
var command = new AssignContributor { ContributorId = "3" };
var contributors = new AppContributors();
contributors.Assign("1", AppContributorPermission.Owner);
contributors.Assign("2", AppContributorPermission.Editor);
return Assert.ThrowsAsync<ValidationException>(() => GuardAppContributors.CanAssign(contributors, command, users, appPlan));
}
[Fact]
public Task CanAssign_should_not_throw_exception_if_user_found()
{
var command = new AssignContributor { ContributorId = "1" };
var contributors = new AppContributors();
return GuardAppContributors.CanAssign(contributors, command, users, appPlan);
}
[Fact]
public Task CanAssign_should_not_throw_exception_if_contributor_has_another_permission()
{
var command = new AssignContributor { ContributorId = "1" };
var contributors = new AppContributors();
contributors.Assign("1", AppContributorPermission.Editor);
return GuardAppContributors.CanAssign(contributors, command, users, appPlan);
}
[Fact]
public Task CanAssign_should_not_throw_exception_if_contributor_max_reached_but_permission_changed()
{
A.CallTo(() => appPlan.MaxContributors)
.Returns(2);
var command = new AssignContributor { ContributorId = "1" };
var contributors = new AppContributors();
contributors.Assign("1", AppContributorPermission.Editor);
contributors.Assign("2", AppContributorPermission.Editor);
return GuardAppContributors.CanAssign(contributors, command, users, appPlan);
}
[Fact]
public void CanRemove_should_throw_exception_if_contributor_id_is_null()
{
var command = new RemoveContributor();
var contributors = new AppContributors();
Assert.Throws<ValidationException>(() => GuardAppContributors.CanRemove(contributors, command));
}
[Fact]
public void CanRemove_should_throw_exception_if_contributor_not_found()
{
var command = new RemoveContributor { ContributorId = "1" };
var contributors = new AppContributors();
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppContributors.CanRemove(contributors, command));
}
[Fact]
public void CanRemove_should_throw_exception_if_contributor_is_only_owner()
{
var command = new RemoveContributor { ContributorId = "1" };
var contributors = new AppContributors();
contributors.Assign("1", AppContributorPermission.Owner);
contributors.Assign("2", AppContributorPermission.Editor);
Assert.Throws<ValidationException>(() => GuardAppContributors.CanRemove(contributors, command));
}
[Fact]
public void CanRemove_should_not_throw_exception_if_contributor_not_only_owner()
{
var command = new RemoveContributor { ContributorId = "1" };
var contributors = new AppContributors();
contributors.Assign("1", AppContributorPermission.Owner);
contributors.Assign("2", AppContributorPermission.Owner);
GuardAppContributors.CanRemove(contributors, command);
}
}
}

129
tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppLanguagesTests.cs

@ -0,0 +1,129 @@
// ==========================================================================
// GuardAppLanguagesTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Write.Apps.Commands;
using Squidex.Infrastructure;
using Xunit;
namespace Squidex.Domain.Apps.Write.Apps.Guards
{
public class GuardAppLanguagesTests
{
[Fact]
public void CanAddLanguage_should_throw_exception_if_language_is_null()
{
var command = new AddLanguage();
var languages = LanguagesConfig.Build(Language.DE);
Assert.Throws<ValidationException>(() => GuardAppLanguages.CanAdd(languages, command));
}
[Fact]
public void CanAddLanguage_should_throw_exception_if_language_already_added()
{
var command = new AddLanguage { Language = Language.DE };
var languages = LanguagesConfig.Build(Language.DE);
Assert.Throws<ValidationException>(() => GuardAppLanguages.CanAdd(languages, command));
}
[Fact]
public void CanAddLanguage_should_not_throw_exception_if_language_valid()
{
var command = new AddLanguage { Language = Language.EN };
var languages = LanguagesConfig.Build(Language.DE);
GuardAppLanguages.CanAdd(languages, command);
}
[Fact]
public void CanRemoveLanguage_should_throw_exception_if_language_is_null()
{
var command = new RemoveLanguage();
var languages = LanguagesConfig.Build(Language.DE);
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppLanguages.CanRemove(languages, command));
}
[Fact]
public void CanRemoveLanguage_should_throw_exception_if_language_not_found()
{
var command = new RemoveLanguage { Language = Language.EN };
var languages = LanguagesConfig.Build(Language.DE);
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppLanguages.CanRemove(languages, command));
}
[Fact]
public void CanRemoveLanguage_should_throw_exception_if_language_is_master()
{
var command = new RemoveLanguage { Language = Language.DE };
var languages = LanguagesConfig.Build(Language.DE);
Assert.Throws<ValidationException>(() => GuardAppLanguages.CanRemove(languages, command));
}
[Fact]
public void CanRemoveLanguage_should_not_throw_exception_if_language_is_valid()
{
var command = new RemoveLanguage { Language = Language.EN };
var languages = LanguagesConfig.Build(Language.DE, Language.EN);
GuardAppLanguages.CanRemove(languages, command);
}
[Fact]
public void CanUpdateLanguage_should_throw_exception_if_language_is_optional_and_master()
{
var command = new UpdateLanguage { Language = Language.DE, IsOptional = true };
var languages = LanguagesConfig.Build(Language.DE, Language.EN);
Assert.Throws<ValidationException>(() => GuardAppLanguages.CanUpdate(languages, command));
}
[Fact]
public void CanUpdateLanguage_should_throw_exception_if_language_has_invalid_fallback()
{
var command = new UpdateLanguage { Language = Language.DE, Fallback = new List<Language> { Language.IT } };
var languages = LanguagesConfig.Build(Language.DE, Language.EN);
Assert.Throws<ValidationException>(() => GuardAppLanguages.CanUpdate(languages, command));
}
[Fact]
public void CanUpdateLanguage_should_throw_exception_if_not_found()
{
var command = new UpdateLanguage { Language = Language.IT };
var languages = LanguagesConfig.Build(Language.DE, Language.EN);
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppLanguages.CanUpdate(languages, command));
}
[Fact]
public void CanUpdateLanguage_should_not_throw_exception_if_language_is_valid()
{
var command = new UpdateLanguage { Language = Language.DE, Fallback = new List<Language> { Language.EN } };
var languages = LanguagesConfig.Build(Language.DE, Language.EN);
GuardAppLanguages.CanUpdate(languages, command);
}
}
}

119
tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppTests.cs

@ -0,0 +1,119 @@
// ==========================================================================
// GuardAppTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Write.Apps.Commands;
using Squidex.Infrastructure;
using Squidex.Shared.Users;
using Xunit;
namespace Squidex.Domain.Apps.Write.Apps.Guards
{
public class GuardAppTests
{
private readonly IAppProvider apps = A.Fake<IAppProvider>();
private readonly IUserResolver users = A.Fake<IUserResolver>();
private readonly IAppPlansProvider appPlans = A.Fake<IAppPlansProvider>();
public GuardAppTests()
{
A.CallTo(() => apps.FindAppByNameAsync("new-app"))
.Returns(Task.FromResult<IAppEntity>(null));
A.CallTo(() => users.FindByIdAsync(A<string>.Ignored))
.Returns(A.Fake<IUser>());
A.CallTo(() => appPlans.GetPlan("free"))
.Returns(A.Fake<IAppLimitsPlan>());
}
[Fact]
public Task CanCreate_should_throw_exception_if_name_already_in_use()
{
A.CallTo(() => apps.FindAppByNameAsync("new-app"))
.Returns(A.Fake<IAppEntity>());
var command = new CreateApp { Name = "new-app" };
return Assert.ThrowsAsync<ValidationException>(() => GuardApp.CanCreate(command, apps));
}
[Fact]
public Task CanCreate_should_throw_exception_if_name_not_valid()
{
var command = new CreateApp { Name = "INVALID NAME" };
return Assert.ThrowsAsync<ValidationException>(() => GuardApp.CanCreate(command, apps));
}
[Fact]
public Task CanCreate_should_not_throw_exception_if_app_name_is_free()
{
var command = new CreateApp { Name = "new-app" };
return GuardApp.CanCreate(command, apps);
}
[Fact]
public void CanChangePlan_should_throw_exception_if_plan_id_null()
{
var command = new ChangePlan { Actor = new RefToken("user", "me") };
AppPlan plan = null;
Assert.Throws<ValidationException>(() => GuardApp.CanChangePlan(command, plan, appPlans));
}
[Fact]
public void CanChangePlan_should_throw_exception_if_plan_not_found()
{
A.CallTo(() => appPlans.GetPlan("free"))
.Returns(null);
var command = new ChangePlan { PlanId = "free", Actor = new RefToken("user", "me") };
AppPlan plan = null;
Assert.Throws<ValidationException>(() => GuardApp.CanChangePlan(command, plan, appPlans));
}
[Fact]
public void CanChangePlan_should_throw_exception_if_plan_was_configured_from_another_user()
{
var command = new ChangePlan { PlanId = "free", Actor = new RefToken("user", "me") };
var plan = new AppPlan(new RefToken("user", "other"), "premium");
Assert.Throws<ValidationException>(() => GuardApp.CanChangePlan(command, plan, appPlans));
}
[Fact]
public void CanChangePlan_should_throw_exception_if_plan_is_the_same()
{
var command = new ChangePlan { PlanId = "free", Actor = new RefToken("user", "me") };
var plan = new AppPlan(new RefToken("user", "me"), "free");
Assert.Throws<ValidationException>(() => GuardApp.CanChangePlan(command, plan, appPlans));
}
[Fact]
public void CanChangePlan_should_not_throw_exception_if_same_user_but_other_plan()
{
var command = new ChangePlan { PlanId = "free", Actor = new RefToken("user", "me") };
var plan = new AppPlan(new RefToken("user", "me"), "premium");
GuardApp.CanChangePlan(command, plan, appPlans);
}
}
}

139
tests/Squidex.Domain.Apps.Write.Tests/Assets/AssetCommandMiddlewareTests.cs

@ -0,0 +1,139 @@
// ==========================================================================
// AssetCommandMiddlewareTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.IO;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Write.Assets.Commands;
using Squidex.Domain.Apps.Write.TestHelpers;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Tasks;
using Xunit;
namespace Squidex.Domain.Apps.Write.Assets
{
public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject>
{
private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake<IAssetThumbnailGenerator>();
private readonly IAssetStore assetStore = A.Fake<IAssetStore>();
private readonly AssetCommandMiddleware sut;
private readonly AssetDomainObject asset;
private readonly Guid assetId = Guid.NewGuid();
private readonly Stream stream = new MemoryStream();
private readonly ImageInfo image = new ImageInfo(2048, 2048);
private readonly AssetFile file;
public AssetCommandMiddlewareTests()
{
file = new AssetFile("my-image.png", "image/png", 1024, () => stream);
asset = new AssetDomainObject(assetId, -1);
sut = new AssetCommandMiddleware(Handler, assetStore, assetThumbnailGenerator);
}
[Fact]
public async Task Create_should_create_domain_object()
{
var context = CreateContextForCommand(new CreateAsset { AssetId = assetId, File = file });
SetupStore(0, context.ContextId);
SetupImageInfo();
await TestCreate(asset, async _ =>
{
await sut.HandleAsync(context);
});
Assert.Equal(assetId, context.Result<EntityCreatedResult<Guid>>().IdOrValue);
VerifyStore(0, context.ContextId);
VerifyImageInfo();
}
[Fact]
public async Task Update_should_update_domain_object()
{
var context = CreateContextForCommand(new UpdateAsset { AssetId = assetId, File = file });
SetupStore(1, context.ContextId);
SetupImageInfo();
CreateAsset();
await TestUpdate(asset, async _ =>
{
await sut.HandleAsync(context);
});
VerifyStore(1, context.ContextId);
VerifyImageInfo();
}
[Fact]
public async Task Rename_should_update_domain_object()
{
CreateAsset();
var context = CreateContextForCommand(new RenameAsset { AssetId = assetId, FileName = "my-new-image.png" });
await TestUpdate(asset, async _ =>
{
await sut.HandleAsync(context);
});
}
[Fact]
public async Task Delete_should_update_domain_object()
{
CreateAsset();
var command = CreateContextForCommand(new DeleteAsset { AssetId = assetId });
await TestUpdate(asset, async _ =>
{
await sut.HandleAsync(command);
});
}
private void CreateAsset()
{
asset.Create(new CreateAsset { File = file });
}
private void SetupImageInfo()
{
A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(stream))
.Returns(image);
}
private void SetupStore(long version, Guid commitId)
{
A.CallTo(() => assetStore.UploadTemporaryAsync(commitId.ToString(), stream))
.Returns(TaskHelper.Done);
A.CallTo(() => assetStore.CopyTemporaryAsync(commitId.ToString(), assetId.ToString(), version, null))
.Returns(TaskHelper.Done);
A.CallTo(() => assetStore.DeleteTemporaryAsync(commitId.ToString()))
.Returns(TaskHelper.Done);
}
private void VerifyImageInfo()
{
A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(stream)).MustHaveHappened();
}
private void VerifyStore(long version, Guid commitId)
{
A.CallTo(() => assetStore.UploadTemporaryAsync(commitId.ToString(), stream)).MustHaveHappened();
A.CallTo(() => assetStore.CopyTemporaryAsync(commitId.ToString(), assetId.ToString(), version, null)).MustHaveHappened();
A.CallTo(() => assetStore.DeleteTemporaryAsync(commitId.ToString())).MustHaveHappened();
}
}
}

213
tests/Squidex.Domain.Apps.Write.Tests/Assets/AssetDomainObjectTests.cs

@ -0,0 +1,213 @@
// ==========================================================================
// AssetDomainObjectTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.IO;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Domain.Apps.Write.Assets.Commands;
using Squidex.Domain.Apps.Write.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.CQRS;
using Xunit;
namespace Squidex.Domain.Apps.Write.Assets
{
public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject>
{
private readonly AssetDomainObject sut;
private readonly ImageInfo image = new ImageInfo(2048, 2048);
private readonly AssetFile file = new AssetFile("my-image.png", "image/png", 1024, () => new MemoryStream());
public Guid AssetId { get; } = Guid.NewGuid();
public AssetDomainObjectTests()
{
sut = new AssetDomainObject(AssetId, 0);
}
[Fact]
public void Create_should_throw_exception_if_created()
{
sut.Create(new CreateAsset { File = file });
Assert.Throws<DomainException>(() =>
{
sut.Create(CreateAssetCommand(new CreateAsset { File = file }));
});
}
[Fact]
public void Create_should_create_events()
{
sut.Create(CreateAssetCommand(new CreateAsset { File = file, ImageInfo = image }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateAssetEvent(new AssetCreated
{
IsImage = true,
FileName = file.FileName,
FileSize = file.FileSize,
FileVersion = 0,
MimeType = file.MimeType,
PixelWidth = image.PixelWidth,
PixelHeight = image.PixelHeight
})
);
}
[Fact]
public void Update_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.Update(CreateAssetCommand(new UpdateAsset { File = file }));
});
}
[Fact]
public void Update_should_throw_exception_if_asset_is_deleted()
{
CreateAsset();
DeleteAsset();
Assert.Throws<DomainException>(() =>
{
sut.Update(CreateAssetCommand(new UpdateAsset()));
});
}
[Fact]
public void Update_should_create_events()
{
CreateAsset();
sut.Update(CreateAssetCommand(new UpdateAsset { File = file, ImageInfo = image }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateAssetEvent(new AssetUpdated
{
IsImage = true,
FileSize = file.FileSize,
FileVersion = 1,
MimeType = file.MimeType,
PixelWidth = image.PixelWidth,
PixelHeight = image.PixelHeight
})
);
}
[Fact]
public void Rename_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.Rename(CreateAssetCommand(new RenameAsset { FileName = "new-file.png" }));
});
}
[Fact]
public void Rename_should_throw_exception_if_asset_is_deleted()
{
CreateAsset();
DeleteAsset();
Assert.Throws<DomainException>(() =>
{
sut.Update(CreateAssetCommand(new UpdateAsset()));
});
}
[Fact]
public void Rename_should_create_events()
{
CreateAsset();
sut.Rename(CreateAssetCommand(new RenameAsset { FileName = "my-new-image.png" }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateAssetEvent(new AssetRenamed { FileName = "my-new-image.png" })
);
}
[Fact]
public void Delete_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.Delete(CreateAssetCommand(new DeleteAsset()));
});
}
[Fact]
public void Delete_should_throw_exception_if_already_deleted()
{
CreateAsset();
DeleteAsset();
Assert.Throws<DomainException>(() =>
{
sut.Delete(CreateAssetCommand(new DeleteAsset()));
});
}
[Fact]
public void Delete_should_create_events_with_total_file_size()
{
CreateAsset();
UpdateAsset();
sut.Delete(CreateAssetCommand(new DeleteAsset()));
Assert.True(sut.IsDeleted);
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateAssetEvent(new AssetDeleted { DeletedSize = 2048 })
);
}
private void CreateAsset()
{
sut.Create(CreateAssetCommand(new CreateAsset { File = file }));
((IAggregate)sut).ClearUncommittedEvents();
}
private void UpdateAsset()
{
sut.Update(CreateAssetCommand(new UpdateAsset { File = file }));
((IAggregate)sut).ClearUncommittedEvents();
}
private void DeleteAsset()
{
sut.Delete(CreateAssetCommand(new DeleteAsset()));
((IAggregate)sut).ClearUncommittedEvents();
}
protected T CreateAssetEvent<T>(T @event) where T : AssetEvent
{
@event.AssetId = AssetId;
return CreateEvent(@event);
}
protected T CreateAssetCommand<T>(T command) where T : AssetAggregateCommand
{
command.AssetId = AssetId;
return CreateCommand(command);
}
}
}

65
tests/Squidex.Domain.Apps.Write.Tests/Assets/Guards/GuardAssetTests.cs

@ -0,0 +1,65 @@
// ==========================================================================
// GuardAssetTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Domain.Apps.Write.Assets.Commands;
using Squidex.Infrastructure;
using Xunit;
namespace Squidex.Domain.Apps.Write.Assets.Guards
{
public class GuardAssetTests
{
[Fact]
public void CanRename_should_throw_exception_if_name_not_defined()
{
var command = new RenameAsset();
Assert.Throws<ValidationException>(() => GuardAsset.CanRename(command, "asset-name"));
}
[Fact]
public void CanRename_should_throw_exception_if_name_are_the_same()
{
var command = new RenameAsset { FileName = "asset-name" };
Assert.Throws<ValidationException>(() => GuardAsset.CanRename(command, "asset-name"));
}
[Fact]
public void CanRename_not_should_throw_exception_if_name_are_different()
{
var command = new RenameAsset { FileName = "new-name" };
GuardAsset.CanRename(command, "asset-name");
}
[Fact]
public void CanCreate_should_not_throw_exception()
{
var command = new CreateAsset();
GuardAsset.CanCreate(command);
}
[Fact]
public void CanUpdate_should_not_throw_exception()
{
var command = new UpdateAsset();
GuardAsset.CanUpdate(command);
}
[Fact]
public void CanDelete_should_not_throw_exception()
{
var command = new DeleteAsset();
GuardAsset.CanDelete(command);
}
}
}

249
tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandMiddlewareTests.cs

@ -0,0 +1,249 @@
// ==========================================================================
// ContentCommandMiddlewareTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Read.Assets.Repositories;
using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Write.Contents.Commands;
using Squidex.Domain.Apps.Write.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Xunit;
namespace Squidex.Domain.Apps.Write.Contents
{
public class ContentCommandMiddlewareTests : HandlerTestBase<ContentDomainObject>
{
private readonly ContentCommandMiddleware sut;
private readonly ContentDomainObject content;
private readonly ISchemaProvider schemas = A.Fake<ISchemaProvider>();
private readonly ISchemaEntity schema = A.Fake<ISchemaEntity>();
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IAppEntity app = A.Fake<IAppEntity>();
private readonly ClaimsPrincipal user = new ClaimsPrincipal();
private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.DE);
private readonly Guid contentId = Guid.NewGuid();
private readonly NamedContentData invalidData =
new NamedContentData()
.AddField("my-field1", new ContentFieldData()
.AddValue(null))
.AddField("my-field2", new ContentFieldData()
.AddValue(1));
private readonly NamedContentData data =
new NamedContentData()
.AddField("my-field1", new ContentFieldData()
.AddValue(1))
.AddField("my-field2", new ContentFieldData()
.AddValue(1));
private readonly NamedContentData patch =
new NamedContentData()
.AddField("my-field1", new ContentFieldData()
.AddValue(1));
public ContentCommandMiddlewareTests()
{
var schemaDef = new Schema("my-schema");
schemaDef.AddField(new NumberField(1, "my-field1", Partitioning.Invariant,
new NumberFieldProperties { IsRequired = true }));
schemaDef.AddField(new NumberField(2, "my-field2", Partitioning.Invariant,
new NumberFieldProperties { IsRequired = false }));
content = new ContentDomainObject(contentId, -1);
sut = new ContentCommandMiddleware(Handler, appProvider, A.Dummy<IAssetRepository>(), schemas, scriptEngine, A.Dummy<IContentRepository>());
A.CallTo(() => app.LanguagesConfig).Returns(languagesConfig);
A.CallTo(() => app.PartitionResolver).Returns(languagesConfig.ToResolver());
A.CallTo(() => appProvider.FindAppByIdAsync(AppId)).Returns(app);
A.CallTo(() => schema.SchemaDef).Returns(schemaDef);
A.CallTo(() => schema.ScriptCreate).Returns("<create-script>");
A.CallTo(() => schema.ScriptChange).Returns("<change-script>");
A.CallTo(() => schema.ScriptUpdate).Returns("<update-script>");
A.CallTo(() => schema.ScriptDelete).Returns("<delete-script>");
A.CallTo(() => schemas.FindSchemaByIdAsync(SchemaId, false)).Returns(schema);
}
[Fact]
public async Task Create_should_throw_exception_if_data_is_not_valid()
{
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored))
.Returns(invalidData);
var context = CreateContextForCommand(new CreateContent { ContentId = contentId, Data = invalidData, User = user });
await TestCreate(content, async _ =>
{
await Assert.ThrowsAsync<ValidationException>(() => sut.HandleAsync(context));
}, false);
}
[Fact]
public async Task Create_should_create_content()
{
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored))
.Returns(data);
var context = CreateContextForCommand(new CreateContent { ContentId = contentId, Data = data, User = user });
await TestCreate(content, async _ =>
{
await sut.HandleAsync(context);
});
Assert.Equal(data, context.Result<EntityCreatedResult<NamedContentData>>().IdOrValue);
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, "<create-script>")).MustHaveHappened();
A.CallTo(() => scriptEngine.Execute(A<ScriptContext>.Ignored, "<change-script>")).MustNotHaveHappened();
}
[Fact]
public async Task Create_should_also_invoke_publish_script_when_publishing()
{
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored))
.Returns(data);
var context = CreateContextForCommand(new CreateContent { ContentId = contentId, Data = data, User = user, Publish = true });
await TestCreate(content, async _ =>
{
await sut.HandleAsync(context);
});
Assert.Equal(data, context.Result<EntityCreatedResult<NamedContentData>>().IdOrValue);
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, "<create-script>")).MustHaveHappened();
A.CallTo(() => scriptEngine.Execute(A<ScriptContext>.Ignored, "<change-script>")).MustHaveHappened();
}
[Fact]
public async Task Update_should_throw_exception_if_data_is_not_valid()
{
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored))
.Returns(invalidData);
CreateContent();
var context = CreateContextForCommand(new UpdateContent { ContentId = contentId, Data = invalidData, User = user });
await TestUpdate(content, async _ =>
{
await Assert.ThrowsAsync<ValidationException>(() => sut.HandleAsync(context));
}, false);
}
[Fact]
public async Task Update_should_update_domain_object()
{
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored))
.Returns(data);
CreateContent();
var context = CreateContextForCommand(new UpdateContent { ContentId = contentId, Data = data, User = user });
await TestUpdate(content, async _ =>
{
await sut.HandleAsync(context);
});
Assert.Equal(data, context.Result<ContentDataChangedResult>().Data);
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, "<update-script>")).MustHaveHappened();
}
[Fact]
public async Task Patch_should_throw_exception_if_data_is_not_valid()
{
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored))
.Returns(invalidData);
CreateContent();
var context = CreateContextForCommand(new PatchContent { ContentId = contentId, Data = invalidData, User = user });
await TestUpdate(content, async _ =>
{
await Assert.ThrowsAsync<ValidationException>(() => sut.HandleAsync(context));
}, false);
}
[Fact]
public async Task Patch_should_update_domain_object()
{
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored))
.Returns(data);
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored)).Returns(patch);
CreateContent();
var context = CreateContextForCommand(new PatchContent { ContentId = contentId, Data = patch, User = user });
await TestUpdate(content, async _ =>
{
await sut.HandleAsync(context);
});
Assert.NotNull(context.Result<ContentDataChangedResult>().Data);
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, "<update-script>")).MustHaveHappened();
}
[Fact]
public async Task ChangeStatus_should_publish_domain_object()
{
CreateContent();
var context = CreateContextForCommand(new ChangeContentStatus { ContentId = contentId, User = user, Status = Status.Published });
await TestUpdate(content, async _ =>
{
await sut.HandleAsync(context);
});
A.CallTo(() => scriptEngine.Execute(A<ScriptContext>.Ignored, "<change-script>")).MustHaveHappened();
}
[Fact]
public async Task Delete_should_update_domain_object()
{
CreateContent();
var command = CreateContextForCommand(new DeleteContent { ContentId = contentId, User = user });
await TestUpdate(content, async _ =>
{
await sut.HandleAsync(command);
});
A.CallTo(() => scriptEngine.Execute(A<ScriptContext>.Ignored, "<delete-script>")).MustHaveHappened();
}
private void CreateContent()
{
content.Create(new CreateContent { Data = data });
}
}
}

280
tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentDomainObjectTests.cs

@ -0,0 +1,280 @@
// ==========================================================================
// ContentDomainObjectTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Domain.Apps.Write.Contents.Commands;
using Squidex.Domain.Apps.Write.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS;
using Xunit;
namespace Squidex.Domain.Apps.Write.Contents
{
public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject>
{
private readonly ContentDomainObject sut;
private readonly NamedContentData data =
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("iv", 1));
private readonly NamedContentData otherData =
new NamedContentData()
.AddField("field2",
new ContentFieldData()
.AddValue("iv", 2));
public Guid ContentId { get; } = Guid.NewGuid();
public ContentDomainObjectTests()
{
sut = new ContentDomainObject(ContentId, 0);
}
[Fact]
public void Create_should_throw_exception_if_created()
{
sut.Create(new CreateContent { Data = data });
Assert.Throws<DomainException>(() =>
{
sut.Create(CreateContentCommand(new CreateContent { Data = data }));
});
}
[Fact]
public void Create_should_create_events()
{
sut.Create(CreateContentCommand(new CreateContent { Data = data }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateContentEvent(new ContentCreated { Data = data })
);
}
[Fact]
public void Create_should_also_publish_if_set_to_true()
{
sut.Create(CreateContentCommand(new CreateContent { Data = data, Publish = true }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateContentEvent(new ContentCreated { Data = data }),
CreateContentEvent(new ContentStatusChanged { Status = Status.Published })
);
}
[Fact]
public void Update_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.Update(CreateContentCommand(new UpdateContent { Data = data }));
});
}
[Fact]
public void Update_should_throw_exception_if_content_is_deleted()
{
CreateContent();
DeleteContent();
Assert.Throws<DomainException>(() =>
{
sut.Update(CreateContentCommand(new UpdateContent()));
});
}
[Fact]
public void Update_should_create_events()
{
CreateContent();
sut.Update(CreateContentCommand(new UpdateContent { Data = otherData }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateContentEvent(new ContentUpdated { Data = otherData })
);
}
[Fact]
public void Update_should_not_create_event_for_same_data()
{
CreateContent();
UpdateContent();
sut.Update(CreateContentCommand(new UpdateContent { Data = data }));
sut.GetUncomittedEvents().Should().BeEmpty();
}
[Fact]
public void Patch_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.Patch(CreateContentCommand(new PatchContent { Data = data }));
});
}
[Fact]
public void Patch_should_throw_exception_if_content_is_deleted()
{
CreateContent();
DeleteContent();
Assert.Throws<DomainException>(() =>
{
sut.Patch(CreateContentCommand(new PatchContent()));
});
}
[Fact]
public void Patch_should_create_events()
{
CreateContent();
sut.Patch(CreateContentCommand(new PatchContent { Data = otherData }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateContentEvent(new ContentUpdated { Data = otherData })
);
}
[Fact]
public void Patch_should_not_create_event_for_same_data()
{
CreateContent();
UpdateContent();
sut.Patch(CreateContentCommand(new PatchContent { Data = data }));
sut.GetUncomittedEvents().Should().BeEmpty();
}
[Fact]
public void ChangeStatus_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.ChangeStatus(CreateContentCommand(new ChangeContentStatus()));
});
}
[Fact]
public void ChangeStatus_should_throw_exception_if_content_is_deleted()
{
CreateContent();
DeleteContent();
Assert.Throws<DomainException>(() =>
{
sut.ChangeStatus(CreateContentCommand(new ChangeContentStatus()));
});
}
[Fact]
public void ChangeStatus_should_refresh_properties_and_create_events()
{
CreateContent();
sut.ChangeStatus(CreateContentCommand(new ChangeContentStatus { Status = Status.Published }));
Assert.Equal(Status.Published, sut.Status);
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateContentEvent(new ContentStatusChanged { Status = Status.Published })
);
}
[Fact]
public void Delete_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.Delete(CreateContentCommand(new DeleteContent()));
});
}
[Fact]
public void Delete_should_throw_exception_if_already_deleted()
{
CreateContent();
DeleteContent();
Assert.Throws<DomainException>(() =>
{
sut.Delete(CreateContentCommand(new DeleteContent()));
});
}
[Fact]
public void Delete_should_update_properties_and_create_events()
{
CreateContent();
sut.Delete(CreateContentCommand(new DeleteContent()));
Assert.True(sut.IsDeleted);
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateContentEvent(new ContentDeleted())
);
}
private void CreateContent()
{
sut.Create(CreateContentCommand(new CreateContent { Data = data }));
((IAggregate)sut).ClearUncommittedEvents();
}
private void UpdateContent()
{
sut.Update(CreateContentCommand(new UpdateContent { Data = data }));
((IAggregate)sut).ClearUncommittedEvents();
}
private void ChangeStatus(Status status)
{
sut.ChangeStatus(CreateContentCommand(new ChangeContentStatus { Status = status }));
((IAggregate)sut).ClearUncommittedEvents();
}
private void DeleteContent()
{
sut.Delete(CreateContentCommand(new DeleteContent()));
((IAggregate)sut).ClearUncommittedEvents();
}
protected T CreateContentEvent<T>(T @event) where T : ContentEvent
{
@event.ContentId = ContentId;
return CreateEvent(@event);
}
protected T CreateContentCommand<T>(T command) where T : ContentCommand
{
command.ContentId = ContentId;
return CreateCommand(command);
}
}
}

70
tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentEventTests.cs

@ -0,0 +1,70 @@
// ==========================================================================
// SchemaEventTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Domain.Apps.Events.Contents.Old;
using Squidex.Domain.Apps.Write.TestHelpers;
using Squidex.Infrastructure;
using Xunit;
#pragma warning disable CS0612 // Type or member is obsolete
namespace Squidex.Domain.Apps.Write.Contents
{
public class ContentEventTests
{
private readonly RefToken actor = new RefToken("User", Guid.NewGuid().ToString());
private readonly NamedId<Guid> appId = new NamedId<Guid>(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = new NamedId<Guid>(Guid.NewGuid(), "my-schema");
private readonly Guid contentId = Guid.NewGuid();
[Fact]
public void Should_migrate_content_published_to_content_status_changed()
{
var source = CreateEvent(new ContentPublished());
source.Migrate().ShouldBeSameEvent(CreateEvent(new ContentStatusChanged { Status = Status.Published }));
}
[Fact]
public void Should_migrate_content_unpublished_to_content_status_changed()
{
var source = CreateEvent(new ContentUnpublished());
source.Migrate().ShouldBeSameEvent(CreateEvent(new ContentStatusChanged { Status = Status.Draft }));
}
[Fact]
public void Should_migrate_content_restored_to_content_status_changed()
{
var source = CreateEvent(new ContentRestored());
source.Migrate().ShouldBeSameEvent(CreateEvent(new ContentStatusChanged { Status = Status.Draft }));
}
[Fact]
public void Should_migrate_content_archived_to_content_status_changed()
{
var source = CreateEvent(new ContentArchived());
source.Migrate().ShouldBeSameEvent(CreateEvent(new ContentStatusChanged { Status = Status.Archived }));
}
private T CreateEvent<T>(T contentEvent) where T : ContentEvent
{
contentEvent.Actor = actor;
contentEvent.AppId = appId;
contentEvent.SchemaId = schemaId;
contentEvent.ContentId = contentId;
return contentEvent;
}
}
}

139
tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentVersionLoaderTests.cs

@ -0,0 +1,139 @@
// ==========================================================================
// ContentVersionLoaderTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Xunit;
namespace Squidex.Domain.Apps.Write.Contents
{
public class ContentVersionLoaderTests
{
private readonly IEventStore eventStore = A.Fake<IEventStore>();
private readonly IStreamNameResolver nameResolver = A.Fake<IStreamNameResolver>();
private readonly EventDataFormatter formatter = A.Fake<EventDataFormatter>();
private readonly Guid id = Guid.NewGuid();
private readonly Guid appId = Guid.NewGuid();
private readonly string streamName = Guid.NewGuid().ToString();
private readonly ContentVersionLoader sut;
public ContentVersionLoaderTests()
{
A.CallTo(() => nameResolver.GetStreamName(typeof(ContentDomainObject), id))
.Returns(streamName);
sut = new ContentVersionLoader(eventStore, nameResolver, formatter);
}
[Fact]
public async Task Should_throw_exception_when_event_store_returns_no_events()
{
A.CallTo(() => eventStore.GetEventsAsync(streamName))
.Returns(new List<StoredEvent>());
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.LoadAsync(appId, id, -1));
}
[Fact]
public async Task Should_throw_exception_when_version_not_found()
{
A.CallTo(() => eventStore.GetEventsAsync(streamName))
.Returns(new List<StoredEvent>());
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.LoadAsync(appId, id, 3));
}
[Fact]
public async Task Should_throw_exception_when_content_is_from_another_event()
{
var eventData1 = new EventData();
var event1 = new ContentCreated { Data = new NamedContentData(), AppId = new NamedId<Guid>(Guid.NewGuid(), "my-app") };
var events = new List<StoredEvent>
{
new StoredEvent("0", 0, eventData1)
};
A.CallTo(() => eventStore.GetEventsAsync(streamName))
.Returns(events);
A.CallTo(() => formatter.Parse(eventData1, true))
.Returns(new Envelope<IEvent>(event1));
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.LoadAsync(appId, id, 0));
}
[Fact]
public async Task Should_load_content_from_created_event()
{
var eventData1 = new EventData();
var eventData2 = new EventData();
var event1 = new ContentCreated { Data = new NamedContentData(), AppId = new NamedId<Guid>(appId, "my-app") };
var event2 = new ContentStatusChanged();
var events = new List<StoredEvent>
{
new StoredEvent("0", 0, eventData1),
new StoredEvent("1", 1, eventData2)
};
A.CallTo(() => eventStore.GetEventsAsync(streamName))
.Returns(events);
A.CallTo(() => formatter.Parse(eventData1, true))
.Returns(new Envelope<IEvent>(event1));
A.CallTo(() => formatter.Parse(eventData2, true))
.Returns(new Envelope<IEvent>(event2));
var data = await sut.LoadAsync(appId, id, 3);
Assert.Same(event1.Data, data);
}
[Fact]
public async Task Should_load_content_from_correct_version()
{
var eventData1 = new EventData();
var eventData2 = new EventData();
var eventData3 = new EventData();
var event1 = new ContentCreated { Data = new NamedContentData(), AppId = new NamedId<Guid>(appId, "my-app") };
var event2 = new ContentUpdated { Data = new NamedContentData() };
var event3 = new ContentUpdated { Data = new NamedContentData() };
var events = new List<StoredEvent>
{
new StoredEvent("0", 0, eventData1),
new StoredEvent("1", 1, eventData2),
new StoredEvent("2", 2, eventData3)
};
A.CallTo(() => eventStore.GetEventsAsync(streamName))
.Returns(events);
A.CallTo(() => formatter.Parse(eventData1, true))
.Returns(new Envelope<IEvent>(event1));
A.CallTo(() => formatter.Parse(eventData2, true))
.Returns(new Envelope<IEvent>(event2));
A.CallTo(() => formatter.Parse(eventData3, true))
.Returns(new Envelope<IEvent>(event3));
var data = await sut.LoadAsync(appId, id, 1);
Assert.Equal(event2.Data, data);
}
}
}

27
tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/JsonFieldPropertiesTests.cs

@ -0,0 +1,27 @@
// ==========================================================================
// JsonFieldPropertiesTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Xunit;
namespace Squidex.Domain.Apps.Write.Schemas.Guards.FieldProperties
{
public class JsonFieldPropertiesTests
{
[Fact]
public void Should_add_error_if_editor_is_not_valid()
{
var sut = new JsonFieldProperties();
var errors = FieldPropertiesValidator.Validate(sut).ToList();
Assert.Empty(errors);
}
}
}

1
tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/NumberFieldPropertiesTests.cs

@ -7,7 +7,6 @@
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using FluentAssertions; using FluentAssertions;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;

1
tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/StringFieldPropertiesTests.cs

@ -7,7 +7,6 @@
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using FluentAssertions; using FluentAssertions;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;

20
tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/GuardSchemaTests.cs

@ -36,26 +36,26 @@ namespace Squidex.Domain.Apps.Write.Schemas.Guards
} }
[Fact] [Fact]
public async Task CanCreate_should_throw_exception_if_name_not_valid() public Task CanCreate_should_throw_exception_if_name_not_valid()
{ {
var command = new CreateSchema { AppId = appId, Name = "INVALID NAME" }; var command = new CreateSchema { AppId = appId, Name = "INVALID NAME" };
await Assert.ThrowsAsync<ValidationException>(() => GuardSchema.CanCreate(command, schemas)); return Assert.ThrowsAsync<ValidationException>(() => GuardSchema.CanCreate(command, schemas));
} }
[Fact] [Fact]
public async Task CanCreate_should_throw_exception_if_name_already_in_use() public Task CanCreate_should_throw_exception_if_name_already_in_use()
{ {
A.CallTo(() => schemas.FindSchemaByNameAsync(A<Guid>.Ignored, "new-schema")) A.CallTo(() => schemas.FindSchemaByNameAsync(A<Guid>.Ignored, "new-schema"))
.Returns(Task.FromResult(A.Fake<ISchemaEntity>())); .Returns(Task.FromResult(A.Fake<ISchemaEntity>()));
var command = new CreateSchema { AppId = appId, Name = "new-schema" }; var command = new CreateSchema { AppId = appId, Name = "new-schema" };
await Assert.ThrowsAsync<ValidationException>(() => GuardSchema.CanCreate(command, schemas)); return Assert.ThrowsAsync<ValidationException>(() => GuardSchema.CanCreate(command, schemas));
} }
[Fact] [Fact]
public async Task CanCreate_should_throw_exception_if_fields_not_valid() public Task CanCreate_should_throw_exception_if_fields_not_valid()
{ {
var command = new CreateSchema var command = new CreateSchema
{ {
@ -78,11 +78,11 @@ namespace Squidex.Domain.Apps.Write.Schemas.Guards
Name = "new-schema" Name = "new-schema"
}; };
await Assert.ThrowsAsync<ValidationException>(() => GuardSchema.CanCreate(command, schemas)); return Assert.ThrowsAsync<ValidationException>(() => GuardSchema.CanCreate(command, schemas));
} }
[Fact] [Fact]
public async Task CanCreate_should_throw_exception_if_fields_contain_duplicate_names() public Task CanCreate_should_throw_exception_if_fields_contain_duplicate_names()
{ {
var command = new CreateSchema var command = new CreateSchema
{ {
@ -105,15 +105,15 @@ namespace Squidex.Domain.Apps.Write.Schemas.Guards
Name = "new-schema" Name = "new-schema"
}; };
await Assert.ThrowsAsync<ValidationException>(() => GuardSchema.CanCreate(command, schemas)); return Assert.ThrowsAsync<ValidationException>(() => GuardSchema.CanCreate(command, schemas));
} }
[Fact] [Fact]
public async Task CanCreate_should_not_throw_exception_if_command_is_valid() public Task CanCreate_should_not_throw_exception_if_command_is_valid()
{ {
var command = new CreateSchema { AppId = appId, Name = "new-schema" }; var command = new CreateSchema { AppId = appId, Name = "new-schema" };
await GuardSchema.CanCreate(command, schemas); return GuardSchema.CanCreate(command, schemas);
} }
[Fact] [Fact]

138
tests/Squidex.Domain.Apps.Write.Tests/Webhooks/Guards/GuardWebhookTests.cs

@ -0,0 +1,138 @@
// ==========================================================================
// GuardWebhookTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Webhooks;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Write.Webhooks.Commands;
using Squidex.Infrastructure;
using Xunit;
namespace Squidex.Domain.Apps.Write.Webhooks.Guards
{
public class GuardWebhookTests
{
private readonly ISchemaProvider schemas = A.Fake<ISchemaProvider>();
public GuardWebhookTests()
{
A.CallTo(() => schemas.FindSchemaByIdAsync(A<Guid>.Ignored, false))
.Returns(A.Fake<ISchemaEntity>());
}
[Fact]
public async Task CanCreate_should_throw_exception_if_url_defined()
{
var command = new CreateWebhook();
await Assert.ThrowsAsync<ValidationException>(() => GuardWebhook.CanCreate(command, schemas));
}
[Fact]
public async Task CanCreate_should_throw_exception_if_url_not_valid()
{
var command = new CreateWebhook { Url = new Uri("/invalid", UriKind.Relative) };
await Assert.ThrowsAsync<ValidationException>(() => GuardWebhook.CanCreate(command, schemas));
}
[Fact]
public async Task CanCreate_should_throw_exception_if_schema_id_not_found()
{
A.CallTo(() => schemas.FindSchemaByIdAsync(A<Guid>.Ignored, false))
.Returns(Task.FromResult<ISchemaEntity>(null));
var command = new CreateWebhook
{
Schemas = new List<WebhookSchema>
{
new WebhookSchema()
},
Url = new Uri("/invalid", UriKind.Relative)
};
await Assert.ThrowsAsync<ValidationException>(() => GuardWebhook.CanCreate(command, schemas));
}
[Fact]
public async Task CanCreate_should_not_throw_exception_if_schema_id_found()
{
var command = new CreateWebhook
{
Schemas = new List<WebhookSchema>
{
new WebhookSchema()
},
Url = new Uri("/invalid", UriKind.Relative)
};
await Assert.ThrowsAsync<ValidationException>(() => GuardWebhook.CanCreate(command, schemas));
}
[Fact]
public async Task CanUpdate_should_throw_exception_if_url_not_defined()
{
var command = new UpdateWebhook();
await Assert.ThrowsAsync<ValidationException>(() => GuardWebhook.CanUpdate(command, schemas));
}
[Fact]
public async Task CanUpdate_should_throw_exception_if_url_not_valid()
{
var command = new UpdateWebhook { Url = new Uri("/invalid", UriKind.Relative) };
await Assert.ThrowsAsync<ValidationException>(() => GuardWebhook.CanUpdate(command, schemas));
}
[Fact]
public async Task CanUpdate_should_throw_exception_if_schema_id_not_found()
{
A.CallTo(() => schemas.FindSchemaByIdAsync(A<Guid>.Ignored, false))
.Returns(Task.FromResult<ISchemaEntity>(null));
var command = new UpdateWebhook
{
Schemas = new List<WebhookSchema>
{
new WebhookSchema()
},
Url = new Uri("/invalid", UriKind.Relative)
};
await Assert.ThrowsAsync<ValidationException>(() => GuardWebhook.CanUpdate(command, schemas));
}
[Fact]
public async Task CanUpdate_should_not_throw_exception_if_schema_id_found()
{
var command = new UpdateWebhook
{
Schemas = new List<WebhookSchema>
{
new WebhookSchema()
},
Url = new Uri("/invalid", UriKind.Relative)
};
await Assert.ThrowsAsync<ValidationException>(() => GuardWebhook.CanUpdate(command, schemas));
}
[Fact]
public void CanDelete_should_not_throw_exception()
{
var command = new DeleteWebhook();
GuardWebhook.CanDelete(command);
}
}
}

115
tests/Squidex.Domain.Apps.Write.Tests/Webhooks/WebhookCommandMiddlewareTests.cs

@ -0,0 +1,115 @@
// ==========================================================================
// WebhookCommandMiddlewareTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Webhooks;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Write.TestHelpers;
using Squidex.Domain.Apps.Write.Webhooks.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Xunit;
namespace Squidex.Domain.Apps.Write.Webhooks
{
public class WebhookCommandMiddlewareTests : HandlerTestBase<WebhookDomainObject>
{
private readonly ISchemaProvider schemas = A.Fake<ISchemaProvider>();
private readonly WebhookCommandMiddleware sut;
private readonly WebhookDomainObject webhook;
private readonly Uri url = new Uri("http://squidex.io");
private readonly Guid schemaId = Guid.NewGuid();
private readonly Guid webhookId = Guid.NewGuid();
private readonly List<WebhookSchema> webhookSchemas;
public WebhookCommandMiddlewareTests()
{
A.CallTo(() => schemas.FindSchemaByIdAsync(schemaId, false))
.Returns(A.Fake<ISchemaEntity>());
webhook = new WebhookDomainObject(webhookId, -1);
webhookSchemas = new List<WebhookSchema>
{
new WebhookSchema { SchemaId = schemaId }
};
sut = new WebhookCommandMiddleware(Handler, schemas);
}
[Fact]
public async Task Create_should_create_domain_object()
{
var context = CreateContextForCommand(new CreateWebhook { Schemas = webhookSchemas, Url = url, WebhookId = webhookId });
await TestCreate(webhook, async _ =>
{
await sut.HandleAsync(context);
});
A.CallTo(() => schemas.FindSchemaByIdAsync(schemaId, false)).MustHaveHappened();
}
[Fact]
public async Task Update_should_update_domain_object()
{
var context = CreateContextForCommand(new UpdateWebhook { Schemas = webhookSchemas, Url = url, WebhookId = webhookId });
A.CallTo(() => schemas.FindSchemaByIdAsync(schemaId, false)).Returns(A.Fake<ISchemaEntity>());
CreateWebhook();
await TestUpdate(webhook, async _ =>
{
await sut.HandleAsync(context);
});
A.CallTo(() => schemas.FindSchemaByIdAsync(schemaId, false)).MustHaveHappened();
}
[Fact]
public async Task Update_should_throw_exception_when_schema_is_not_found()
{
var context = CreateContextForCommand(new UpdateWebhook { Schemas = webhookSchemas, Url = url, WebhookId = webhookId });
A.CallTo(() => schemas.FindSchemaByIdAsync(schemaId, false)).Returns((ISchemaEntity)null);
CreateWebhook();
await Assert.ThrowsAsync<ValidationException>(async () =>
{
await TestCreate(webhook, async _ =>
{
await sut.HandleAsync(context);
});
});
}
[Fact]
public async Task Delete_should_update_domain_object()
{
CreateWebhook();
var command = CreateContextForCommand(new DeleteWebhook { WebhookId = webhookId });
await TestUpdate(webhook, async _ =>
{
await sut.HandleAsync(command);
});
}
private void CreateWebhook()
{
webhook.Create(new CreateWebhook { Url = url });
}
}
}

159
tests/Squidex.Domain.Apps.Write.Tests/Webhooks/WebhookDomainObjectTests.cs

@ -0,0 +1,159 @@
// ==========================================================================
// WebhookDomainObjectTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Squidex.Domain.Apps.Events.Webhooks;
using Squidex.Domain.Apps.Write.TestHelpers;
using Squidex.Domain.Apps.Write.Webhooks.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS;
using Xunit;
namespace Squidex.Domain.Apps.Write.Webhooks
{
public class WebhookDomainObjectTests : HandlerTestBase<WebhookDomainObject>
{
private readonly Uri url = new Uri("http://squidex.io");
private readonly WebhookDomainObject sut;
public Guid WebhookId { get; } = Guid.NewGuid();
public WebhookDomainObjectTests()
{
sut = new WebhookDomainObject(WebhookId, 0);
}
[Fact]
public void Create_should_throw_exception_if_created()
{
sut.Create(new CreateWebhook { Url = url });
Assert.Throws<DomainException>(() =>
{
sut.Create(CreateWebhookCommand(new CreateWebhook { Url = url }));
});
}
[Fact]
public void Create_should_create_events()
{
var command = new CreateWebhook { Url = url };
sut.Create(CreateWebhookCommand(command));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateWebhookEvent(new WebhookCreated
{
Url = url,
Schemas = command.Schemas,
SharedSecret = command.SharedSecret,
WebhookId = command.WebhookId
})
);
}
[Fact]
public void Update_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.Update(CreateWebhookCommand(new UpdateWebhook { Url = url }));
});
}
[Fact]
public void Update_should_throw_exception_if_webhook_is_deleted()
{
CreateWebhook();
DeleteWebhook();
Assert.Throws<DomainException>(() =>
{
sut.Update(CreateWebhookCommand(new UpdateWebhook { Url = url }));
});
}
[Fact]
public void Update_should_create_events()
{
CreateWebhook();
var command = new UpdateWebhook { Url = url };
sut.Update(CreateWebhookCommand(command));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateWebhookEvent(new WebhookUpdated { Url = url, Schemas = command.Schemas })
);
}
[Fact]
public void Delete_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.Delete(CreateWebhookCommand(new DeleteWebhook()));
});
}
[Fact]
public void Delete_should_throw_exception_if_already_deleted()
{
CreateWebhook();
DeleteWebhook();
Assert.Throws<DomainException>(() =>
{
sut.Delete(CreateWebhookCommand(new DeleteWebhook()));
});
}
[Fact]
public void Delete_should_update_properties_create_events()
{
CreateWebhook();
sut.Delete(CreateWebhookCommand(new DeleteWebhook()));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateWebhookEvent(new WebhookDeleted())
);
}
private void CreateWebhook()
{
sut.Create(CreateWebhookCommand(new CreateWebhook { Url = url }));
((IAggregate)sut).ClearUncommittedEvents();
}
private void DeleteWebhook()
{
sut.Delete(CreateWebhookCommand(new DeleteWebhook()));
((IAggregate)sut).ClearUncommittedEvents();
}
protected T CreateWebhookEvent<T>(T @event) where T : WebhookEvent
{
@event.WebhookId = WebhookId;
return CreateEvent(@event);
}
protected T CreateWebhookCommand<T>(T command) where T : WebhookAggregateCommand
{
command.WebhookId = WebhookId;
return CreateCommand(command);
}
}
}
Loading…
Cancel
Save