mirror of https://github.com/Squidex/squidex.git
Browse Source
* Improve consistency for content repositories.
* Temp
* Improved consistency for assets.
* Improve tests
* Fixes to app plan management
* Remove referrer
* Fixes cancellation token.
* Fix method naming.
* Remove test loop.
* Another test to improve tests
* Small improvement for tests.
* Perhaps the potential to get better error messages.
* Revert batch change.
* Test logging.
* Fix JSON
* Better polling subscription.
* Store recent events.
* Test output.
* Test
* Another test
* Fix asset deleter.
* Build fix.
* Revert "Store recent events."
This reverts commit 127491d61d.
pull/908/head
committed by
GitHub
72 changed files with 1825 additions and 881 deletions
@ -1,151 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Squidex.Domain.Apps.Core.Contents; |
|
||||
using Squidex.Domain.Apps.Core.Schemas; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.Json.Objects; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Tags |
|
||||
{ |
|
||||
public static class TagNormalizer |
|
||||
{ |
|
||||
public static async Task NormalizeAsync(this ITagService tagService, DomainId appId, DomainId schemaId, Schema schema, ContentData newData, ContentData? oldData) |
|
||||
{ |
|
||||
Guard.NotNull(tagService); |
|
||||
Guard.NotNull(schema); |
|
||||
Guard.NotNull(newData); |
|
||||
|
|
||||
var newValues = new HashSet<string>(); |
|
||||
var newArrays = new List<JsonValue>(); |
|
||||
|
|
||||
var oldValues = new HashSet<string>(); |
|
||||
var oldArrays = new List<JsonValue>(); |
|
||||
|
|
||||
GetValues(schema, newValues, newArrays, newData); |
|
||||
|
|
||||
if (oldData != null) |
|
||||
{ |
|
||||
GetValues(schema, oldValues, oldArrays, oldData); |
|
||||
} |
|
||||
|
|
||||
if (newValues.Count > 0) |
|
||||
{ |
|
||||
var normalized = await tagService.NormalizeTagsAsync(appId, TagGroups.Schemas(schemaId), newValues, oldValues); |
|
||||
|
|
||||
foreach (var source in newArrays) |
|
||||
{ |
|
||||
var array = source.AsArray; |
|
||||
|
|
||||
for (var i = 0; i < array.Count; i++) |
|
||||
{ |
|
||||
if (normalized.TryGetValue(array[i].ToString(), out var result)) |
|
||||
{ |
|
||||
array[i] = result; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static async Task DenormalizeAsync(this ITagService tagService, DomainId appId, DomainId schemaId, Schema schema, params ContentData[] datas) |
|
||||
{ |
|
||||
Guard.NotNull(tagService); |
|
||||
Guard.NotNull(schema); |
|
||||
|
|
||||
var tagsValues = new HashSet<string>(); |
|
||||
var tagsArrays = new List<JsonValue>(); |
|
||||
|
|
||||
GetValues(schema, tagsValues, tagsArrays, datas); |
|
||||
|
|
||||
if (tagsValues.Count > 0) |
|
||||
{ |
|
||||
var denormalized = await tagService.DenormalizeTagsAsync(appId, TagGroups.Schemas(schemaId), tagsValues); |
|
||||
|
|
||||
foreach (var source in tagsArrays) |
|
||||
{ |
|
||||
var array = source.AsArray; |
|
||||
|
|
||||
for (var i = 0; i < array.Count; i++) |
|
||||
{ |
|
||||
if (denormalized.TryGetValue(array[i].ToString(), out var result)) |
|
||||
{ |
|
||||
array[i] = result; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private static void GetValues(Schema schema, HashSet<string> values, List<JsonValue> arrays, params ContentData[] datas) |
|
||||
{ |
|
||||
foreach (var field in schema.Fields) |
|
||||
{ |
|
||||
if (field is IField<TagsFieldProperties> tags && tags.Properties.Normalization == TagsFieldNormalization.Schema) |
|
||||
{ |
|
||||
foreach (var data in datas) |
|
||||
{ |
|
||||
if (data.TryGetValue(field.Name, out var fieldData) && fieldData != null) |
|
||||
{ |
|
||||
foreach (var partition in fieldData) |
|
||||
{ |
|
||||
ExtractTags(partition.Value, values, arrays); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
else if (field is IArrayField arrayField) |
|
||||
{ |
|
||||
foreach (var nestedField in arrayField.Fields) |
|
||||
{ |
|
||||
if (nestedField is IField<TagsFieldProperties> nestedTags && nestedTags.Properties.Normalization == TagsFieldNormalization.Schema) |
|
||||
{ |
|
||||
foreach (var data in datas) |
|
||||
{ |
|
||||
if (data.TryGetValue(field.Name, out var fieldData) && fieldData != null) |
|
||||
{ |
|
||||
foreach (var partition in fieldData) |
|
||||
{ |
|
||||
if (partition.Value.Value is JsonArray a) |
|
||||
{ |
|
||||
foreach (var value in a) |
|
||||
{ |
|
||||
if (value.Value is JsonObject o) |
|
||||
{ |
|
||||
if (o.TryGetValue(nestedField.Name, out var nestedValue)) |
|
||||
{ |
|
||||
ExtractTags(nestedValue, values, arrays); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private static void ExtractTags(JsonValue value, ISet<string> values, ICollection<JsonValue> arrays) |
|
||||
{ |
|
||||
if (value.Value is JsonArray a) |
|
||||
{ |
|
||||
foreach (var item in a) |
|
||||
{ |
|
||||
if (item.Value is string s) |
|
||||
{ |
|
||||
values.Add(s); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
arrays.Add(value); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,13 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Apps.Plans |
|
||||
{ |
|
||||
public sealed class PlanChangeAsyncResult : IChangePlanResult |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
@ -1,23 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Squidex.Infrastructure; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Apps.Plans |
|
||||
{ |
|
||||
public sealed class RedirectToCheckoutResult : IChangePlanResult |
|
||||
{ |
|
||||
public Uri Url { get; } |
|
||||
|
|
||||
public RedirectToCheckoutResult(Uri url) |
|
||||
{ |
|
||||
Guard.NotNull(url); |
|
||||
|
|
||||
Url = url; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,136 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using FakeItEasy; |
|
||||
using Squidex.Domain.Apps.Core.Contents; |
|
||||
using Squidex.Domain.Apps.Core.Schemas; |
|
||||
using Squidex.Domain.Apps.Core.Tags; |
|
||||
using Squidex.Domain.Apps.Core.TestHelpers; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.Json.Objects; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Operations.Tags |
|
||||
{ |
|
||||
public class TagNormalizerTests |
|
||||
{ |
|
||||
private readonly ITagService tagService = A.Fake<ITagService>(); |
|
||||
private readonly DomainId appId = DomainId.NewGuid(); |
|
||||
private readonly DomainId schemaId = DomainId.NewGuid(); |
|
||||
private readonly Schema schema; |
|
||||
|
|
||||
public TagNormalizerTests() |
|
||||
{ |
|
||||
schema = |
|
||||
new Schema("my-schema") |
|
||||
.AddTags(1, "tags1", Partitioning.Invariant) |
|
||||
.AddTags(2, "tags2", Partitioning.Invariant, new TagsFieldProperties { Normalization = TagsFieldNormalization.Schema }) |
|
||||
.AddString(3, "string", Partitioning.Invariant) |
|
||||
.AddArray(4, "array", Partitioning.Invariant, f => f |
|
||||
.AddTags(401, "nestedTags1") |
|
||||
.AddTags(402, "nestedTags2", new TagsFieldProperties { Normalization = TagsFieldNormalization.Schema }) |
|
||||
.AddString(403, "string")); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_normalize_tags_with_old_data() |
|
||||
{ |
|
||||
var newData = GenerateData("n_raw"); |
|
||||
var oldData = GenerateData("o_raw"); |
|
||||
|
|
||||
A.CallTo(() => tagService.NormalizeTagsAsync(appId, TagGroups.Schemas(schemaId), |
|
||||
A<HashSet<string>>.That.Is("n_raw2_1", "n_raw2_2", "n_raw4"), |
|
||||
A<HashSet<string>>.That.Is("o_raw2_1", "o_raw2_2", "o_raw4"), |
|
||||
default)) |
|
||||
.Returns(new Dictionary<string, string> |
|
||||
{ |
|
||||
["n_raw2_2"] = "id2_2", |
|
||||
["n_raw2_1"] = "id2_1", |
|
||||
["n_raw4"] = "id4" |
|
||||
}); |
|
||||
|
|
||||
await tagService.NormalizeAsync(appId, schemaId, schema, newData, oldData); |
|
||||
|
|
||||
Assert.Equal(JsonValue.Array("id2_1", "id2_2"), newData["tags2"]!["iv"]); |
|
||||
Assert.Equal(JsonValue.Array("id4"), GetNestedTags(newData)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_normalize_tags_without_old_data() |
|
||||
{ |
|
||||
var newData = GenerateData("name"); |
|
||||
|
|
||||
A.CallTo(() => tagService.NormalizeTagsAsync(appId, TagGroups.Schemas(schemaId), |
|
||||
A<HashSet<string>>.That.Is("name2_1", "name2_2", "name4"), |
|
||||
A<HashSet<string>>.That.IsEmpty(), |
|
||||
default)) |
|
||||
.Returns(new Dictionary<string, string> |
|
||||
{ |
|
||||
["name2_2"] = "id2_2", |
|
||||
["name2_1"] = "id2_1", |
|
||||
["name4"] = "id4" |
|
||||
}); |
|
||||
|
|
||||
await tagService.NormalizeAsync(appId, schemaId, schema, newData, null); |
|
||||
|
|
||||
Assert.Equal(JsonValue.Array("id2_1", "id2_2"), newData["tags2"]!["iv"]); |
|
||||
Assert.Equal(JsonValue.Array("id4"), GetNestedTags(newData)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_denormalize_tags() |
|
||||
{ |
|
||||
var newData = GenerateData("id"); |
|
||||
|
|
||||
A.CallTo(() => tagService.NormalizeTagsAsync(appId, TagGroups.Schemas(schemaId), |
|
||||
A<HashSet<string>>.That.Is("id2_1", "id2_2", "id4"), |
|
||||
A<HashSet<string>>.That.IsEmpty(), |
|
||||
default)) |
|
||||
.Returns(new Dictionary<string, string> |
|
||||
{ |
|
||||
["id2_2"] = "name2_2", |
|
||||
["id2_1"] = "name2_1", |
|
||||
["id4"] = "name4" |
|
||||
}); |
|
||||
|
|
||||
await tagService.NormalizeAsync(appId, schemaId, schema, newData, null); |
|
||||
|
|
||||
Assert.Equal(JsonValue.Array("name2_1", "name2_2"), newData["tags2"]!["iv"]); |
|
||||
Assert.Equal(JsonValue.Array("name4"), GetNestedTags(newData)); |
|
||||
} |
|
||||
|
|
||||
private static JsonValue GetNestedTags(ContentData newData) |
|
||||
{ |
|
||||
var arrayValue = newData["array"]!["iv"].AsArray; |
|
||||
var arrayItem = arrayValue[0].AsObject; |
|
||||
|
|
||||
return arrayItem["nestedTags2"]; |
|
||||
} |
|
||||
|
|
||||
private static ContentData GenerateData(string prefix) |
|
||||
{ |
|
||||
return new ContentData() |
|
||||
.AddField("tags1", |
|
||||
new ContentFieldData() |
|
||||
.AddInvariant(JsonValue.Array($"{prefix}1"))) |
|
||||
.AddField("tags2", |
|
||||
new ContentFieldData() |
|
||||
.AddInvariant(JsonValue.Array($"{prefix}2_1", $"{prefix}2_2"))) |
|
||||
.AddField("string", |
|
||||
new ContentFieldData() |
|
||||
.AddInvariant($"{prefix}stringValue")) |
|
||||
.AddField("array", |
|
||||
new ContentFieldData() |
|
||||
.AddInvariant( |
|
||||
JsonValue.Array( |
|
||||
new JsonObject() |
|
||||
.Add("nestedTags1", JsonValue.Array($"{prefix}3")) |
|
||||
.Add("nestedTags2", JsonValue.Array($"{prefix}4")) |
|
||||
.Add("string", $"{prefix}nestedStringValue")))); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,208 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using FakeItEasy; |
||||
|
using Squidex.Infrastructure.TestHelpers; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.States |
||||
|
{ |
||||
|
public class SimpleStateTests |
||||
|
{ |
||||
|
private readonly CancellationTokenSource cts = new CancellationTokenSource(); |
||||
|
private readonly CancellationToken ct; |
||||
|
private readonly TestState<MyDomainState> testState = new TestState<MyDomainState>(DomainId.NewGuid()); |
||||
|
private readonly SimpleState<MyDomainState> sut; |
||||
|
|
||||
|
public SimpleStateTests() |
||||
|
{ |
||||
|
ct = cts.Token; |
||||
|
|
||||
|
sut = new SimpleState<MyDomainState>(testState.PersistenceFactory, GetType(), testState.Id); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_init_with_base_data() |
||||
|
{ |
||||
|
Assert.Equal(-1, sut.Version); |
||||
|
Assert.NotNull(sut.Value); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_get_state_from_persistence_on_load() |
||||
|
{ |
||||
|
testState.Version = 42; |
||||
|
testState.Snapshot = new MyDomainState { Value = 50 }; |
||||
|
|
||||
|
await sut.LoadAsync(ct); |
||||
|
|
||||
|
Assert.Equal(42, sut.Version); |
||||
|
Assert.Equal(50, sut.Value.Value); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.ReadAsync(-2, ct)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_delete_when_clearing() |
||||
|
{ |
||||
|
await sut.ClearAsync(ct); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.DeleteAsync(ct)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_invoke_persistence_when_writing_state() |
||||
|
{ |
||||
|
await sut.WriteAsync(ct); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_load_once_on_update() |
||||
|
{ |
||||
|
await sut.UpdateAsync(x => true, ct: ct); |
||||
|
await sut.UpdateAsync(x => true, ct: ct); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.ReadAsync(-2, ct)) |
||||
|
.MustHaveHappenedOnceExactly(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_write_state_on_update_when_callback_returns_true() |
||||
|
{ |
||||
|
await sut.UpdateAsync(x => true, ct: ct); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_write_state_on_update_when_callback_returns_false() |
||||
|
{ |
||||
|
await sut.UpdateAsync(x => true, ct: ct); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_write_state_on_update_and_return_when_callback_returns_true() |
||||
|
{ |
||||
|
var result = await sut.UpdateAsync(x => (true, 42), ct: ct); |
||||
|
|
||||
|
Assert.Equal(42, result); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_write_state_on_update_and_return_when_callback_returns_false() |
||||
|
{ |
||||
|
var result = await sut.UpdateAsync(x => (false, 42), ct: ct); |
||||
|
|
||||
|
Assert.Equal(42, result); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.MustNotHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_retry_update_when_failed_with_inconsistency_issue() |
||||
|
{ |
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.Throws(new InconsistentStateException(1, 2)).NumberOfTimes(5); |
||||
|
|
||||
|
await sut.UpdateAsync(x => true, ct: ct); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.MustHaveHappenedANumberOfTimesMatching(x => x == 6); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.ReadAsync(A<long>._, ct)) |
||||
|
.MustHaveHappenedANumberOfTimesMatching(x => x == 6); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_give_up_update_after_too_many_inconsistency_issues() |
||||
|
{ |
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.Throws(new InconsistentStateException(1, 2)).NumberOfTimes(100); |
||||
|
|
||||
|
await Assert.ThrowsAsync<InconsistentStateException>(() => sut.UpdateAsync(x => true, ct: ct)); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.MustHaveHappenedANumberOfTimesMatching(x => x == 20); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.ReadAsync(A<long>._, ct)) |
||||
|
.MustHaveHappenedANumberOfTimesMatching(x => x == 20); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_retry_update_with_other_exception() |
||||
|
{ |
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.Throws(new InvalidOperationException()); |
||||
|
|
||||
|
await Assert.ThrowsAsync<InvalidOperationException>(() => sut.UpdateAsync(x => true, ct: ct)); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.MustHaveHappenedANumberOfTimesMatching(x => x == 1); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.ReadAsync(A<long>._, ct)) |
||||
|
.MustHaveHappenedANumberOfTimesMatching(x => x == 1); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_retry_update_and_return_when_failed_with_inconsistency_issue() |
||||
|
{ |
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.Throws(new InconsistentStateException(1, 2)).NumberOfTimes(5); |
||||
|
|
||||
|
await sut.UpdateAsync(x => (true, 42), ct: ct); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.MustHaveHappenedANumberOfTimesMatching(x => x == 6); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.ReadAsync(A<long>._, ct)) |
||||
|
.MustHaveHappenedANumberOfTimesMatching(x => x == 6); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_give_up_update_and_return_after_too_many_inconsistency_issues() |
||||
|
{ |
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.Throws(new InconsistentStateException(1, 2)).NumberOfTimes(100); |
||||
|
|
||||
|
await Assert.ThrowsAsync<InconsistentStateException>(() => sut.UpdateAsync(x => (true, 42), ct: ct)); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.MustHaveHappenedANumberOfTimesMatching(x => x == 20); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.ReadAsync(A<long>._, ct)) |
||||
|
.MustHaveHappenedANumberOfTimesMatching(x => x == 20); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_retry_update_and_return_with_other_exception() |
||||
|
{ |
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.Throws(new InvalidOperationException()); |
||||
|
|
||||
|
await Assert.ThrowsAsync<InvalidOperationException>(() => sut.UpdateAsync(x => (true, 42), ct: ct)); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.WriteSnapshotAsync(sut.Value, ct)) |
||||
|
.MustHaveHappenedANumberOfTimesMatching(x => x == 1); |
||||
|
|
||||
|
A.CallTo(() => testState.Persistence.ReadAsync(A<long>._, ct)) |
||||
|
.MustHaveHappenedANumberOfTimesMatching(x => x == 1); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue