mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
75 changed files with 1102 additions and 191 deletions
@ -0,0 +1,90 @@ |
|||
// ==========================================================================
|
|||
// 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; |
|||
using System.Threading.Tasks; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Bson.Serialization; |
|||
using MongoDB.Bson.Serialization.Serializers; |
|||
using MongoDB.Driver; |
|||
using NodaTime; |
|||
using Squidex.Domain.Apps.Entities.Rules.Repositories; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Rules |
|||
{ |
|||
public sealed class MongoRuleStatisticsCollection : MongoRepositoryBase<RuleStatistics> |
|||
{ |
|||
static MongoRuleStatisticsCollection() |
|||
{ |
|||
var guidSerializer = new GuidSerializer().WithRepresentation(BsonType.String); |
|||
|
|||
BsonClassMap.RegisterClassMap<RuleStatistics>(map => |
|||
{ |
|||
map.AutoMap(); |
|||
|
|||
map.MapProperty(x => x.AppId).SetSerializer(guidSerializer); |
|||
map.MapProperty(x => x.RuleId).SetSerializer(guidSerializer); |
|||
|
|||
map.SetIgnoreExtraElements(true); |
|||
}); |
|||
} |
|||
|
|||
public MongoRuleStatisticsCollection(IMongoDatabase database) |
|||
: base(database) |
|||
{ |
|||
} |
|||
|
|||
protected override string CollectionName() |
|||
{ |
|||
return "RuleStatistics"; |
|||
} |
|||
|
|||
protected override Task SetupCollectionAsync(IMongoCollection<RuleStatistics> collection, CancellationToken ct = default) |
|||
{ |
|||
return collection.Indexes.CreateOneAsync( |
|||
new CreateIndexModel<RuleStatistics>( |
|||
Index |
|||
.Ascending(x => x.AppId) |
|||
.Ascending(x => x.RuleId)), |
|||
cancellationToken: ct); |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<RuleStatistics>> QueryByAppAsync(Guid appId) |
|||
{ |
|||
var statistics = await Collection.Find(x => x.AppId == appId).ToListAsync(); |
|||
|
|||
return statistics; |
|||
} |
|||
|
|||
public Task IncrementSuccess(Guid appId, Guid ruleId, Instant now) |
|||
{ |
|||
return Collection.UpdateOneAsync( |
|||
x => x.AppId == appId && x.RuleId == ruleId, |
|||
Update |
|||
.Inc(x => x.NumSucceeded, 1) |
|||
.Set(x => x.LastExecuted, now) |
|||
.SetOnInsert(x => x.AppId, appId) |
|||
.SetOnInsert(x => x.RuleId, ruleId), |
|||
Upsert); |
|||
} |
|||
|
|||
public Task IncrementFailed(Guid appId, Guid ruleId, Instant now) |
|||
{ |
|||
return Collection.UpdateOneAsync( |
|||
x => x.AppId == appId && x.RuleId == ruleId, |
|||
Update |
|||
.Inc(x => x.NumFailed, 1) |
|||
.Set(x => x.LastExecuted, now) |
|||
.SetOnInsert(x => x.AppId, appId) |
|||
.SetOnInsert(x => x.RuleId, ruleId), |
|||
Upsert); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using NodaTime; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules |
|||
{ |
|||
public interface IEnrichedRuleEntity : IRuleEntity, IEntityWithCacheDependencies |
|||
{ |
|||
int NumSucceeded { get; } |
|||
|
|||
int NumFailed { get; } |
|||
|
|||
Instant? LastExecuted { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// ==========================================================================
|
|||
// 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.Domain.Apps.Entities.Rules |
|||
{ |
|||
public interface IRuleEnricher |
|||
{ |
|||
Task<IEnrichedRuleEntity> EnrichAsync(IRuleEntity rule, Context context); |
|||
|
|||
Task<IReadOnlyList<IEnrichedRuleEntity>> EnrichAsync(IEnumerable<IRuleEntity> rules, Context context); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// ==========================================================================
|
|||
// 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.Domain.Apps.Entities.Rules |
|||
{ |
|||
public interface IRuleQueryService |
|||
{ |
|||
Task<IReadOnlyList<IEnrichedRuleEntity>> QueryAsync(Context context); |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
// ==========================================================================
|
|||
// 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 Squidex.Domain.Apps.Entities.Rules.Repositories; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules.Queries |
|||
{ |
|||
public sealed class RuleEnricher : IRuleEnricher |
|||
{ |
|||
private readonly IRuleEventRepository ruleEventRepository; |
|||
|
|||
public RuleEnricher(IRuleEventRepository ruleEventRepository) |
|||
{ |
|||
Guard.NotNull(ruleEventRepository, nameof(ruleEventRepository)); |
|||
|
|||
this.ruleEventRepository = ruleEventRepository; |
|||
} |
|||
|
|||
public async Task<IEnrichedRuleEntity> EnrichAsync(IRuleEntity rule, Context context) |
|||
{ |
|||
Guard.NotNull(rule, nameof(rule)); |
|||
|
|||
var enriched = await EnrichAsync(Enumerable.Repeat(rule, 1), context); |
|||
|
|||
return enriched[0]; |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<IEnrichedRuleEntity>> EnrichAsync(IEnumerable<IRuleEntity> rules, Context context) |
|||
{ |
|||
Guard.NotNull(rules, nameof(rules)); |
|||
Guard.NotNull(context, nameof(context)); |
|||
|
|||
using (Profiler.TraceMethod<RuleEnricher>()) |
|||
{ |
|||
var results = new List<RuleEntity>(); |
|||
|
|||
foreach (var rule in rules) |
|||
{ |
|||
var result = SimpleMapper.Map(rule, new RuleEntity()); |
|||
|
|||
results.Add(result); |
|||
} |
|||
|
|||
foreach (var group in results.GroupBy(x => x.AppId.Id)) |
|||
{ |
|||
var statistics = await ruleEventRepository.QueryStatisticsByAppAsync(group.Key); |
|||
|
|||
foreach (var rule in group) |
|||
{ |
|||
var statistic = statistics.FirstOrDefault(x => x.RuleId == rule.Id); |
|||
|
|||
if (statistic != null) |
|||
{ |
|||
rule.LastExecuted = statistic.LastExecuted; |
|||
rule.NumFailed = statistic.NumFailed; |
|||
rule.NumSucceeded = statistic.NumSucceeded; |
|||
|
|||
rule.CacheDependencies = new HashSet<object> |
|||
{ |
|||
statistic.LastExecuted |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return results; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Entities.Rules.Indexes; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules.Queries |
|||
{ |
|||
public sealed class RuleQueryService : IRuleQueryService |
|||
{ |
|||
private readonly IRulesIndex rulesIndex; |
|||
private readonly IRuleEnricher ruleEnricher; |
|||
|
|||
public RuleQueryService(IRulesIndex rulesIndex, IRuleEnricher ruleEnricher) |
|||
{ |
|||
Guard.NotNull(rulesIndex, nameof(rulesIndex)); |
|||
Guard.NotNull(ruleEnricher, nameof(ruleEnricher)); |
|||
|
|||
this.rulesIndex = rulesIndex; |
|||
this.ruleEnricher = ruleEnricher; |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<IEnrichedRuleEntity>> QueryAsync(Context context) |
|||
{ |
|||
var rules = await rulesIndex.GetRulesAsync(context.App.Id); |
|||
|
|||
var enriched = await ruleEnricher.EnrichAsync(rules, context); |
|||
|
|||
return enriched; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using NodaTime; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules.Repositories |
|||
{ |
|||
public class RuleStatistics |
|||
{ |
|||
public Guid AppId { get; set; } |
|||
|
|||
public Guid RuleId { get; set; } |
|||
|
|||
public int NumSucceeded { get; set; } |
|||
|
|||
public int NumFailed { get; set; } |
|||
|
|||
public Instant? LastExecuted { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// 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 |
|||
{ |
|||
public sealed class RuleCommandMiddleware : GrainCommandMiddleware<RuleCommand, IRuleGrain> |
|||
{ |
|||
private readonly IRuleEnricher ruleEnricher; |
|||
private readonly IContextProvider contextProvider; |
|||
|
|||
public RuleCommandMiddleware(IGrainFactory grainFactory, IRuleEnricher ruleEnricher, IContextProvider contextProvider) |
|||
: base(grainFactory) |
|||
{ |
|||
Guard.NotNull(ruleEnricher, nameof(ruleEnricher)); |
|||
Guard.NotNull(contextProvider, nameof(contextProvider)); |
|||
|
|||
this.ruleEnricher = ruleEnricher; |
|||
this.contextProvider = contextProvider; |
|||
} |
|||
|
|||
public override async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
await base.HandleAsync(context, next); |
|||
|
|||
if (context.PlainResult is IRuleEntity rule && NotEnriched(context)) |
|||
{ |
|||
var enriched = await ruleEnricher.EnrichAsync(rule, contextProvider.Context); |
|||
|
|||
context.Complete(enriched); |
|||
} |
|||
} |
|||
|
|||
private static bool NotEnriched(CommandContext context) |
|||
{ |
|||
return !(context.PlainResult is IEnrichedRuleEntity); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using NodaTime; |
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules |
|||
{ |
|||
public sealed class RuleEntity : IEnrichedRuleEntity |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public NamedId<Guid> AppId { get; set; } |
|||
|
|||
public NamedId<Guid> SchemaId { get; set; } |
|||
|
|||
public long Version { get; set; } |
|||
|
|||
public Instant Created { get; set; } |
|||
|
|||
public Instant LastModified { get; set; } |
|||
|
|||
public RefToken CreatedBy { get; set; } |
|||
|
|||
public RefToken LastModifiedBy { get; set; } |
|||
|
|||
public Rule RuleDef { get; set; } |
|||
|
|||
public bool IsDeleted { get; set; } |
|||
|
|||
public int NumSucceeded { get; set; } |
|||
|
|||
public int NumFailed { get; set; } |
|||
|
|||
public Instant? LastExecuted { get; set; } |
|||
|
|||
public HashSet<object> CacheDependencies { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
<div class="card"> |
|||
<div class="card-header"> |
|||
<div class="row"> |
|||
<div class="col col-name"> |
|||
<sqx-editable-title |
|||
fallback="Unnamed Rule" |
|||
[name]="rule.name" |
|||
(nameChange)="rename($event)" |
|||
[maxLength]="60" |
|||
[isRequired]="false" |
|||
[disabled]="!rule.canUpdate"> |
|||
</sqx-editable-title> |
|||
</div> |
|||
<div class="col-auto"> |
|||
<button type="button" class="btn btn-text-danger" |
|||
[disabled]="!rule.canDelete" |
|||
(sqxConfirmClick)="delete()" |
|||
confirmTitle="Delete rule" |
|||
confirmText="Do you really want to delete the rule?"> |
|||
<i class="icon-bin2"></i> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="card-body"> |
|||
<div class="row align-items-center"> |
|||
<div class="col col-auto"> |
|||
<h3>If</h3> |
|||
</div> |
|||
<div class="col"> |
|||
<span (click)="editTrigger.emit()"> |
|||
<sqx-rule-element [type]="rule.triggerType" [element]="ruleTriggers[rule.triggerType]"></sqx-rule-element> |
|||
</span> |
|||
</div> |
|||
<div class="col col-auto"> |
|||
<h3>then</h3> |
|||
</div> |
|||
<div class="col"> |
|||
<span (click)="editAction.emit()"> |
|||
<sqx-rule-element [type]="rule.actionType" [element]="ruleActions[rule.actionType]"></sqx-rule-element> |
|||
</span> |
|||
</div> |
|||
<div class="col col-auto"> |
|||
<sqx-toggle [disabled]="!rule.canDisable && !rule.canEnable" [ngModel]="rule.isEnabled" (ngModelChange)="toggle()"></sqx-toggle> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="card-footer"> |
|||
<div class="row"> |
|||
<div class="col-3"> |
|||
Succeeded: <strong>{{rule.numSucceeded}}</strong> |
|||
</div> |
|||
<div class="col-3"> |
|||
Failed: <strong>{{rule.numFailed}}</strong> |
|||
</div> |
|||
<div class="col"> |
|||
Last Executed: |
|||
|
|||
<ng-container *ngIf="rule.lastExecuted; else notExecuted"> |
|||
{{rule.lastExecuted | sqxFromNow}} |
|||
</ng-container> |
|||
|
|||
<ng-template #notExecuted> |
|||
- |
|||
</ng-template> |
|||
</div> |
|||
<div class="col-auto"> |
|||
<a routerLink="events" [queryParams]="{ ruleId: rule.id }"> |
|||
Logs |
|||
</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,22 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.card { |
|||
& { |
|||
@include border-radius(0); |
|||
border-bottom-width: 2px; |
|||
border-top-width: 0; |
|||
margin-bottom: .25rem; |
|||
} |
|||
|
|||
&-header, |
|||
&-body { |
|||
padding: .75rem 1.25rem; |
|||
} |
|||
|
|||
&-footer { |
|||
background: $color-border; |
|||
font-weight: normal; |
|||
font-size: 90%; |
|||
} |
|||
} |
|||
@ -1,23 +1,30 @@ |
|||
<div class="title"> |
|||
<form *ngIf="isRenaming; else noRenaming" class="form-inline" [formGroup]="renameForm" (ngSubmit)="rename()"> |
|||
<div class="form-group mr-1"> |
|||
<sqx-control-errors for="name"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control form-underlined" formControlName="name" maxlength="20" sqxFocusOnInit (keydown)="onKeyDown($event.keyCode)" spellcheck="false" /> |
|||
<form *ngIf="renaming; else noRenaming" [formGroup]="renameForm" (ngSubmit)="rename()"> |
|||
<div class="row no-gutters"> |
|||
<div class="col"> |
|||
<div class="form-group mr-2"> |
|||
<sqx-control-errors for="name"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control form-underlined" formControlName="name" [maxLength]="maxLength" sqxFocusOnInit (keydown)="onKeyDown($event.keyCode)" spellcheck="false" /> |
|||
</div> |
|||
</div> |
|||
<div class="col-auto"> |
|||
<button type="submit" class="btn btn-primary mr-1" [disabled]="!renameForm.valid || !renameForm.dirty">Save</button> |
|||
|
|||
<button type="button" class="btn btn-text-secondary btn-cancel mr-4" (click)="toggleRename()"> |
|||
<i class="icon-close"></i> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
|
|||
<button type="submit" class="btn btn-primary" [disabled]="!renameForm.valid || !renameForm.dirty">Save</button> |
|||
|
|||
<button type="button" class="btn btn-text-secondary btn-cancel" (click)="toggleRename()"> |
|||
<i class="icon-close"></i> |
|||
</button> |
|||
</form> |
|||
|
|||
<ng-template #noRenaming> |
|||
<h3 class="title-name" (dblclick)="toggleRename()"> |
|||
{{name}} |
|||
</h3> |
|||
<div class="title-view"> |
|||
<h3 class="title-name" [class.fallback]="!name" (dblclick)="toggleRename()"> |
|||
{{name || fallback}} |
|||
</h3> |
|||
|
|||
<i class="title-edit icon-pencil" *ngIf="!disabled" (click)="toggleRename()"></i> |
|||
<i class="title-edit icon-pencil" *ngIf="!disabled" (click)="toggleRename()"></i> |
|||
</div> |
|||
</ng-template> |
|||
</div> |
|||
@ -0,0 +1,76 @@ |
|||
// ==========================================================================
|
|||
// 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 FakeItEasy; |
|||
using NodaTime; |
|||
using Squidex.Domain.Apps.Entities.Rules.Repositories; |
|||
using Squidex.Infrastructure; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules.Queries |
|||
{ |
|||
public class RuleEnricherTests |
|||
{ |
|||
private readonly IRuleEventRepository ruleEventRepository = A.Fake<IRuleEventRepository>(); |
|||
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); |
|||
private readonly Context requestContext = Context.Anonymous(); |
|||
private readonly RuleEnricher sut; |
|||
|
|||
public RuleEnricherTests() |
|||
{ |
|||
sut = new RuleEnricher(ruleEventRepository); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_enrich_if_statistics_not_found() |
|||
{ |
|||
var source = new RuleEntity { AppId = appId }; |
|||
|
|||
var result = await sut.EnrichAsync(source, requestContext); |
|||
|
|||
Assert.Equal(0, result.NumFailed); |
|||
Assert.Equal(0, result.NumSucceeded); |
|||
Assert.Null(result.LastExecuted); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_enrich_rules_with_found_statistics() |
|||
{ |
|||
var source1 = new RuleEntity { AppId = appId, Id = Guid.NewGuid() }; |
|||
var source2 = new RuleEntity { AppId = appId, Id = Guid.NewGuid() }; |
|||
|
|||
var stats = new RuleStatistics |
|||
{ |
|||
RuleId = source1.Id, |
|||
NumFailed = 12, |
|||
NumSucceeded = 17, |
|||
LastExecuted = SystemClock.Instance.GetCurrentInstant() |
|||
}; |
|||
|
|||
A.CallTo(() => ruleEventRepository.QueryStatisticsByAppAsync(appId.Id)) |
|||
.Returns(new List<RuleStatistics> { stats }); |
|||
|
|||
var result = await sut.EnrichAsync(new[] { source1, source2 }, requestContext); |
|||
|
|||
var enriched1 = result.ElementAt(0); |
|||
|
|||
Assert.Equal(12, enriched1.NumFailed); |
|||
Assert.Equal(17, enriched1.NumSucceeded); |
|||
Assert.Equal(stats.LastExecuted, enriched1.LastExecuted); |
|||
|
|||
var enriched2 = result.ElementAt(1); |
|||
|
|||
Assert.Equal(0, enriched2.NumFailed); |
|||
Assert.Equal(0, enriched2.NumSucceeded); |
|||
Assert.Null(enriched2.LastExecuted); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
// ==========================================================================
|
|||
// 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.Domain.Apps.Entities.Rules.Indexes; |
|||
using Squidex.Domain.Apps.Entities.TestHelpers; |
|||
using Squidex.Infrastructure; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules.Queries |
|||
{ |
|||
public class RuleQueryServiceTests |
|||
{ |
|||
private readonly IRulesIndex rulesIndex = A.Fake<IRulesIndex>(); |
|||
private readonly IRuleEnricher ruleEnricher = A.Fake<IRuleEnricher>(); |
|||
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); |
|||
private readonly Context requestContext = Context.Anonymous(); |
|||
private readonly RuleQueryService sut; |
|||
|
|||
public RuleQueryServiceTests() |
|||
{ |
|||
requestContext.App = Mocks.App(appId); |
|||
|
|||
sut = new RuleQueryService(rulesIndex, ruleEnricher); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_get_rules_from_index_and_enrich() |
|||
{ |
|||
var original = new List<IRuleEntity> |
|||
{ |
|||
new RuleEntity() |
|||
}; |
|||
|
|||
var enriched = new List<IEnrichedRuleEntity> |
|||
{ |
|||
new RuleEntity() |
|||
}; |
|||
|
|||
A.CallTo(() => rulesIndex.GetRulesAsync(appId.Id)) |
|||
.Returns(original); |
|||
|
|||
A.CallTo(() => ruleEnricher.EnrichAsync(original, requestContext)) |
|||
.Returns(enriched); |
|||
|
|||
var result = await sut.QueryAsync(requestContext); |
|||
|
|||
Assert.Same(enriched, result); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,96 @@ |
|||
// ==========================================================================
|
|||
// 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.State; |
|||
using Squidex.Domain.Apps.Entities.TestHelpers; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules |
|||
{ |
|||
public sealed class RuleCommandMiddlewareTests : HandlerTestBase<RuleState> |
|||
{ |
|||
private readonly IRuleEnricher ruleEnricher = A.Fake<IRuleEnricher>(); |
|||
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>(); |
|||
private readonly Guid ruleId = Guid.NewGuid(); |
|||
private readonly Context requestContext = Context.Anonymous(); |
|||
private readonly RuleCommandMiddleware sut; |
|||
|
|||
public sealed class MyCommand : SquidexCommand |
|||
{ |
|||
} |
|||
|
|||
protected override Guid Id |
|||
{ |
|||
get { return ruleId; } |
|||
} |
|||
|
|||
public RuleCommandMiddlewareTests() |
|||
{ |
|||
A.CallTo(() => contextProvider.Context) |
|||
.Returns(requestContext); |
|||
|
|||
sut = new RuleCommandMiddleware(A.Fake<IGrainFactory>(), ruleEnricher, contextProvider); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_invoke_enricher_for_other_result() |
|||
{ |
|||
var command = CreateCommand(new MyCommand()); |
|||
var context = CreateContextForCommand(command); |
|||
|
|||
context.Complete(12); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
A.CallTo(() => ruleEnricher.EnrichAsync(A<IEnrichedRuleEntity>.Ignored, requestContext)) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_invoke_enricher_if_already_enriched() |
|||
{ |
|||
var result = new RuleEntity(); |
|||
|
|||
var command = CreateCommand(new MyCommand()); |
|||
var context = CreateContextForCommand(command); |
|||
|
|||
context.Complete(result); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
Assert.Same(result, context.Result<IEnrichedRuleEntity>()); |
|||
|
|||
A.CallTo(() => ruleEnricher.EnrichAsync(A<IEnrichedRuleEntity>.Ignored, requestContext)) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_enrich_rule_result() |
|||
{ |
|||
var result = A.Fake<IRuleEntity>(); |
|||
|
|||
var command = CreateCommand(new MyCommand()); |
|||
var context = CreateContextForCommand(command); |
|||
|
|||
context.Complete(result); |
|||
|
|||
var enriched = new RuleEntity(); |
|||
|
|||
A.CallTo(() => ruleEnricher.EnrichAsync(result, requestContext)) |
|||
.Returns(enriched); |
|||
|
|||
await sut.HandleAsync(context); |
|||
|
|||
Assert.Same(enriched, context.Result<IEnrichedRuleEntity>()); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue