Browse Source

Feature/check before unpublish (#605)

* First attempt.

* Texts fixed.
pull/607/head
Sebastian Stehle 5 years ago
committed by GitHub
parent
commit
41315bca55
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      backend/i18n/frontend_en.json
  2. 2
      backend/i18n/frontend_it.json
  3. 2
      backend/i18n/frontend_nl.json
  4. 4
      backend/i18n/source/frontend_en.json
  5. 11
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollectionPublished.cs
  6. 17
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  7. 34
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs
  8. 5
      backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs
  9. 14
      backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs
  10. 10
      backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs
  11. 12
      backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs
  12. 14
      backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs
  13. 16
      backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs
  14. 8
      backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppWorkflows.cs
  15. 15
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs
  16. 2
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetFolderDomainObject.cs
  17. 18
      backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs
  18. 6
      backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAssetFolder.cs
  19. 4
      backend/src/Squidex.Domain.Apps.Entities/Comments/CommentsGrain.cs
  20. 4
      backend/src/Squidex.Domain.Apps.Entities/Comments/Guards/GuardComments.cs
  21. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs
  22. 10
      backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs
  23. 56
      backend/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs
  24. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs
  25. 4
      backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs
  26. 2
      backend/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs
  27. 8
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ChangeStatusDto.cs
  28. 41
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppClientsTests.cs
  29. 42
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs
  30. 37
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs
  31. 41
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs
  32. 59
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppRolesTests.cs
  33. 20
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppTests.cs
  34. 33
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppWorkflowTests.cs
  35. 5
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs
  36. 27
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetFolderTests.cs
  37. 55
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetTests.cs
  38. 22
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/Guards/GuardCommentsTests.cs
  39. 40
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs
  40. 69
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs
  41. 35
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs
  42. 4
      frontend/app/app.module.ts
  43. 2
      frontend/app/features/content/pages/content/content-history-page.component.ts
  44. 2
      frontend/app/shared/services/contents.service.spec.ts
  45. 4
      frontend/app/shared/services/contents.service.ts
  46. 41
      frontend/app/shared/state/contents.state.ts

2
backend/i18n/frontend_en.json

@ -422,6 +422,8 @@
"contents.tableHeaders.nextStatus": "Next Status",
"contents.tableHeaders.status": "Status",
"contents.tableHeaders.version": "Version",
"contents.unpublishReferrerConfirmText": "The content is referenced by another published content item.\n\nDo you really want to unpublish this content?",
"contents.unpublishReferrerConfirmTitle": "Unpublish content",
"contents.unsavedChangesText": "You have unsaved changes. Do you want to load them now?",
"contents.unsavedChangesTitle": "Unsaved changes",
"contents.updated": "Content updated successfully.",

2
backend/i18n/frontend_it.json

@ -422,6 +422,8 @@
"contents.tableHeaders.nextStatus": "Stato successivo",
"contents.tableHeaders.status": "Stato",
"contents.tableHeaders.version": "Versione",
"contents.unpublishReferrerConfirmText": "The content is referenced by another published content item.\n\nDo you really want to unpublish this content?",
"contents.unpublishReferrerConfirmTitle": "Unpublish content",
"contents.unsavedChangesText": "Non hai salvato le modifiche. Vuoi salvarle adesso?",
"contents.unsavedChangesTitle": "Modifiche non salvate",
"contents.updated": "Contenuto aggiornato con successo.",

2
backend/i18n/frontend_nl.json

@ -422,6 +422,8 @@
"contents.tableHeaders.nextStatus": "Volgende status",
"contents.tableHeaders.status": "Status",
"contents.tableHeaders.version": "Versie",
"contents.unpublishReferrerConfirmText": "The content is referenced by another published content item.\n\nDo you really want to unpublish this content?",
"contents.unpublishReferrerConfirmTitle": "Unpublish content",
"contents.unsavedChangesText": "Je hebt niet-opgeslagen wijzigingen. Wil je ze nu laden?",
"contents.unsavedChangesTitle": "Niet-opgeslagen wijzigingen",
"contents.updated": "Inhoud succesvol bijgewerkt.",

4
backend/i18n/source/frontend_en.json

@ -366,8 +366,10 @@
"contents.deleteConfirmTitle": "Delete content",
"contents.deleteFailed": "Failed to delete content. Please reload.",
"contents.deleteManyConfirmText": "Do you really want to delete the selected content items?",
"contents.deleteReferrerConfirmText": "The content is referenced by another content item.\n\nDo you really want to delete the content?",
"contents.deleteReferrerConfirmText": "The content is referenced by another content item.\n\nDo you really want to delete this content?",
"contents.deleteReferrerConfirmTitle": "Delete content",
"contents.unpublishReferrerConfirmText": "The content is referenced by another published content item.\n\nDo you really want to unpublish this content?",
"contents.unpublishReferrerConfirmTitle": "Unpublish content",
"contents.deleteVersionConfirmText": "Do you really want to delete this version?",
"contents.deleteVersionFailed": "Failed to delete version. Please reload.",
"contents.draftNew": "New Draft",

11
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollectionPublished.cs

@ -30,6 +30,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
private readonly QueryContentsByIds queryContentsById;
private readonly QueryContentsByQuery queryContentsByQuery;
private readonly QueryIdsAsync queryIdsAsync;
private readonly QueryReferrersAsync queryReferrersAsync;
public MongoContentCollectionPublished(IMongoDatabase database, IAppProvider appProvider, ITextIndex indexer, DataConverter converter)
: base(database)
@ -37,6 +38,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
queryContentAsync = new QueryContent(converter);
queryContentsById = new QueryContentsByIds(converter, appProvider);
queryContentsByQuery = new QueryContentsByQuery(converter, indexer, appProvider);
queryReferrersAsync = new QueryReferrersAsync();
queryIdsAsync = new QueryIdsAsync(appProvider);
}
@ -63,6 +65,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
await queryContentAsync.PrepareAsync(collection, ct);
await queryContentsById.PrepareAsync(collection, ct);
await queryContentsByQuery.PrepareAsync(collection, ct);
await queryReferrersAsync.PrepareAsync(collection, ct);
await queryIdsAsync.PrepareAsync(collection, ct);
}
@ -114,6 +117,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
}
}
public async Task<bool> HasReferrersAsync(DomainId appId, DomainId contentId)
{
using (Profiler.TraceMethod<MongoContentRepository>())
{
return await queryReferrersAsync.DoAsync(appId, contentId);
}
}
public Task UpsertVersionedAsync(DomainId documentId, long oldVersion, MongoContentEntity entity)
{
return Collection.UpsertVersionedAsync(documentId, oldVersion, entity.Version, entity);

17
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -120,6 +120,18 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
}
}
public Task<bool> HasReferrersAsync(DomainId appId, DomainId contentId, SearchScope scope)
{
if (scope == SearchScope.All)
{
return collectionAll.HasReferrersAsync(appId, contentId);
}
else
{
return collectionPublished.HasReferrersAsync(appId, contentId);
}
}
public Task ResetScheduledAsync(DomainId documentId)
{
return collectionAll.ResetScheduledAsync(documentId);
@ -135,11 +147,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return collectionAll.QueryIdsAsync(appId, schemaId, filterNode);
}
public Task<bool> HasReferrersAsync(DomainId appId, DomainId contentId)
{
return collectionAll.HasReferrersAsync(appId, contentId);
}
public IEnumerable<IMongoCollection<MongoContentEntity>> GetInternalCollections()
{
yield return collectionAll.GetInternalCollection();

34
backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs

@ -112,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case AssignContributor assignContributor:
return UpdateReturnAsync(assignContributor, async c =>
{
await GuardAppContributors.CanAssign(Snapshot.Contributors, Snapshot.Roles, c, userResolver, GetPlan());
await GuardAppContributors.CanAssign(c, Snapshot, userResolver, GetPlan());
AssignContributor(c, !Snapshot.Contributors.ContainsKey(assignContributor.ContributorId));
@ -122,7 +122,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case RemoveContributor removeContributor:
return UpdateReturn(removeContributor, c =>
{
GuardAppContributors.CanRemove(Snapshot.Contributors, c);
GuardAppContributors.CanRemove(c, Snapshot);
RemoveContributor(c);
@ -132,7 +132,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case AttachClient attachClient:
return UpdateReturn(attachClient, c =>
{
GuardAppClients.CanAttach(Snapshot.Clients, c);
GuardAppClients.CanAttach(c, Snapshot);
AttachClient(c);
@ -142,7 +142,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case UpdateClient updateClient:
return UpdateReturn(updateClient, c =>
{
GuardAppClients.CanUpdate(Snapshot.Clients, c, Snapshot.Roles);
GuardAppClients.CanUpdate(c, Snapshot);
UpdateClient(c);
@ -152,7 +152,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case RevokeClient revokeClient:
return UpdateReturn(revokeClient, c =>
{
GuardAppClients.CanRevoke(Snapshot.Clients, c);
GuardAppClients.CanRevoke(c, Snapshot);
RevokeClient(c);
@ -172,7 +172,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case UpdateWorkflow updateWorkflow:
return UpdateReturn(updateWorkflow, c =>
{
GuardAppWorkflows.CanUpdate(Snapshot.Workflows, c);
GuardAppWorkflows.CanUpdate(c, Snapshot);
UpdateWorkflow(c);
@ -182,7 +182,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case DeleteWorkflow deleteWorkflow:
return UpdateReturn(deleteWorkflow, c =>
{
GuardAppWorkflows.CanDelete(Snapshot.Workflows, c);
GuardAppWorkflows.CanDelete(c, Snapshot);
DeleteWorkflow(c);
@ -192,7 +192,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case AddLanguage addLanguage:
return UpdateReturn(addLanguage, c =>
{
GuardAppLanguages.CanAdd(Snapshot.Languages, c);
GuardAppLanguages.CanAdd(c, Snapshot);
AddLanguage(c);
@ -202,7 +202,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case RemoveLanguage removeLanguage:
return UpdateReturn(removeLanguage, c =>
{
GuardAppLanguages.CanRemove(Snapshot.Languages, c);
GuardAppLanguages.CanRemove(c, Snapshot);
RemoveLanguage(c);
@ -212,7 +212,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case UpdateLanguage updateLanguage:
return UpdateReturn(updateLanguage, c =>
{
GuardAppLanguages.CanUpdate(Snapshot.Languages, c);
GuardAppLanguages.CanUpdate(c, Snapshot);
UpdateLanguage(c);
@ -222,7 +222,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case AddRole addRole:
return UpdateReturn(addRole, c =>
{
GuardAppRoles.CanAdd(Snapshot.Roles, c);
GuardAppRoles.CanAdd(c, Snapshot);
AddRole(c);
@ -232,7 +232,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case DeleteRole deleteRole:
return UpdateReturn(deleteRole, c =>
{
GuardAppRoles.CanDelete(Snapshot.Roles, c, Snapshot.Contributors, Snapshot.Clients);
GuardAppRoles.CanDelete(c, Snapshot);
DeleteRole(c);
@ -242,7 +242,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case UpdateRole updateRole:
return UpdateReturn(updateRole, c =>
{
GuardAppRoles.CanUpdate(Snapshot.Roles, c);
GuardAppRoles.CanUpdate(c, Snapshot);
UpdateRole(c);
@ -252,7 +252,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case AddPattern addPattern:
return UpdateReturn(addPattern, c =>
{
GuardAppPatterns.CanAdd(Snapshot.Patterns, c);
GuardAppPatterns.CanAdd(c, Snapshot);
AddPattern(c);
@ -262,7 +262,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case DeletePattern deletePattern:
return UpdateReturn(deletePattern, c =>
{
GuardAppPatterns.CanDelete(Snapshot.Patterns, c);
GuardAppPatterns.CanDelete(c, Snapshot);
DeletePattern(c);
@ -272,7 +272,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case UpdatePattern updatePattern:
return UpdateReturn(updatePattern, c =>
{
GuardAppPatterns.CanUpdate(Snapshot.Patterns, c);
GuardAppPatterns.CanUpdate(c, Snapshot);
UpdatePattern(c);
@ -282,7 +282,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case ChangePlan changePlan:
return UpdateReturnAsync(changePlan, async c =>
{
GuardApp.CanChangePlan(c, Snapshot.Plan, appPlansProvider);
GuardApp.CanChangePlan(c, Snapshot, appPlansProvider);
if (c.FromCallback)
{

5
backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Plans;
using Squidex.Infrastructure;
@ -53,10 +52,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
Guard.NotNull(command, nameof(command));
}
public static void CanChangePlan(ChangePlan command, AppPlan? plan, IAppPlansProvider appPlans)
public static void CanChangePlan(ChangePlan command, IAppEntity app, IAppPlansProvider appPlans)
{
Guard.NotNull(command, nameof(command));
var plan = app.Plan;
Validate.It(e =>
{
if (string.IsNullOrWhiteSpace(command.PlanId))

14
backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs

@ -15,10 +15,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
public static class GuardAppClients
{
public static void CanAttach(AppClients clients, AttachClient command)
public static void CanAttach(AttachClient command, IAppEntity app)
{
Guard.NotNull(command, nameof(command));
var clients = app.Clients;
Validate.It(e =>
{
if (string.IsNullOrWhiteSpace(command.Id))
@ -32,10 +34,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
});
}
public static void CanRevoke(AppClients clients, RevokeClient command)
public static void CanRevoke(RevokeClient command, IAppEntity app)
{
Guard.NotNull(command, nameof(command));
var clients = app.Clients;
GetClientOrThrow(clients, command.Id);
Validate.It(e =>
@ -47,10 +51,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
});
}
public static void CanUpdate(AppClients clients, UpdateClient command, Roles roles)
public static void CanUpdate(UpdateClient command, IAppEntity app)
{
Guard.NotNull(command, nameof(command));
var clients = app.Clients;
GetClientOrThrow(clients, command.Id);
Validate.It(e =>
@ -60,7 +66,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
e(Not.Defined("Clientd"), nameof(command.Id));
}
if (command.Role != null && !roles.Contains(command.Role))
if (command.Role != null && !app.Roles.Contains(command.Role))
{
e(Not.Valid(nameof(command.Role)), nameof(command.Role));
}

10
backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs

@ -20,13 +20,15 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
public static class GuardAppContributors
{
public static Task CanAssign(AppContributors contributors, Roles roles, AssignContributor command, IUserResolver users, IAppLimitsPlan plan)
public static Task CanAssign(AssignContributor command, IAppEntity app, IUserResolver users, IAppLimitsPlan plan)
{
Guard.NotNull(command, nameof(command));
var contributors = app.Contributors;
return Validate.It(async e =>
{
if (!roles.Contains(command.Role))
if (!app.Roles.Contains(command.Role))
{
e(Not.Valid(nameof(command.Role)), nameof(command.Role));
}
@ -63,10 +65,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
});
}
public static void CanRemove(AppContributors contributors, RemoveContributor command)
public static void CanRemove(RemoveContributor command, IAppEntity app)
{
Guard.NotNull(command, nameof(command));
var contributors = app.Contributors;
Validate.It(e =>
{
if (string.IsNullOrWhiteSpace(command.ContributorId))

12
backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs

@ -15,10 +15,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
public static class GuardAppLanguages
{
public static void CanAdd(LanguagesConfig languages, AddLanguage command)
public static void CanAdd(AddLanguage command, IAppEntity app)
{
Guard.NotNull(command, nameof(command));
var languages = app.Languages;
Validate.It(e =>
{
var language = command.Language;
@ -34,10 +36,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
});
}
public static void CanRemove(LanguagesConfig languages, RemoveLanguage command)
public static void CanRemove(RemoveLanguage command, IAppEntity app)
{
Guard.NotNull(command, nameof(command));
var languages = app.Languages;
Validate.It(e =>
{
var language = command.Language;
@ -58,10 +62,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
});
}
public static void CanUpdate(LanguagesConfig languages, UpdateLanguage command)
public static void CanUpdate(UpdateLanguage command, IAppEntity app)
{
Guard.NotNull(command, nameof(command));
var languages = app.Languages;
Validate.It(e =>
{
var language = command.Language;

14
backend/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;
using Squidex.Infrastructure.Translations;
@ -16,10 +16,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
public static class GuardAppPatterns
{
public static void CanAdd(AppPatterns patterns, AddPattern command)
public static void CanAdd(AddPattern command, IAppEntity app)
{
Guard.NotNull(command, nameof(command));
var patterns = app.Patterns;
Validate.It(e =>
{
if (command.PatternId == DomainId.Empty)
@ -53,20 +55,24 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
});
}
public static void CanDelete(AppPatterns patterns, DeletePattern command)
public static void CanDelete(DeletePattern command, IAppEntity app)
{
Guard.NotNull(command, nameof(command));
var patterns = app.Patterns;
if (!patterns.ContainsKey(command.PatternId))
{
throw new DomainObjectNotFoundException(command.PatternId.ToString());
}
}
public static void CanUpdate(AppPatterns patterns, UpdatePattern command)
public static void CanUpdate(UpdatePattern command, IAppEntity app)
{
Guard.NotNull(command, nameof(command));
var patterns = app.Patterns;
if (!patterns.ContainsKey(command.PatternId))
{
throw new DomainObjectNotFoundException(command.PatternId.ToString());

16
backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs

@ -17,10 +17,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
public static class GuardAppRoles
{
public static void CanAdd(Roles roles, AddRole command)
public static void CanAdd(AddRole command, IAppEntity app)
{
Guard.NotNull(command, nameof(command));
var roles = app.Roles;
Validate.It(e =>
{
if (string.IsNullOrWhiteSpace(command.Name))
@ -34,10 +36,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
});
}
public static void CanDelete(Roles roles, DeleteRole command, AppContributors contributors, AppClients clients)
public static void CanDelete(DeleteRole command, IAppEntity app)
{
Guard.NotNull(command, nameof(command));
var roles = app.Roles;
CheckRoleExists(roles, command.Name);
Validate.It(e =>
@ -51,22 +55,24 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
e(T.Get("apps.roles.defaultRoleNotRemovable"));
}
if (clients.Values.Any(x => string.Equals(x.Role, command.Name, StringComparison.OrdinalIgnoreCase)))
if (app.Clients.Values.Any(x => string.Equals(x.Role, command.Name, StringComparison.OrdinalIgnoreCase)))
{
e(T.Get("apps.roles.usedRoleByClientsNotRemovable"));
}
if (contributors.Values.Any(x => string.Equals(x, command.Name, StringComparison.OrdinalIgnoreCase)))
if (app.Contributors.Values.Any(x => string.Equals(x, command.Name, StringComparison.OrdinalIgnoreCase)))
{
e(T.Get("apps.roles.usedRoleByContributorsNotRemovable"));
}
});
}
public static void CanUpdate(Roles roles, UpdateRole command)
public static void CanUpdate(UpdateRole command, IAppEntity app)
{
Guard.NotNull(command, nameof(command));
var roles = app.Roles;
CheckRoleExists(roles, command.Name);
Validate.It(e =>

8
backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppWorkflows.cs

@ -28,10 +28,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
});
}
public static void CanUpdate(Workflows workflows, UpdateWorkflow command)
public static void CanUpdate(UpdateWorkflow command, IAppEntity app)
{
Guard.NotNull(command, nameof(command));
var workflows = app.Workflows;
CheckWorkflowExists(workflows, command.WorkflowId);
Validate.It(e =>
@ -90,10 +92,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
});
}
public static void CanDelete(Workflows workflows, DeleteWorkflow command)
public static void CanDelete(DeleteWorkflow command, IAppEntity app)
{
Guard.NotNull(command, nameof(command));
var workflows = app.Workflows;
CheckWorkflowExists(workflows, command.WorkflowId);
}

15
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs

@ -20,7 +20,6 @@ using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Translations;
using Squidex.Log;
using IAssetTagService = Squidex.Domain.Apps.Core.Tags.ITagService;
@ -102,7 +101,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
case MoveAsset moveAsset:
return UpdateReturnAsync(moveAsset, async c =>
{
await GuardAsset.CanMove(c, assetQuery, Snapshot.ParentId);
await GuardAsset.CanMove(c, Snapshot, assetQuery);
Move(c);
@ -111,17 +110,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
case DeleteAsset deleteAsset:
return UpdateAsync(deleteAsset, async c =>
{
GuardAsset.CanDelete(c);
if (c.CheckReferrers)
{
var hasReferrer = await contentRepository.HasReferrersAsync(Snapshot.AppId.Id, c.AssetId);
if (hasReferrer)
{
throw new DomainException(T.Get("assets.referenced"));
}
}
await GuardAsset.CanDelete(c, Snapshot, contentRepository);
await assetTags.NormalizeTagsAsync(Snapshot.AppId.Id, TagGroups.Assets, null, Snapshot.Tags);

2
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetFolderDomainObject.cs

@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
case MoveAssetFolder moveAssetFolder:
return UpdateReturnAsync(moveAssetFolder, async c =>
{
await GuardAssetFolder.CanMove(c, assetQuery, Snapshot.Id, Snapshot.ParentId);
await GuardAssetFolder.CanMove(c, Snapshot, assetQuery);
Move(c);

18
backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs

@ -7,6 +7,8 @@
using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation;
@ -30,13 +32,13 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
});
}
public static Task CanMove(MoveAsset command, IAssetQueryService assetQuery, DomainId oldParentId)
public static Task CanMove(MoveAsset command, IAssetEntity asset, IAssetQueryService assetQuery)
{
Guard.NotNull(command, nameof(command));
return Validate.It(async e =>
{
if (command.ParentId != oldParentId)
if (command.ParentId != asset.ParentId)
{
await CheckPathAsync(command.AppId.Id, command.ParentId, assetQuery, e);
}
@ -48,9 +50,19 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
Guard.NotNull(command, nameof(command));
}
public static void CanDelete(DeleteAsset command)
public static async Task CanDelete(DeleteAsset command, IAssetEntity asset, IContentRepository contentRepository)
{
Guard.NotNull(command, nameof(command));
if (command.CheckReferrers)
{
var hasReferrer = await contentRepository.HasReferrersAsync(asset.AppId.Id, asset.Id, SearchScope.All);
if (hasReferrer)
{
throw new DomainException(T.Get("assets.referenced"));
}
}
}
private static async Task CheckPathAsync(DomainId appId, DomainId parentId, IAssetQueryService assetQuery, AddValidation e)

6
backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAssetFolder.cs

@ -43,15 +43,15 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
});
}
public static Task CanMove(MoveAssetFolder command, IAssetQueryService assetQuery, DomainId id, DomainId oldParentId)
public static Task CanMove(MoveAssetFolder command, IAssetFolderEntity assetFolder, IAssetQueryService assetQuery)
{
Guard.NotNull(command, nameof(command));
return Validate.It(async e =>
{
if (command.ParentId != oldParentId)
if (command.ParentId != assetFolder.ParentId)
{
await CheckPathAsync(command.AppId.Id, command.ParentId, assetQuery, id, e);
await CheckPathAsync(command.AppId.Id, command.ParentId, assetQuery, assetFolder.Id, e);
}
});
}

4
backend/src/Squidex.Domain.Apps.Entities/Comments/CommentsGrain.cs

@ -83,7 +83,7 @@ namespace Squidex.Domain.Apps.Entities.Comments
case UpdateComment updateComment:
return Upsert(updateComment, c =>
{
GuardComments.CanUpdate(Key, events, c);
GuardComments.CanUpdate(c, Key, events);
Update(c);
@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Comments
case DeleteComment deleteComment:
return Upsert(deleteComment, c =>
{
GuardComments.CanDelete(Key, events, c);
GuardComments.CanDelete(c, Key, events);
Delete(c);

4
backend/src/Squidex.Domain.Apps.Entities/Comments/Guards/GuardComments.cs

@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
});
}
public static void CanUpdate(string commentsId, List<Envelope<CommentsEvent>> events, UpdateComment command)
public static void CanUpdate(UpdateComment command, string commentsId, List<Envelope<CommentsEvent>> events)
{
Guard.NotNull(command, nameof(command));
@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
});
}
public static void CanDelete(string commentsId, List<Envelope<CommentsEvent>> events, DeleteComment command)
public static void CanDelete(DeleteComment command, string commentsId, List<Envelope<CommentsEvent>> events)
{
Guard.NotNull(command, nameof(command));

2
backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs

@ -19,6 +19,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Commands
public DomainId? StatusJobId { get; set; }
public bool CheckReferrers { get; set; }
public bool DoNotValidate { get; set; }
}
}

10
backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs

@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
await LoadContext(c.AppId, c.SchemaId, c, c.OptimizeValidation);
await GuardContent.CanCreate(context.Schema, context.Workflow, c);
await GuardContent.CanCreate(c, context.Workflow, context.Schema);
var status = await context.GetInitialStatusAsync();
@ -155,7 +155,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
case UpdateContent updateContent:
return UpdateReturnAsync(updateContent, async c =>
{
await GuardContent.CanUpdate(Snapshot, context.Workflow, c);
await GuardContent.CanUpdate(c, Snapshot, context.Workflow);
return await UpdateAsync(c, x => c.Data, false);
});
@ -163,7 +163,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
case PatchContent patchContent:
return UpdateReturnAsync(patchContent, async c =>
{
await GuardContent.CanPatch(Snapshot, context.Workflow, c);
await GuardContent.CanPatch(c, Snapshot, context.Workflow);
return await UpdateAsync(c, c.Data.MergeInto, true);
});
@ -175,7 +175,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c);
await GuardContent.CanChangeStatus(context.Schema, Snapshot, context.Workflow, c);
await GuardContent.CanChangeStatus(c, Snapshot, context.Workflow, context.Repository, context.Schema);
if (c.DueTime.HasValue)
{
@ -234,7 +234,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c);
await GuardContent.CanDelete(context.Schema, Snapshot, context.Repository, c);
await GuardContent.CanDelete(c, Snapshot, context.Repository, context.Schema);
if (!c.DoNotScript)
{

56
backend/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs

@ -11,7 +11,6 @@ using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
@ -21,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
{
public static class GuardContent
{
public static async Task CanCreate(ISchemaEntity schema, IContentWorkflow contentWorkflow, CreateContent command)
public static async Task CanCreate(CreateContent command, IContentWorkflow contentWorkflow, ISchemaEntity schema)
{
Guard.NotNull(command, nameof(command));
@ -41,7 +40,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
});
}
public static async Task CanUpdate(ContentState content, IContentWorkflow contentWorkflow, UpdateContent command)
public static async Task CanUpdate(UpdateContent command,
IContentEntity content,
IContentWorkflow contentWorkflow)
{
Guard.NotNull(command, nameof(command));
@ -53,7 +54,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
await ValidateCanUpdate(content, contentWorkflow, command.User);
}
public static async Task CanPatch(ContentState content, IContentWorkflow contentWorkflow, PatchContent command)
public static async Task CanPatch(PatchContent command,
IContentEntity content,
IContentWorkflow contentWorkflow)
{
Guard.NotNull(command, nameof(command));
@ -65,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
await ValidateCanUpdate(content, contentWorkflow, command.User);
}
public static void CanDeleteDraft(DeleteContentDraft command, ContentState content)
public static void CanDeleteDraft(DeleteContentDraft command, IContentEntity content)
{
Guard.NotNull(command, nameof(command));
@ -75,7 +78,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
}
}
public static void CanCreateDraft(CreateContentDraft command, ContentState content)
public static void CanCreateDraft(CreateContentDraft command, IContentEntity content)
{
Guard.NotNull(command, nameof(command));
@ -85,13 +88,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
}
}
public static Task CanChangeStatus(ISchemaEntity schema, ContentState content, IContentWorkflow contentWorkflow, ChangeContentStatus command)
public static Task CanChangeStatus(ChangeContentStatus command,
IContentEntity content,
IContentWorkflow contentWorkflow,
IContentRepository contentRepository,
ISchemaEntity schema)
{
Guard.NotNull(command, nameof(command));
if (schema.SchemaDef.IsSingleton)
{
if (content.NewVersion == null || command.Status != Status.Published)
if (content.NewStatus == null || command.Status != Status.Published)
{
throw new DomainException(T.Get("contents.singletonNotChangeable"));
}
@ -101,9 +108,23 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
return Validate.It(async e =>
{
if (!await contentWorkflow.CanMoveToAsync(content, content.EditingStatus, command.Status, command.User))
var status = content.NewStatus ?? content.Status;
if (!await contentWorkflow.CanMoveToAsync(content, status, command.Status, command.User))
{
e(T.Get("contents.statusTransitionNotAllowed", new { oldStatus = content.EditingStatus, newStatus = command.Status }), nameof(command.Status));
var values = new { oldStatus = status, newStatus = command.Status };
e(T.Get("contents.statusTransitionNotAllowed", values), nameof(command.Status));
}
if (content.Status == Status.Published && command.CheckReferrers)
{
var hasReferrer = await contentRepository.HasReferrersAsync(content.AppId.Id, command.ContentId, SearchScope.Published);
if (hasReferrer)
{
throw new DomainException(T.Get("contents.referenced"));
}
}
if (command.DueTime.HasValue && command.DueTime.Value < SystemClock.Instance.GetCurrentInstant())
@ -113,7 +134,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
});
}
public static async Task CanDelete(ISchemaEntity schema, ContentState content, IContentRepository contentRepository, DeleteContent command)
public static async Task CanDelete(DeleteContent command,
IContentEntity content,
IContentRepository contentRepository,
ISchemaEntity schema)
{
Guard.NotNull(command, nameof(command));
@ -124,7 +148,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
if (command.CheckReferrers)
{
var hasReferrer = await contentRepository.HasReferrersAsync(content.AppId.Id, command.ContentId);
var hasReferrer = await contentRepository.HasReferrersAsync(content.AppId.Id, command.ContentId, SearchScope.All);
if (hasReferrer)
{
@ -141,11 +165,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
}
}
private static async Task ValidateCanUpdate(ContentState content, IContentWorkflow contentWorkflow, ClaimsPrincipal user)
private static async Task ValidateCanUpdate(IContentEntity content, IContentWorkflow contentWorkflow, ClaimsPrincipal user)
{
if (!await contentWorkflow.CanUpdateAsync(content, content.EditingStatus, user))
var status = content.NewStatus ?? content.Status;
if (!await contentWorkflow.CanUpdateAsync(content, status, user))
{
throw new DomainException(T.Get("contents.workflowErrorUpdate", new { status = content.EditingStatus }));
throw new DomainException(T.Get("contents.workflowErrorUpdate", new { status }));
}
}
}

2
backend/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs

@ -33,7 +33,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Repositories
Task<IContentEntity?> FindContentAsync(IAppEntity app, ISchemaEntity schema, DomainId id, SearchScope scope);
Task<bool> HasReferrersAsync(DomainId appId, DomainId contentId);
Task<bool> HasReferrersAsync(DomainId appId, DomainId contentId, SearchScope scope);
Task ResetScheduledAsync(DomainId documentId);

4
backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs

@ -44,7 +44,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
});
}
public static Task CanUpdate(UpdateRule command, DomainId appId, IAppProvider appProvider)
public static Task CanUpdate(UpdateRule command, IRuleEntity rule, IAppProvider appProvider)
{
Guard.NotNull(command, nameof(command));
@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
{
if (command.Trigger != null)
{
var errors = await RuleTriggerValidator.ValidateAsync(appId, command.Trigger, appProvider);
var errors = await RuleTriggerValidator.ValidateAsync(rule.AppId.Id, command.Trigger, appProvider);
errors.Foreach((x, _) => x.AddTo(e));
}

2
backend/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs

@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
case UpdateRule updateRule:
return UpdateReturnAsync(updateRule, async c =>
{
await GuardRule.CanUpdate(c, Snapshot.AppId.Id, appProvider);
await GuardRule.CanUpdate(c, Snapshot, appProvider);
Update(c);

8
backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ChangeStatusDto.cs

@ -9,6 +9,7 @@ using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Validation;
namespace Squidex.Areas.Api.Controllers.Contents.Models
@ -26,9 +27,14 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
/// </summary>
public Instant? DueTime { get; set; }
/// <summary>
/// True to check referrers of this content.
/// </summary>
public bool CheckReferrers { get; set; }
public ChangeContentStatus ToCommand(DomainId id)
{
return new ChangeContentStatus { ContentId = id, Status = Status, DueTime = DueTime };
return SimpleMapper.Map(this, new ChangeContentStatus { ContentId = id });
}
}
}

41
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppClientsTests.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using FakeItEasy;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Apps.Commands;
@ -27,7 +28,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AttachClient();
ValidationAssert.Throws(() => GuardAppClients.CanAttach(clients_0, command),
ValidationAssert.Throws(() => GuardAppClients.CanAttach(command, App(clients_0)),
new ValidationError("Client ID is required.", "Id"));
}
@ -38,7 +39,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var clients_1 = clients_0.Add("android", "secret");
ValidationAssert.Throws(() => GuardAppClients.CanAttach(clients_1, command),
ValidationAssert.Throws(() => GuardAppClients.CanAttach(command, App(clients_1)),
new ValidationError("A client with the same id already exists."));
}
@ -49,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var clients_1 = clients_0.Add("android", "secret");
GuardAppClients.CanAttach(clients_1, command);
GuardAppClients.CanAttach(command, App(clients_1));
}
[Fact]
@ -57,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new RevokeClient();
ValidationAssert.Throws(() => GuardAppClients.CanRevoke(clients_0, command),
ValidationAssert.Throws(() => GuardAppClients.CanRevoke(command, App(clients_0)),
new ValidationError("Client ID is required.", "Id"));
}
@ -66,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new RevokeClient { Id = "ios" };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppClients.CanRevoke(clients_0, command));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppClients.CanRevoke(command, App(clients_0)));
}
[Fact]
@ -76,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var clients_1 = clients_0.Add("ios", "secret");
GuardAppClients.CanRevoke(clients_1, command);
GuardAppClients.CanRevoke(command, App(clients_1));
}
[Fact]
@ -84,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new UpdateClient { Name = "iOS" };
ValidationAssert.Throws(() => GuardAppClients.CanUpdate(clients_0, command, Roles.Empty),
ValidationAssert.Throws(() => GuardAppClients.CanUpdate(command, App(clients_0)),
new ValidationError("Client ID is required.", "Id"));
}
@ -93,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new UpdateClient { Id = "ios", Name = "iOS" };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppClients.CanUpdate(clients_0, command, Roles.Empty));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppClients.CanUpdate(command, App(clients_0)));
}
[Fact]
@ -103,7 +104,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var clients_1 = clients_0.Add("ios", "secret");
ValidationAssert.Throws(() => GuardAppClients.CanUpdate(clients_1, command, roles),
ValidationAssert.Throws(() => GuardAppClients.CanUpdate(command, App(clients_1)),
new ValidationError("Role is not a valid value.", "Role"));
}
@ -114,7 +115,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var clients_1 = clients_0.Add("ios", "secret");
ValidationAssert.Throws(() => GuardAppClients.CanUpdate(clients_1, command, roles),
ValidationAssert.Throws(() => GuardAppClients.CanUpdate(command, App(clients_1)),
new ValidationError("ApiCallsLimit must be greater or equal to 0.", "ApiCallsLimit"));
}
@ -125,7 +126,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var clients_1 = clients_0.Add("ios", "secret");
ValidationAssert.Throws(() => GuardAppClients.CanUpdate(clients_1, command, roles),
ValidationAssert.Throws(() => GuardAppClients.CanUpdate(command, App(clients_1)),
new ValidationError("ApiTrafficLimit must be greater or equal to 0.", "ApiTrafficLimit"));
}
@ -136,7 +137,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var clients_1 = clients_0.Add("ios", "secret");
GuardAppClients.CanUpdate(clients_1, command, roles);
GuardAppClients.CanUpdate(command, App(clients_1));
}
[Fact]
@ -146,7 +147,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var clients_1 = clients_0.Add("ios", "secret");
GuardAppClients.CanUpdate(clients_1, command, roles);
GuardAppClients.CanUpdate(command, App(clients_1));
}
[Fact]
@ -156,7 +157,19 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var clients_1 = clients_0.Add("ios", "secret");
GuardAppClients.CanUpdate(clients_1, command, roles);
GuardAppClients.CanUpdate(command, App(clients_1));
}
private IAppEntity App(AppClients clients)
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Clients)
.Returns(clients);
A.CallTo(() => app.Roles)
.Returns(roles);
return app;
}
}
}

42
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs

@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AssignContributor();
await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_0, roles, command, users, appPlan),
await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(command, App(contributors_0), users, appPlan),
new ValidationError("Contributor ID or email is required.", "ContributorId"));
}
@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AssignContributor { ContributorId = "1", Role = "Invalid" };
await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_0, roles, command, users, appPlan),
await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(command, App(contributors_0), users, appPlan),
new ValidationError("Role is not a valid value.", "Role"));
}
@ -73,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var contributors_1 = contributors_0.Assign("1", Role.Owner);
await GuardAppContributors.CanAssign(contributors_1, roles, command, users, appPlan);
await GuardAppContributors.CanAssign(command, App(contributors_1), users, appPlan);
}
[Fact]
@ -83,7 +83,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var contributors_1 = contributors_0.Assign("1", Role.Owner);
await GuardAppContributors.CanAssign(contributors_1, roles, command, users, appPlan);
await GuardAppContributors.CanAssign(command, App(contributors_1), users, appPlan);
}
[Fact]
@ -91,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AssignContributor { ContributorId = "notfound", Role = Role.Owner };
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => GuardAppContributors.CanAssign(contributors_0, roles, command, users, appPlan));
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => GuardAppContributors.CanAssign(command, App(contributors_0), users, appPlan));
}
[Fact]
@ -99,7 +99,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AssignContributor { ContributorId = "3", Role = Role.Editor, Actor = new RefToken("user", "3") };
await Assert.ThrowsAsync<DomainForbiddenException>(() => GuardAppContributors.CanAssign(contributors_0, roles, command, users, appPlan));
await Assert.ThrowsAsync<DomainForbiddenException>(() => GuardAppContributors.CanAssign(command, App(contributors_0), users, appPlan));
}
[Fact]
@ -113,7 +113,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var contributors_1 = contributors_0.Assign("1", Role.Owner);
var contributors_2 = contributors_1.Assign("2", Role.Editor);
await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_2, roles, command, users, appPlan),
await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(command, App(contributors_2), users, appPlan),
new ValidationError("You have reached the maximum number of contributors for your plan."));
}
@ -125,7 +125,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new AssignContributor { ContributorId = "1" };
await GuardAppContributors.CanAssign(contributors_0, roles, command, users, appPlan);
await GuardAppContributors.CanAssign(command, App(contributors_0), users, appPlan);
}
[Fact]
@ -135,7 +135,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var contributors_1 = contributors_0.Assign("1", Role.Developer);
await GuardAppContributors.CanAssign(contributors_1, roles, command, users, appPlan);
await GuardAppContributors.CanAssign(command, App(contributors_1), users, appPlan);
}
[Fact]
@ -149,7 +149,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var contributors_1 = contributors_0.Assign("1", Role.Developer);
var contributors_2 = contributors_1.Assign("2", Role.Developer);
await GuardAppContributors.CanAssign(contributors_2, roles, command, users, appPlan);
await GuardAppContributors.CanAssign(command, App(contributors_2), users, appPlan);
}
[Fact]
@ -163,7 +163,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var contributors_1 = contributors_0.Assign("1", Role.Editor);
var contributors_2 = contributors_1.Assign("2", Role.Editor);
await GuardAppContributors.CanAssign(contributors_2, roles, command, users, appPlan);
await GuardAppContributors.CanAssign(command, App(contributors_2), users, appPlan);
}
[Fact]
@ -171,7 +171,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new RemoveContributor();
ValidationAssert.Throws(() => GuardAppContributors.CanRemove(contributors_0, command),
ValidationAssert.Throws(() => GuardAppContributors.CanRemove(command, App(contributors_0)),
new ValidationError("Contributor ID or email is required.", "ContributorId"));
}
@ -180,7 +180,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new RemoveContributor { ContributorId = "1" };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppContributors.CanRemove(contributors_0, command));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppContributors.CanRemove(command, App(contributors_0)));
}
[Fact]
@ -191,7 +191,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var contributors_1 = contributors_0.Assign("1", Role.Owner);
var contributors_2 = contributors_1.Assign("2", Role.Editor);
ValidationAssert.Throws(() => GuardAppContributors.CanRemove(contributors_2, command),
ValidationAssert.Throws(() => GuardAppContributors.CanRemove(command, App(contributors_2)),
new ValidationError("Cannot remove the only owner."));
}
@ -203,7 +203,19 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var contributors_1 = contributors_0.Assign("1", Role.Owner);
var contributors_2 = contributors_1.Assign("2", Role.Owner);
GuardAppContributors.CanRemove(contributors_2, command);
GuardAppContributors.CanRemove(command, App(contributors_2));
}
private IAppEntity App(AppContributors contributors)
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Contributors)
.Returns(contributors);
A.CallTo(() => app.Roles)
.Returns(roles);
return app;
}
}
}

37
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using FakeItEasy;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Apps.Commands;
@ -24,7 +25,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AddLanguage();
ValidationAssert.Throws(() => GuardAppLanguages.CanAdd(languages, command),
ValidationAssert.Throws(() => GuardAppLanguages.CanAdd(command, App(languages)),
new ValidationError("Language code is required.", "Language"));
}
@ -33,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AddLanguage { Language = Language.EN };
ValidationAssert.Throws(() => GuardAppLanguages.CanAdd(languages, command),
ValidationAssert.Throws(() => GuardAppLanguages.CanAdd(command, App(languages)),
new ValidationError("Language has already been added."));
}
@ -42,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AddLanguage { Language = Language.IT };
GuardAppLanguages.CanAdd(languages, command);
GuardAppLanguages.CanAdd(command, App(languages));
}
[Fact]
@ -50,7 +51,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new RemoveLanguage();
ValidationAssert.Throws(() => GuardAppLanguages.CanRemove(languages, command),
ValidationAssert.Throws(() => GuardAppLanguages.CanRemove(command, App(languages)),
new ValidationError("Language code is required.", "Language"));
}
@ -59,7 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new RemoveLanguage { Language = Language.IT };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppLanguages.CanRemove(languages, command));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppLanguages.CanRemove(command, App(languages)));
}
[Fact]
@ -67,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new RemoveLanguage { Language = Language.EN };
ValidationAssert.Throws(() => GuardAppLanguages.CanRemove(languages, command),
ValidationAssert.Throws(() => GuardAppLanguages.CanRemove(command, App(languages)),
new ValidationError("Master language cannot be removed."));
}
@ -76,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new RemoveLanguage { Language = Language.DE };
GuardAppLanguages.CanRemove(languages, command);
GuardAppLanguages.CanRemove(command, App(languages));
}
[Fact]
@ -84,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new UpdateLanguage();
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(languages, command),
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages)),
new ValidationError("Language code is required.", "Language"));
}
@ -93,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new UpdateLanguage { Language = Language.EN, IsOptional = true };
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(languages, command),
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages)),
new ValidationError("Master language cannot be made optional.", "IsMaster"));
}
@ -102,7 +103,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new UpdateLanguage { Language = Language.EN, Fallback = new[] { Language.DE } };
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(languages, command),
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages)),
new ValidationError("Master language cannot have fallback languages.", "Fallback"));
}
@ -111,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new UpdateLanguage { Language = Language.DE, Fallback = new[] { Language.IT } };
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(languages, command),
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages)),
new ValidationError("App does not have fallback language 'Italian'.", "Fallback"));
}
@ -120,7 +121,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new UpdateLanguage { Language = Language.IT };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppLanguages.CanUpdate(languages, command));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppLanguages.CanUpdate(command, App(languages)));
}
[Fact]
@ -128,7 +129,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new UpdateLanguage { Language = Language.DE, Fallback = new[] { Language.EN } };
GuardAppLanguages.CanUpdate(languages, command);
GuardAppLanguages.CanUpdate(command, App(languages));
}
private static IAppEntity App(LanguagesConfig languages)
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Languages)
.Returns(languages);
return app;
}
}
}

41
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using FakeItEasy;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Apps.Commands;
@ -27,7 +28,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AddPattern { PatternId = patternId, Name = string.Empty, Pattern = ".*" };
ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_0, command),
ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(command, App(patterns_0)),
new ValidationError("Name is required.", "Name"));
}
@ -36,7 +37,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = string.Empty };
ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_0, command),
ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(command, App(patterns_0)),
new ValidationError("Pattern is required.", "Pattern"));
}
@ -45,7 +46,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = "[0-9{1}" };
ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_0, command),
ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(command, App(patterns_0)),
new ValidationError("Pattern is not a valid value.", "Pattern"));
}
@ -56,7 +57,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = ".*" };
ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_1, command),
ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(command, App(patterns_1)),
new ValidationError("A pattern with the same name already exists."));
}
@ -67,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new AddPattern { PatternId = patternId, Name = "other", Pattern = "[a-z]" };
ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_1, command),
ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(command, App(patterns_1)),
new ValidationError("This pattern already exists but with another name."));
}
@ -76,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = ".*" };
GuardAppPatterns.CanAdd(patterns_0, command);
GuardAppPatterns.CanAdd(command, App(patterns_0));
}
[Fact]
@ -84,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new DeletePattern { PatternId = patternId };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppPatterns.CanDelete(patterns_0, command));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppPatterns.CanDelete(command, App(patterns_0)));
}
[Fact]
@ -94,7 +95,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new DeletePattern { PatternId = patternId };
GuardAppPatterns.CanDelete(patterns_1, command);
GuardAppPatterns.CanDelete(command, App(patterns_1));
}
[Fact]
@ -104,7 +105,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new UpdatePattern { PatternId = patternId, Name = string.Empty, Pattern = ".*" };
ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_1, command),
ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_1)),
new ValidationError("Name is required.", "Name"));
}
@ -115,7 +116,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new UpdatePattern { PatternId = patternId, Name = "any", Pattern = string.Empty };
ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_1, command),
ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_1)),
new ValidationError("Pattern is required.", "Pattern"));
}
@ -126,7 +127,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new UpdatePattern { PatternId = patternId, Name = "any", Pattern = "[0-9{1}" };
ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_1, command),
ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_1)),
new ValidationError("Pattern is not a valid value.", "Pattern"));
}
@ -141,7 +142,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new UpdatePattern { PatternId = id2, Name = "Pattern1", Pattern = "[0-4]" };
ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_2, command),
ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_2)),
new ValidationError("A pattern with the same name already exists."));
}
@ -156,7 +157,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new UpdatePattern { PatternId = id2, Name = "Pattern2", Pattern = "[0-5]" };
ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_2, command),
ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_2)),
new ValidationError("This pattern already exists but with another name."));
}
@ -165,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new UpdatePattern { PatternId = patternId, Name = "Pattern1", Pattern = ".*" };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppPatterns.CanUpdate(patterns_0, command));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppPatterns.CanUpdate(command, App(patterns_0)));
}
[Fact]
@ -175,7 +176,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new UpdatePattern { PatternId = patternId, Name = "Pattern1", Pattern = ".*" };
GuardAppPatterns.CanUpdate(patterns_1, command);
GuardAppPatterns.CanUpdate(command, App(patterns_1));
}
private static IAppEntity App(AppPatterns patterns)
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Patterns)
.Returns(patterns);
return app;
}
}
}

