mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
68 changed files with 2399 additions and 1718 deletions
@ -1,72 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Apps.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Squidex.Infrastructure.Validation; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public sealed class AppsByNameIndexCommandMiddleware : ICommandMiddleware |
|||
{ |
|||
private readonly IAppsByNameIndex index; |
|||
|
|||
public AppsByNameIndexCommandMiddleware(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
index = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id); |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
var createApp = context.Command as CreateApp; |
|||
|
|||
var isReserved = false; |
|||
try |
|||
{ |
|||
if (createApp != null) |
|||
{ |
|||
isReserved = await index.ReserveAppAsync(createApp.AppId, createApp.Name); |
|||
|
|||
if (!isReserved) |
|||
{ |
|||
var error = new ValidationError("An app with the same name already exists.", nameof(createApp.Name)); |
|||
|
|||
throw new ValidationException("Cannot create app.", error); |
|||
} |
|||
} |
|||
|
|||
await next(); |
|||
|
|||
if (context.IsCompleted) |
|||
{ |
|||
if (createApp != null) |
|||
{ |
|||
await index.AddAppAsync(createApp.AppId, createApp.Name); |
|||
} |
|||
else if (context.Command is ArchiveApp archiveApp) |
|||
{ |
|||
await index.RemoveAppAsync(archiveApp.AppId); |
|||
} |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
if (isReserved) |
|||
{ |
|||
await index.RemoveReservationAsync(createApp.AppId, createApp.Name); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,80 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Apps.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public sealed class AppsByUserIndexCommandMiddleware : ICommandMiddleware |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public AppsByUserIndexCommandMiddleware(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
if (context.IsCompleted) |
|||
{ |
|||
switch (context.Command) |
|||
{ |
|||
case CreateApp createApp: |
|||
await Index(GetUserId(createApp)).AddAppAsync(createApp.AppId); |
|||
break; |
|||
case AssignContributor assignContributor: |
|||
await Index(GetUserId(assignContributor)).AddAppAsync(assignContributor.AppId); |
|||
break; |
|||
case RemoveContributor removeContributor: |
|||
await Index(GetUserId(removeContributor)).RemoveAppAsync(removeContributor.AppId); |
|||
break; |
|||
case ArchiveApp archiveApp: |
|||
{ |
|||
var appState = await grainFactory.GetGrain<IAppGrain>(archiveApp.AppId).GetStateAsync(); |
|||
|
|||
foreach (var contributorId in appState.Value.Contributors.Keys) |
|||
{ |
|||
await Index(contributorId).RemoveAppAsync(archiveApp.AppId); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
await next(); |
|||
} |
|||
|
|||
private static string GetUserId(CreateApp createApp) |
|||
{ |
|||
return createApp.Actor.Identifier; |
|||
} |
|||
|
|||
private static string GetUserId(AssignContributor assignContributor) |
|||
{ |
|||
return assignContributor.ContributorId; |
|||
} |
|||
|
|||
private static string GetUserId(RemoveContributor removeContributor) |
|||
{ |
|||
return removeContributor.ContributorId; |
|||
} |
|||
|
|||
private IAppsByUserIndex Index(string id) |
|||
{ |
|||
return grainFactory.GetGrain<IAppsByUserIndex>(id); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,286 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Apps.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Squidex.Infrastructure.Security; |
|||
using Squidex.Infrastructure.Validation; |
|||
using Squidex.Shared; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public sealed class AppsIndex : IAppsIndex, ICommandMiddleware |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public AppsIndex(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public async Task RebuildByContributorsAsync(Guid appId, HashSet<string> contributors) |
|||
{ |
|||
foreach (var contributorId in contributors) |
|||
{ |
|||
await Index(contributorId).AddAsync(appId); |
|||
} |
|||
} |
|||
|
|||
public Task RebuildByContributorsAsync(string contributorId, HashSet<Guid> apps) |
|||
{ |
|||
return Index(contributorId).RebuildAsync(apps); |
|||
} |
|||
|
|||
public Task RebuildAsync(Dictionary<string, Guid> appsByName) |
|||
{ |
|||
return Index().RebuildAsync(appsByName); |
|||
} |
|||
|
|||
public Task RemoveReservationAsync(string token) |
|||
{ |
|||
return Index().RemoveReservationAsync(token); |
|||
} |
|||
|
|||
public Task<List<Guid>> GetIdsAsync() |
|||
{ |
|||
return Index().GetIdsAsync(); |
|||
} |
|||
|
|||
public Task<bool> AddAsync(string token) |
|||
{ |
|||
return Index().AddAsync(token); |
|||
} |
|||
|
|||
public Task<string> ReserveAsync(Guid id, string name) |
|||
{ |
|||
return Index().ReserveAsync(id, name); |
|||
} |
|||
|
|||
public async Task<List<IAppEntity>> GetAppsAsync() |
|||
{ |
|||
using (Profiler.TraceMethod<AppsIndex>()) |
|||
{ |
|||
var ids = await GetAppIdsAsync(); |
|||
|
|||
var apps = |
|||
await Task.WhenAll(ids |
|||
.Select(id => GetAppAsync(id))); |
|||
|
|||
return apps.Where(x => x != null).ToList(); |
|||
} |
|||
} |
|||
|
|||
public async Task<List<IAppEntity>> GetAppsForUserAsync(string userId, PermissionSet permissions) |
|||
{ |
|||
using (Profiler.TraceMethod<AppsIndex>()) |
|||
{ |
|||
var ids = |
|||
await Task.WhenAll( |
|||
GetAppIdsByUserAsync(userId), |
|||
GetAppIdsAsync(permissions.ToAppNames())); |
|||
|
|||
var apps = |
|||
await Task.WhenAll(ids |
|||
.SelectMany(x => x) |
|||
.Select(id => GetAppAsync(id))); |
|||
|
|||
return apps.Where(x => x != null).ToList(); |
|||
} |
|||
} |
|||
|
|||
public async Task<IAppEntity> GetAppAsync(string name) |
|||
{ |
|||
using (Profiler.TraceMethod<AppsIndex>()) |
|||
{ |
|||
var appId = await GetAppIdAsync(name); |
|||
|
|||
if (appId == default) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return await GetAppAsync(appId); |
|||
} |
|||
} |
|||
|
|||
public async Task<IAppEntity> GetAppAsync(Guid appId) |
|||
{ |
|||
using (Profiler.TraceMethod<AppsIndex>()) |
|||
{ |
|||
var app = await grainFactory.GetGrain<IAppGrain>(appId).GetStateAsync(); |
|||
|
|||
if (IsFound(app.Value)) |
|||
{ |
|||
return app.Value; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private async Task<List<Guid>> GetAppIdsByUserAsync(string userId) |
|||
{ |
|||
using (Profiler.TraceMethod<AppProvider>()) |
|||
{ |
|||
return await grainFactory.GetGrain<IAppsByUserIndexGrain>(userId).GetIdsAsync(); |
|||
} |
|||
} |
|||
|
|||
private async Task<List<Guid>> GetAppIdsAsync() |
|||
{ |
|||
using (Profiler.TraceMethod<AppProvider>()) |
|||
{ |
|||
return await grainFactory.GetGrain<IAppsByNameIndexGrain>(SingleGrain.Id).GetIdsAsync(); |
|||
} |
|||
} |
|||
|
|||
private async Task<List<Guid>> GetAppIdsAsync(string[] names) |
|||
{ |
|||
using (Profiler.TraceMethod<AppProvider>()) |
|||
{ |
|||
return await grainFactory.GetGrain<IAppsByNameIndexGrain>(SingleGrain.Id).GetIdsAsync(names); |
|||
} |
|||
} |
|||
|
|||
private async Task<Guid> GetAppIdAsync(string name) |
|||
{ |
|||
using (Profiler.TraceMethod<AppProvider>()) |
|||
{ |
|||
return await grainFactory.GetGrain<IAppsByNameIndexGrain>(SingleGrain.Id).GetIdAsync(name); |
|||
} |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
if (context.Command is CreateApp createApp) |
|||
{ |
|||
var index = Index(); |
|||
|
|||
string token = await CheckAppAsync(index, createApp); |
|||
|
|||
try |
|||
{ |
|||
await next(); |
|||
} |
|||
finally |
|||
{ |
|||
if (token != null) |
|||
{ |
|||
if (context.IsCompleted) |
|||
{ |
|||
await index.AddAsync(token); |
|||
|
|||
if (createApp.Actor.IsSubject) |
|||
{ |
|||
await Index(createApp.Actor.Identifier).AddAsync(createApp.AppId); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
await index.RemoveReservationAsync(token); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
await next(); |
|||
|
|||
if (context.IsCompleted) |
|||
{ |
|||
switch (context.Command) |
|||
{ |
|||
case AssignContributor assignContributor: |
|||
await AssignContributorAsync(assignContributor); |
|||
break; |
|||
|
|||
case RemoveContributor removeContributor: |
|||
await RemoveContributorAsync(removeContributor); |
|||
break; |
|||
|
|||
case ArchiveApp archiveApp: |
|||
await ArchiveAppAsync(archiveApp); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private async Task<string> CheckAppAsync(IAppsByNameIndexGrain index, CreateApp command) |
|||
{ |
|||
var name = command.Name; |
|||
|
|||
if (name.IsSlug()) |
|||
{ |
|||
var token = await index.ReserveAsync(command.AppId, name); |
|||
|
|||
if (token == null) |
|||
{ |
|||
var error = new ValidationError("An app with this already exists."); |
|||
|
|||
throw new ValidationException("Cannot create app.", error); |
|||
} |
|||
|
|||
return token; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private Task AssignContributorAsync(AssignContributor command) |
|||
{ |
|||
return Index(command.ContributorId).AddAsync(command.AppId); |
|||
} |
|||
|
|||
private Task RemoveContributorAsync(RemoveContributor command) |
|||
{ |
|||
return Index(command.ContributorId).RemoveAsync(command.AppId); |
|||
} |
|||
|
|||
private async Task ArchiveAppAsync(ArchiveApp command) |
|||
{ |
|||
var appId = command.AppId; |
|||
|
|||
var app = await grainFactory.GetGrain<IAppGrain>(appId).GetStateAsync(); |
|||
|
|||
if (IsFound(app.Value)) |
|||
{ |
|||
await Index().RemoveAsync(appId); |
|||
} |
|||
|
|||
foreach (var contributorId in app.Value.Contributors.Keys) |
|||
{ |
|||
await Index(contributorId).RemoveAsync(appId); |
|||
} |
|||
} |
|||
|
|||
private static bool IsFound(IAppEntity app) |
|||
{ |
|||
return app.Version > EtagVersion.Empty && !app.IsArchived; |
|||
} |
|||
|
|||
private IAppsByNameIndexGrain Index() |
|||
{ |
|||
return grainFactory.GetGrain<IAppsByNameIndexGrain>(SingleGrain.Id); |
|||
} |
|||
|
|||
private IAppsByUserIndexGrain Index(string id) |
|||
{ |
|||
return grainFactory.GetGrain<IAppsByUserIndexGrain>(id); |
|||
} |
|||
} |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public interface IAppsByNameIndex : IGrainWithStringKey |
|||
{ |
|||
Task<long> CountAsync(); |
|||
|
|||
Task<bool> ReserveAppAsync(Guid appId, string name); |
|||
|
|||
Task AddAppAsync(Guid appId, string name); |
|||
|
|||
Task RemoveAppAsync(Guid appId); |
|||
|
|||
Task RebuildAsync(Dictionary<string, Guid> apps); |
|||
|
|||
Task RemoveReservationAsync(Guid appId, string name); |
|||
|
|||
Task<List<Guid>> GetAppIdsAsync(); |
|||
|
|||
Task<List<Guid>> GetAppIdsAsync(string[] names); |
|||
|
|||
Task<Guid> GetAppIdAsync(string name); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Orleans; |
|||
using Squidex.Infrastructure.Orleans.Indexes; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public interface IAppsByUserIndexGrain : IIdsIndexGrain<Guid>, IGrainWithStringKey |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure.Security; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public interface IAppsIndex |
|||
{ |
|||
Task<List<Guid>> GetIdsAsync(); |
|||
|
|||
Task<List<IAppEntity>> GetAppsAsync(); |
|||
|
|||
Task<List<IAppEntity>> GetAppsForUserAsync(string userId, PermissionSet permissions); |
|||
|
|||
Task<IAppEntity> GetAppAsync(string name); |
|||
|
|||
Task<IAppEntity> GetAppAsync(Guid appId); |
|||
|
|||
Task<string> ReserveAsync(Guid id, string name); |
|||
|
|||
Task<bool> AddAsync(string token); |
|||
|
|||
Task RemoveReservationAsync(string token); |
|||
|
|||
Task RebuildByContributorsAsync(string contributorId, HashSet<Guid> apps); |
|||
|
|||
Task RebuildAsync(Dictionary<string, Guid> apps); |
|||
|
|||
Task RebuildByContributorsAsync(Guid appId, HashSet<string> contributors); |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets |
|||
{ |
|||
public interface IAssetLoader |
|||
{ |
|||
Task<IAssetEntity> GetAsync(Guid id, long version = EtagVersion.Any); |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Log; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.Queries |
|||
{ |
|||
public sealed class AssetLoader : IAssetLoader |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public AssetLoader(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public async Task<IAssetEntity> GetAsync(Guid id, long version) |
|||
{ |
|||
using (Profiler.TraceMethod<AssetLoader>()) |
|||
{ |
|||
var grain = grainFactory.GetGrain<IAssetGrain>(id); |
|||
|
|||
var content = await grain.GetStateAsync(version); |
|||
|
|||
if (content.Value == null || content.Value.Version != version) |
|||
{ |
|||
throw new DomainObjectNotFoundException(id.ToString(), typeof(IAssetEntity)); |
|||
} |
|||
|
|||
return content.Value; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Orleans; |
|||
using Squidex.Infrastructure.Orleans.Indexes; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules.Indexes |
|||
{ |
|||
public interface IRulesByAppIndexGrain : IIdsIndexGrain<Guid>, IGrainWithGuidKey |
|||
{ |
|||
} |
|||
} |
|||
@ -1,56 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Rules.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules.Indexes |
|||
{ |
|||
public sealed class RulesByAppIndexCommandMiddleware : ICommandMiddleware |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public RulesByAppIndexCommandMiddleware(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
if (context.IsCompleted) |
|||
{ |
|||
switch (context.Command) |
|||
{ |
|||
case CreateRule createRule: |
|||
await Index(createRule.AppId.Id).AddRuleAsync(createRule.RuleId); |
|||
break; |
|||
case DeleteRule deleteRule: |
|||
{ |
|||
var schema = await grainFactory.GetGrain<IRuleGrain>(deleteRule.RuleId).GetStateAsync(); |
|||
|
|||
await Index(schema.Value.AppId.Id).RemoveRuleAsync(deleteRule.RuleId); |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
await next(); |
|||
} |
|||
|
|||
private IRulesByAppIndex Index(Guid appId) |
|||
{ |
|||
return grainFactory.GetGrain<IRulesByAppIndex>(appId); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,118 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Rules.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Log; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules.Indexes |
|||
{ |
|||
public sealed class RulesIndex : ICommandMiddleware, IRulesIndex |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public RulesIndex(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public Task RebuildAsync(Guid appId, HashSet<Guid> rues) |
|||
{ |
|||
return Index(appId).RebuildAsync(rues); |
|||
} |
|||
|
|||
public async Task<List<IRuleEntity>> GetRulesAsync(Guid appId) |
|||
{ |
|||
using (Profiler.TraceMethod<RulesIndex>()) |
|||
{ |
|||
var ids = await GetRuleIdsAsync(appId); |
|||
|
|||
var rules = |
|||
await Task.WhenAll( |
|||
ids.Select(GetRuleAsync)); |
|||
|
|||
return rules.Where(x => x != null).ToList(); |
|||
} |
|||
} |
|||
|
|||
private async Task<IRuleEntity> GetRuleAsync(Guid id) |
|||
{ |
|||
using (Profiler.TraceMethod<RulesIndex>()) |
|||
{ |
|||
var ruleEntity = await grainFactory.GetGrain<IRuleGrain>(id).GetStateAsync(); |
|||
|
|||
if (IsFound(ruleEntity.Value)) |
|||
{ |
|||
return ruleEntity.Value; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private async Task<List<Guid>> GetRuleIdsAsync(Guid appId) |
|||
{ |
|||
using (Profiler.TraceMethod<RulesIndex>()) |
|||
{ |
|||
return await Index(appId).GetIdsAsync(); |
|||
} |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
await next(); |
|||
|
|||
if (context.IsCompleted) |
|||
{ |
|||
switch (context.Command) |
|||
{ |
|||
case CreateRule createRule: |
|||
await CreateRuleAsync(createRule); |
|||
break; |
|||
case DeleteRule deleteRule: |
|||
await DeleteRuleAsync(deleteRule); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private async Task CreateRuleAsync(CreateRule command) |
|||
{ |
|||
await Index(command.AppId.Id).AddAsync(command.RuleId); |
|||
} |
|||
|
|||
private async Task DeleteRuleAsync(DeleteRule command) |
|||
{ |
|||
var id = command.RuleId; |
|||
|
|||
var rule = await grainFactory.GetGrain<IRuleGrain>(id).GetStateAsync(); |
|||
|
|||
if (IsFound(rule.Value)) |
|||
{ |
|||
await Index(rule.Value.AppId.Id).RemoveAsync(id); |
|||
} |
|||
} |
|||
|
|||
private IRulesByAppIndexGrain Index(Guid appId) |
|||
{ |
|||
return grainFactory.GetGrain<IRulesByAppIndexGrain>(appId); |
|||
} |
|||
|
|||
private static bool IsFound(IRuleEntity rule) |
|||
{ |
|||
return rule.Version > EtagVersion.Empty && !rule.IsDeleted; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Orleans; |
|||
using Squidex.Infrastructure.Orleans.Indexes; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Schemas.Indexes |
|||
{ |
|||
public interface ISchemasByAppIndexGrain : IUniqueNameIndexGrain<Guid>, IGrainWithGuidKey |
|||
{ |
|||
} |
|||
} |
|||
@ -1,56 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Schemas.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Schemas.Indexes |
|||
{ |
|||
public sealed class SchemasByAppIndexCommandMiddleware : ICommandMiddleware |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public SchemasByAppIndexCommandMiddleware(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
if (context.IsCompleted) |
|||
{ |
|||
switch (context.Command) |
|||
{ |
|||
case CreateSchema createSchema: |
|||
await Index(createSchema.AppId.Id).AddSchemaAsync(createSchema.SchemaId, createSchema.Name); |
|||
break; |
|||
case DeleteSchema deleteSchema: |
|||
{ |
|||
var schema = await grainFactory.GetGrain<ISchemaGrain>(deleteSchema.SchemaId).GetStateAsync(); |
|||
|
|||
await Index(schema.Value.AppId.Id).RemoveSchemaAsync(deleteSchema.SchemaId); |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
await next(); |
|||
} |
|||
|
|||
private ISchemasByAppIndex Index(Guid appId) |
|||
{ |
|||
return grainFactory.GetGrain<ISchemasByAppIndex>(appId); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,181 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Schemas.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.Validation; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Schemas.Indexes |
|||
{ |
|||
public sealed class SchemasIndex : ICommandMiddleware, ISchemasIndex |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public SchemasIndex(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public Task RebuildAsync(Guid appId, Dictionary<string, Guid> schemas) |
|||
{ |
|||
return Index(appId).RebuildAsync(schemas); |
|||
} |
|||
|
|||
public async Task<List<ISchemaEntity>> GetSchemasAsync(Guid appId, bool allowDeleted = false) |
|||
{ |
|||
using (Profiler.TraceMethod<SchemasIndex>()) |
|||
{ |
|||
var ids = await GetSchemaIdsAsync(appId); |
|||
|
|||
var schemas = |
|||
await Task.WhenAll( |
|||
ids.Select(id => GetSchemaAsync(appId, id, allowDeleted))); |
|||
|
|||
return schemas.Where(x => x != null).ToList(); |
|||
} |
|||
} |
|||
|
|||
public async Task<ISchemaEntity> GetSchemaAsync(Guid appId, string name, bool allowDeleted = false) |
|||
{ |
|||
using (Profiler.TraceMethod<SchemasIndex>()) |
|||
{ |
|||
var id = await GetSchemaIdAsync(appId, name); |
|||
|
|||
if (id == default) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return await GetSchemaAsync(appId, id, allowDeleted); |
|||
} |
|||
} |
|||
|
|||
public async Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid id, bool allowDeleted = false) |
|||
{ |
|||
using (Profiler.TraceMethod<SchemasIndex>()) |
|||
{ |
|||
var schema = await grainFactory.GetGrain<ISchemaGrain>(id).GetStateAsync(); |
|||
|
|||
if (IsFound(schema.Value, allowDeleted)) |
|||
{ |
|||
return schema.Value; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private async Task<Guid> GetSchemaIdAsync(Guid appId, string name) |
|||
{ |
|||
using (Profiler.TraceMethod<SchemasIndex>()) |
|||
{ |
|||
return await Index(appId).GetIdAsync(name); |
|||
} |
|||
} |
|||
|
|||
private async Task<List<Guid>> GetSchemaIdsAsync(Guid appId) |
|||
{ |
|||
using (Profiler.TraceMethod<SchemasIndex>()) |
|||
{ |
|||
return await Index(appId).GetIdsAsync(); |
|||
} |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
if (context.Command is CreateSchema createSchema) |
|||
{ |
|||
var index = Index(createSchema.AppId.Id); |
|||
|
|||
string token = await CheckSchemaAsync(index, createSchema); |
|||
|
|||
try |
|||
{ |
|||
await next(); |
|||
} |
|||
finally |
|||
{ |
|||
if (token != null) |
|||
{ |
|||
if (context.IsCompleted) |
|||
{ |
|||
await index.AddAsync(token); |
|||
} |
|||
else |
|||
{ |
|||
await index.RemoveReservationAsync(token); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
await next(); |
|||
|
|||
if (context.IsCompleted) |
|||
{ |
|||
if (context.Command is DeleteSchema deleteSchema) |
|||
{ |
|||
await DeleteSchemaAsync(deleteSchema); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private async Task<string> CheckSchemaAsync(ISchemasByAppIndexGrain index, CreateSchema command) |
|||
{ |
|||
var name = command.Name; |
|||
|
|||
if (name.IsSlug()) |
|||
{ |
|||
var token = await index.ReserveAsync(command.SchemaId, name); |
|||
|
|||
if (token == null) |
|||
{ |
|||
var error = new ValidationError("A schema with this name already exists."); |
|||
|
|||
throw new ValidationException("Cannot create schema.", error); |
|||
} |
|||
|
|||
return token; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private async Task DeleteSchemaAsync(DeleteSchema commmand) |
|||
{ |
|||
var schemaId = commmand.SchemaId; |
|||
|
|||
var schema = await grainFactory.GetGrain<ISchemaGrain>(schemaId).GetStateAsync(); |
|||
|
|||
if (IsFound(schema.Value, true)) |
|||
{ |
|||
await Index(schema.Value.AppId.Id).RemoveAsync(schemaId); |
|||
} |
|||
} |
|||
|
|||
private ISchemasByAppIndexGrain Index(Guid appId) |
|||
{ |
|||
return grainFactory.GetGrain<ISchemasByAppIndexGrain>(appId); |
|||
} |
|||
|
|||
private static bool IsFound(ISchemaEntity entity, bool allowDeleted) |
|||
{ |
|||
return entity.Version > EtagVersion.Empty && (!entity.IsDeleted || allowDeleted); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans.Indexes |
|||
{ |
|||
public interface IIdsIndexGrain<T> |
|||
{ |
|||
Task<long> CountAsync(); |
|||
|
|||
Task RebuildAsync(HashSet<T> ids); |
|||
|
|||
Task AddAsync(T id); |
|||
|
|||
Task RemoveAsync(T id); |
|||
|
|||
Task ClearAsync(); |
|||
|
|||
Task<List<T>> GetIdsAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans.Indexes |
|||
{ |
|||
public interface IUniqueNameIndexGrain<T> |
|||
{ |
|||
Task<string> ReserveAsync(T id, string name); |
|||
|
|||
Task<bool> AddAsync(string token); |
|||
|
|||
Task<long> CountAsync(); |
|||
|
|||
Task RemoveReservationAsync(string token); |
|||
|
|||
Task RemoveAsync(T id); |
|||
|
|||
Task RebuildAsync(Dictionary<string, T> values); |
|||
|
|||
Task ClearAsync(); |
|||
|
|||
Task<T> GetIdAsync(string name); |
|||
|
|||
Task<List<T>> GetIdsAsync(string[] names); |
|||
|
|||
Task<List<T>> GetIdsAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans.Indexes |
|||
{ |
|||
public class IdsIndexGrain<TState, T> : Grain, IIdsIndexGrain<T> where TState : IdsIndexState<T>, new() |
|||
{ |
|||
private readonly IGrainState<TState> state; |
|||
|
|||
public IdsIndexGrain(IGrainState<TState> state) |
|||
{ |
|||
Guard.NotNull(state, nameof(state)); |
|||
|
|||
this.state = state; |
|||
} |
|||
|
|||
public Task<long> CountAsync() |
|||
{ |
|||
return Task.FromResult<long>(state.Value.Ids.Count); |
|||
} |
|||
|
|||
public Task RebuildAsync(HashSet<T> ids) |
|||
{ |
|||
state.Value = new TState { Ids = ids }; |
|||
|
|||
return state.WriteAsync(); |
|||
} |
|||
|
|||
public Task AddAsync(T id) |
|||
{ |
|||
state.Value.Ids.Add(id); |
|||
|
|||
return state.WriteAsync(); |
|||
} |
|||
|
|||
public Task RemoveAsync(T id) |
|||
{ |
|||
state.Value.Ids.Remove(id); |
|||
|
|||
return state.WriteAsync(); |
|||
} |
|||
|
|||
public Task ClearAsync() |
|||
{ |
|||
return state.ClearAsync(); |
|||
} |
|||
|
|||
public Task<List<T>> GetIdsAsync() |
|||
{ |
|||
return Task.FromResult(state.Value.Ids.ToList()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans.Indexes |
|||
{ |
|||
public class IdsIndexState<T> |
|||
{ |
|||
public HashSet<T> Ids { get; set; } = new HashSet<T>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,136 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans.Indexes |
|||
{ |
|||
public class UniqueNameIndexGrain<TState, T> : Grain, IUniqueNameIndexGrain<T> where TState : UniqueNameIndexState<T>, new() |
|||
{ |
|||
private readonly Dictionary<string, (string Name, T Id)> reservations = new Dictionary<string, (string Name, T Id)>(); |
|||
private readonly IGrainState<TState> state; |
|||
|
|||
public UniqueNameIndexGrain(IGrainState<TState> state) |
|||
{ |
|||
Guard.NotNull(state, nameof(state)); |
|||
|
|||
this.state = state; |
|||
} |
|||
|
|||
public Task<long> CountAsync() |
|||
{ |
|||
return Task.FromResult<long>(state.Value.Names.Count); |
|||
} |
|||
|
|||
public Task ClearAsync() |
|||
{ |
|||
reservations.Clear(); |
|||
|
|||
return state.ClearAsync(); |
|||
} |
|||
|
|||
public Task RebuildAsync(Dictionary<string, T> names) |
|||
{ |
|||
state.Value = new TState { Names = names }; |
|||
|
|||
return state.WriteAsync(); |
|||
} |
|||
|
|||
public Task<string> ReserveAsync(T id, string name) |
|||
{ |
|||
string token = default; |
|||
|
|||
if (!IsInUse(name) && !IsReserved(name)) |
|||
{ |
|||
token = RandomHash.Simple(); |
|||
|
|||
reservations.Add(token, (name, id)); |
|||
} |
|||
|
|||
return Task.FromResult(token); |
|||
} |
|||
|
|||
public async Task<bool> AddAsync(string token) |
|||
{ |
|||
if (reservations.TryGetValue(token ?? string.Empty, out var reservation)) |
|||
{ |
|||
state.Value.Names.Add(reservation.Name, reservation.Id); |
|||
|
|||
await state.WriteAsync(); |
|||
|
|||
reservations.Remove(token); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public Task RemoveReservationAsync(string token) |
|||
{ |
|||
reservations.Remove(token ?? string.Empty); |
|||
|
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public async Task RemoveAsync(T id) |
|||
{ |
|||
var name = state.Value.Names.FirstOrDefault(x => Equals(x.Value, id)).Key; |
|||
|
|||
if (name != null) |
|||
{ |
|||
state.Value.Names.Remove(name); |
|||
|
|||
await state.WriteAsync(); |
|||
} |
|||
} |
|||
|
|||
public Task<List<T>> GetIdsAsync(string[] names) |
|||
{ |
|||
var result = new List<T>(); |
|||
|
|||
if (names != null) |
|||
{ |
|||
foreach (var name in names) |
|||
{ |
|||
if (state.Value.Names.TryGetValue(name, out var id)) |
|||
{ |
|||
result.Add(id); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return Task.FromResult(result); |
|||
} |
|||
|
|||
public Task<T> GetIdAsync(string name) |
|||
{ |
|||
state.Value.Names.TryGetValue(name, out var id); |
|||
|
|||
return Task.FromResult(id); |
|||
} |
|||
|
|||
public Task<List<T>> GetIdsAsync() |
|||
{ |
|||
return Task.FromResult(state.Value.Names.Values.ToList()); |
|||
} |
|||
|
|||
private bool IsInUse(string name) |
|||
{ |
|||
return state.Value.Names.ContainsKey(name); |
|||
} |
|||
|
|||
private bool IsReserved(string name) |
|||
{ |
|||
return reservations.Values.Any(x => x.Name == name); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans.Indexes |
|||
{ |
|||
public class UniqueNameIndexState<T> |
|||
{ |
|||
public Dictionary<string, T> Names { get; set; } = new Dictionary<string, T>(); |
|||
} |
|||
} |
|||
@ -1,93 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Apps.Commands; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Squidex.Infrastructure.Validation; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public class AppsByNameIndexCommandMiddlewareTests |
|||
{ |
|||
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>(); |
|||
private readonly ICommandBus commandBus = A.Fake<ICommandBus>(); |
|||
private readonly IAppsByNameIndex index = A.Fake<IAppsByNameIndex>(); |
|||
private readonly Guid appId = Guid.NewGuid(); |
|||
private readonly AppsByNameIndexCommandMiddleware sut; |
|||
|
|||
public AppsByNameIndexCommandMiddlewareTests() |
|||
{ |
|||
A.CallTo(() => grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id, null)) |
|||
.Returns(index); |
|||
|
|||
sut = new AppsByNameIndexCommandMiddleware(grainFactory); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_app_to_index_on_create() |
|||
{ |
|||
A.CallTo(() => index.ReserveAppAsync(appId, "my-app")) |
|||
.Returns(true); |
|||
|
|||
var context = |
|||
new CommandContext(new CreateApp { AppId = appId, Name = "my-app" }, commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => index.ReserveAppAsync(appId, "my-app")) |
|||
.MustHaveHappened(); |
|||
|
|||
A.CallTo(() => index.AddAppAsync(appId, "my-app")) |
|||
.MustHaveHappened(); |
|||
|
|||
A.CallTo(() => index.RemoveReservationAsync(appId, "my-app")) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_remove_reservation_when_not_reserved() |
|||
{ |
|||
A.CallTo(() => index.ReserveAppAsync(appId, "my-app")) |
|||
.Returns(false); |
|||
|
|||
var context = |
|||
new CommandContext(new CreateApp { AppId = appId, Name = "my-app" }, commandBus) |
|||
.Complete(); |
|||
|
|||
await Assert.ThrowsAsync<ValidationException>(() => sut.HandleAsync(context)); |
|||
|
|||
A.CallTo(() => index.ReserveAppAsync(appId, "my-app")) |
|||
.MustHaveHappened(); |
|||
|
|||
A.CallTo(() => index.AddAppAsync(appId, "my-app")) |
|||
.MustNotHaveHappened(); |
|||
|
|||
A.CallTo(() => index.RemoveReservationAsync(appId, "my-app")) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_app_from_index_on_archive() |
|||
{ |
|||
var context = |
|||
new CommandContext(new ArchiveApp { AppId = appId }, commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => index.RemoveAppAsync(appId)) |
|||
.MustHaveHappened(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,149 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public class AppsByNameIndexGrainTests |
|||
{ |
|||
private readonly IGrainState<AppsByNameIndexGrain.GrainState> grainState = A.Fake<IGrainState<AppsByNameIndexGrain.GrainState>>(); |
|||
private readonly NamedId<Guid> appId1 = NamedId.Of(Guid.NewGuid(), "my-app1"); |
|||
private readonly NamedId<Guid> appId2 = NamedId.Of(Guid.NewGuid(), "my-app2"); |
|||
private readonly AppsByNameIndexGrain sut; |
|||
|
|||
public AppsByNameIndexGrainTests() |
|||
{ |
|||
sut = new AppsByNameIndexGrain(grainState); |
|||
sut.ActivateAsync(SingleGrain.Id).Wait(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_app_id_to_index() |
|||
{ |
|||
await sut.AddAppAsync(appId1.Id, appId1.Name); |
|||
|
|||
var result = await sut.GetAppIdAsync(appId1.Name); |
|||
|
|||
Assert.Equal(appId1.Id, result); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_be_able_to_reserve_index_if_name_taken() |
|||
{ |
|||
await sut.AddAppAsync(appId2.Id, appId1.Name); |
|||
|
|||
Assert.False(await sut.ReserveAppAsync(appId1.Id, appId1.Name)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_be_able_to_reserve_if_name_reserved() |
|||
{ |
|||
await sut.ReserveAppAsync(appId2.Id, appId1.Name); |
|||
|
|||
Assert.False(await sut.ReserveAppAsync(appId1.Id, appId1.Name)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_be_able_to_reserve_if_id_taken() |
|||
{ |
|||
await sut.AddAppAsync(appId1.Id, appId1.Name); |
|||
|
|||
Assert.False(await sut.ReserveAppAsync(appId1.Id, appId2.Name)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_be_able_to_reserve_if_id_reserved() |
|||
{ |
|||
await sut.ReserveAppAsync(appId1.Id, appId1.Name); |
|||
|
|||
Assert.False(await sut.ReserveAppAsync(appId1.Id, appId2.Name)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_be_able_to_reserve_if_id_and_name_not_reserved() |
|||
{ |
|||
await sut.ReserveAppAsync(appId1.Id, appId1.Name); |
|||
|
|||
Assert.True(await sut.ReserveAppAsync(appId2.Id, appId2.Name)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_be_able_to_reserve_after_app_removed() |
|||
{ |
|||
await sut.AddAppAsync(appId1.Id, appId1.Name); |
|||
await sut.RemoveAppAsync(appId1.Id); |
|||
|
|||
Assert.True(await sut.ReserveAppAsync(appId1.Id, appId1.Name)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_be_able_to_reserve_after_reservation_removed() |
|||
{ |
|||
await sut.ReserveAppAsync(appId1.Id, appId1.Name); |
|||
await sut.RemoveReservationAsync(appId1.Id, appId1.Name); |
|||
|
|||
Assert.True(await sut.ReserveAppAsync(appId1.Id, appId1.Name)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_return_many_app_ids() |
|||
{ |
|||
await sut.AddAppAsync(appId1.Id, appId1.Name); |
|||
await sut.AddAppAsync(appId2.Id, appId2.Name); |
|||
|
|||
var ids = await sut.GetAppIdsAsync(appId1.Name, appId2.Name); |
|||
|
|||
Assert.Equal(new List<Guid> { appId1.Id, appId2.Id }, ids); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_app_id_from_index() |
|||
{ |
|||
await sut.AddAppAsync(appId1.Id, appId1.Name); |
|||
await sut.RemoveAppAsync(appId1.Id); |
|||
|
|||
var result = await sut.GetAppIdAsync(appId1.Name); |
|||
|
|||
Assert.Equal(Guid.Empty, result); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappenedTwiceExactly(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_replace_app_ids_on_rebuild() |
|||
{ |
|||
var state = new Dictionary<string, Guid> |
|||
{ |
|||
[appId1.Name] = appId1.Id, |
|||
[appId2.Name] = appId2.Id |
|||
}; |
|||
|
|||
await sut.RebuildAsync(state); |
|||
|
|||
Assert.Equal(appId1.Id, await sut.GetAppIdAsync(appId1.Name)); |
|||
Assert.Equal(appId2.Id, await sut.GetAppIdAsync(appId2.Name)); |
|||
|
|||
Assert.Equal(new List<Guid> { appId1.Id, appId2.Id }, await sut.GetAppIdsAsync()); |
|||
|
|||
Assert.Equal(2, await sut.CountAsync()); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappened(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,103 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
using Squidex.Domain.Apps.Entities.Apps.Commands; |
|||
using Squidex.Domain.Apps.Entities.TestHelpers; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public class AppsByUserIndexCommandMiddlewareTests |
|||
{ |
|||
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>(); |
|||
private readonly ICommandBus commandBus = A.Fake<ICommandBus>(); |
|||
private readonly IAppsByUserIndex index = A.Fake<IAppsByUserIndex>(); |
|||
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); |
|||
private readonly string userId = "123"; |
|||
private readonly AppsByUserIndexCommandMiddleware sut; |
|||
|
|||
public AppsByUserIndexCommandMiddlewareTests() |
|||
{ |
|||
A.CallTo(() => grainFactory.GetGrain<IAppsByUserIndex>(userId, null)) |
|||
.Returns(index); |
|||
|
|||
sut = new AppsByUserIndexCommandMiddleware(grainFactory); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_app_to_index_on_create() |
|||
{ |
|||
var context = |
|||
new CommandContext(new CreateApp { AppId = appId.Id, Actor = new RefToken("user", userId) }, commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => index.AddAppAsync(appId.Id)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_app_to_index_on_assign_of_contributor() |
|||
{ |
|||
var context = |
|||
new CommandContext(new AssignContributor { AppId = appId.Id, ContributorId = userId }, commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => index.AddAppAsync(appId.Id)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_app_to_index_on_remove_of_contributor() |
|||
{ |
|||
var context = |
|||
new CommandContext(new RemoveContributor { AppId = appId.Id, ContributorId = userId }, commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => index.RemoveAppAsync(appId.Id)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_app_from_index_on_archive() |
|||
{ |
|||
var appGrain = A.Fake<IAppGrain>(); |
|||
var appState = Mocks.App(appId); |
|||
|
|||
A.CallTo(() => grainFactory.GetGrain<IAppGrain>(appId.Id, null)) |
|||
.Returns(appGrain); |
|||
|
|||
A.CallTo(() => appGrain.GetStateAsync()) |
|||
.Returns(J.AsTask(appState)); |
|||
|
|||
A.CallTo(() => appState.Contributors) |
|||
.Returns(AppContributors.Empty.Assign(userId, Role.Owner)); |
|||
|
|||
var context = |
|||
new CommandContext(new ArchiveApp { AppId = appId.Id }, commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => index.RemoveAppAsync(appId.Id)) |
|||
.MustHaveHappened(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,76 +0,0 @@ |
|||
// ==========================================================================.WriteAsync()
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public class AppsByUserIndexGrainTests |
|||
{ |
|||
private readonly IGrainState<AppsByUserIndexGrain.GrainState> grainState = A.Fake<IGrainState<AppsByUserIndexGrain.GrainState>>(); |
|||
private readonly Guid appId1 = Guid.NewGuid(); |
|||
private readonly Guid appId2 = Guid.NewGuid(); |
|||
private readonly string userId = "user"; |
|||
private readonly AppsByUserIndexGrain sut; |
|||
|
|||
public AppsByUserIndexGrainTests() |
|||
{ |
|||
sut = new AppsByUserIndexGrain(grainState); |
|||
sut.ActivateAsync(userId).Wait(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_app_id_to_index() |
|||
{ |
|||
await sut.AddAppAsync(appId1); |
|||
await sut.AddAppAsync(appId2); |
|||
|
|||
var result = await sut.GetAppIdsAsync(); |
|||
|
|||
Assert.Equal(new List<Guid> { appId1, appId2 }, result); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappenedTwiceExactly(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_app_id_from_index() |
|||
{ |
|||
await sut.AddAppAsync(appId1); |
|||
await sut.AddAppAsync(appId2); |
|||
await sut.RemoveAppAsync(appId1); |
|||
|
|||
var result = await sut.GetAppIdsAsync(); |
|||
|
|||
Assert.Equal(new List<Guid> { appId2 }, result); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappenedTwiceOrMore(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_replace_app_ids_on_rebuild() |
|||
{ |
|||
var state = HashSet.Of(appId1, appId2); |
|||
|
|||
await sut.RebuildAsync(state); |
|||
|
|||
var result = await sut.GetAppIdsAsync(); |
|||
|
|||
Assert.Equal(new List<Guid> { appId1, appId2 }, result); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappened(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,387 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
using Squidex.Domain.Apps.Entities.Apps.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Squidex.Infrastructure.Security; |
|||
using Squidex.Infrastructure.Validation; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public sealed class AppsIndexTests |
|||
{ |
|||
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>(); |
|||
private readonly IAppsByNameIndexGrain indexByName = A.Fake<IAppsByNameIndexGrain>(); |
|||
private readonly IAppsByUserIndexGrain indexByUser = A.Fake<IAppsByUserIndexGrain>(); |
|||
private readonly ICommandBus commandBus = A.Fake<ICommandBus>(); |
|||
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); |
|||
private readonly string userId = "user-1"; |
|||
private readonly AppsIndex sut; |
|||
|
|||
public AppsIndexTests() |
|||
{ |
|||
A.CallTo(() => grainFactory.GetGrain<IAppsByNameIndexGrain>(SingleGrain.Id, null)) |
|||
.Returns(indexByName); |
|||
|
|||
A.CallTo(() => grainFactory.GetGrain<IAppsByUserIndexGrain>(userId, null)) |
|||
.Returns(indexByUser); |
|||
|
|||
sut = new AppsIndex(grainFactory); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_resolve_all_apps_from_user_permissions() |
|||
{ |
|||
var expected = SetupApp(0, false); |
|||
|
|||
A.CallTo(() => indexByName.GetIdsAsync(A<string[]>.That.IsSameSequenceAs(new string[] { appId.Name }))) |
|||
.Returns(new List<Guid> { appId.Id }); |
|||
|
|||
var actual = await sut.GetAppsForUserAsync(userId, new PermissionSet($"squidex.apps.{appId.Name}")); |
|||
|
|||
Assert.Same(expected, actual[0]); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_resolve_all_apps_from_user() |
|||
{ |
|||
var expected = SetupApp(0, false); |
|||
|
|||
A.CallTo(() => indexByUser.GetIdsAsync()) |
|||
.Returns(new List<Guid> { appId.Id }); |
|||
|
|||
var actual = await sut.GetAppsForUserAsync(userId, PermissionSet.Empty); |
|||
|
|||
Assert.Same(expected, actual[0]); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_resolve_all_apps() |
|||
{ |
|||
var expected = SetupApp(0, false); |
|||
|
|||
A.CallTo(() => indexByName.GetIdsAsync()) |
|||
.Returns(new List<Guid> { appId.Id }); |
|||
|
|||
var actual = await sut.GetAppsAsync(); |
|||
|
|||
Assert.Same(expected, actual[0]); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_resolve_app_by_name() |
|||
{ |
|||
var expected = SetupApp(0, false); |
|||
|
|||
A.CallTo(() => indexByName.GetIdAsync(appId.Name)) |
|||
.Returns(appId.Id); |
|||
|
|||
var actual = await sut.GetAppAsync(appId.Name); |
|||
|
|||
Assert.Same(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_resolve_app_by_id() |
|||
{ |
|||
var expected = SetupApp(0, false); |
|||
|
|||
var actual = await sut.GetAppAsync(appId.Id); |
|||
|
|||
Assert.Same(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_return_null_if_app_archived() |
|||
{ |
|||
SetupApp(0, true); |
|||
|
|||
var actual = await sut.GetAppAsync(appId.Id); |
|||
|
|||
Assert.Null(actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_return_null_if_app_not_created() |
|||
{ |
|||
SetupApp(-1, false); |
|||
|
|||
var actual = await sut.GetAppAsync(appId.Id); |
|||
|
|||
Assert.Null(actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_app_to_indexes_on_create() |
|||
{ |
|||
var token = RandomHash.Simple(); |
|||
|
|||
A.CallTo(() => indexByName.ReserveAsync(appId.Id, appId.Name)) |
|||
.Returns(token); |
|||
|
|||
var context = |
|||
new CommandContext(Create(appId.Name), commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => indexByName.AddAsync(token)) |
|||
.MustHaveHappened(); |
|||
|
|||
A.CallTo(() => indexByName.RemoveReservationAsync(A<string>.Ignored)) |
|||
.MustNotHaveHappened(); |
|||
|
|||
A.CallTo(() => indexByUser.AddAsync(appId.Id)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_app_to_user_index_if_app_created_by_client() |
|||
{ |
|||
var token = RandomHash.Simple(); |
|||
|
|||
A.CallTo(() => indexByName.ReserveAsync(appId.Id, appId.Name)) |
|||
.Returns(token); |
|||
|
|||
var context = |
|||
new CommandContext(CreateFromClient(appId.Name), commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => indexByName.AddAsync(token)) |
|||
.MustHaveHappened(); |
|||
|
|||
A.CallTo(() => indexByName.RemoveReservationAsync(A<string>.Ignored)) |
|||
.MustNotHaveHappened(); |
|||
|
|||
A.CallTo(() => indexByUser.AddAsync(appId.Id)) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_clear_reservation_when_app_creation_failed() |
|||
{ |
|||
var token = RandomHash.Simple(); |
|||
|
|||
A.CallTo(() => indexByName.ReserveAsync(appId.Id, appId.Name)) |
|||
.Returns(token); |
|||
|
|||
var context = |
|||
new CommandContext(CreateFromClient(appId.Name), commandBus); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => indexByName.AddAsync(token)) |
|||
.MustNotHaveHappened(); |
|||
|
|||
A.CallTo(() => indexByName.RemoveReservationAsync(token)) |
|||
.MustHaveHappened(); |
|||
|
|||
A.CallTo(() => indexByUser.AddAsync(appId.Id)) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_add_to_indexes_on_create_if_name_taken() |
|||
{ |
|||
A.CallTo(() => indexByName.ReserveAsync(appId.Id, appId.Name)) |
|||
.Returns(Task.FromResult<string>(null)); |
|||
|
|||
var context = |
|||
new CommandContext(Create(appId.Name), commandBus) |
|||
.Complete(); |
|||
|
|||
await Assert.ThrowsAsync<ValidationException>(() => sut.HandleAsync(context)); |
|||
|
|||
A.CallTo(() => indexByName.AddAsync(A<string>.Ignored)) |
|||
.MustNotHaveHappened(); |
|||
|
|||
A.CallTo(() => indexByName.RemoveReservationAsync(A<string>.Ignored)) |
|||
.MustNotHaveHappened(); |
|||
|
|||
A.CallTo(() => indexByUser.AddAsync(appId.Id)) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_add_to_indexes_on_create_if_name_invalid() |
|||
{ |
|||
var context = |
|||
new CommandContext(Create("INVALID"), commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => indexByName.ReserveAsync(appId.Id, A<string>.Ignored)) |
|||
.MustNotHaveHappened(); |
|||
|
|||
A.CallTo(() => indexByName.RemoveReservationAsync(A<string>.Ignored)) |
|||
.MustNotHaveHappened(); |
|||
|
|||
A.CallTo(() => indexByUser.AddAsync(appId.Id)) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_app_to_index_on_contributor_assignment() |
|||
{ |
|||
var context = |
|||
new CommandContext(new AssignContributor { AppId = appId.Id, ContributorId = userId }, commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => indexByUser.AddAsync(appId.Id)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_from_user_index_on_remove_of_contributor() |
|||
{ |
|||
var context = |
|||
new CommandContext(new RemoveContributor { AppId = appId.Id, ContributorId = userId }, commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => indexByUser.RemoveAsync(appId.Id)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_app_from_indexes_on_archive() |
|||
{ |
|||
var app = SetupApp(0, false); |
|||
|
|||
var context = |
|||
new CommandContext(new ArchiveApp { AppId = appId.Id }, commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => indexByName.RemoveAsync(appId.Id)) |
|||
.MustHaveHappened(); |
|||
|
|||
A.CallTo(() => indexByUser.RemoveAsync(appId.Id)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_forward_call_when_rebuilding_for_contributors1() |
|||
{ |
|||
var apps = new HashSet<Guid>(); |
|||
|
|||
await sut.RebuildByContributorsAsync(userId, apps); |
|||
|
|||
A.CallTo(() => indexByUser.RebuildAsync(apps)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_forward_call_when_rebuilding_for_contributors2() |
|||
{ |
|||
var users = new HashSet<string> { userId }; |
|||
|
|||
await sut.RebuildByContributorsAsync(appId.Id, users); |
|||
|
|||
A.CallTo(() => indexByUser.AddAsync(appId.Id)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_forward_call_when_rebuilding() |
|||
{ |
|||
var apps = new Dictionary<string, Guid>(); |
|||
|
|||
await sut.RebuildAsync(apps); |
|||
|
|||
A.CallTo(() => indexByName.RebuildAsync(apps)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_forward_reserveration() |
|||
{ |
|||
await sut.AddAsync("token"); |
|||
|
|||
A.CallTo(() => indexByName.AddAsync("token")) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_forward_remove_reservation() |
|||
{ |
|||
await sut.RemoveReservationAsync("token"); |
|||
|
|||
A.CallTo(() => indexByName.RemoveReservationAsync("token")) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_forward_request_for_ids() |
|||
{ |
|||
await sut.GetIdsAsync(); |
|||
|
|||
A.CallTo(() => indexByName.GetIdsAsync()) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
private IAppEntity SetupApp(long version, bool archived) |
|||
{ |
|||
var appEntity = A.Fake<IAppEntity>(); |
|||
|
|||
A.CallTo(() => appEntity.Name) |
|||
.Returns(appId.Name); |
|||
A.CallTo(() => appEntity.Version) |
|||
.Returns(version); |
|||
A.CallTo(() => appEntity.IsArchived) |
|||
.Returns(archived); |
|||
A.CallTo(() => appEntity.Contributors) |
|||
.Returns(AppContributors.Empty.Assign(userId, Role.Owner)); |
|||
|
|||
var appGrain = A.Fake<IAppGrain>(); |
|||
|
|||
A.CallTo(() => appGrain.GetStateAsync()) |
|||
.Returns(J.Of(appEntity)); |
|||
|
|||
A.CallTo(() => grainFactory.GetGrain<IAppGrain>(appId.Id, null)) |
|||
.Returns(appGrain); |
|||
|
|||
return appEntity; |
|||
} |
|||
|
|||
private CreateApp Create(string name) |
|||
{ |
|||
return new CreateApp { AppId = appId.Id, Name = name, Actor = ActorSubject() }; |
|||
} |
|||
|
|||
private CreateApp CreateFromClient(string name) |
|||
{ |
|||
return new CreateApp { AppId = appId.Id, Name = name, Actor = ActorClient() }; |
|||
} |
|||
|
|||
private RefToken ActorSubject() |
|||
{ |
|||
return new RefToken(RefTokenType.Subject, userId); |
|||
} |
|||
|
|||
private RefToken ActorClient() |
|||
{ |
|||
return new RefToken(RefTokenType.Client, userId); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Orleans; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.Queries |
|||
{ |
|||
public class AssetLoaderTests |
|||
{ |
|||
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>(); |
|||
private readonly IAssetGrain grain = A.Fake<IAssetGrain>(); |
|||
private readonly Guid id = Guid.NewGuid(); |
|||
private readonly AssetLoader sut; |
|||
|
|||
public AssetLoaderTests() |
|||
{ |
|||
A.CallTo(() => grainFactory.GetGrain<IAssetGrain>(id, null)) |
|||
.Returns(grain); |
|||
|
|||
sut = new AssetLoader(grainFactory); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_throw_exception_if_no_state_returned() |
|||
{ |
|||
A.CallTo(() => grain.GetStateAsync(10)) |
|||
.Returns(J.Of<IAssetEntity>(null)); |
|||
|
|||
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetAsync(id, 10)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_throw_exception_if_state_has_other_version() |
|||
{ |
|||
var content = new AssetEntity { Version = 5 }; |
|||
|
|||
A.CallTo(() => grain.GetStateAsync(10)) |
|||
.Returns(J.Of<IAssetEntity>(content)); |
|||
|
|||
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetAsync(id, 10)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_return_content_from_state() |
|||
{ |
|||
var content = new AssetEntity { Version = 10 }; |
|||
|
|||
A.CallTo(() => grain.GetStateAsync(10)) |
|||
.Returns(J.Of<IAssetEntity>(content)); |
|||
|
|||
var result = await sut.GetAsync(id, 10); |
|||
|
|||
Assert.Same(content, result); |
|||
} |
|||
} |
|||
} |
|||
@ -1,79 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Rules.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules.Indexes |
|||
{ |
|||
public class RulesByAppIndexCommandMiddlewareTests |
|||
{ |
|||
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>(); |
|||
private readonly ICommandBus commandBus = A.Fake<ICommandBus>(); |
|||
private readonly IRulesByAppIndex index = A.Fake<IRulesByAppIndex>(); |
|||
private readonly Guid appId = Guid.NewGuid(); |
|||
private readonly RulesByAppIndexCommandMiddleware sut; |
|||
|
|||
public RulesByAppIndexCommandMiddlewareTests() |
|||
{ |
|||
A.CallTo(() => grainFactory.GetGrain<IRulesByAppIndex>(appId, null)) |
|||
.Returns(index); |
|||
|
|||
sut = new RulesByAppIndexCommandMiddleware(grainFactory); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_rule_to_index_on_create() |
|||
{ |
|||
var context = |
|||
new CommandContext(new CreateRule { RuleId = appId, AppId = BuildAppId() }, commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => index.AddRuleAsync(appId)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_rule_from_index_on_delete() |
|||
{ |
|||
var ruleGrain = A.Fake<IRuleGrain>(); |
|||
var ruleState = A.Fake<IRuleEntity>(); |
|||
|
|||
A.CallTo(() => grainFactory.GetGrain<IRuleGrain>(appId, null)) |
|||
.Returns(ruleGrain); |
|||
|
|||
A.CallTo(() => ruleGrain.GetStateAsync()) |
|||
.Returns(J.AsTask(ruleState)); |
|||
|
|||
A.CallTo(() => ruleState.AppId) |
|||
.Returns(BuildAppId()); |
|||
|
|||
var context = |
|||
new CommandContext(new DeleteRule { RuleId = appId }, commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => index.RemoveRuleAsync(appId)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
private NamedId<Guid> BuildAppId() |
|||
{ |
|||
return NamedId.Of(appId, "my-app"); |
|||
} |
|||
} |
|||
} |
|||
@ -1,97 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules.Indexes |
|||
{ |
|||
public class RulesByAppIndexGrainTests |
|||
{ |
|||
private readonly IGrainState<RulesByAppIndexGrain.GrainState> grainState = A.Fake<IGrainState<RulesByAppIndexGrain.GrainState>>(); |
|||
private readonly Guid appId = Guid.NewGuid(); |
|||
private readonly Guid ruleId1 = Guid.NewGuid(); |
|||
private readonly Guid ruleId2 = Guid.NewGuid(); |
|||
private readonly RulesByAppIndexGrain sut; |
|||
|
|||
public RulesByAppIndexGrainTests() |
|||
{ |
|||
A.CallTo(() => grainState.ClearAsync()) |
|||
.Invokes(() => grainState.Value = new RulesByAppIndexGrain.GrainState()); |
|||
|
|||
sut = new RulesByAppIndexGrain(grainState); |
|||
sut.ActivateAsync(appId).Wait(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_rule_id_to_index() |
|||
{ |
|||
await sut.AddRuleAsync(ruleId1); |
|||
await sut.AddRuleAsync(ruleId2); |
|||
|
|||
var result = await sut.GetRuleIdsAsync(); |
|||
|
|||
Assert.Equal(new List<Guid> { ruleId1, ruleId2 }, result); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappenedTwiceExactly(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_delete_and_reset_state_when_cleaning() |
|||
{ |
|||
await sut.AddRuleAsync(ruleId1); |
|||
await sut.AddRuleAsync(ruleId2); |
|||
await sut.ClearAsync(); |
|||
|
|||
var ids = await sut.GetRuleIdsAsync(); |
|||
|
|||
Assert.Empty(ids); |
|||
|
|||
A.CallTo(() => grainState.ClearAsync()) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_rule_id_from_index() |
|||
{ |
|||
await sut.AddRuleAsync(ruleId1); |
|||
await sut.AddRuleAsync(ruleId2); |
|||
await sut.RemoveRuleAsync(ruleId1); |
|||
|
|||
var result = await sut.GetRuleIdsAsync(); |
|||
|
|||
Assert.Equal(new List<Guid> { ruleId2 }, result); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappenedTwiceOrMore(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_replace_rule_ids_on_rebuild() |
|||
{ |
|||
var state = new HashSet<Guid> |
|||
{ |
|||
ruleId1, |
|||
ruleId2 |
|||
}; |
|||
|
|||
await sut.RebuildAsync(state); |
|||
|
|||
var result = await sut.GetRuleIdsAsync(); |
|||
|
|||
Assert.Equal(new List<Guid> { ruleId1, ruleId2 }, result); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappened(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,143 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Rules.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules.Indexes |
|||
{ |
|||
public class RulesIndexTests |
|||
{ |
|||
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>(); |
|||
private readonly ICommandBus commandBus = A.Fake<ICommandBus>(); |
|||
private readonly IRulesByAppIndexGrain index = A.Fake<IRulesByAppIndexGrain>(); |
|||
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); |
|||
private readonly RulesIndex sut; |
|||
|
|||
public RulesIndexTests() |
|||
{ |
|||
A.CallTo(() => grainFactory.GetGrain<IRulesByAppIndexGrain>(appId.Id, null)) |
|||
.Returns(index); |
|||
|
|||
sut = new RulesIndex(grainFactory); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_resolve_rules_by_id() |
|||
{ |
|||
var rule = SetupRule(0, false); |
|||
|
|||
A.CallTo(() => index.GetIdsAsync()) |
|||
.Returns(new List<Guid> { rule.Id }); |
|||
|
|||
var actual = await sut.GetRulesAsync(appId.Id); |
|||
|
|||
Assert.Same(actual[0], rule); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_return_empty_rule_if_rule_not_created() |
|||
{ |
|||
var rule = SetupRule(-1, false); |
|||
|
|||
A.CallTo(() => index.GetIdsAsync()) |
|||
.Returns(new List<Guid> { rule.Id }); |
|||
|
|||
var actual = await sut.GetRulesAsync(appId.Id); |
|||
|
|||
Assert.Empty(actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_return_empty_rule_if_rule_deleted() |
|||
{ |
|||
var rule = SetupRule(-1, false); |
|||
|
|||
A.CallTo(() => index.GetIdsAsync()) |
|||
.Returns(new List<Guid> { rule.Id }); |
|||
|
|||
var actual = await sut.GetRulesAsync(appId.Id); |
|||
|
|||
Assert.Empty(actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_rule_to_index_on_create() |
|||
{ |
|||
var ruleId = Guid.NewGuid(); |
|||
|
|||
var context = |
|||
new CommandContext(new CreateRule { RuleId = ruleId, AppId = appId }, commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => index.AddAsync(ruleId)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_rule_from_index_on_delete() |
|||
{ |
|||
var rule = SetupRule(0, false); |
|||
|
|||
var context = |
|||
new CommandContext(new DeleteRule { RuleId = rule.Id }, commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => index.RemoveAsync(rule.Id)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_forward_call_when_rebuilding() |
|||
{ |
|||
var rules = new HashSet<Guid>(); |
|||
|
|||
await sut.RebuildAsync(appId.Id, rules); |
|||
|
|||
A.CallTo(() => index.RebuildAsync(rules)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
private IRuleEntity SetupRule(long version, bool deleted) |
|||
{ |
|||
var ruleEntity = A.Fake<IRuleEntity>(); |
|||
|
|||
var ruleId = Guid.NewGuid(); |
|||
|
|||
A.CallTo(() => ruleEntity.Id) |
|||
.Returns(ruleId); |
|||
A.CallTo(() => ruleEntity.AppId) |
|||
.Returns(appId); |
|||
A.CallTo(() => ruleEntity.Version) |
|||
.Returns(version); |
|||
A.CallTo(() => ruleEntity.IsDeleted) |
|||
.Returns(deleted); |
|||
|
|||
var ruleGrain = A.Fake<IRuleGrain>(); |
|||
|
|||
A.CallTo(() => ruleGrain.GetStateAsync()) |
|||
.Returns(J.Of(ruleEntity)); |
|||
|
|||
A.CallTo(() => grainFactory.GetGrain<IRuleGrain>(ruleId, null)) |
|||
.Returns(ruleGrain); |
|||
|
|||
return ruleEntity; |
|||
} |
|||
} |
|||
} |
|||
@ -1,73 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Schemas.Commands; |
|||
using Squidex.Domain.Apps.Entities.TestHelpers; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Schemas.Indexes |
|||
{ |
|||
public class SchemasByAppIndexCommandMiddlewareTests |
|||
{ |
|||
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>(); |
|||
private readonly ICommandBus commandBus = A.Fake<ICommandBus>(); |
|||
private readonly ISchemasByAppIndex index = A.Fake<ISchemasByAppIndex>(); |
|||
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); |
|||
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema"); |
|||
private readonly SchemasByAppIndexCommandMiddleware sut; |
|||
|
|||
public SchemasByAppIndexCommandMiddlewareTests() |
|||
{ |
|||
A.CallTo(() => grainFactory.GetGrain<ISchemasByAppIndex>(appId.Id, null)) |
|||
.Returns(index); |
|||
|
|||
sut = new SchemasByAppIndexCommandMiddleware(grainFactory); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_schema_to_index_on_create() |
|||
{ |
|||
var context = |
|||
new CommandContext(new CreateSchema { SchemaId = schemaId.Id, Name = schemaId.Name, AppId = appId }, commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => index.AddSchemaAsync(schemaId.Id, schemaId.Name)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_schema_from_index_on_delete() |
|||
{ |
|||
var schemaGrain = A.Fake<ISchemaGrain>(); |
|||
var schemaState = Mocks.Schema(appId, schemaId); |
|||
|
|||
A.CallTo(() => grainFactory.GetGrain<ISchemaGrain>(schemaId.Id, null)) |
|||
.Returns(schemaGrain); |
|||
|
|||
A.CallTo(() => schemaGrain.GetStateAsync()) |
|||
.Returns(J.AsTask(schemaState)); |
|||
|
|||
var context = |
|||
new CommandContext(new DeleteSchema { SchemaId = schemaId.Id }, commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => index.RemoveSchemaAsync(schemaId.Id)) |
|||
.MustHaveHappened(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,96 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Schemas.Indexes |
|||
{ |
|||
public class SchemasByAppIndexGrainTests |
|||
{ |
|||
private readonly IGrainState<SchemasByAppIndexGrain.GrainState> grainState = A.Fake<IGrainState<SchemasByAppIndexGrain.GrainState>>(); |
|||
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); |
|||
private readonly NamedId<Guid> schemaId1 = NamedId.Of(Guid.NewGuid(), "my-schema1"); |
|||
private readonly NamedId<Guid> schemaId2 = NamedId.Of(Guid.NewGuid(), "my-schema2"); |
|||
private readonly SchemasByAppIndexGrain sut; |
|||
|
|||
public SchemasByAppIndexGrainTests() |
|||
{ |
|||
A.CallTo(() => grainState.ClearAsync()) |
|||
.Invokes(() => grainState.Value = new SchemasByAppIndexGrain.GrainState()); |
|||
|
|||
sut = new SchemasByAppIndexGrain(grainState); |
|||
sut.ActivateAsync(appId.Id).Wait(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_schema_id_to_index() |
|||
{ |
|||
await sut.AddSchemaAsync(schemaId1.Id, schemaId1.Name); |
|||
|
|||
var result = await sut.GetSchemaIdAsync(schemaId1.Name); |
|||
|
|||
Assert.Equal(schemaId1.Id, result); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_delete_and_reset_state_when_cleaning() |
|||
{ |
|||
await sut.AddSchemaAsync(schemaId1.Id, schemaId1.Name); |
|||
await sut.ClearAsync(); |
|||
|
|||
var id = await sut.GetSchemaIdAsync(schemaId1.Name); |
|||
|
|||
Assert.Equal(id, Guid.Empty); |
|||
|
|||
A.CallTo(() => grainState.ClearAsync()) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_schema_id_from_index() |
|||
{ |
|||
await sut.AddSchemaAsync(schemaId1.Id, schemaId1.Name); |
|||
await sut.RemoveSchemaAsync(schemaId1.Id); |
|||
|
|||
var result = await sut.GetSchemaIdAsync(schemaId1.Name); |
|||
|
|||
Assert.Equal(Guid.Empty, result); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappenedTwiceExactly(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_replace_schema_ids_on_rebuild() |
|||
{ |
|||
var state = new Dictionary<string, Guid> |
|||
{ |
|||
[schemaId1.Name] = schemaId1.Id, |
|||
[schemaId2.Name] = schemaId2.Id |
|||
}; |
|||
|
|||
await sut.RebuildAsync(state); |
|||
|
|||
Assert.Equal(schemaId1.Id, await sut.GetSchemaIdAsync(schemaId1.Name)); |
|||
Assert.Equal(schemaId2.Id, await sut.GetSchemaIdAsync(schemaId2.Name)); |
|||
|
|||
Assert.Equal(new List<Guid> { schemaId1.Id, schemaId2.Id }, await sut.GetSchemaIdsAsync()); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappened(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,248 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Entities.Schemas.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Squidex.Infrastructure.Validation; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Schemas.Indexes |
|||
{ |
|||
public class SchemasIndexTests |
|||
{ |
|||
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>(); |
|||
private readonly ICommandBus commandBus = A.Fake<ICommandBus>(); |
|||
private readonly ISchemasByAppIndexGrain index = A.Fake<ISchemasByAppIndexGrain>(); |
|||
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); |
|||
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema"); |
|||
private readonly SchemasIndex sut; |
|||
|
|||
public SchemasIndexTests() |
|||
{ |
|||
A.CallTo(() => grainFactory.GetGrain<ISchemasByAppIndexGrain>(appId.Id, null)) |
|||
.Returns(index); |
|||
|
|||
sut = new SchemasIndex(grainFactory); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_resolve_schema_by_id() |
|||
{ |
|||
var schema = SetupSchema(0, false); |
|||
|
|||
var actual = await sut.GetSchemaAsync(appId.Id, schema.Id); |
|||
|
|||
Assert.Same(actual, schema); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_resolve_schema_by_name() |
|||
{ |
|||
var schema = SetupSchema(0, false); |
|||
|
|||
A.CallTo(() => index.GetIdAsync(schema.SchemaDef.Name)) |
|||
.Returns(schema.Id); |
|||
|
|||
var actual = await sut.GetSchemaAsync(appId.Id, schema.SchemaDef.Name); |
|||
|
|||
Assert.Same(actual, schema); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_resolve_schemas_by_id() |
|||
{ |
|||
var schema = SetupSchema(0, false); |
|||
|
|||
A.CallTo(() => index.GetIdsAsync()) |
|||
.Returns(new List<Guid> { schema.Id }); |
|||
|
|||
var actual = await sut.GetSchemasAsync(appId.Id); |
|||
|
|||
Assert.Same(actual[0], schema); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_return_empty_schema_if_schema_not_created() |
|||
{ |
|||
var schema = SetupSchema(-1, false); |
|||
|
|||
A.CallTo(() => index.GetIdsAsync()) |
|||
.Returns(new List<Guid> { schema.Id }); |
|||
|
|||
var actual = await sut.GetSchemasAsync(appId.Id); |
|||
|
|||
Assert.Empty(actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_return_empty_schema_if_schema_deleted() |
|||
{ |
|||
var schema = SetupSchema(0, true); |
|||
|
|||
A.CallTo(() => index.GetIdAsync(schema.SchemaDef.Name)) |
|||
.Returns(schema.Id); |
|||
|
|||
var actual = await sut.GetSchemasAsync(appId.Id); |
|||
|
|||
Assert.Empty(actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_also_return_schema_if_deleted_allowed() |
|||
{ |
|||
var schema = SetupSchema(-1, true); |
|||
|
|||
A.CallTo(() => index.GetIdAsync(schema.SchemaDef.Name)) |
|||
.Returns(schema.Id); |
|||
|
|||
var actual = await sut.GetSchemasAsync(appId.Id, true); |
|||
|
|||
Assert.Empty(actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_schema_to_index_on_create() |
|||
{ |
|||
var token = RandomHash.Simple(); |
|||
|
|||
A.CallTo(() => index.ReserveAsync(schemaId.Id, schemaId.Name)) |
|||
.Returns(token); |
|||
|
|||
var context = |
|||
new CommandContext(Create(schemaId.Name), commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => index.AddAsync(token)) |
|||
.MustHaveHappened(); |
|||
|
|||
A.CallTo(() => index.RemoveReservationAsync(A<string>.Ignored)) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_clear_reservation_when_schema_creation_failed() |
|||
{ |
|||
var token = RandomHash.Simple(); |
|||
|
|||
A.CallTo(() => index.ReserveAsync(schemaId.Id, schemaId.Name)) |
|||
.Returns(token); |
|||
|
|||
var context = |
|||
new CommandContext(Create(schemaId.Name), commandBus); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => index.AddAsync(token)) |
|||
.MustNotHaveHappened(); |
|||
|
|||
A.CallTo(() => index.RemoveReservationAsync(token)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_add_to_index_on_create_if_name_taken() |
|||
{ |
|||
A.CallTo(() => index.ReserveAsync(schemaId.Id, schemaId.Name)) |
|||
.Returns(Task.FromResult<string>(null)); |
|||
|
|||
var context = |
|||
new CommandContext(Create(schemaId.Name), commandBus) |
|||
.Complete(); |
|||
|
|||
await Assert.ThrowsAsync<ValidationException>(() => sut.HandleAsync(context)); |
|||
|
|||
A.CallTo(() => index.AddAsync(A<string>.Ignored)) |
|||
.MustNotHaveHappened(); |
|||
|
|||
A.CallTo(() => index.RemoveReservationAsync(A<string>.Ignored)) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_add_to_index_on_create_if_name_invalid() |
|||
{ |
|||
var context = |
|||
new CommandContext(Create("INVALID"), commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => index.ReserveAsync(schemaId.Id, A<string>.Ignored)) |
|||
.MustNotHaveHappened(); |
|||
|
|||
A.CallTo(() => index.RemoveReservationAsync(A<string>.Ignored)) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_schema_from_index_on_delete() |
|||
{ |
|||
var schema = SetupSchema(0, false); |
|||
|
|||
var context = |
|||
new CommandContext(new DeleteSchema { SchemaId = schema.Id }, commandBus) |
|||
.Complete(); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => index.RemoveAsync(schema.Id)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_forward_call_when_rebuilding() |
|||
{ |
|||
var schemas = new Dictionary<string, Guid>(); |
|||
|
|||
await sut.RebuildAsync(appId.Id, schemas); |
|||
|
|||
A.CallTo(() => index.RebuildAsync(schemas)) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
private CreateSchema Create(string name) |
|||
{ |
|||
return new CreateSchema { SchemaId = schemaId.Id, Name = name, AppId = appId }; |
|||
} |
|||
|
|||
private ISchemaEntity SetupSchema(long version, bool deleted) |
|||
{ |
|||
var schemaEntity = A.Fake<ISchemaEntity>(); |
|||
|
|||
A.CallTo(() => schemaEntity.SchemaDef) |
|||
.Returns(new Schema(schemaId.Name)); |
|||
A.CallTo(() => schemaEntity.Id) |
|||
.Returns(schemaId.Id); |
|||
A.CallTo(() => schemaEntity.AppId) |
|||
.Returns(appId); |
|||
A.CallTo(() => schemaEntity.Version) |
|||
.Returns(version); |
|||
A.CallTo(() => schemaEntity.IsDeleted) |
|||
.Returns(deleted); |
|||
|
|||
var schemaGrain = A.Fake<ISchemaGrain>(); |
|||
|
|||
A.CallTo(() => schemaGrain.GetStateAsync()) |
|||
.Returns(J.Of(schemaEntity)); |
|||
|
|||
A.CallTo(() => grainFactory.GetGrain<ISchemaGrain>(schemaId.Id, null)) |
|||
.Returns(schemaGrain); |
|||
|
|||
return schemaEntity; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,103 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans.Indexes |
|||
{ |
|||
public class IdsIndexGrainTests |
|||
{ |
|||
private readonly IGrainState<IdsIndexState<Guid>> grainState = A.Fake<IGrainState<IdsIndexState<Guid>>>(); |
|||
private readonly Guid id1 = Guid.NewGuid(); |
|||
private readonly Guid id2 = Guid.NewGuid(); |
|||
private readonly IdsIndexGrain<IdsIndexState<Guid>, Guid> sut; |
|||
|
|||
public IdsIndexGrainTests() |
|||
{ |
|||
A.CallTo(() => grainState.ClearAsync()) |
|||
.Invokes(() => grainState.Value = new IdsIndexState<Guid>()); |
|||
|
|||
sut = new IdsIndexGrain<IdsIndexState<Guid>, Guid>(grainState); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_id_to_index() |
|||
{ |
|||
await sut.AddAsync(id1); |
|||
await sut.AddAsync(id2); |
|||
|
|||
var result = await sut.GetIdsAsync(); |
|||
|
|||
Assert.Equal(new List<Guid> { id1, id2 }, result); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappenedTwiceExactly(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_provide_number_of_entries() |
|||
{ |
|||
await sut.AddAsync(id1); |
|||
await sut.AddAsync(id2); |
|||
|
|||
var count = await sut.CountAsync(); |
|||
|
|||
Assert.Equal(2, count); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_clear_all_entries() |
|||
{ |
|||
await sut.AddAsync(id1); |
|||
await sut.AddAsync(id2); |
|||
|
|||
await sut.ClearAsync(); |
|||
|
|||
var count = await sut.CountAsync(); |
|||
|
|||
Assert.Equal(0, count); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_id_from_index() |
|||
{ |
|||
await sut.AddAsync(id1); |
|||
await sut.AddAsync(id2); |
|||
await sut.RemoveAsync(id1); |
|||
|
|||
var result = await sut.GetIdsAsync(); |
|||
|
|||
Assert.Equal(new List<Guid> { id2 }, result); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappenedTwiceOrMore(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_replace__ids_on_rebuild() |
|||
{ |
|||
var state = new HashSet<Guid> |
|||
{ |
|||
id1, |
|||
id2 |
|||
}; |
|||
|
|||
await sut.RebuildAsync(state); |
|||
|
|||
var result = await sut.GetIdsAsync(); |
|||
|
|||
Assert.Equal(new List<Guid> { id1, id2 }, result); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappened(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,197 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans.Indexes |
|||
{ |
|||
public class UniqueNameIndexGrainTests |
|||
{ |
|||
private readonly IGrainState<UniqueNameIndexState<Guid>> grainState = A.Fake<IGrainState<UniqueNameIndexState<Guid>>>(); |
|||
private readonly NamedId<Guid> id1 = NamedId.Of(Guid.NewGuid(), "my-name1"); |
|||
private readonly NamedId<Guid> id2 = NamedId.Of(Guid.NewGuid(), "my-name2"); |
|||
private readonly UniqueNameIndexGrain<UniqueNameIndexState<Guid>, Guid> sut; |
|||
|
|||
public UniqueNameIndexGrainTests() |
|||
{ |
|||
A.CallTo(() => grainState.ClearAsync()) |
|||
.Invokes(() => grainState.Value = new UniqueNameIndexState<Guid>()); |
|||
|
|||
sut = new UniqueNameIndexGrain<UniqueNameIndexState<Guid>, Guid>(grainState); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_write_to_state_for_reservation() |
|||
{ |
|||
await sut.ReserveAsync(id1.Id, id1.Name); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_to_index_if_reservation_token_acquired() |
|||
{ |
|||
await AddAsync(id1); |
|||
|
|||
var result = await sut.GetIdAsync(id1.Name); |
|||
|
|||
Assert.Equal(id1.Id, result); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_make_reservation_if_name_already_reserved() |
|||
{ |
|||
await sut.ReserveAsync(id1.Id, id1.Name); |
|||
|
|||
var newToken = await sut.ReserveAsync(id1.Id, id1.Name); |
|||
|
|||
Assert.Null(newToken); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_make_reservation_if_name_taken() |
|||
{ |
|||
await AddAsync(id1); |
|||
|
|||
var newToken = await sut.ReserveAsync(id1.Id, id1.Name); |
|||
|
|||
Assert.Null(newToken); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_provide_number_of_entries() |
|||
{ |
|||
await AddAsync(id1); |
|||
await AddAsync(id2); |
|||
|
|||
var count = await sut.CountAsync(); |
|||
|
|||
Assert.Equal(2, count); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_clear_all_entries() |
|||
{ |
|||
await AddAsync(id1); |
|||
await AddAsync(id2); |
|||
|
|||
await sut.ClearAsync(); |
|||
|
|||
var count = await sut.CountAsync(); |
|||
|
|||
Assert.Equal(0, count); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_make_reservation_after_reservation_removed() |
|||
{ |
|||
var token = await sut.ReserveAsync(id1.Id, id1.Name); |
|||
|
|||
await sut.RemoveReservationAsync(token); |
|||
|
|||
var newToken = await sut.ReserveAsync(id1.Id, id1.Name); |
|||
|
|||
Assert.NotNull(newToken); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_make_reservation_after_id_removed() |
|||
{ |
|||
await AddAsync(id1); |
|||
|
|||
await sut.RemoveAsync(id1.Id); |
|||
|
|||
var newToken = await sut.ReserveAsync(id1.Id, id1.Name); |
|||
|
|||
Assert.NotNull(newToken); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_id_from_index() |
|||
{ |
|||
await AddAsync(id1); |
|||
|
|||
await sut.RemoveAsync(id1.Id); |
|||
|
|||
var result = await sut.GetIdAsync(id1.Name); |
|||
|
|||
Assert.Equal(Guid.Empty, result); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappenedTwiceExactly(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_write_to_state_if_nothing_removed() |
|||
{ |
|||
await sut.RemoveAsync(id1.Id); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_ignore_error_if_removing_reservation_with_Invalid_token() |
|||
{ |
|||
await sut.RemoveReservationAsync(null); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_ignore_error_if_completing_reservation_with_Invalid_token() |
|||
{ |
|||
await sut.AddAsync(null); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_replace_ids_on_rebuild() |
|||
{ |
|||
var state = new Dictionary<string, Guid> |
|||
{ |
|||
[id1.Name] = id1.Id, |
|||
[id2.Name] = id2.Id |
|||
}; |
|||
|
|||
await sut.RebuildAsync(state); |
|||
|
|||
Assert.Equal(id1.Id, await sut.GetIdAsync(id1.Name)); |
|||
Assert.Equal(id2.Id, await sut.GetIdAsync(id2.Name)); |
|||
|
|||
var result = await sut.GetIdsAsync(); |
|||
|
|||
Assert.Equal(new List<Guid> { id1.Id, id2.Id }, result); |
|||
|
|||
A.CallTo(() => grainState.WriteAsync()) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_provide_multiple_ids_by_names() |
|||
{ |
|||
await AddAsync(id1); |
|||
await AddAsync(id2); |
|||
|
|||
var result = await sut.GetIdsAsync(new string[] { id1.Name, id2.Name, "not-found" }); |
|||
|
|||
Assert.Equal(new List<Guid> { id1.Id, id2.Id }, result); |
|||
} |
|||
|
|||
private async Task AddAsync(NamedId<Guid> id) |
|||
{ |
|||
var token = await sut.ReserveAsync(id.Id, id.Name); |
|||
|
|||
await sut.AddAsync(token); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue