Browse Source

Bson fixes.

pull/666/head
Sebastian 5 years ago
parent
commit
7fba825004
  1. 54
      backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs
  2. 2
      backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesIndex.cs
  3. 2
      backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleQueryService.cs
  4. 22
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonHelper.cs
  5. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexIntegrationTests.cs
  6. 49
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/StatusSerializerTests.cs
  7. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Indexes/RulesIndexTests.cs
  8. 19
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Queries/RuleQueryServiceTests.cs
  9. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexIntegrationTests.cs
  10. 68
      backend/tests/Squidex.Infrastructure.Tests/MongoDb/BsonConverterTests.cs
  11. 8
      backend/tests/Squidex.Infrastructure.Tests/MongoDb/DomainIdSerializerTests.cs
  12. 12
      backend/tests/Squidex.Infrastructure.Tests/MongoDb/TypeConverterStringSerializerTests.cs

54
backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs

@ -61,14 +61,19 @@ namespace Squidex.Domain.Apps.Entities
public async Task<IAppEntity?> GetAppAsync(DomainId appId, bool canCache = false) public async Task<IAppEntity?> 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) if (app != null)
{ {
localCache.Add(AppCacheKey(app.Id), app); localCache.Add(cacheKey, app);
localCache.Add(AppCacheKey(app.Name), app);
} }
return app; return app;
@ -76,13 +81,18 @@ namespace Squidex.Domain.Apps.Entities
public async Task<IAppEntity?> GetAppAsync(string appName, bool canCache = false) public async Task<IAppEntity?> 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) if (app != null)
{ {
localCache.Add(cacheKey, app);
localCache.Add(AppCacheKey(app.Id), app); localCache.Add(AppCacheKey(app.Id), app);
} }
@ -91,13 +101,18 @@ namespace Squidex.Domain.Apps.Entities
public async Task<ISchemaEntity?> GetSchemaAsync(DomainId appId, string name, bool canCache = false) public async Task<ISchemaEntity?> 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) if (schema != null)
{ {
localCache.Add(cacheKey, schema);
localCache.Add(SchemaCacheKey(appId, schema.Id), schema); localCache.Add(SchemaCacheKey(appId, schema.Id), schema);
} }
@ -106,14 +121,19 @@ namespace Squidex.Domain.Apps.Entities
public async Task<ISchemaEntity?> GetSchemaAsync(DomainId appId, DomainId id, bool canCache = false) public async Task<ISchemaEntity?> 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) if (schema != null)
{ {
localCache.Add(SchemaCacheKey(appId, schema.Id), schema); localCache.Add(cacheKey, schema);
localCache.Add(SchemaCacheKey(appId, schema.SchemaDef.Name), schema);
} }
return schema; return schema;
@ -126,7 +146,7 @@ namespace Squidex.Domain.Apps.Entities
return indexForApps.GetAppsForUserAsync(userId, permissions); return indexForApps.GetAppsForUserAsync(userId, permissions);
}); });
return apps.Where(x => !x.IsArchived).ToList(); return apps;
} }
public async Task<List<ISchemaEntity>> GetSchemasAsync(DomainId appId) public async Task<List<ISchemaEntity>> GetSchemasAsync(DomainId appId)
@ -136,7 +156,7 @@ namespace Squidex.Domain.Apps.Entities
return indexSchemas.GetSchemasAsync(appId); return indexSchemas.GetSchemasAsync(appId);
}); });
return schemas.Where(x => !x.IsDeleted).ToList(); return schemas;
} }
public async Task<List<IRuleEntity>> GetRulesAsync(DomainId appId) public async Task<List<IRuleEntity>> GetRulesAsync(DomainId appId)
@ -146,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities
return indexRules.GetRulesAsync(appId); return indexRules.GetRulesAsync(appId);
}); });
return rules.Where(x => !x.IsDeleted).ToList(); return rules.ToList();
} }
private static string AppCacheKey(DomainId appId) private static string AppCacheKey(DomainId appId)

2
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<IRuleGrain>(id.ToString()).GetStateAsync()).Value; var rule = (await grainFactory.GetGrain<IRuleGrain>(id.ToString()).GetStateAsync()).Value;
if (rule.Version <= EtagVersion.Empty) if (rule.Version <= EtagVersion.Empty || rule.IsDeleted)
{ {
return null; return null;
} }