59
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppRolesTests.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using FakeItEasy;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Apps.Commands;
@ -21,15 +22,15 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
private readonly string roleName = "Role1";
private readonly Roles roles_0 = Roles.Empty;
private readonly AppContributors contributors = AppContributors.Empty;
private readonly AppClients clients = AppClients.Empty;
private readonly AppClients clients = AppClients.Empty.Add("client", "secret", "clientRole");
private readonly AppContributors contributors = AppContributors.Empty.Assign("contributor", "contributorRole");
[Fact]
public void CanAdd_should_throw_exception_if_name_empty()
{
var command = new AddRole { Name = null! };
ValidationAssert.Throws(() => GuardAppRoles.CanAdd(roles_0, command),
ValidationAssert.Throws(() => GuardAppRoles.CanAdd(command, App(roles_0)),
new ValidationError("Name is required.", "Name"));
}
@ -40,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new AddRole { Name = roleName };
ValidationAssert.Throws(() => GuardAppRoles.CanAdd(roles_1, command),
ValidationAssert.Throws(() => GuardAppRoles.CanAdd(command, App(roles_1)),
new ValidationError("A role with the same name already exists."));
}
@ -49,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new AddRole { Name = roleName };
GuardAppRoles.CanAdd(roles_0, command);
GuardAppRoles.CanAdd(command, App(roles_0));
}
[Fact]
@ -57,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new DeleteRole { Name = null! };
ValidationAssert.Throws(() => GuardAppRoles.CanDelete(roles_0, command, contributors, clients),
ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App(roles_0)),
new ValidationError("Name is required.", "Name"));
}
@ -66,30 +67,28 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new DeleteRole { Name = roleName };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppRoles.CanDelete(roles_0, command, contributors, clients));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppRoles.CanDelete(command, App(roles_0)));
}
[Fact]
public void CanDelete_should_throw_exception_if_contributor_found()
{
var roles_1 = roles_0.Add(roleName);
var roles_1 = roles_0.Add("contributorRole");
var command = new DeleteRole { Name = roleName };
var command = new DeleteRole { Name = "contributorRole" };
ValidationAssert.Throws(() => GuardAppRoles.CanDelete(roles_1, command, contributors.Assign("1", roleName), clients),
ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App(roles_1)),
new ValidationError("Cannot remove a role when a contributor is assigned."));
}
[Fact]
public void CanDelete_should_throw_exception_if_client_found()
{
var roles_1 = roles_0.Add(roleName);
var roles_1 = roles_0.Add("clientRole");
var clients_1 = clients.Add("1", "my-secret", roleName);
var command = new DeleteRole { Name = roleName };
var command = new DeleteRole { Name = "clientRole" };
ValidationAssert.Throws(() => GuardAppRoles.CanDelete(roles_1, command, contributors, clients_1),
ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App(roles_1)),
new ValidationError("Cannot remove a role when a client is assigned."));
}
@ -100,7 +99,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new DeleteRole { Name = Role.Developer };
ValidationAssert.Throws(() => GuardAppRoles.CanDelete(roles_1, command, contributors, clients),
ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App(roles_1)),
new ValidationError("Cannot delete a default role."));
}
@ -111,7 +110,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new DeleteRole { Name = roleName };
GuardAppRoles.CanDelete(roles_1, command, contributors, clients);
GuardAppRoles.CanDelete(command, App(roles_1));
}
[Fact]
@ -121,7 +120,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new UpdateRole { Name = null!, Permissions = new[] { "P1" } };
ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(roles_1, command),
ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App(roles_1)),
new ValidationError("Name is required.", "Name"));
}
@ -132,7 +131,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new UpdateRole { Name = roleName };
ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(roles_1, command),
ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App(roles_1)),
new ValidationError("Permissions is required.", "Permissions"));
}
@ -143,7 +142,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new UpdateRole { Name = Role.Developer, Permissions = new[] { "P1" } };
ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(roles_1, command),
ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App(roles_1)),
new ValidationError("Cannot update a default role."));
}
@ -152,7 +151,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new UpdateRole { Name = roleName, Permissions = new[] { "P1" } };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppRoles.CanUpdate(roles_0, command));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppRoles.CanUpdate(command, App(roles_0)));
}
[Fact]
@ -162,7 +161,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new UpdateRole { Name = roleName, Permissions = new[] { "P1" } };
GuardAppRoles.CanUpdate(roles_1, command);
GuardAppRoles.CanUpdate(command, App(roles_1));
}
[Fact]
@ -172,7 +171,21 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var command = new UpdateRole { Name = roleName, Permissions = new[] { "P1" } };
GuardAppRoles.CanUpdate(roles_1, command);
GuardAppRoles.CanUpdate(command, App(roles_1));
}
private IAppEntity App(Roles roles)
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Contributors)
.Returns(contributors);
A.CallTo(() => app.Clients)
.Returns(clients);
A.CallTo(() => app.Roles)
.Returns(roles);
return app;
}
}
}

