Browse Source

Patterns simplified.

pull/370/head
Sebastian 7 years ago
parent
commit
03c23a8ec7
  1. 11
      src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs
  2. 20
      src/Squidex.Domain.Apps.Entities/Apps/AppExtensions.cs
  3. 77
      src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs
  4. 27
      src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs
  5. 8
      src/Squidex.Domain.Apps.Entities/Apps/Commands/ConfigurePatterns.cs
  6. 8
      src/Squidex.Domain.Apps.Entities/Apps/Commands/UpsertAppPattern.cs
  7. 94
      src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs
  8. 5
      src/Squidex.Domain.Apps.Entities/Apps/InitialPatterns.cs
  9. 3
      src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlanBillingManager.cs
  10. 3
      src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/NoopAppPlanBillingManager.cs
  11. 5
      src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs
  12. 21
      src/Squidex.Domain.Apps.Entities/EntityExtensions.cs
  13. 84
      src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs
  14. 8
      src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs
  15. 18
      src/Squidex.Domain.Apps.Events/Apps/AppPatternsConfigured.cs
  16. 2
      src/Squidex.Shared/Permissions.cs
  17. 2
      src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs
  18. 2
      src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs
  19. 66
      src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs
  20. 13
      src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs
  21. 31
      src/Squidex/Areas/Api/Controllers/Apps/Models/ConfigurePatternsDto.cs
  22. 44
      src/Squidex/Areas/Api/Controllers/Apps/Models/UpdatePatternDto.cs
  23. 2
      src/Squidex/Config/Domain/EntitiesServices.cs
  24. 26
      tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs
  25. 115
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs
  26. 3
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Billing/NoopAppPlanBillingManagerTests.cs
  27. 209
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs
  28. 2
      tests/Squidex.Domain.Apps.Entities.Tests/History/Notifications/NotificationEmailEventConsumerTests.cs
  29. 4
      tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs
  30. 27
      tools/Migrate_01/Migrations/AddPatterns.cs