2
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); var rules = await rulesIndex.GetRulesAsync(context.App.Id);
rules.RemoveAll(x => x.IsDeleted);
if (rules.Count > 0) if (rules.Count > 0)
{ {
var enriched = await ruleEnricher.EnrichAsync(rules, context); var enriched = await ruleEnricher.EnrichAsync(rules, context);

22
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonHelper.cs

@ -9,31 +9,47 @@ namespace Squidex.Infrastructure.MongoDb
{ {
public static class BsonHelper public static class BsonHelper
{ {
private const string Empty = "§empty";
private const string TypeBson = "§type"; private const string TypeBson = "§type";
private const string TypeJson = "$type"; private const string TypeJson = "$type";
private const string DotSource = ".";
private const string DotReplacement = "_§§_"; private const string DotReplacement = "_§§_";
public static string UnescapeBson(this string value) public static string UnescapeBson(this string value)
{ {
if (value == Empty)
{
return string.Empty;
}
if (value == TypeBson) if (value == TypeBson)
{ {
return TypeJson; return TypeJson;
} }
return ReplaceFirstCharacter(value, '§', '$').Replace(DotReplacement, "."); var result = value.ReplaceFirst('§', '$').Replace(DotReplacement, DotSource);
return result;
} }
public static string EscapeJson(this string value) public static string EscapeJson(this string value)
{ {
if (value.Length == 0)
{
return Empty;
}
if (value == TypeJson) if (value == TypeJson)
{ {
return TypeBson; 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) if (value.Length == 0 || value[0] != toReplace)
{ {

4
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexIntegrationTests.cs

@ -106,8 +106,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
} }
[Theory] [Theory]
[InlineData(3, 100, 300, false)] [InlineData(3, 100, 400, false)]
[InlineData(3, 100, 102, true)] [InlineData(3, 100, 202, true)]
public async Task Should_distribute_and_cache_domain_objects(short numSilos, int numRuns, int expectedCounts, bool shouldBreak) public async Task Should_distribute_and_cache_domain_objects(short numSilos, int numRuns, int expectedCounts, bool shouldBreak)
{ {
var env = new GrainEnvironment(); var env = new GrainEnvironment();

49
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/StatusSerializerTests.cs

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using MongoDB.Bson; using System.IO;
using MongoDB.Bson.IO; using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
@ -16,35 +16,60 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
{ {
public sealed class StatusSerializerTests public sealed class StatusSerializerTests
{ {
private sealed class TestObject private sealed record ValueHolder<T>
{ {
public Status Status { get; set; } public T Value { get; set; }
}
public StatusSerializerTests()
{
TypeConverterStringSerializer<Status>.Register();
} }
[Fact] [Fact]
public void Should_serialize_and_deserialize_status() public void Should_serialize_and_deserialize_status()
{ {
TypeConverterStringSerializer<Status>.Register(); var source = new ValueHolder<Status>
{
Value = Status.Published
};
var source = new TestObject var deserialized = SerializeAndDeserializeBson(source);
Assert.Equal(source, deserialized);
}
[Fact]
public void Should_serialize_and_deserialize_default_status()
{ {
Status = Status.Published var source = new ValueHolder<Status>
{
Value = default
}; };
var document = new BsonDocument(); var deserialized = SerializeAndDeserializeBson(source);
Assert.Equal(source, deserialized);
}
private static T SerializeAndDeserializeBson<T>(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(); writer.Flush();
} }
using (var reader = new BsonDocumentReader(document)) stream.Position = 0;
using (var reader = new BsonBinaryReader(stream))
{ {
var result = BsonSerializer.Deserialize<TestObject>(reader); var result = BsonSerializer.Deserialize<T>(reader);
Assert.Equal(source.Status, result.Status); return result;
} }
} }
} }

4
backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Indexes/RulesIndexTests.cs

@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Indexes
} }
[Fact] [Fact]
public async Task Should_return_rule_if_deleted() public async Task Should_return_empty_rules_if_rule_deleted()
{ {
var rule = SetupRule(0, true); var rule = SetupRule(0, true);
@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Indexes
var actual = await sut.GetRulesAsync(appId.Id); var actual = await sut.GetRulesAsync(appId.Id);
Assert.Same(actual[0], rule); Assert.Empty(actual);
} }
[Fact] [Fact]

19
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); Assert.Same(enriched, result);
} }
[Fact]
public async Task Should_not_get_deleted_rules()
{
var original = new List<IRuleEntity>
{
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<IEnumerable<IRuleEntity>>._, requestContext))
.MustNotHaveHappened();
}
} }
} }

4
backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexIntegrationTests.cs

@ -102,8 +102,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
} }
[Theory] [Theory]
[InlineData(3, 100, 300, false)] [InlineData(3, 100, 400, false)]
[InlineData(3, 100, 102, true)] [InlineData(3, 100, 202, true)]
public async Task Should_distribute_and_cache_domain_objects(short numSilos, int numRuns, int expectedCounts, bool shouldBreak) public async Task Should_distribute_and_cache_domain_objects(short numSilos, int numRuns, int expectedCounts, bool shouldBreak)
{ {
var env = new GrainEnvironment(); var env = new GrainEnvironment();

68
backend/tests/Squidex.Infrastructure.Tests/MongoDb/BsonConverterTests.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using FluentAssertions; using FluentAssertions;
@ -114,13 +115,10 @@ namespace Squidex.Infrastructure.MongoDb
} }
} }
private readonly TestObject source = TestObject.CreateWithValues();
private readonly JsonSerializer serializer = JsonSerializer.CreateDefault();
[Fact] [Fact]
public void Should_write_problematic_object() public void Should_write_problematic_object()
{ {
var buggy = new var source = new
{ {
a = 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()
{ {
serializer.Serialize(writer, buggy); var source = TestObject.CreateWithValues();
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_dollar()
{ {
var target = serializer.Deserialize(reader, buggy.GetType()); var source = new Dictionary<string, int>
{
["$key"] = 12
};
target.Should().BeEquivalentTo(buggy); var deserialized = SerializeAndDeserialize(source);
deserialized.Should().BeEquivalentTo(source);
} }
[Fact]
public void Should_deserialize_property_with_dot()
{
var source = new Dictionary<string, int>
{
["type.of.value"] = 12
};
var deserialized = SerializeAndDeserialize(source);
deserialized.Should().BeEquivalentTo(source);
} }
[Fact] [Fact]
public void Should_serialize_with_reader_and_writer() public void Should_deserialize_property_as_empty_string()
{
var source = new Dictionary<string, int>
{ {
[string.Empty] = 12
};
var deserialized = SerializeAndDeserialize(source);
deserialized.Should().BeEquivalentTo(source);
}
private static T SerializeAndDeserialize<T>(T value)
{
var serializer = JsonSerializer.CreateDefault();
var stream = new MemoryStream(); var stream = new MemoryStream();
using (var writer = new BsonJsonWriter(new BsonBinaryWriter(stream))) using (var writer = new BsonJsonWriter(new BsonBinaryWriter(stream)))
{ {
serializer.Serialize(writer, source); serializer.Serialize(writer, value);
writer.Flush(); writer.Flush();
} }
@ -171,9 +205,7 @@ namespace Squidex.Infrastructure.MongoDb
using (var reader = new BsonJsonReader(new BsonBinaryReader(stream))) using (var reader = new BsonJsonReader(new BsonBinaryReader(stream)))
{ {
var target = serializer.Deserialize<TestObject>(reader); return serializer.Deserialize<T>(reader)!;
target.Should().BeEquivalentTo(source);
} }
} }
} }

8
backend/tests/Squidex.Infrastructure.Tests/MongoDb/DomainIdSerializerTests.cs

@ -40,7 +40,7 @@ namespace Squidex.Infrastructure.MongoDb
var source = new IdEntity<string> { Id = id.ToString() }; var source = new IdEntity<string> { Id = id.ToString() };
var result = SerializeAndDeserialize<IdEntity<string>, IdEntity<DomainId>>(source); var result = SerializeAndDeserializeBson<IdEntity<string>, IdEntity<DomainId>>(source);
Assert.Equal(result.Id.ToString(), id.ToString()); Assert.Equal(result.Id.ToString(), id.ToString());
} }
@ -52,7 +52,7 @@ namespace Squidex.Infrastructure.MongoDb
var source = new StringEntity<Guid> { Id = id }; var source = new StringEntity<Guid> { Id = id };
var result = SerializeAndDeserialize<StringEntity<Guid>, IdEntity<DomainId>>(source); var result = SerializeAndDeserializeBson<StringEntity<Guid>, IdEntity<DomainId>>(source);
Assert.Equal(result.Id.ToString(), id.ToString()); Assert.Equal(result.Id.ToString(), id.ToString());
} }
@ -64,12 +64,12 @@ namespace Squidex.Infrastructure.MongoDb
var source = new IdEntity<Guid> { Id = id }; var source = new IdEntity<Guid> { Id = id };
var result = SerializeAndDeserialize<IdEntity<Guid>, IdEntity<DomainId>>(source); var result = SerializeAndDeserializeBson<IdEntity<Guid>, IdEntity<DomainId>>(source);
Assert.Equal(result.Id.ToString(), id.ToString()); Assert.Equal(result.Id.ToString(), id.ToString());
} }
public static TOut SerializeAndDeserialize<TIn, TOut>(TIn source) private static TOut SerializeAndDeserializeBson<TIn, TOut>(TIn source)
{ {
var stream = new MemoryStream(); var stream = new MemoryStream();

12
backend/tests/Squidex.Infrastructure.Tests/MongoDb/TypeConverterStringSerializerTests.cs

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using MongoDB.Bson; using System.IO;
using MongoDB.Bson.IO; using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization;
using Xunit; using Xunit;
@ -14,7 +14,7 @@ namespace Squidex.Infrastructure.MongoDb
{ {
public class TypeConverterStringSerializerTests public class TypeConverterStringSerializerTests
{ {
public sealed record ValueHolder<T> private sealed record ValueHolder<T>
{ {
public T Value { get; set; } public T Value { get; set; }
} }
@ -92,16 +92,18 @@ namespace Squidex.Infrastructure.MongoDb
private static T SerializeAndDeserializeBson<T>(T value) private static T SerializeAndDeserializeBson<T>(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); BsonSerializer.Serialize(writer, value);
writer.Flush(); writer.Flush();
} }
using (var reader = new BsonDocumentReader(document)) stream.Position = 0;
using (var reader = new BsonBinaryReader(stream))
{ {
var result = BsonSerializer.Deserialize<T>(reader); var result = BsonSerializer.Deserialize<T>(reader);

Loading…
Cancel
Save