20
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppTests.cs

@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
AppPlan? plan = null;
ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, plan, appPlans),
ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App(plan), appPlans),
new ValidationError("Plan ID is required.", "PlanId"));
}
@ -92,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
AppPlan? plan = null;
ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, plan, appPlans),
ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App(plan), appPlans),
new ValidationError("A plan with this id does not exist.", "PlanId"));
}
@ -103,7 +103,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var plan = new AppPlan(new RefToken("user", "other"), "premium");
ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, plan, appPlans),
ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App(plan), appPlans),
new ValidationError("Plan can only changed from the user who configured the plan initially."));
}
@ -114,7 +114,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var plan = new AppPlan(command.Actor, "basic");
GuardApp.CanChangePlan(command, plan, appPlans);
GuardApp.CanChangePlan(command, App(plan), appPlans);
}
[Fact]
@ -124,7 +124,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
var plan = new AppPlan(command.Actor, "premium");
GuardApp.CanChangePlan(command, plan, appPlans);
GuardApp.CanChangePlan(command, App(plan), appPlans);
}
private static IAppEntity App(AppPlan? plan)
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Plan)
.Returns(plan);
return app;
}
}
}

33
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppWorkflowTests.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System.Collections.Generic;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Apps.Commands;
@ -52,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
WorkflowId = DomainId.NewGuid()
};
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppWorkflows.CanUpdate(workflows, command));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppWorkflows.CanUpdate(command, App()));
}
[Fact]
@ -60,7 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new UpdateWorkflow { WorkflowId = workflowId };
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command),
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()),
new ValidationError("Workflow is required.", "Workflow"));
}
@ -78,7 +79,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
WorkflowId = workflowId
};
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command),
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()),
new ValidationError("Initial step is required.", "Workflow.Initial"));
}
@ -96,7 +97,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
WorkflowId = workflowId
};
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command),
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()),
new ValidationError("Initial step cannot be published step.", "Workflow.Initial"));
}
@ -114,7 +115,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
WorkflowId = workflowId
};
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command),
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()),
new ValidationError("Workflow must have a published step.", "Workflow.Steps"));
}
@ -133,7 +134,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
WorkflowId = workflowId
};
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command),
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()),
new ValidationError("Step is required.", "Workflow.Steps.Published"));
}
@ -157,7 +158,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
WorkflowId = workflowId
};
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command),
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()),
new ValidationError("Transition has an invalid target.", "Workflow.Steps.Published.Transitions.Archived"));
}
@ -182,7 +183,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
WorkflowId = workflowId
};
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command),
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()),
new ValidationError("Transition is required.", "Workflow.Steps.Published.Transitions.Draft"));
}
@ -191,7 +192,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new UpdateWorkflow { Workflow = Workflow.Default, WorkflowId = workflowId };
GuardAppWorkflows.CanUpdate(workflows, command);
GuardAppWorkflows.CanUpdate(command, App());
}
[Fact]
@ -199,7 +200,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new DeleteWorkflow { WorkflowId = DomainId.NewGuid() };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppWorkflows.CanDelete(workflows, command));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppWorkflows.CanDelete(command, App()));
}
[Fact]
@ -207,7 +208,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
var command = new DeleteWorkflow { WorkflowId = workflowId };
GuardAppWorkflows.CanDelete(workflows, command);
GuardAppWorkflows.CanDelete(command, App());
}
private IAppEntity App()
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Workflows)
.Returns(workflows);
return app;
}
}
}