11
src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
@ -26,6 +27,16 @@ namespace Squidex.Domain.Apps.Core.Apps
{
}
public static AppPatterns Create(IEnumerable<AppPattern> patterns)
{
if (patterns == null || !patterns.Any())
{
return Empty;
}
return new AppPatterns(patterns.Select(x => new KeyValuePair<Guid, AppPattern>(Guid.NewGuid(), x)).ToArray());
}
[Pure]
public AppPatterns Remove(Guid id)
{

20
src/Squidex.Domain.Apps.Entities/Apps/AppExtensions.cs

@ -0,0 +1,20 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps
{
public static class AppExtensions
{
public static NamedId<Guid> NamedId(this IAppEntity app)
{
return new NamedId<Guid>(app.Id, app.Name);
}
}
}

77
src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs

@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
@ -157,28 +158,12 @@ namespace Squidex.Domain.Apps.Entities.Apps
UpdateRole(c);
});
case AddPattern addPattern:
return UpdateAsync(addPattern, c =>
case ConfigurePatterns configurePatterns:
return UpdateAsync(configurePatterns, c =>
{
GuardAppPatterns.CanAdd(Snapshot.Patterns, c);
GuardAppPatterns.CanConfigure(c);
AddPattern(c);
});
case DeletePattern deletePattern:
return UpdateAsync(deletePattern, c =>
{
GuardAppPatterns.CanDelete(Snapshot.Patterns, c);
DeletePattern(c);
});
case UpdatePattern updatePattern:
return UpdateAsync(updatePattern, c =>
{
GuardAppPatterns.CanUpdate(Snapshot.Patterns, c);
UpdatePattern(c);
ConfigurePatterns(c);
});
case ChangePlan changePlan:
@ -194,7 +179,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
}
else
{
var result = await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier, Snapshot.Id, Snapshot.Name, c.PlanId);
var result = await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier, Snapshot.NamedId(), c.PlanId);
switch (result)
{
@ -213,7 +198,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case ArchiveApp archiveApp:
return UpdateAsync(archiveApp, async c =>
{
await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier, Snapshot.Id, Snapshot.Name, null);
await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier, Snapshot.NamedId(), null);
ArchiveApp(c);
});
@ -234,16 +219,12 @@ namespace Squidex.Domain.Apps.Entities.Apps
var events = new List<AppEvent>
{
CreateInitalEvent(command.Name),
CreateInitialEvent(command.Name),
CreateInitialLanguage(),
CreateInitialOwner(command.Actor),
CreateInitialLanguage()
CreateInitialPatterns(initialPatterns)
};
foreach (var pattern in initialPatterns)
{
events.Add(CreateInitialPattern(pattern.Key, pattern.Value));
}
foreach (var @event in events)
{
@event.Actor = command.Actor;
@ -311,19 +292,9 @@ namespace Squidex.Domain.Apps.Entities.Apps
RaiseEvent(SimpleMapper.Map(command, new AppPlanReset()));
}
public void AddPattern(AddPattern command)
public void ConfigurePatterns(ConfigurePatterns command)
{
RaiseEvent(SimpleMapper.Map(command, new AppPatternAdded()));
}
public void DeletePattern(DeletePattern command)
{
RaiseEvent(SimpleMapper.Map(command, new AppPatternDeleted()));
}
public void UpdatePattern(UpdatePattern command)
{
RaiseEvent(SimpleMapper.Map(command, new AppPatternUpdated()));
RaiseEvent(SimpleMapper.Map(command, CreatePatterns(command)));
}
public void AddRole(AddRole command)
@ -358,22 +329,17 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
if (@event.AppId == null)
{
@event.AppId = NamedId.Of(Snapshot.Id, Snapshot.Name);
@event.AppId = Snapshot.NamedId();
}
RaiseEvent(Envelope.Create(@event));
}
private static AppCreated CreateInitalEvent(string name)
private static AppCreated CreateInitialEvent(string name)
{
return new AppCreated { Name = name };
}
private static AppPatternAdded CreateInitialPattern(Guid id, AppPattern pattern)
{
return new AppPatternAdded { PatternId = id, Name = pattern.Name, Pattern = pattern.Pattern, Message = pattern.Message };
}
private static AppLanguageAdded CreateInitialLanguage()
{
return new AppLanguageAdded { Language = Language.EN };
@ -384,6 +350,21 @@ namespace Squidex.Domain.Apps.Entities.Apps
return new AppContributorAssigned { ContributorId = actor.Identifier, Role = Role.Owner };
}
private static AppPatternsConfigured CreateInitialPatterns(InitialPatterns patterns)
{
return new AppPatternsConfigured { Patterns = patterns.ToArray() };
}
private static AppPatternsConfigured CreatePatterns(ConfigurePatterns command)
{
return new AppPatternsConfigured { Patterns = command.Patterns?.Select(Convert).ToArray() };
}
private static AppPattern Convert(UpsertAppPattern source)
{
return new AppPattern(source.Name, source.Pattern, source.Message);
}
public Task<J<IAppEntity>> GetStateAsync()
{
return J.AsTask<IAppEntity>(Snapshot);

27
src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs

@ -1,27 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Domain.Apps.Entities.Apps.Commands
{
public sealed class AddPattern : AppCommand
{
public Guid PatternId { get; set; }
public string Name { get; set; }
public string Pattern { get; set; }
public string Message { get; set; }
public AddPattern()
{
PatternId = Guid.NewGuid();
}
}
}

8
src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs → src/Squidex.Domain.Apps.Entities/Apps/Commands/ConfigurePatterns.cs

@ -1,16 +1,14 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Domain.Apps.Entities.Apps.Commands
{
public sealed class DeletePattern : AppCommand
public sealed class ConfigurePatterns : AppCommand
{
public Guid PatternId { get; set; }
public UpsertAppPattern[] Patterns { get; set; }
}
}

8
src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs → src/Squidex.Domain.Apps.Entities/Apps/Commands/UpsertAppPattern.cs

@ -1,18 +1,14 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Domain.Apps.Entities.Apps.Commands
{
public sealed class UpdatePattern : AppCommand
public sealed class UpsertAppPattern
{
public Guid PatternId { get; set; }
public string Name { get; set; }
public string Pattern { get; set; }

94
src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs

@ -4,9 +4,9 @@
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure;
@ -14,88 +14,64 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
public static class GuardAppPatterns
{
public static void CanAdd(AppPatterns patterns, AddPattern command)
public static void CanConfigure(ConfigurePatterns command)
{
Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot add pattern.", e =>
Validate.It(() => "Cannot configure patterns.", e =>
{
if (command.PatternId == Guid.Empty)
if (command.Patterns?.Length > 0)
{
e(Not.Defined("Id"), nameof(command.PatternId));
}
var patternIndex = 0;
var patternPrefix = string.Empty;
if (string.IsNullOrWhiteSpace(command.Name))
{
e(Not.Defined("Name"), nameof(command.Name));
}
foreach (var pattern in command.Patterns)
{
patternIndex++;
patternPrefix = $"{nameof(command.Patterns)}[{patternIndex}]";
if (patterns.Values.Any(x => x.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase)))
{
e("A pattern with the same name already exists.");
}
ValidatePattern(pattern, patternPrefix, e);
}
if (string.IsNullOrWhiteSpace(command.Pattern))
{
e(Not.Defined("Pattern"), nameof(command.Pattern));
}
else if (!command.Pattern.IsValidRegex())
{
e(Not.Valid("Pattern"), nameof(command.Pattern));
}
var validNames = command.Patterns.Select(p => p?.Name).Where(p => !string.IsNullOrWhiteSpace(p));
if (patterns.Values.Any(x => x.Pattern == command.Pattern))
{
e("This pattern already exists but with another name.");
}
});
}
if (validNames.Count() != validNames.Distinct(StringComparer.OrdinalIgnoreCase).Count())
{
e("Two patterns with the same name exist.", nameof(command.Patterns));
}
public static void CanDelete(AppPatterns patterns, DeletePattern command)
{
Guard.NotNull(command, nameof(command));
var validPatterns = command.Patterns.Select(p => p?.Pattern).Where(p => !string.IsNullOrWhiteSpace(p));
if (!patterns.ContainsKey(command.PatternId))
{
throw new DomainObjectNotFoundException(command.PatternId.ToString(), typeof(AppPattern));
}
if (validPatterns.Count() != validPatterns.Distinct().Count())
{
e("Two patterns with the same expression exist.", nameof(command.Patterns));
}
}
});
}
public static void CanUpdate(AppPatterns patterns, UpdatePattern command)
private static void ValidatePattern(UpsertAppPattern pattern, string prefix, AddValidation e)
{
Guard.NotNull(command, nameof(command));
if (!patterns.ContainsKey(command.PatternId))
if (pattern == null)
{
throw new DomainObjectNotFoundException(command.PatternId.ToString(), typeof(AppPattern));
e(Not.Defined("Pattern"), prefix);
}
Validate.It(() => "Cannot update pattern.", e =>
else
{
if (string.IsNullOrWhiteSpace(command.Name))
if (string.IsNullOrWhiteSpace(pattern.Name))
{
e(Not.Defined("Name"), nameof(command.Name));
e(Not.Defined("Name"), $"{prefix}.{nameof(pattern.Name)}");
}
if (patterns.Any(x => x.Key != command.PatternId && x.Value.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase)))
if (string.IsNullOrWhiteSpace(pattern.Pattern))
{
e("A pattern with the same name already exists.");
e(Not.Defined("Expression"), $"{prefix}.{nameof(pattern.Pattern)}");
}
if (string.IsNullOrWhiteSpace(command.Pattern))
else if (!pattern.Pattern.IsValidRegex())
{
e(Not.Defined("Pattern"), nameof(command.Pattern));
e(Not.Valid("Expression"), $"{prefix}.{nameof(pattern.Pattern)}");
}
else if (!command.Pattern.IsValidRegex())
{
e(Not.Valid("Pattern"), nameof(command.Pattern));
}
if (patterns.Any(x => x.Key != command.PatternId && x.Value.Pattern == command.Pattern))
{
e("This pattern already exists but with another name.");
}
});
}
}
}
}

5
src/Squidex.Domain.Apps.Entities/Apps/InitialPatterns.cs

@ -5,19 +5,18 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Apps;
namespace Squidex.Domain.Apps.Entities.Apps
{
public sealed class InitialPatterns : Dictionary<Guid, AppPattern>
public sealed class InitialPatterns : List<AppPattern>
{
public InitialPatterns()
{
}
public InitialPatterns(Dictionary<Guid, AppPattern> patterns)
public InitialPatterns(IEnumerable<AppPattern> patterns)
: base(patterns)
{
}

3
src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlanBillingManager.cs

@ -7,6 +7,7 @@
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps.Services
{
@ -14,7 +15,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Services
{
bool HasPortal { get; }
Task<IChangePlanResult> ChangePlanAsync(string userId, Guid appId, string appName, string planId);
Task<IChangePlanResult> ChangePlanAsync(string userId, NamedId<Guid> appId, string planId);
Task<string> GetPortalLinkAsync(string userId);
}

3
src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/NoopAppPlanBillingManager.cs

@ -7,6 +7,7 @@
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps.Services.Implementations
{
@ -17,7 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Services.Implementations
get { return false; }
}
public Task<IChangePlanResult> ChangePlanAsync(string userId, Guid appId, string appName, string planId)
public Task<IChangePlanResult> ChangePlanAsync(string userId, NamedId<Guid> appId, string planId)
{
return Task.FromResult<IChangePlanResult>(new PlanResetResult());
}

5
src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs

@ -92,6 +92,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.State
Clients = Clients.Revoke(@event.Id);
}
protected void On(AppPatternsConfigured @event)
{
Patterns = AppPatterns.Create(@event.Patterns);
}
protected void On(AppPatternAdded @event)
{
Patterns = Patterns.Add(@event.PatternId, @event.Name, @event.Pattern, @event.Message);

21
src/Squidex.Domain.Apps.Entities/EntityExtensions.cs

@ -0,0 +1,21 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities
{
public static class EntityExtensions
{
public static NamedId<Guid> NamedId(this IAppEntity entity)
{
return new NamedId<Guid>(entity.Id, entity.Name);
}
}
}

84
src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs

@ -142,49 +142,73 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
fieldIndex++;
fieldPrefix = $"Fields[{fieldIndex}]";
if (!field.Partitioning.IsValidPartitioning())
{
e(Not.Valid("Partitioning"), $"{fieldPrefix}.{nameof(field.Partitioning)}");
}
ValidateRootField(field, fieldPrefix, e);
}
ValidateField(field, fieldPrefix, e);
if (command.Fields.Select(x => x?.Name).Distinct().Count() != command.Fields.Count)
{
e("Fields cannot have duplicate names.", nameof(command.Fields));
}
}
}
if (field.Nested?.Count > 0)
{
if (field.Properties is ArrayFieldProperties)
{
var nestedIndex = 0;
var nestedPrefix = string.Empty;
private static void ValidateRootField(UpsertSchemaField field, string prefix, AddValidation e)
{
if (field == null)
{
e(Not.Defined("Field"), prefix);
}
else
{
if (!field.Partitioning.IsValidPartitioning())
{
e(Not.Valid("Partitioning"), $"{prefix}.{nameof(field.Partitioning)}");
}
foreach (var nestedField in field.Nested)
{
nestedIndex++;
nestedPrefix = $"{fieldPrefix}.Nested[{nestedIndex}]";
ValidateField(field, prefix, e);
if (nestedField.Properties is ArrayFieldProperties)
{
e("Nested field cannot be array fields.", $"{nestedPrefix}.{nameof(nestedField.Properties)}");
}
if (field.Nested?.Count > 0)
{
if (field.Properties is ArrayFieldProperties)
{
var nestedIndex = 0;
var nestedPrefix = string.Empty;
ValidateField(nestedField, nestedPrefix, e);
}
}
else if (field.Nested.Count > 0)
foreach (var nestedField in field.Nested)
{
e("Only array fields can have nested fields.", $"{fieldPrefix}.{nameof(field.Partitioning)}");
}
nestedIndex++;
nestedPrefix = $"{prefix}.Nested[{nestedIndex}]";
if (field.Nested.Select(x => x.Name).Distinct().Count() != field.Nested.Count)
{
e("Fields cannot have duplicate names.", $"{fieldPrefix}.Nested");
ValidateNestedField(nestedField, nestedPrefix, e);
}
}
else if (field.Nested.Count > 0)
{
e("Only array fields can have nested fields.", $"{prefix}.{nameof(field.Partitioning)}");
}
if (field.Nested.Select(x => x.Name).Distinct().Count() != field.Nested.Count)
{
e("Fields cannot have duplicate names.", $"{prefix}.Nested");
}
}
}
}
if (command.Fields.Select(x => x.Name).Distinct().Count() != command.Fields.Count)
private static void ValidateNestedField(UpsertSchemaNestedField nestedField, string prefix, AddValidation e)
{
if (nestedField == null)
{
e(Not.Defined("Field"), prefix);
}
else
{
if (nestedField.Properties is ArrayFieldProperties)
{
e("Fields cannot have duplicate names.", nameof(command.Fields));
e("Nested field cannot be array fields.", $"{prefix}.{nameof(nestedField.Properties)}");
}
ValidateField(nestedField, prefix, e);
}
}

8
src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs

@ -321,7 +321,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{
if (id.HasValue && Snapshot.SchemaDef.FieldsById.TryGetValue(id.Value, out var field))
{
return NamedId.Of(field.Id, field.Name);
return field.NamedId();
}
return null;
@ -333,13 +333,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{
if (Snapshot.SchemaDef.FieldsById.TryGetValue(pc.ParentFieldId.Value, out var field))
{
pe.ParentFieldId = NamedId.Of(field.Id, field.Name);
pe.ParentFieldId = field.NamedId();
if (command is FieldCommand fc && @event is FieldEvent fe)
{
if (field is IArrayField arrayField && arrayField.FieldsById.TryGetValue(fc.FieldId, out var nestedField))
{
fe.FieldId = NamedId.Of(nestedField.Id, nestedField.Name);
fe.FieldId = nestedField.NamedId();
}
}
}
@ -357,7 +357,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{
if (@event.SchemaId == null)
{
@event.SchemaId = NamedId.Of(Snapshot.Id, Snapshot.SchemaDef.Name);
@event.SchemaId = Snapshot.NamedId();
}
if (@event.AppId == null)

18
src/Squidex.Domain.Apps.Events/Apps/AppPatternsConfigured.cs

@ -0,0 +1,18 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events.Apps
{
[EventType(nameof(AppPatternsConfigured))]
public sealed class AppPatternsConfigured : AppEvent
{
public AppPattern[] Patterns { get; set; }
}
}

2
src/Squidex.Shared/Permissions.cs

@ -80,9 +80,7 @@ namespace Squidex.Shared
public const string AppPatterns = "squidex.apps.{app}.patterns";
public const string AppPatternsRead = "squidex.apps.{app}.patterns.read";
public const string AppPatternsCreate = "squidex.apps.{app}.patterns.create";
public const string AppPatternsUpdate = "squidex.apps.{app}.patterns.update";
public const string AppPatternsDelete = "squidex.apps.{app}.patterns.delete";
public const string AppBackups = "squidex.apps.{app}.backups";
public const string AppBackupsRead = "squidex.apps.{app}.backups.read";

2
src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs

@ -57,7 +57,7 @@ namespace Squidex.Web.CommandMiddlewares
throw new InvalidOperationException("Cannot resolve app.");
}
return NamedId.Of(appFeature.App.Id, appFeature.App.Name);
return appFeature.App.NamedId();
}
}
}

