diff --git a/backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs b/backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs index c540a068f..4a2904ba3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs @@ -61,14 +61,19 @@ namespace Squidex.Domain.Apps.Entities public async Task GetAppAsync(DomainId appId, bool canCache = false) { - var app = await localCache.GetOrCreateAsync(AppCacheKey(appId), () => + var cacheKey = AppCacheKey(appId); + + if (localCache.TryGetValue(cacheKey, out var cached) && cached is IAppEntity found) { - return indexForApps.GetAppAsync(appId, canCache); - }); + return found; + } + + var app = await indexForApps.GetAppAsync(appId, canCache); if (app != null) { - localCache.Add(AppCacheKey(app.Id), app); + localCache.Add(cacheKey, app); + localCache.Add(AppCacheKey(app.Name), app); } return app; @@ -76,13 +81,18 @@ namespace Squidex.Domain.Apps.Entities public async Task GetAppAsync(string appName, bool canCache = false) { - var app = await localCache.GetOrCreateAsync(AppCacheKey(appName), () => + var cacheKey = AppCacheKey(appName); + + if (localCache.TryGetValue(cacheKey, out var cached) && cached is IAppEntity found) { - return indexForApps.GetAppByNameAsync(appName, canCache); - }); + return found; + } + + var app = await indexForApps.GetAppByNameAsync(appName, canCache); if (app != null) { + localCache.Add(cacheKey, app); localCache.Add(AppCacheKey(app.Id), app); } @@ -91,13 +101,18 @@ namespace Squidex.Domain.Apps.Entities public async Task GetSchemaAsync(DomainId appId, string name, bool canCache = false) { - var schema = await localCache.GetOrCreateAsync(SchemaCacheKey(appId, name), () => + var cacheKey = SchemaCacheKey(appId, name); + + if (localCache.TryGetValue(cacheKey, out var cached) && cached is ISchemaEntity found) { - return indexSchemas.GetSchemaByNameAsync(appId, name, canCache); - }); + return found; + } + + var schema = await indexSchemas.GetSchemaByNameAsync(appId, name, canCache); if (schema != null) { + localCache.Add(cacheKey, schema); localCache.Add(SchemaCacheKey(appId, schema.Id), schema); } @@ -106,14 +121,19 @@ namespace Squidex.Domain.Apps.Entities public async Task GetSchemaAsync(DomainId appId, DomainId id, bool canCache = false) { - var schema = await localCache.GetOrCreateAsync(SchemaCacheKey(appId, id), () => + var cacheKey = SchemaCacheKey(appId, id); + + if (localCache.TryGetValue(cacheKey, out var cached) && cached is ISchemaEntity found) { - return indexSchemas.GetSchemaAsync(appId, id, canCache); - }); + return found; + } + + var schema = await indexSchemas.GetSchemaAsync(appId, id, canCache); if (schema != null) { - localCache.Add(SchemaCacheKey(appId, schema.Id), schema); + localCache.Add(cacheKey, schema); + localCache.Add(SchemaCacheKey(appId, schema.SchemaDef.Name), schema); } return schema; @@ -126,7 +146,7 @@ namespace Squidex.Domain.Apps.Entities return indexForApps.GetAppsForUserAsync(userId, permissions); }); - return apps.Where(x => !x.IsArchived).ToList(); + return apps; } public async Task> GetSchemasAsync(DomainId appId) @@ -136,7 +156,7 @@ namespace Squidex.Domain.Apps.Entities return indexSchemas.GetSchemasAsync(appId); }); - return schemas.Where(x => !x.IsDeleted).ToList(); + return schemas; } public async Task> GetRulesAsync(DomainId appId) @@ -146,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities return indexRules.GetRulesAsync(appId); }); - return rules.Where(x => !x.IsDeleted).ToList(); + return rules.ToList(); } private static string AppCacheKey(DomainId appId) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesIndex.cs index 2f0d40fd9..1d07650c9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesIndex.cs @@ -97,7 +97,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Indexes { var rule = (await grainFactory.GetGrain(id.ToString()).GetStateAsync()).Value; - if (rule.Version <= EtagVersion.Empty) + if (rule.Version <= EtagVersion.Empty || rule.IsDeleted) { return null; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleQueryService.cs index 7ee10f429..c73d6bfda 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleQueryService.cs @@ -31,8 +31,6 @@ namespace Squidex.Domain.Apps.Entities.Rules.Queries { var rules = await rulesIndex.GetRulesAsync(context.App.Id); - rules.RemoveAll(x => x.IsDeleted); - if (rules.Count > 0) { var enriched = await ruleEnricher.EnrichAsync(rules, context); diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonHelper.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonHelper.cs index 09608b744..d4b7d2c54 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonHelper.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonHelper.cs @@ -9,31 +9,47 @@ namespace Squidex.Infrastructure.MongoDb { public static class BsonHelper { + private const string Empty = "§empty"; private const string TypeBson = "§type"; private const string TypeJson = "$type"; + private const string DotSource = "."; private const string DotReplacement = "_§§_"; public static string UnescapeBson(this string value) { + if (value == Empty) + { + return string.Empty; + } + if (value == TypeBson) { return TypeJson; } - return ReplaceFirstCharacter(value, '§', '$').Replace(DotReplacement, "."); + var result = value.ReplaceFirst('§', '$').Replace(DotReplacement, DotSource); + + return result; } public static string EscapeJson(this string value) { + if (value.Length == 0) + { + return Empty; + } + if (value == TypeJson) { return TypeBson; } - return ReplaceFirstCharacter(value, '$', '§').Replace(".", DotReplacement); + var result = value.ReplaceFirst('$', '§').Replace(DotSource, DotReplacement); + + return result; } - private static string ReplaceFirstCharacter(string value, char toReplace, char replacement) + private static string ReplaceFirst(this string value, char toReplace, char replacement) { if (value.Length == 0 || value[0] != toReplace) { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexIntegrationTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexIntegrationTests.cs index 1eb1511ac..ed28a69a1 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexIntegrationTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexIntegrationTests.cs @@ -106,8 +106,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes } [Theory] - [InlineData(3, 100, 300, false)] - [InlineData(3, 100, 102, true)] + [InlineData(3, 100, 400, false)] + [InlineData(3, 100, 202, true)] public async Task Should_distribute_and_cache_domain_objects(short numSilos, int numRuns, int expectedCounts, bool shouldBreak) { var env = new GrainEnvironment(); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/StatusSerializerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/StatusSerializerTests.cs index 083bc8189..462b485ec 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/StatusSerializerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/StatusSerializerTests.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using MongoDB.Bson; +using System.IO; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using Squidex.Domain.Apps.Core.Contents; @@ -16,35 +16,60 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb { public sealed class StatusSerializerTests { - private sealed class TestObject + private sealed record ValueHolder { - public Status Status { get; set; } + public T Value { get; set; } + } + + public StatusSerializerTests() + { + TypeConverterStringSerializer.Register(); } [Fact] public void Should_serialize_and_deserialize_status() { - TypeConverterStringSerializer.Register(); + var source = new ValueHolder + { + Value = Status.Published + }; - var source = new TestObject + var deserialized = SerializeAndDeserializeBson(source); + + Assert.Equal(source, deserialized); + } + + [Fact] + public void Should_serialize_and_deserialize_default_status() + { + var source = new ValueHolder { - Status = Status.Published + Value = default }; - var document = new BsonDocument(); + var deserialized = SerializeAndDeserializeBson(source); + + Assert.Equal(source, deserialized); + } + + private static T SerializeAndDeserializeBson(T value) + { + var stream = new MemoryStream(); - using (var writer = new BsonDocumentWriter(document)) + using (var writer = new BsonBinaryWriter(stream)) { - BsonSerializer.Serialize(writer, source); + BsonSerializer.Serialize(writer, value); writer.Flush(); } - using (var reader = new BsonDocumentReader(document)) + stream.Position = 0; + + using (var reader = new BsonBinaryReader(stream)) { - var result = BsonSerializer.Deserialize(reader); + var result = BsonSerializer.Deserialize(reader); - Assert.Equal(source.Status, result.Status); + return result; } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Indexes/RulesIndexTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Indexes/RulesIndexTests.cs index 7ecff6886..bf8b5a4a2 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Indexes/RulesIndexTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Indexes/RulesIndexTests.cs @@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Indexes } [Fact] - public async Task Should_return_rule_if_deleted() + public async Task Should_return_empty_rules_if_rule_deleted() { var rule = SetupRule(0, true); @@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Indexes var actual = await sut.GetRulesAsync(appId.Id); - Assert.Same(actual[0], rule); + Assert.Empty(actual); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Queries/RuleQueryServiceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Queries/RuleQueryServiceTests.cs index 41d94039c..01a0dcd67 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Queries/RuleQueryServiceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Queries/RuleQueryServiceTests.cs @@ -53,24 +53,5 @@ namespace Squidex.Domain.Apps.Entities.Rules.Queries Assert.Same(enriched, result); } - - [Fact] - public async Task Should_not_get_deleted_rules() - { - var original = new List - { - new RuleEntity { IsDeleted = true } - }; - - A.CallTo(() => rulesIndex.GetRulesAsync(appId.Id)) - .Returns(original); - - var result = await sut.QueryAsync(requestContext); - - Assert.Empty(result); - - A.CallTo(() => ruleEnricher.EnrichAsync(A>._, requestContext)) - .MustNotHaveHappened(); - } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexIntegrationTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexIntegrationTests.cs index d442b2e9c..6154312f1 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexIntegrationTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexIntegrationTests.cs @@ -102,8 +102,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes } [Theory] - [InlineData(3, 100, 300, false)] - [InlineData(3, 100, 102, true)] + [InlineData(3, 100, 400, false)] + [InlineData(3, 100, 202, true)] public async Task Should_distribute_and_cache_domain_objects(short numSilos, int numRuns, int expectedCounts, bool shouldBreak) { var env = new GrainEnvironment(); diff --git a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/BsonConverterTests.cs b/backend/tests/Squidex.Infrastructure.Tests/MongoDb/BsonConverterTests.cs index c8ae76adf..fde89af15 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/BsonConverterTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/MongoDb/BsonConverterTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.IO; using System.Linq; using FluentAssertions; @@ -114,13 +115,10 @@ namespace Squidex.Infrastructure.MongoDb } } - private readonly TestObject source = TestObject.CreateWithValues(); - private readonly JsonSerializer serializer = JsonSerializer.CreateDefault(); - [Fact] public void Should_write_problematic_object() { - var buggy = new + var source = new { a = new { @@ -136,33 +134,69 @@ namespace Squidex.Infrastructure.MongoDb } }; - var stream = new MemoryStream(); + var deserialized = SerializeAndDeserialize(source); - using (var writer = new BsonJsonWriter(new BsonBinaryWriter(stream))) + deserialized.Should().BeEquivalentTo(source); + } + + [Fact] + public void Should_serialize_with_reader_and_writer() + { + var source = TestObject.CreateWithValues(); + + var deserialized = SerializeAndDeserialize(source); + + deserialized.Should().BeEquivalentTo(source); + } + + [Fact] + public void Should_deserialize_property_with_dollar() + { + var source = new Dictionary { - serializer.Serialize(writer, buggy); + ["$key"] = 12 + }; - writer.Flush(); - } + var deserialized = SerializeAndDeserialize(source); - stream.Position = 0; + deserialized.Should().BeEquivalentTo(source); + } - using (var reader = new BsonJsonReader(new BsonBinaryReader(stream))) + [Fact] + public void Should_deserialize_property_with_dot() + { + var source = new Dictionary { - var target = serializer.Deserialize(reader, buggy.GetType()); + ["type.of.value"] = 12 + }; - target.Should().BeEquivalentTo(buggy); - } + var deserialized = SerializeAndDeserialize(source); + + deserialized.Should().BeEquivalentTo(source); } [Fact] - public void Should_serialize_with_reader_and_writer() + public void Should_deserialize_property_as_empty_string() { + var source = new Dictionary + { + [string.Empty] = 12 + }; + + var deserialized = SerializeAndDeserialize(source); + + deserialized.Should().BeEquivalentTo(source); + } + + private static T SerializeAndDeserialize(T value) + { + var serializer = JsonSerializer.CreateDefault(); + var stream = new MemoryStream(); using (var writer = new BsonJsonWriter(new BsonBinaryWriter(stream))) { - serializer.Serialize(writer, source); + serializer.Serialize(writer, value); writer.Flush(); } @@ -171,9 +205,7 @@ namespace Squidex.Infrastructure.MongoDb using (var reader = new BsonJsonReader(new BsonBinaryReader(stream))) { - var target = serializer.Deserialize(reader); - - target.Should().BeEquivalentTo(source); + return serializer.Deserialize(reader)!; } } } diff --git a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/DomainIdSerializerTests.cs b/backend/tests/Squidex.Infrastructure.Tests/MongoDb/DomainIdSerializerTests.cs index 781e27025..7892d96a7 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/DomainIdSerializerTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/MongoDb/DomainIdSerializerTests.cs @@ -40,7 +40,7 @@ namespace Squidex.Infrastructure.MongoDb var source = new IdEntity { Id = id.ToString() }; - var result = SerializeAndDeserialize, IdEntity>(source); + var result = SerializeAndDeserializeBson, IdEntity>(source); Assert.Equal(result.Id.ToString(), id.ToString()); } @@ -52,7 +52,7 @@ namespace Squidex.Infrastructure.MongoDb var source = new StringEntity { Id = id }; - var result = SerializeAndDeserialize, IdEntity>(source); + var result = SerializeAndDeserializeBson, IdEntity>(source); Assert.Equal(result.Id.ToString(), id.ToString()); } @@ -64,12 +64,12 @@ namespace Squidex.Infrastructure.MongoDb var source = new IdEntity { Id = id }; - var result = SerializeAndDeserialize, IdEntity>(source); + var result = SerializeAndDeserializeBson, IdEntity>(source); Assert.Equal(result.Id.ToString(), id.ToString()); } - public static TOut SerializeAndDeserialize(TIn source) + private static TOut SerializeAndDeserializeBson(TIn source) { var stream = new MemoryStream(); diff --git a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/TypeConverterStringSerializerTests.cs b/backend/tests/Squidex.Infrastructure.Tests/MongoDb/TypeConverterStringSerializerTests.cs index 2a5e0bfbd..5d30a259b 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/TypeConverterStringSerializerTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/MongoDb/TypeConverterStringSerializerTests.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using MongoDB.Bson; +using System.IO; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using Xunit; @@ -14,7 +14,7 @@ namespace Squidex.Infrastructure.MongoDb { public class TypeConverterStringSerializerTests { - public sealed record ValueHolder + private sealed record ValueHolder { public T Value { get; set; } } @@ -92,16 +92,18 @@ namespace Squidex.Infrastructure.MongoDb private static T SerializeAndDeserializeBson(T value) { - var document = new BsonDocument(); + var stream = new MemoryStream(); - using (var writer = new BsonDocumentWriter(document)) + using (var writer = new BsonBinaryWriter(stream)) { BsonSerializer.Serialize(writer, value); writer.Flush(); } - using (var reader = new BsonDocumentReader(document)) + stream.Position = 0; + + using (var reader = new BsonBinaryReader(stream)) { var result = BsonSerializer.Deserialize(reader);