5
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs

@ -14,6 +14,7 @@ using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Assets.State;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Assets;
@ -254,7 +255,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
await ExecuteCreateAsync();
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, Id))
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, Id, SearchScope.All))
.Returns(true);
await Assert.ThrowsAsync<DomainException>(() => PublishAsync(command));
@ -267,7 +268,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
await ExecuteCreateAsync();
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, Id))
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, Id, SearchScope.All))
.Returns(true);
await PublishAsync(command);

27
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetFolderTests.cs

@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
var command = new CreateAssetFolder { AppId = appId, FolderName = "My Folder", ParentId = DomainId.NewGuid() };
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, command.ParentId))
.Returns(new List<IAssetFolderEntity> { CreateFolder() });
.Returns(new List<IAssetFolderEntity> { AssetFolder() });
await GuardAssetFolder.CanCreate(command, assetQuery);
}
@ -75,11 +75,11 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, command.ParentId))
.Returns(new List<IAssetFolderEntity>
{
CreateFolder(id),
CreateFolder(command.ParentId)
AssetFolder(id),
AssetFolder(command.ParentId)
});
await ValidationAssert.ThrowsAsync(() => GuardAssetFolder.CanMove(command, assetQuery, id, DomainId.NewGuid()),
await ValidationAssert.ThrowsAsync(() => GuardAssetFolder.CanMove(command, AssetFolder(id), assetQuery),
new ValidationError("Cannot add folder to its own child.", "ParentId"));
}
@ -91,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, command.ParentId))
.Returns(new List<IAssetFolderEntity>());
await ValidationAssert.ThrowsAsync(() => GuardAssetFolder.CanMove(command, assetQuery, DomainId.NewGuid(), DomainId.NewGuid()),
await ValidationAssert.ThrowsAsync(() => GuardAssetFolder.CanMove(command, AssetFolder(), assetQuery),
new ValidationError("Asset folder does not exist.", "ParentId"));
}
@ -101,9 +101,9 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
var command = new MoveAssetFolder { AppId = appId, ParentId = DomainId.NewGuid() };
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, command.ParentId))
.Returns(new List<IAssetFolderEntity> { CreateFolder() });
.Returns(new List<IAssetFolderEntity> { AssetFolder() });
await GuardAssetFolder.CanMove(command, assetQuery, DomainId.NewGuid(), DomainId.NewGuid());
await GuardAssetFolder.CanMove(command, AssetFolder(), assetQuery);
}
[Fact]
@ -111,7 +111,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
{
var command = new MoveAssetFolder { AppId = appId, ParentId = DomainId.NewGuid() };
await GuardAssetFolder.CanMove(command, assetQuery, DomainId.NewGuid(), command.ParentId);
await GuardAssetFolder.CanMove(command, AssetFolder(parentId: command.ParentId), assetQuery);
}
[Fact]
@ -119,7 +119,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
{
var command = new MoveAssetFolder { AppId = appId };
await GuardAssetFolder.CanMove(command, assetQuery, DomainId.NewGuid(), DomainId.NewGuid());
await GuardAssetFolder.CanMove(command, AssetFolder(), assetQuery);
}
[Fact]
@ -147,11 +147,16 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
GuardAssetFolder.CanDelete(command);
}
private static IAssetFolderEntity CreateFolder(DomainId id = default)
private IAssetFolderEntity AssetFolder(DomainId id = default, DomainId parentId = default)
{
var assetFolder = A.Fake<IAssetFolderEntity>();
A.CallTo(() => assetFolder.Id).Returns(id);
A.CallTo(() => assetFolder.Id)
.Returns(id == default ? DomainId.NewGuid() : id);
A.CallTo(() => assetFolder.AppId)
.Returns(appId);
A.CallTo(() => assetFolder.ParentId)
.Returns(parentId == default ? DomainId.NewGuid() : parentId);
return assetFolder;
}

55
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetTests.cs

@ -10,6 +10,8 @@ using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Validation;
@ -20,6 +22,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
public class GuardAssetTests : IClassFixture<TranslationsFixture>
{
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
[Fact]
@ -28,7 +31,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
var command = new CreateAsset { AppId = appId, ParentId = DomainId.NewGuid() };
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, command.ParentId))
.Returns(new List<IAssetFolderEntity> { CreateFolder() });
.Returns(new List<IAssetFolderEntity> { AssetFolder() });
await GuardAsset.CanCreate(command, assetQuery);
}
@ -61,7 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, command.ParentId))
.Returns(new List<IAssetFolderEntity>());
await ValidationAssert.ThrowsAsync(() => GuardAsset.CanMove(command, assetQuery, DomainId.NewGuid()),
await ValidationAssert.ThrowsAsync(() => GuardAsset.CanMove(command, Asset(), assetQuery),
new ValidationError("Asset folder does not exist.", "ParentId"));
}
@ -70,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
{
var command = new MoveAsset { AppId = appId, ParentId = DomainId.NewGuid() };
await GuardAsset.CanMove(command, assetQuery, command.ParentId);
await GuardAsset.CanMove(command, Asset(parentId: command.ParentId), assetQuery);
}
[Fact]
@ -79,9 +82,9 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
var command = new MoveAsset { AppId = appId, ParentId = DomainId.NewGuid() };
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, command.ParentId))
.Returns(new List<IAssetFolderEntity> { CreateFolder() });
.Returns(new List<IAssetFolderEntity> { AssetFolder() });
await GuardAsset.CanMove(command, assetQuery, DomainId.NewGuid());
await GuardAsset.CanMove(command, Asset(), assetQuery);
}
[Fact]
@ -89,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
{
var command = new MoveAsset { AppId = appId };
await GuardAsset.CanMove(command, assetQuery, DomainId.NewGuid());
await GuardAsset.CanMove(command, Asset(), assetQuery);
}
[Fact]
@ -117,18 +120,50 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
}
[Fact]
public void CanDelete_should_not_throw_exception()
public async Task CanDelete_should_throw_exception_if_referenced()
{
var asset = Asset();
var command = new DeleteAsset { AppId = appId, CheckReferrers = true };
A.CallTo(() => contentRepository.HasReferrersAsync(appId.Id, asset.Id, SearchScope.All))
.Returns(true);
await Assert.ThrowsAsync<DomainException>(() => GuardAsset.CanDelete(command, asset, contentRepository));
}
[Fact]
public async Task CanDelete_should_not_throw_exception()
{
var command = new DeleteAsset { AppId = appId };
GuardAsset.CanDelete(command);
await GuardAsset.CanDelete(command, Asset(), contentRepository);
}
private IAssetEntity Asset(DomainId id = default, DomainId parentId = default)
{
var asset = A.Fake<IAssetEntity>();
A.CallTo(() => asset.Id)
.Returns(id == default ? DomainId.NewGuid() : id);
A.CallTo(() => asset.AppId)
.Returns(appId);
A.CallTo(() => asset.ParentId)
.Returns(parentId == default ? DomainId.NewGuid() : parentId);
return asset;
}
private static IAssetFolderEntity CreateFolder(DomainId id = default)
private IAssetFolderEntity AssetFolder(DomainId id = default, DomainId parentId = default)
{
var assetFolder = A.Fake<IAssetFolderEntity>();
A.CallTo(() => assetFolder.Id).Returns(id);
A.CallTo(() => assetFolder.Id)
.Returns(id == default ? DomainId.NewGuid() : id);
A.CallTo(() => assetFolder.AppId)
.Returns(appId);
A.CallTo(() => assetFolder.ParentId)
.Returns(parentId == default ? DomainId.NewGuid() : parentId);
return assetFolder;
}