2
src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs

@ -69,7 +69,7 @@ namespace Squidex.Web.CommandMiddlewares
if (appFeature?.App != null)
{
appId = NamedId.Of(appFeature.App.Id, appFeature.App.Name);
appId = appFeature.App.NamedId();
}
}

66
src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs

@ -11,7 +11,6 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure.Commands;
using Squidex.Shared;
using Squidex.Web;
@ -55,73 +54,24 @@ namespace Squidex.Areas.Api.Controllers.Apps
}
/// <summary>
/// Create a new app pattern.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="request">Pattern to be added to the app.</param>
/// <returns>
/// 201 => Pattern generated.
/// 400 => Pattern request not valid.
/// 404 => App not found.
/// </returns>
[HttpPost]
[Route("apps/{app}/patterns/")]
[ProducesResponseType(typeof(AppPatternDto), 201)]
[ApiPermission(Permissions.AppPatternsCreate)]
[ApiCosts(1)]
public async Task<IActionResult> PostPattern(string app, [FromBody] UpdatePatternDto request)
{
var command = request.ToAddCommand();
await CommandBus.PublishAsync(command);
var response = AppPatternDto.FromCommand(command);
return CreatedAtAction(nameof(GetPatterns), new { app }, response);
}
/// <summary>
/// Update an existing app pattern.
/// Updates all app pattern.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="id">The id of the pattern to be updated.</param>
/// <param name="request">Pattern to be updated for the app.</param>
/// <param name="request">Patterns to be updated for the app.</param>
/// <returns>
/// 204 => Pattern updated.
/// 400 => Pattern request not valid.
/// 404 => Pattern or app not found.
/// 204 => Patterns updated.
/// 400 => Patterns request not valid.
/// 404 => App not found.
/// </returns>
[HttpPut]
[Route("apps/{app}/patterns/{id}/")]
[Route("apps/{app}/patterns/")]
[ProducesResponseType(typeof(AppPatternDto), 201)]
[ApiPermission(Permissions.AppPatternsUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> UpdatePattern(string app, Guid id, [FromBody] UpdatePatternDto request)
{
await CommandBus.PublishAsync(request.ToUpdateCommand(id));
return NoContent();
}
/// <summary>
/// Delete an existing app pattern.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="id">The id of the pattern to be deleted.</param>
/// <returns>
/// 204 => Pattern removed.
/// 404 => Pattern or app not found.
/// </returns>
/// <remarks>
/// Schemas using this pattern will still function using the same Regular Expression.
/// </remarks>
[HttpDelete]
[Route("apps/{app}/patterns/{id}/")]
[ApiPermission(Permissions.AppPatternsDelete)]
[ApiCosts(1)]
public async Task<IActionResult> DeletePattern(string app, Guid id)
public async Task<IActionResult> UpdatePatterns(string app, Guid id, [FromBody] ConfigurePatternsDto request)
{
await CommandBus.PublishAsync(new DeletePattern { PatternId = id });
await CommandBus.PublishAsync(request.ToConfigureCommand());
return NoContent();
}

13
src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs

@ -9,18 +9,12 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Apps.Models
{
public sealed class AppPatternDto
{
/// <summary>
/// Unique id of the pattern.
/// </summary>
public Guid PatternId { get; set; }
/// <summary>
/// The name of the suggestion.
/// </summary>
@ -40,12 +34,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
public static AppPatternDto FromKvp(KeyValuePair<Guid, AppPattern> kvp)
{
return SimpleMapper.Map(kvp.Value, new AppPatternDto { PatternId = kvp.Key });
}
public static AppPatternDto FromCommand(AddPattern command)
{
return SimpleMapper.Map(command, new AppPatternDto());
return SimpleMapper.Map(kvp.Value, new AppPatternDto());
}
}
}

31
src/Squidex/Areas/Api/Controllers/Apps/Models/ConfigurePatternsDto.cs

@ -0,0 +1,31 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Apps.Models
{
public sealed class ConfigurePatternsDto
{
/// <summary>
/// The list of patterns.
/// </summary>
[Required]
public AppPatternDto[] Patterns { get; set; }
public ConfigurePatterns ToConfigureCommand()
{
return new ConfigurePatterns
{
Patterns = Patterns?.Select(p => SimpleMapper.Map(p, new UpsertAppPattern())).ToArray()
};
}
}
}

44
src/Squidex/Areas/Api/Controllers/Apps/Models/UpdatePatternDto.cs

@ -1,44 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Apps.Models
{
public class UpdatePatternDto
{
/// <summary>
/// The name of the suggestion.
/// </summary>
[Required]
public string Name { get; set; }
/// <summary>
/// The regex pattern.
/// </summary>
[Required]
public string Pattern { get; set; }
/// <summary>
/// The regex message.
/// </summary>
public string Message { get; set; }
public AddPattern ToAddCommand()
{
return SimpleMapper.Map(this, new AddPattern());
}
public UpdatePattern ToUpdateCommand(Guid id)
{
return SimpleMapper.Map(this, new UpdatePattern { PatternId = id });
}
}
}

2
src/Squidex/Config/Domain/EntitiesServices.cs

@ -144,7 +144,7 @@ namespace Squidex.Config.Domain
if (!string.IsNullOrWhiteSpace(pattern.Key) &&
!string.IsNullOrWhiteSpace(pattern.Value))
{
result[Guid.NewGuid()] = new AppPattern(pattern.Key, pattern.Value);
result.Add(new AppPattern(pattern.Key, pattern.Value));
}
}

26
tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Linq;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Apps;
using Xunit;
@ -25,6 +26,31 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
patterns_0 = AppPatterns.Empty.Add(firstId, "Default", "Default Pattern", "Message");
}
[Fact]
public void Should_create_patterns()
{
var pattern = new AppPattern("NewPattern", "New Pattern", "Message");
var patterns = AppPatterns.Create(Enumerable.Repeat(pattern, 1));
Assert.Same(pattern, patterns.Values.First());
}
[Fact]
public void Should_create_empty_from_null_enumerable()
{
var patterns = AppPatterns.Create(null);
Assert.Same(AppPatterns.Empty, patterns);
}
[Fact]
public void Should_create_empty_from_empty_enumerable()
{
var patterns = AppPatterns.Create(Enumerable.Empty<AppPattern>());
Assert.Same(AppPatterns.Empty, patterns);
}
[Fact]
public void Should_add_pattern()
{

115
tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs

@ -7,8 +7,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FakeItEasy;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Services;
@ -37,9 +39,6 @@ namespace Squidex.Domain.Apps.Entities.Apps
private readonly string planIdPaid = "premium";
private readonly string planIdFree = "free";
private readonly AppGrain sut;
private readonly Guid patternId1 = Guid.NewGuid();
private readonly Guid patternId2 = Guid.NewGuid();
private readonly Guid patternId3 = Guid.NewGuid();
private readonly InitialPatterns initialPatterns;
protected override Guid Id
@ -60,8 +59,8 @@ namespace Squidex.Domain.Apps.Entities.Apps
initialPatterns = new InitialPatterns
{
{ patternId1, new AppPattern("Number", "[0-9]") },
{ patternId2, new AppPattern("Numbers", "[0-9]*") }
new AppPattern("Number", "[0-9]"),
new AppPattern("Numbers", "[0-9]*")
};
sut = new AppGrain(initialPatterns, Store, A.Dummy<ISemanticLog>(), appPlansProvider, appPlansBillingManager, userResolver);
@ -84,17 +83,16 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(EntityCreatedResult.Create(Id, 4));
result.ShouldBeEquivalent(EntityCreatedResult.Create(Id, 3));
Assert.Equal(AppName, sut.Snapshot.Name);
LastEvents
.ShouldHaveSameEvents(
CreateEvent(new AppCreated { Name = AppName }),
CreateEvent(new AppContributorAssigned { ContributorId = User.Identifier, Role = Role.Owner }),
CreateEvent(new AppLanguageAdded { Language = Language.EN }),
CreateEvent(new AppPatternAdded { PatternId = patternId1, Name = "Number", Pattern = "[0-9]" }),
CreateEvent(new AppPatternAdded { PatternId = patternId2, Name = "Numbers", Pattern = "[0-9]*" })
CreateEvent(new AppContributorAssigned { ContributorId = User.Identifier, Role = Role.Owner }),
CreateEvent(new AppPatternsConfigured { Patterns = initialPatterns.ToArray() })
);
}
@ -103,7 +101,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
var command = new ChangePlan { PlanId = planIdPaid };
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppId, AppName, planIdPaid))
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppNamedId, planIdPaid))
.Returns(new PlanChangedResult());
await ExecuteCreateAsync();
@ -125,10 +123,10 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
var command = new ChangePlan { PlanId = planIdFree };
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppId, AppName, planIdPaid))
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppNamedId, planIdPaid))
.Returns(new PlanChangedResult());
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppId, AppName, planIdFree))
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppNamedId, planIdFree))
.Returns(new PlanResetResult());
await ExecuteCreateAsync();
@ -151,7 +149,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
var command = new ChangePlan { PlanId = planIdPaid };
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppId, AppName, planIdPaid))
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppNamedId, planIdPaid))
.Returns(new RedirectToCheckoutResult(new Uri("http://squidex.io")));
await ExecuteCreateAsync();
@ -172,9 +170,9 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(5));
result.ShouldBeEquivalent(new EntitySavedResult(4));
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppId, AppName, planIdPaid))
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppNamedId, planIdPaid))
.MustNotHaveHappened();
}
@ -187,7 +185,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(EntityCreatedResult.Create(contributorId, 5));
result.ShouldBeEquivalent(EntityCreatedResult.Create(contributorId, 4));
Assert.Equal(Role.Editor, sut.Snapshot.Contributors[contributorId]);
@ -207,7 +205,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(EntityCreatedResult.Create(contributorId, 6));
result.ShouldBeEquivalent(EntityCreatedResult.Create(contributorId, 5));
Assert.Equal(Role.Owner, sut.Snapshot.Contributors[contributorId]);
@ -227,7 +225,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(6));
result.ShouldBeEquivalent(new EntitySavedResult(5));
Assert.False(sut.Snapshot.Contributors.ContainsKey(contributorId));
@ -246,7 +244,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(5));
result.ShouldBeEquivalent(new EntitySavedResult(4));
Assert.True(sut.Snapshot.Clients.ContainsKey(clientId));
@ -266,7 +264,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(6));
result.ShouldBeEquivalent(new EntitySavedResult(5));
Assert.False(sut.Snapshot.Clients.ContainsKey(clientId));
@ -286,7 +284,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(7));
result.ShouldBeEquivalent(new EntitySavedResult(6));
Assert.Equal(clientNewName, sut.Snapshot.Clients[clientId].Name);
@ -306,7 +304,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(5));
result.ShouldBeEquivalent(new EntitySavedResult(4));
Assert.True(sut.Snapshot.LanguagesConfig.Contains(Language.DE));
@ -326,7 +324,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(6));
result.ShouldBeEquivalent(new EntitySavedResult(5));
Assert.False(sut.Snapshot.LanguagesConfig.Contains(Language.DE));
@ -346,7 +344,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(6));
result.ShouldBeEquivalent(new EntitySavedResult(5));
Assert.True(sut.Snapshot.LanguagesConfig.Contains(Language.DE));
@ -365,7 +363,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(5));
result.ShouldBeEquivalent(new EntitySavedResult(4));
Assert.Equal(5, sut.Snapshot.Roles.Count);
@ -385,7 +383,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(6));
result.ShouldBeEquivalent(new EntitySavedResult(5));
Assert.Equal(4, sut.Snapshot.Roles.Count);
@ -405,7 +403,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(6));
result.ShouldBeEquivalent(new EntitySavedResult(5));
LastEvents
.ShouldHaveSameEvents(
@ -414,59 +412,31 @@ namespace Squidex.Domain.Apps.Entities.Apps
}
[Fact]
public async Task AddPattern_should_create_events_and_update_state()
public async Task ConfigurePatterns_should_create_events_and_update_state()
{
var command = new AddPattern { PatternId = patternId3, Name = "Any", Pattern = ".*", Message = "Msg" };
await ExecuteCreateAsync();
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(5));
Assert.Equal(initialPatterns.Count + 1, sut.Snapshot.Patterns.Count);
var newPatterns = new[]
{
new UpsertAppPattern { Name = "chars", Pattern = "[a-z]*", Message = "Must be a character." }
};
LastEvents
.ShouldHaveSameEvents(
CreateEvent(new AppPatternAdded { PatternId = patternId3, Name = "Any", Pattern = ".*", Message = "Msg" })
);
}
var resultPatterns = new[]
{
new AppPattern("chars", "[a-z]*", "Must be a character.")
};
[Fact]
public async Task DeletePattern_should_create_events_and_update_state()
{
var command = new DeletePattern { PatternId = patternId3 };
var command = new ConfigurePatterns { Patterns = newPatterns };
await ExecuteCreateAsync();
await ExecuteAddPatternAsync();
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(6));
Assert.Equal(initialPatterns.Count, sut.Snapshot.Patterns.Count);
LastEvents
.ShouldHaveSameEvents(
CreateEvent(new AppPatternDeleted { PatternId = patternId3 })
);
}
[Fact]
public async Task UpdatePattern_should_create_events_and_update_state()
{
var command = new UpdatePattern { PatternId = patternId3, Name = "Any", Pattern = ".*", Message = "Msg" };
await ExecuteCreateAsync();
await ExecuteAddPatternAsync();
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(4));
result.ShouldBeEquivalent(new EntitySavedResult(6));
resultPatterns.Should().BeEquivalentTo(sut.Snapshot.Patterns.Values.ToArray());
LastEvents
.ShouldHaveSameEvents(
CreateEvent(new AppPatternUpdated { PatternId = patternId3, Name = "Any", Pattern = ".*", Message = "Msg" })
CreateEvent(new AppPatternsConfigured { Patterns = resultPatterns })
);
}
@ -479,22 +449,17 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(5));
result.ShouldBeEquivalent(new EntitySavedResult(4));
LastEvents
.ShouldHaveSameEvents(
CreateEvent(new AppArchived())
);
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, AppId, AppName, null))
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, AppNamedId, null))
.MustHaveHappened();
}
private Task ExecuteAddPatternAsync()
{
return sut.ExecuteAsync(CreateCommand(new AddPattern { PatternId = patternId3, Name = "Name", Pattern = ".*" }));
}
private Task ExecuteCreateAsync()
{
return sut.ExecuteAsync(CreateCommand(new CreateApp { Name = AppName }));

3
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Billing/NoopAppPlanBillingManagerTests.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Apps.Services.Implementations;
using Xunit;
@ -25,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Billing
[Fact]
public async Task Should_do_nothing_when_changing_plan()
{
await sut.ChangePlanAsync(null, Guid.Empty, null, null);
await sut.ChangePlanAsync(null, null, null);
}
[Fact]

209
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs

@ -5,176 +5,135 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Xunit;
#pragma warning disable SA1310 // Field names must not contain underscore
namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
public class GuardAppPatternsTests
{
private readonly Guid patternId = Guid.NewGuid();
private readonly AppPatterns patterns_0 = AppPatterns.Empty;
[Fact]
public void CanAdd_should_throw_exception_if_name_empty()
{
var command = new AddPattern { PatternId = patternId, Name = string.Empty, Pattern = ".*" };
ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_0, command),
new ValidationError("Name is required.", "Name"));
}
[Fact]
public void CanAdd_should_throw_exception_if_pattern_empty()
{
var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = string.Empty };
ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_0, command),
new ValidationError("Pattern is required.", "Pattern"));
}
[Fact]
public void CanAdd_should_throw_exception_if_pattern_not_valid()
{
var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = "[0-9{1}" };
ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_0, command),
new ValidationError("Pattern is not a valid value.", "Pattern"));
}
[Fact]
public void CanAdd_should_throw_exception_if_name_exists()
{
var patterns_1 = patterns_0.Add(Guid.NewGuid(), "any", "[a-z]", "Message");
var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = ".*" };
ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_1, command),
new ValidationError("A pattern with the same name already exists."));
}
[Fact]
public void CanAdd_should_throw_exception_if_pattern_exists()
{
var patterns_1 = patterns_0.Add(Guid.NewGuid(), "any", "[a-z]", "Message");
var command = new AddPattern { PatternId = patternId, Name = "other", Pattern = "[a-z]" };
ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_1, command),
new ValidationError("This pattern already exists but with another name."));
}
[Fact]
public void CanAdd_should_not_throw_exception_if_success()
public void CanConfigure_should_throw_exception_if_two_patterns_with_same_pattern_exist()
{
var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = ".*" };
GuardAppPatterns.CanAdd(patterns_0, command);
var command = new ConfigurePatterns
{
Patterns = new[]
{
new UpsertAppPattern { Name = "name1", Pattern = "[a-z]" },
new UpsertAppPattern { Name = "name2", Pattern = "[a-z]" }
}
};
ValidationAssert.Throws(() => GuardAppPatterns.CanConfigure(command),
new ValidationError("Two patterns with the same expression exist.", "Patterns"));
}
[Fact]
public void CanDelete_should_throw_exception_if_pattern_not_found()
public void CanConfigure_should_throw_exception_if_two_patterns_with_same_name_exist()
{
var command = new DeletePattern { PatternId = patternId };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppPatterns.CanDelete(patterns_0, command));
var command = new ConfigurePatterns
{
Patterns = new[]
{
new UpsertAppPattern { Name = "name", Pattern = "[a-z]" },
new UpsertAppPattern { Name = "name", Pattern = "[0-9]" }
}
};
ValidationAssert.Throws(() => GuardAppPatterns.CanConfigure(command),
new ValidationError("Two patterns with the same name exist.", "Patterns"));
}
[Fact]
public void CanDelete_should_not_throw_exception_if_success()
public void CanConfigure_should_throw_exception_if_expression_not_valid()
{
var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message");
var command = new DeletePattern { PatternId = patternId };
GuardAppPatterns.CanDelete(patterns_1, command);
var command = new ConfigurePatterns
{
Patterns = new[]
{
new UpsertAppPattern { Name = "name", Pattern = "((" }
}
};
ValidationAssert.Throws(() => GuardAppPatterns.CanConfigure(command),
new ValidationError("Expression is not a valid value.", "Patterns[1].Pattern"));
}
[Fact]
public void CanUpdate_should_throw_exception_if_name_empty()
public void CanConfigure_should_throw_exception_if_expression_is_empty()
{
var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message");
var command = new UpdatePattern { PatternId = patternId, Name = string.Empty, Pattern = ".*" };
ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_1, command),
new ValidationError("Name is required.", "Name"));
var command = new ConfigurePatterns
{
Patterns = new[]
{
new UpsertAppPattern { Name = "name" }
}
};
ValidationAssert.Throws(() => GuardAppPatterns.CanConfigure(command),
new ValidationError("Expression is required.", "Patterns[1].Pattern"));
}
[Fact]
public void CanUpdate_should_throw_exception_if_pattern_empty()
public void CanConfigure_should_throw_exception_if_name_is_empty()
{
var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message");
var command = new UpdatePattern { PatternId = patternId, Name = "any", Pattern = string.Empty };
ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_1, command),
new ValidationError("Pattern is required.", "Pattern"));
var command = new ConfigurePatterns
{
Patterns = new[]
{
new UpsertAppPattern { Pattern = "[0-9]" }
}
};
ValidationAssert.Throws(() => GuardAppPatterns.CanConfigure(command),
new ValidationError("Name is required.", "Patterns[1].Name"));
}
[Fact]
public void CanUpdate_should_throw_exception_if_pattern_not_valid()
public void CanConfigure_should_throw_exception_if_pattern_is_null()
{
var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message");
var command = new UpdatePattern { PatternId = patternId, Name = "any", Pattern = "[0-9{1}" };
ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_1, command),
new ValidationError("Pattern is not a valid value.", "Pattern"));
}
[Fact]
public void CanUpdate_should_throw_exception_if_name_exists()
{
var id1 = Guid.NewGuid();
var id2 = Guid.NewGuid();
var patterns_1 = patterns_0.Add(id1, "Pattern1", "[0-5]", "Message");
var patterns_2 = patterns_1.Add(id2, "Pattern2", "[0-4]", "Message");
var command = new UpdatePattern { PatternId = id2, Name = "Pattern1", Pattern = "[0-4]" };
ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_2, command),
new ValidationError("A pattern with the same name already exists."));
var command = new ConfigurePatterns
{
Patterns = new UpsertAppPattern[]
{
null
}
};
ValidationAssert.Throws(() => GuardAppPatterns.CanConfigure(command),
new ValidationError("Pattern is required.", "Patterns[1]"));
}
[Fact]
public void CanUpdate_should_throw_exception_if_pattern_exists()
public void CanConfigure_should_not_throw_exception_if_patterns_is_valid()
{
var id1 = Guid.NewGuid();
var id2 = Guid.NewGuid();
var patterns_1 = patterns_0.Add(id1, "Pattern1", "[0-5]", "Message");
var patterns_2 = patterns_1.Add(id2, "Pattern2", "[0-4]", "Message");
var command = new UpdatePattern { PatternId = id2, Name = "Pattern2", Pattern = "[0-5]" };
ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_2, command),
new ValidationError("This pattern already exists but with another name."));
var command = new ConfigurePatterns
{
Patterns = new[]
{
new UpsertAppPattern { Name = "number", Pattern = "[0-9]" }
}
};
GuardAppPatterns.CanConfigure(command);
}
[Fact]
public void CanUpdate_should_throw_exception_if_pattern_does_not_exists()
public void CanConfigure_should_not_throw_exception_if_patterns_is_null()
{
var command = new UpdatePattern { PatternId = patternId, Name = "Pattern1", Pattern = ".*" };
var command = new ConfigurePatterns();
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppPatterns.CanUpdate(patterns_0, command));
GuardAppPatterns.CanConfigure(command);
}
[Fact]
public void CanUpdate_should_not_throw_exception_if_pattern_exist_with_valid_command()
public void CanConfigure_should_not_throw_exception_if_patterns_is_empty()
{
var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message");
var command = new UpdatePattern { PatternId = patternId, Name = "Pattern1", Pattern = ".*" };
var command = new ConfigurePatterns { Patterns = new UpsertAppPattern[0] };
GuardAppPatterns.CanUpdate(patterns_1, command);
GuardAppPatterns.CanConfigure(command);
}
}
}

2
tests/Squidex.Domain.Apps.Entities.Tests/History/Notifications/NotificationEmailEventConsumerTests.cs

@ -174,7 +174,7 @@ namespace Squidex.Domain.Apps.Entities.History.Notifications
var @event = new AppContributorAssigned
{
Actor = new RefToken(assignerType, assignerId),
AppId = new NamedId<Guid>(Guid.NewGuid(), appName),
AppId = NamedId.Of(Guid.NewGuid(), appName),
ContributorId = assigneeId,
IsCreated = isNewUser,
IsAdded = isNewContributor,

4
tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs

@ -81,7 +81,7 @@ namespace Squidex.Web.CommandMiddlewares
[Fact]
public async Task Should_assign_app_id_to_app_self_command()
{
var command = new AddPattern();
var command = new ConfigurePatterns();
var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context);
@ -92,7 +92,7 @@ namespace Squidex.Web.CommandMiddlewares
[Fact]
public async Task Should_not_override_app_id()
{
var command = new AddPattern { AppId = Guid.NewGuid() };
var command = new ConfigurePatterns { AppId = Guid.NewGuid() };
var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context);

27
tools/Migrate_01/Migrations/AddPatterns.cs

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Entities.Apps;
@ -13,6 +13,7 @@ using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection;
namespace Migrate_01.Migrations
{
@ -32,6 +33,11 @@ namespace Migrate_01.Migrations
{
var ids = await grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id).GetAppIdsAsync();
var command = new ConfigurePatterns
{
Patterns = initialPatterns.Select(p => SimpleMapper.Map(p, new UpsertAppPattern())).ToArray()
};
foreach (var id in ids)
{
var app = grainFactory.GetGrain<IAppGrain>(id);
@ -40,21 +46,10 @@ namespace Migrate_01.Migrations
if (state.Value.Patterns.Count == 0)
{
foreach (var pattern in initialPatterns.Values)
{
var command =
new AddPattern
{
Actor = state.Value.CreatedBy,
AppId = state.Value.Id,
Name = pattern.Name,
PatternId = Guid.NewGuid(),
Pattern = pattern.Pattern,
Message = pattern.Message
};
await app.ExecuteAsync(command);
}
command.AppId = state.Value.Id;
command.Actor = state.Value.CreatedBy;
await app.ExecuteAsync(command);
}
}
}

Loading…
Cancel
Save