mirror of https://github.com/Squidex/squidex.git
30 changed files with 1019 additions and 376 deletions
@ -0,0 +1,14 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.States |
|||
{ |
|||
public interface IOnRead |
|||
{ |
|||
ValueTask OnReadAsync(); |
|||
} |
|||
} |
|||
@ -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