22
backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/Guards/GuardCommentsTests.cs

@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
Envelope.Create<CommentsEvent>(new CommentCreated { CommentId = commentId, Actor = user1 }).To<CommentsEvent>()
};
ValidationAssert.Throws(() => GuardComments.CanUpdate(commentsId, events, command),
ValidationAssert.Throws(() => GuardComments.CanUpdate(command, commentsId, events),
new ValidationError("Text is required.", "Text"));
}
@ -66,7 +66,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
Envelope.Create<CommentsEvent>(new CommentCreated { CommentId = commentId, Actor = user1 }).To<CommentsEvent>()
};
Assert.Throws<DomainException>(() => GuardComments.CanUpdate(commentsId, events, command));
Assert.Throws<DomainException>(() => GuardComments.CanUpdate(command, commentsId, events));
}
[Fact]
@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
var events = new List<Envelope<CommentsEvent>>();
Assert.Throws<DomainObjectNotFoundException>(() => GuardComments.CanUpdate(commentsId, events, command));
Assert.Throws<DomainObjectNotFoundException>(() => GuardComments.CanUpdate(command, commentsId, events));
}
[Fact]
@ -92,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
Envelope.Create<CommentsEvent>(new CommentDeleted { CommentId = commentId }).To<CommentsEvent>()
};
Assert.Throws<DomainObjectNotFoundException>(() => GuardComments.CanUpdate(commentsId, events, command));
Assert.Throws<DomainObjectNotFoundException>(() => GuardComments.CanUpdate(command, commentsId, events));
}
[Fact]
@ -106,7 +106,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
Envelope.Create<CommentsEvent>(new CommentCreated { CommentId = commentId, Actor = user1 }).To<CommentsEvent>()
};
GuardComments.CanUpdate(user1.Identifier, events, command);
GuardComments.CanUpdate(command, user1.Identifier, events);
}
[Fact]
@ -120,7 +120,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
Envelope.Create<CommentsEvent>(new CommentCreated { CommentId = commentId, Actor = user1 }).To<CommentsEvent>()
};
GuardComments.CanUpdate(commentsId, events, command);
GuardComments.CanUpdate(command, commentsId, events);
}
[Fact]
@ -134,7 +134,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
Envelope.Create<CommentsEvent>(new CommentCreated { CommentId = commentId, Actor = user1 }).To<CommentsEvent>()
};
Assert.Throws<DomainException>(() => GuardComments.CanDelete(commentsId, events, command));
Assert.Throws<DomainException>(() => GuardComments.CanDelete(command, commentsId, events));
}
[Fact]
@ -145,7 +145,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
var events = new List<Envelope<CommentsEvent>>();
Assert.Throws<DomainObjectNotFoundException>(() => GuardComments.CanDelete(commentsId, events, command));
Assert.Throws<DomainObjectNotFoundException>(() => GuardComments.CanDelete(command, commentsId, events));
}
[Fact]
@ -160,7 +160,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
Envelope.Create<CommentsEvent>(new CommentDeleted { CommentId = commentId })
};
Assert.Throws<DomainObjectNotFoundException>(() => GuardComments.CanDelete(commentsId, events, command));
Assert.Throws<DomainObjectNotFoundException>(() => GuardComments.CanDelete(command, commentsId, events));
}
[Fact]
@ -174,7 +174,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
Envelope.Create<CommentsEvent>(new CommentCreated { CommentId = commentId, Actor = user1 }).To<CommentsEvent>()
};
GuardComments.CanDelete(user1.Identifier, events, command);
GuardComments.CanDelete(command, user1.Identifier, events);
}
[Fact]
@ -188,7 +188,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
Envelope.Create<CommentsEvent>(new CommentCreated { CommentId = commentId, Actor = user1 }).To<CommentsEvent>()
};
GuardComments.CanDelete(commentsId, events, command);
GuardComments.CanDelete(command, commentsId, events);
}
}
}

40
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs

@ -565,12 +565,40 @@ namespace Squidex.Domain.Apps.Entities.Contents
}
[Fact]
public async Task Delete_should_create_events_and_update_deleted_flag()
public async Task ChangeStatus_should_throw_exception_if_referenced_by_other_item()
{
var command = new DeleteContent();
var command = new ChangeContentStatus { Status = Status.Draft, CheckReferrers = true };
await ExecuteCreateAsync();
await ExecuteChangeStatusAsync(Status.Published);
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, contentId, SearchScope.Published))
.Returns(true);
await Assert.ThrowsAsync<DomainException>(() => PublishAsync(command));
}
[Fact]
public async Task ChangeStatus_should_not_throw_exception_if_referenced_by_other_item_but_forced()
{
var command = new ChangeContentStatus { Status = Status.Draft, CheckReferrers = false };
await ExecuteCreateAsync();
await ExecuteChangeStatusAsync(Status.Published);
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, contentId, SearchScope.Published))
.Returns(true);
await PublishAsync(command);
}
[Fact]
public async Task Delete_should_create_events_and_update_deleted_flag()
{
await ExecuteCreateAsync();
var command = new DeleteContent();
var result = await PublishAsync(command);
result.ShouldBeEquivalent(new EntitySavedResult(1));
@ -589,11 +617,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Delete_should_throw_exception_if_referenced_by_other_item()
{
var command = new DeleteContent { CheckReferrers = true };
await ExecuteCreateAsync();
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, contentId))
var command = new DeleteContent { CheckReferrers = true };
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, contentId, SearchScope.All))
.Returns(true);
await Assert.ThrowsAsync<DomainException>(() => PublishAsync(command));
@ -606,7 +634,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
await ExecuteCreateAsync();
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, contentId))
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, contentId, SearchScope.All))
.Returns(true);
await PublishAsync(command);

69
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs

@ -15,7 +15,6 @@ using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.Guards;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
@ -39,7 +38,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var command = new CreateContent();
await ValidationAssert.ThrowsAsync(() => GuardContent.CanCreate(schema, contentWorkflow, command),
await ValidationAssert.ThrowsAsync(() => GuardContent.CanCreate(command, contentWorkflow, schema),
new ValidationError("Data is required.", "Data"));
}
@ -50,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var command = new CreateContent { Data = new NamedContentData() };
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanCreate(schema, contentWorkflow, command));
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanCreate(command, contentWorkflow, schema));
}
[Fact]
@ -60,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var command = new CreateContent { Data = new NamedContentData(), ContentId = schema.Id };
await GuardContent.CanCreate(schema, contentWorkflow, command);
await GuardContent.CanCreate(command, contentWorkflow, schema);
}
[Fact]
@ -72,7 +71,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var command = new CreateContent { Data = new NamedContentData(), Publish = true };
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanCreate(schema, contentWorkflow, command));
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanCreate(command, contentWorkflow, schema));
}
[Fact]
@ -84,7 +83,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var command = new CreateContent { Data = new NamedContentData(), Publish = true };
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanCreate(schema, contentWorkflow, command));
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanCreate(command, contentWorkflow, schema));
}
[Fact]
@ -94,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var command = new CreateContent { Data = new NamedContentData() };
await GuardContent.CanCreate(schema, contentWorkflow, command);
await GuardContent.CanCreate(command, contentWorkflow, schema);
}
[Fact]
@ -105,7 +104,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Draft);
var command = new UpdateContent();
await ValidationAssert.ThrowsAsync(() => GuardContent.CanUpdate(content, contentWorkflow, command),
await ValidationAssert.ThrowsAsync(() => GuardContent.CanUpdate(command, content, contentWorkflow),
new ValidationError("Data is required.", "Data"));
}
@ -117,7 +116,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Draft);
var command = new UpdateContent { Data = new NamedContentData() };
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanUpdate(content, contentWorkflow, command));
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanUpdate(command, content, contentWorkflow));
}
[Fact]
@ -128,7 +127,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Draft);
var command = new UpdateContent { Data = new NamedContentData(), User = user };
await GuardContent.CanUpdate(content, contentWorkflow, command);
await GuardContent.CanUpdate(command, content, contentWorkflow);
}
[Fact]
@ -139,7 +138,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Draft);
var command = new PatchContent();
await ValidationAssert.ThrowsAsync(() => GuardContent.CanPatch(content, contentWorkflow, command),
await ValidationAssert.ThrowsAsync(() => GuardContent.CanPatch(command, content, contentWorkflow),
new ValidationError("Data is required.", "Data"));
}
@ -151,7 +150,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Draft);
var command = new PatchContent { Data = new NamedContentData() };
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanPatch(content, contentWorkflow, command));
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanPatch(command, content, contentWorkflow));
}
[Fact]
@ -162,7 +161,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Draft);
var command = new PatchContent { Data = new NamedContentData(), User = user };
await GuardContent.CanPatch(content, contentWorkflow, command);
await GuardContent.CanPatch(command, content, contentWorkflow);
}
[Fact]
@ -173,7 +172,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Published);
var command = new ChangeContentStatus { Status = Status.Draft };
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanChangeStatus(schema, content, contentWorkflow, command));
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema));
}
[Fact]
@ -187,7 +186,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
A.CallTo(() => contentWorkflow.CanMoveToAsync(content, content.Status, command.Status, user))
.Returns(true);
await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(schema, content, contentWorkflow, command),
await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema),
new ValidationError("Due time must be in the future.", "DueTime"));
}
@ -202,10 +201,24 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
A.CallTo(() => contentWorkflow.CanMoveToAsync(content, content.Status, command.Status, user))
.Returns(false);
await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(schema, content, contentWorkflow, command),
await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema),
new ValidationError("Cannot change status from Draft to Published.", "Status"));
}
[Fact]
public async Task CanChangeStatus_should_throw_exception_if_referenced()
{
var schema = CreateSchema(true);
var content = CreateContent(Status.Published);
var command = new ChangeContentStatus { Status = Status.Draft, User = user };
A.CallTo(() => contentRepository.HasReferrersAsync(appId.Id, content.Id, SearchScope.Published))
.Returns(true);
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema));
}
[Fact]
public async Task CanChangeStatus_should_not_throw_exception_if_singleton_is_published()
{
@ -214,7 +227,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateDraftContent(Status.Draft);
var command = new ChangeContentStatus { Status = Status.Published };
await GuardContent.CanChangeStatus(schema, content, contentWorkflow, command);
await GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema);
}
[Fact]
@ -228,7 +241,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
A.CallTo(() => contentWorkflow.CanMoveToAsync(content, content.Status, command.Status, user))
.Returns(true);
await GuardContent.CanChangeStatus(schema, content, contentWorkflow, command);
await GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema);
}
[Fact]
@ -279,7 +292,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Published);
var command = new DeleteContent();
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanDelete(schema, content, contentRepository, command));
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanDelete(command, content, contentRepository, schema));
}
[Fact]
@ -290,10 +303,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Published);
var command = new DeleteContent();
A.CallTo(() => contentRepository.HasReferrersAsync(appId.Id, content.Id))
A.CallTo(() => contentRepository.HasReferrersAsync(appId.Id, content.Id, SearchScope.All))
.Returns(true);
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanDelete(schema, content, contentRepository, command));
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanDelete(command, content, contentRepository, schema));
}
[Fact]
@ -304,7 +317,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Published);
var command = new DeleteContent();
await GuardContent.CanDelete(schema, content, contentRepository, command);
await GuardContent.CanDelete(command, content, contentRepository, schema);
}
private void SetupCanUpdate(bool canUpdate)
@ -324,22 +337,22 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
return Mocks.Schema(appId, NamedId.Of(DomainId.NewGuid(), "my-schema"), new Schema("schema", isSingleton: isSingleton));
}
private ContentState CreateDraftContent(Status status)
private IContentEntity CreateDraftContent(Status status)
{
return new ContentState
return new ContentEntity
{
Id = DomainId.NewGuid(),
NewVersion = new ContentVersion(status, new NamedContentData()),
NewStatus = status,
AppId = appId
};
}
private ContentState CreateContent(Status status)
private IContentEntity CreateContent(Status status)
{
return new ContentState
return new ContentEntity
{
Id = DomainId.NewGuid(),
CurrentVersion = new ContentVersion(status, new NamedContentData()),
Status = status,
AppId = appId
};
}

35
backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs

@ -41,14 +41,15 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
[Fact]
public async Task CanCreate_should_throw_exception_if_trigger_null()
{
var command = CreateCommand(new CreateRule
var command = new CreateRule
{
Trigger = null!,
Action = new TestAction
{
Url = validUrl
}
});
},
AppId = appId
};
await ValidationAssert.ThrowsAsync(() => GuardRule.CanCreate(command, appProvider),
new ValidationError("Trigger is required.", "Trigger"));
@ -57,14 +58,15 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
[Fact]
public async Task CanCreate_should_throw_exception_if_action_null()
{
var command = CreateCommand(new CreateRule
var command = new CreateRule
{
Trigger = new ContentChangedTriggerV2
{
Schemas = ReadOnlyCollection.Empty<ContentChangedTriggerSchemaV2>()
},
Action = null!
});
Action = null!,
AppId = appId
};
await ValidationAssert.ThrowsAsync(() => GuardRule.CanCreate(command, appProvider),
new ValidationError("Action is required.", "Action"));
@ -73,7 +75,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
[Fact]
public async Task CanCreate_should_not_throw_exception_if_trigger_and_action_valid()
{
var command = CreateCommand(new CreateRule
var command = new CreateRule
{
Trigger = new ContentChangedTriggerV2
{
@ -82,8 +84,9 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
Action = new TestAction
{
Url = validUrl
}
});
},
AppId = appId
};
await GuardRule.CanCreate(command, appProvider);
}
@ -93,7 +96,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
{
var command = new UpdateRule();
await GuardRule.CanUpdate(command, appId.Id, appProvider);
await GuardRule.CanUpdate(command, Rule(), appProvider);
}
[Fact]
@ -101,7 +104,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
{
var command = new UpdateRule { Name = "MyName" };
await GuardRule.CanUpdate(command, appId.Id, appProvider);
await GuardRule.CanUpdate(command, Rule(), appProvider);
}
[Fact]
@ -120,7 +123,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
Name = "NewName"
};
await GuardRule.CanUpdate(command, appId.Id, appProvider);
await GuardRule.CanUpdate(command, Rule(), appProvider);
}
[Fact]
@ -147,11 +150,13 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
GuardRule.CanDelete(command);
}
private CreateRule CreateCommand(CreateRule command)
private IRuleEntity Rule()
{
command.AppId = appId;
var rule = A.Fake<IRuleEntity>();
A.CallTo(() => rule.AppId).Returns(appId);
return command;
return rule;
}
}
}

4
frontend/app/app.module.ts

@ -55,7 +55,7 @@ export function configCurrency() {
return new CurrencyConfig('EUR', '€', true);
}
export function configTranslations() {
export function configLocalizerService() {
if (process.env.NODE_ENV === 'production') {
return new LocalizerService(window['texts']);
} else {
@ -86,8 +86,8 @@ export function configTranslations() {
{ provide: ApiUrlConfig, useFactory: configApiUrl },
{ provide: CurrencyConfig, useFactory: configCurrency },
{ provide: DecimalSeparatorConfig, useFactory: configDecimalSeparator },
{ provide: LocalizerService, useFactory: configLocalizerService },
{ provide: TitlesConfig, useFactory: configTitles },
{ provide: LocalizerService, useFactory: configTranslations },
{ provide: UIOptions, useFactory: configUIOptions }
],
entryComponents: [AppComponent]

2
frontend/app/features/content/pages/content/content-history-page.component.ts

@ -73,7 +73,7 @@ export class ContentHistoryPageComponent extends ResourceOwner implements OnInit
this.contentPage.checkPendingChangesBeforeChangingStatus().pipe(
filter(x => !!x),
switchMap(_ => this.dueTimeSelector.selectDueTime(status)),
switchMap(d => this.contentsState.changeStatus(this.content, status, d)),
switchMap(d => this.contentsState.changeManyStatus([this.content], status, d)),
onErrorResumeNext())
.subscribe();
}

2
frontend/app/shared/services/contents.service.spec.ts

@ -366,7 +366,7 @@ describe('ContentsService', () => {
let content: ContentDto;
contentsService.putStatus('my-app', resource, 'published', '2016-12-12T10:10:00', version).subscribe(result => {
contentsService.putStatus('my-app', resource, 'published', true, '2016-12-12T10:10:00', version).subscribe(result => {
content = result;
});

4
frontend/app/shared/services/contents.service.ts

@ -301,12 +301,12 @@ export class ContentsService {
pretifyError('i18n:contents.deleteVersionFailed'));
}
public putStatus(appName: string, resource: Resource, status: string, dueTime: string | null, version: Version): Observable<ContentDto> {
public putStatus(appName: string, resource: Resource, status: string, checkReferrers: boolean, dueTime: string | null, version: Version): Observable<ContentDto> {
const link = resource._links[`status/${status}`];
const url = this.apiUrl.buildUrl(link.href);
return HTTP.requestVersioned(this.http, link.method, url, version, { status, dueTime }).pipe(
return HTTP.requestVersioned(this.http, link.method, url, version, { status, dueTime, checkReferrers }).pipe(
map(({ payload }) => {
return parseContent(payload.body);
}),

41
frontend/app/shared/state/contents.state.ts

@ -224,7 +224,28 @@ export abstract class ContentsStateBase extends State<Snapshot> {
}
public changeManyStatus(contentsToChange: ReadonlyArray<ContentDto>, status: string, dueTime: string | null): Observable<any> {
return this.updateManyStatus(contentsToChange, status, dueTime).pipe(
return this.changeManyStatusCore(contentsToChange, status, true, dueTime).pipe(
switchMap(results => {
const referenced = results.filter(x => x.error?.statusCode === 400).map(x => x.content);
if (referenced.length > 0) {
return this.dialogs.confirm(
'i18n:contents.unpublishReferrerConfirmTitle',
'i18n:contents.unpublishReferrerConfirmText',
'unpublishReferencngContent'
).pipe(
switchMap(confirmed => {
if (confirmed) {
return this.changeManyStatusCore(referenced, status, false, dueTime);
} else {
return of([]);
}
})
);
} else {
return of(results);
}
}),
tap(results => {
const errors = results.filter(x => !!x.error);
@ -260,7 +281,7 @@ export abstract class ContentsStateBase extends State<Snapshot> {
return this.dialogs.confirm(
'i18n:contents.deleteReferrerConfirmTitle',
'i18n:contents.deleteReferrerConfirmText',
'deleteReferencedAsset'
'deleteReferencingContent'
).pipe(
switchMap(confirmed => {
if (confirmed) {
@ -302,14 +323,6 @@ export abstract class ContentsStateBase extends State<Snapshot> {
shareSubscribed(this.dialogs, { silent: true }));
}
public changeStatus(content: ContentDto, status: string, dueTime: string | null): Observable<ContentDto> {
return this.contentsService.putStatus(this.appName, content, status, dueTime, content.version).pipe(
tap(updated => {
this.replaceContent(updated, content.version, 'i18n:contents.updated');
}),
shareSubscribed(this.dialogs));
}
public update(content: ContentDto, request: any): Observable<ContentDto> {
return this.contentsService.putContent(this.appName, content, request, content.version).pipe(
tap(updated => {
@ -379,9 +392,9 @@ export abstract class ContentsStateBase extends State<Snapshot> {
contents.map(c => this.deleteCore(c, checkReferrers)));
}
private updateManyStatus(contents: ReadonlyArray<ContentDto>, status: string, dueTime: string | null): Observable<ReadonlyArray<Updated>> {
private changeManyStatusCore(contents: ReadonlyArray<ContentDto>, status: string, checkReferrers: boolean, dueTime: string | null): Observable<ReadonlyArray<Updated>> {
return forkJoin(
contents.map(c => this.updateStatus(c, status, dueTime)));
contents.map(c => this.changeStatusCore(c, status, checkReferrers, dueTime)));
}
private deleteCore(content: ContentDto, checkReferrers: boolean): Observable<Updated> {
@ -389,8 +402,8 @@ export abstract class ContentsStateBase extends State<Snapshot> {
map(() => ({ content })), catchError(error => of({ content, error })));
}
private updateStatus(content: ContentDto, status: string, dueTime: string | null): Observable<Updated> {
return this.contentsService.putStatus(this.appName, content, status, dueTime, content.version).pipe(
private changeStatusCore(content: ContentDto, status: string, checkReferrers: boolean, dueTime: string | null): Observable<Updated> {
return this.contentsService.putStatus(this.appName, content, status, checkReferrers, dueTime, content.version).pipe(
map(x => ({ content: x })), catchError(error => of({ content, error })));
}

Loading…
Cancel
Save