Browse Source

Removed properties bag.

pull/335/head
Sebastian Stehle 8 years ago
parent
commit
5378880556
  1. 2
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  2. 42
      src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs
  3. 4
      src/Squidex.Domain.Apps.Events/SquidexHeaderExtensions.cs
  4. 21
      src/Squidex.Infrastructure.GetEventStore/EventSourcing/Formatter.cs
  5. 14
      src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs
  6. 9
      src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs
  7. 9
      src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs
  8. 155
      src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConverter.cs
  9. 20
      src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs
  10. 46
      src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs
  11. 25
      src/Squidex.Infrastructure/EventSourcing/EnvelopeHeaders.cs
  12. 19
      src/Squidex.Infrastructure/EventSourcing/EventData.cs
  13. 78
      src/Squidex.Infrastructure/Json/Newtonsoft/PropertiesBagConverter.cs
  14. 16
      src/Squidex.Infrastructure/Json/Objects/JsonObject.cs
  15. 112
      src/Squidex.Infrastructure/PropertiesBag.cs
  16. 235
      src/Squidex.Infrastructure/PropertyValue.cs
  17. 3
      src/Squidex/Config/Domain/EventStoreServices.cs
  18. 2
      src/Squidex/Config/Domain/SerializationServices.cs
  19. 2
      tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs
  20. 12
      tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs
  21. 4
      tests/Squidex.Infrastructure.Tests/EventSourcing/DefaultEventDataFormatterTests.cs
  22. 10
      tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeExtensionsTests.cs
  23. 19
      tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeHeaderTests.cs
  24. 2
      tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs
  25. 4
      tests/Squidex.Infrastructure.Tests/EventSourcing/RetrySubscriptionTests.cs
  26. 38
      tests/Squidex.Infrastructure.Tests/MongoDb/BsonConverterTests.cs
  27. 428
      tests/Squidex.Infrastructure.Tests/PropertiesBagTests.cs
  28. 4
      tests/Squidex.Infrastructure.Tests/States/PersistenceEventSourcingTests.cs
  29. 2
      tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs
  30. 2
      tools/Migrate_01/Rebuilder.cs

2
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs

@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
var now = clock.GetCurrentInstant();
var eventTime =
@event.Headers.Contains(CommonHeaders.Timestamp) ?
@event.Headers.ContainsKey(CommonHeaders.Timestamp) ?
@event.Headers.Timestamp() :
now;

42
src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs

@ -9,12 +9,16 @@ using System;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Entities.Backup.Helpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.States;
#pragma warning disable SA1401 // Fields must be private
namespace Squidex.Domain.Apps.Entities.Backup
{
public sealed class BackupReader : DisposableObjectBase
@ -25,6 +29,36 @@ namespace Squidex.Domain.Apps.Entities.Backup
private int readEvents;
private int readAttachments;
private sealed class ComaptibleStoredEvent
{
[JsonProperty]
public string StreamName;
[JsonProperty]
public string EventPosition;
[JsonProperty]
public long EventStreamNumber;
[JsonProperty]
public CompatibleEventData Data;
}
private sealed class CompatibleEventData
{
[JsonProperty]
public string Type;
[JsonProperty]
public JRaw Payload;
[JsonProperty]
public EnvelopeHeaders Headers;
[JsonProperty]
public EnvelopeHeaders Metadata;
}
public int ReadEvents
{
get { return readEvents; }
@ -117,10 +151,14 @@ namespace Squidex.Domain.Apps.Entities.Backup
using (var stream = eventEntry.Open())
{
var storedEvent = serializer.Deserialize<StoredEvent>(stream);
var storedEvent = serializer.Deserialize<ComaptibleStoredEvent>(stream);
var src = storedEvent.Data;
var data = new EventData(src.Type, src.Headers ?? src.Metadata, src.Payload.ToString());
var eventStream = streamNameResolver.WithNewId(storedEvent.StreamName, guidMapper.NewGuidOrNull);
var eventEnvelope = formatter.Parse(storedEvent.Data, true, guidMapper.NewGuidOrValue);
var eventEnvelope = formatter.Parse(data, true, guidMapper.NewGuidOrValue);
await handler((eventStream, eventEnvelope));
}

4
src/Squidex.Domain.Apps.Events/SquidexHeaderExtensions.cs

@ -14,12 +14,12 @@ namespace Squidex.Domain.Apps.Events
{
public static Guid AppId(this EnvelopeHeaders headers)
{
return headers[SquidexHeaders.AppId].ToGuid();
return headers.GetGuid(SquidexHeaders.AppId);
}
public static Envelope<T> SetAppId<T>(this Envelope<T> envelope, Guid value) where T : class
{
envelope.Headers.Set(SquidexHeaders.AppId, value);
envelope.Headers.Add(SquidexHeaders.AppId, value);
return envelope;
}

21
src/Squidex.Infrastructure.GetEventStore/EventSourcing/Formatter.cs

@ -8,20 +8,23 @@
using System;
using System.Text;
using EventStore.ClientAPI;
using Squidex.Infrastructure.Json;
using EventStoreData = EventStore.ClientAPI.EventData;
namespace Squidex.Infrastructure.EventSourcing
{
public static class Formatter
{
public static StoredEvent Read(ResolvedEvent resolvedEvent)
public static StoredEvent Read(ResolvedEvent resolvedEvent, IJsonSerializer serializer)
{
var @event = resolvedEvent.Event;
var body = Encoding.UTF8.GetString(@event.Data);
var meta = Encoding.UTF8.GetString(@event.Metadata);
var metadata = Encoding.UTF8.GetString(@event.Data);
var eventData = new EventData { Type = @event.EventType, Payload = body, Metadata = meta };
var headersJson = Encoding.UTF8.GetString(@event.Metadata);
var headers = serializer.Deserialize<EnvelopeHeaders>(headersJson);
var eventData = new EventData(@event.EventType, headers, metadata);
return new StoredEvent(
@event.EventStreamId,
@ -30,12 +33,14 @@ namespace Squidex.Infrastructure.EventSourcing
eventData);
}
public static EventStoreData Write(EventData eventData)
public static EventStoreData Write(EventData eventData, IJsonSerializer serializer)
{
var body = Encoding.UTF8.GetBytes(eventData.Payload);
var meta = Encoding.UTF8.GetBytes(eventData.Metadata);
var payload = Encoding.UTF8.GetBytes(eventData.Payload);
var headersJson = serializer.Serialize(eventData.Headers);
var headersBytes = Encoding.UTF8.GetBytes(headersJson);
return new EventStoreData(Guid.NewGuid(), eventData.Type, true, body, meta);
return new EventStoreData(Guid.NewGuid(), eventData.Type, true, payload, headersBytes);
}
}
}

14
src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs

@ -11,6 +11,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using EventStore.ClientAPI;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log;
namespace Squidex.Infrastructure.EventSourcing
@ -20,14 +21,17 @@ namespace Squidex.Infrastructure.EventSourcing
private const int WritePageSize = 500;
private const int ReadPageSize = 500;
private readonly IEventStoreConnection connection;
private readonly IJsonSerializer serializer;
private readonly string prefix;
private readonly ProjectionClient projectionClient;
public GetEventStore(IEventStoreConnection connection, string prefix, string projectionHost)
public GetEventStore(IEventStoreConnection connection, IJsonSerializer serializer, string prefix, string projectionHost)
{
Guard.NotNull(connection, nameof(connection));
Guard.NotNull(serializer, nameof(serializer));
this.connection = connection;
this.serializer = serializer;
this.prefix = prefix?.Trim(' ', '-').WithFallback("squidex");
@ -50,7 +54,7 @@ namespace Squidex.Infrastructure.EventSourcing
public IEventSubscription CreateSubscription(IEventSubscriber subscriber, string streamFilter, string position = null)
{
return new GetEventStoreSubscription(connection, subscriber, projectionClient, position, streamFilter);
return new GetEventStoreSubscription(connection, subscriber, serializer, projectionClient, position, streamFilter);
}
public Task CreateIndexAsync(string property)
@ -95,7 +99,7 @@ namespace Squidex.Infrastructure.EventSourcing
foreach (var resolved in currentSlice.Events)
{
var storedEvent = Formatter.Read(resolved);
var storedEvent = Formatter.Read(resolved, serializer);
await callback(storedEvent);
}
@ -123,7 +127,7 @@ namespace Squidex.Infrastructure.EventSourcing
foreach (var resolved in currentSlice.Events)
{
var storedEvent = Formatter.Read(resolved);
var storedEvent = Formatter.Read(resolved, serializer);
result.Add(storedEvent);
}
@ -164,7 +168,7 @@ namespace Squidex.Infrastructure.EventSourcing
return;
}
var eventsToSave = events.Select(Formatter.Write).ToList();
var eventsToSave = events.Select(x => Formatter.Write(x, serializer)).ToList();
if (eventsToSave.Count < WritePageSize)
{

9
src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs

@ -8,6 +8,7 @@
using System.Threading.Tasks;
using EventStore.ClientAPI;
using EventStore.ClientAPI.Exceptions;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.EventSourcing
@ -16,12 +17,14 @@ namespace Squidex.Infrastructure.EventSourcing
{
private readonly IEventStoreConnection connection;
private readonly IEventSubscriber subscriber;
private readonly IJsonSerializer serializer;
private readonly EventStoreCatchUpSubscription subscription;
private readonly long? position;
public GetEventStoreSubscription(
IEventStoreConnection connection,
IEventSubscriber subscriber,
IJsonSerializer serializer,
ProjectionClient projectionClient,
string position,
string streamFilter)
@ -34,8 +37,10 @@ namespace Squidex.Infrastructure.EventSourcing
var streamName = projectionClient.CreateProjectionAsync(streamFilter).Result;
this.serializer = serializer;
this.subscriber = subscriber;
this.subscription = SubscribeToStream(streamName);
subscription = SubscribeToStream(streamName);
}
public Task StopAsync()
@ -56,7 +61,7 @@ namespace Squidex.Infrastructure.EventSourcing
return connection.SubscribeToStreamFrom(streamName, position, settings,
(s, e) =>
{
var storedEvent = Formatter.Read(e);
var storedEvent = Formatter.Read(e, serializer);
subscriber.OnEventAsync(this, storedEvent).Wait();
}, null,

9
src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs

@ -21,18 +21,19 @@ namespace Squidex.Infrastructure.EventSourcing
[BsonRequired]
public string Payload { get; set; }
[BsonElement]
[BsonElement("Metadata")]
[BsonRequired]
public BsonDocument Metadata { get; set; }
[BsonJson]
public EnvelopeHeaders Headers { get; set; }
public static MongoEvent FromEventData(EventData data)
{
return new MongoEvent { Type = data.Type, Metadata = BsonDocument.Parse(data.Payload), Payload = data.Payload };
return new MongoEvent { Type = data.Type, Headers = data.Headers, Payload = data.Payload };
}
public EventData ToEventData()
{
return new EventData { Type = Type, Metadata = Metadata.ToJson().ToString(), Payload = Payload };
return new EventData(Type, Headers, Payload);
}
}
}

155
src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConverter.cs

@ -1,155 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Globalization;
using MongoDB.Bson;
using Newtonsoft.Json.Linq;
namespace Squidex.Infrastructure.MongoDb
{
public static class BsonJsonConverter
{
public static BsonDocument ToBson(this JObject source)
{
var result = new BsonDocument();
foreach (var property in source)
{
result.Add(property.Key.EscapeJson(), property.Value.ToBson());
}
return result;
}
public static JObject ToJson(this BsonDocument source)
{
var result = new JObject();
foreach (var property in source)
{
result.Add(property.Name.UnescapeBson(), property.Value.ToJson());
}
return result;
}
public static BsonArray ToBson(this JArray source)
{
var result = new BsonArray();
foreach (var item in source)
{
result.Add(item.ToBson());
}
return result;
}
public static JArray ToJson(this BsonArray source)
{
var result = new JArray();
foreach (var item in source)
{
result.Add(item.ToJson());
}
return result;
}
public static BsonValue ToBson(this JToken source)
{
switch (source.Type)
{
case JTokenType.Object:
return ((JObject)source).ToBson();
case JTokenType.Array:
return ((JArray)source).ToBson();
case JTokenType.Integer:
return BsonValue.Create(((JValue)source).Value);
case JTokenType.Float:
return BsonValue.Create(((JValue)source).Value);
case JTokenType.String:
return BsonValue.Create(((JValue)source).Value);
case JTokenType.Boolean:
return BsonValue.Create(((JValue)source).Value);
case JTokenType.Null:
return BsonNull.Value;
case JTokenType.Undefined:
return BsonUndefined.Value;
case JTokenType.Bytes:
return BsonValue.Create(((JValue)source).Value);
case JTokenType.Guid:
return BsonValue.Create(((JValue)source).ToString(CultureInfo.InvariantCulture));
case JTokenType.Uri:
return BsonValue.Create(((JValue)source).ToString(CultureInfo.InvariantCulture));
case JTokenType.TimeSpan:
return BsonValue.Create(((JValue)source).ToString(CultureInfo.InvariantCulture));
case JTokenType.Date:
{
var value = ((JValue)source).Value;
if (value is DateTime dateTime)
{
return dateTime.ToString("yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture);
}
else if (value is DateTimeOffset dateTimeOffset)
{
if (dateTimeOffset.Offset == TimeSpan.Zero)
{
return dateTimeOffset.UtcDateTime.ToString("yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture);
}
else
{
return dateTimeOffset.ToString("yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture);
}
}
else
{
return value.ToString();
}
}
}
throw new NotSupportedException($"Cannot convert {source.GetType()} to Bson.");
}
public static JToken ToJson(this BsonValue source)
{
switch (source.BsonType)
{
case BsonType.Document:
return source.AsBsonDocument.ToJson();
case BsonType.Array:
return source.AsBsonArray.ToJson();
case BsonType.Double:
return new JValue(source.AsDouble);
case BsonType.String:
return new JValue(source.AsString);
case BsonType.Boolean:
return new JValue(source.AsBoolean);
case BsonType.DateTime:
return new JValue(source.ToUniversalTime());
case BsonType.Int32:
return new JValue(source.AsInt32);
case BsonType.Int64:
return new JValue(source.AsInt64);
case BsonType.Decimal128:
return new JValue(source.AsDecimal);
case BsonType.Binary:
return new JValue(source.AsBsonBinaryData.Bytes);
case BsonType.Null:
return JValue.CreateNull();
case BsonType.Undefined:
return JValue.CreateUndefined();
}
throw new NotSupportedException($"Cannot convert {source.GetType()} to Json.");
}
}
}

20
src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs

@ -27,17 +27,15 @@ namespace Squidex.Infrastructure.EventSourcing
public Envelope<IEvent> Parse(EventData eventData, bool migrate = true, Func<string, string> stringConverter = null)
{
var eventType = typeNameRegistry.GetType(eventData.Type);
var payloadType = typeNameRegistry.GetType(eventData.Type);
var payload = serializer.Deserialize<IEvent>(eventData.Payload, payloadType, stringConverter);
var eventHeaders = serializer.Deserialize<EnvelopeHeaders>(eventData.Metadata, null, stringConverter);
var eventContent = serializer.Deserialize<IEvent>(eventData.Payload, eventType, stringConverter);
if (migrate && eventContent is IMigratedEvent migratedEvent)
if (migrate && payload is IMigratedEvent migratedEvent)
{
eventContent = migratedEvent.Migrate();
payload = migratedEvent.Migrate();
}
var envelope = new Envelope<IEvent>(eventContent, eventHeaders);
var envelope = new Envelope<IEvent>(payload, eventData.Headers);
return envelope;
}
@ -51,14 +49,12 @@ namespace Squidex.Infrastructure.EventSourcing
eventPayload = migratedEvent.Migrate();
}
var eventType = typeNameRegistry.GetName(eventPayload.GetType());
var payloadType = typeNameRegistry.GetName(eventPayload.GetType());
var payload = serializer.Serialize(envelope.Payload);
envelope.SetCommitId(commitId);
var eventHeaders = serializer.Serialize(envelope.Headers);
var eventContent = serializer.Serialize(envelope.Payload);
return new EventData { Type = eventType, Payload = eventContent, Metadata = eventHeaders };
return new EventData(payloadType, envelope.Headers, payload);
}
}
}

46
src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs

@ -6,7 +6,10 @@
// ==========================================================================
using System;
using System.Globalization;
using NodaTime;
using NodaTime.Text;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Infrastructure.EventSourcing
{
@ -19,69 +22,90 @@ namespace Squidex.Infrastructure.EventSourcing
public static Envelope<T> SetEventPosition<T>(this Envelope<T> envelope, string value) where T : class
{
envelope.Headers.Set(CommonHeaders.EventNumber, value);
envelope.Headers.Add(CommonHeaders.EventNumber, value);
return envelope;
}
public static long EventStreamNumber(this EnvelopeHeaders headers)
{
return headers[CommonHeaders.EventStreamNumber].ToInt64();
return headers.GetInt64(CommonHeaders.EventStreamNumber);
}
public static Envelope<T> SetEventStreamNumber<T>(this Envelope<T> envelope, long value) where T : class
{
envelope.Headers.Set(CommonHeaders.EventStreamNumber, value);
envelope.Headers.Add(CommonHeaders.EventStreamNumber, value);
return envelope;
}
public static Guid CommitId(this EnvelopeHeaders headers)
{
return headers[CommonHeaders.CommitId].ToGuid();
return headers.GetGuid(CommonHeaders.CommitId);
}
public static Envelope<T> SetCommitId<T>(this Envelope<T> envelope, Guid value) where T : class
{
envelope.Headers.Set(CommonHeaders.CommitId, value);
envelope.Headers.Add(CommonHeaders.CommitId, value);
return envelope;
}
public static Guid AggregateId(this EnvelopeHeaders headers)
{
return headers[CommonHeaders.AggregateId].ToGuid();
return headers.GetGuid(CommonHeaders.AggregateId);
}
public static Envelope<T> SetAggregateId<T>(this Envelope<T> envelope, Guid value) where T : class
{
envelope.Headers.Set(CommonHeaders.AggregateId, value);
envelope.Headers.Add(CommonHeaders.AggregateId, value);
return envelope;
}
public static Guid EventId(this EnvelopeHeaders headers)
{
return headers[CommonHeaders.EventId].ToGuid();
return headers.GetGuid(CommonHeaders.EventId);
}
public static Envelope<T> SetEventId<T>(this Envelope<T> envelope, Guid value) where T : class
{
envelope.Headers.Set(CommonHeaders.EventId, value);
envelope.Headers.Add(CommonHeaders.EventId, value);
return envelope;
}
public static Instant Timestamp(this EnvelopeHeaders headers)
{
return headers[CommonHeaders.Timestamp].ToInstant();
return headers.GetInstant(CommonHeaders.Timestamp);
}
public static Envelope<T> SetTimestamp<T>(this Envelope<T> envelope, Instant value) where T : class
{
envelope.Headers.Set(CommonHeaders.Timestamp, value);
envelope.Headers.Add(CommonHeaders.Timestamp, value);
return envelope;
}
public static long GetInt64(this JsonObject obj, string key)
{
var value = obj[key];
return value is JsonScalar<double> s ? (long)s.Value : long.Parse(value.ToString(), CultureInfo.InvariantCulture);
}
public static Guid GetGuid(this JsonObject obj, string key)
{
var value = obj[key];
return Guid.Parse(value.ToString());
}
public static Instant GetInstant(this JsonObject obj, string key)
{
var value = obj[key];
return InstantPattern.General.Parse(value.ToString()).Value;
}
}
}

25
src/Squidex.Infrastructure/EventSourcing/EnvelopeHeaders.cs

@ -5,37 +5,24 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Infrastructure.EventSourcing
{
public sealed class EnvelopeHeaders : PropertiesBag
public sealed class EnvelopeHeaders : JsonObject
{
public EnvelopeHeaders()
{
}
public EnvelopeHeaders(PropertiesBag bag)
public EnvelopeHeaders(JsonObject headers)
: base(headers)
{
if (bag == null)
{
return;
}
foreach (var property in bag.Properties)
{
Set(property.Key, property.Value.RawValue);
}
}
public EnvelopeHeaders Clone()
{
var clone = new EnvelopeHeaders();
foreach (var property in Properties)
{
clone.Set(property.Key, property.Value.RawValue);
}
return clone;
return new EnvelopeHeaders(this);
}
}
}

19
src/Squidex.Infrastructure/EventSourcing/EventData.cs

@ -7,12 +7,25 @@
namespace Squidex.Infrastructure.EventSourcing
{
public class EventData
public sealed class EventData
{
public string Payload { get; set; }
public EnvelopeHeaders Headers { get; }
public string Metadata { get; set; }
public string Payload { get; }
public string Type { get; set; }
public EventData(string type, EnvelopeHeaders headers, string payload)
{
Guard.NotNull(type, nameof(type));
Guard.NotNull(headers, nameof(headers));
Guard.NotNull(payload, nameof(payload));
Headers = headers;
Payload = payload;
Type = type;
}
}
}

78
src/Squidex.Infrastructure/Json/Newtonsoft/PropertiesBagConverter.cs

@ -1,78 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
using NodaTime;
using NodaTime.Extensions;
namespace Squidex.Infrastructure.Json.Newtonsoft
{
public sealed class PropertiesBagConverter<T> : JsonClassConverter<T> where T : PropertiesBag, new()
{
protected override void WriteValue(JsonWriter writer, T value, JsonSerializer serializer)
{
writer.WriteStartObject();
foreach (var kvp in value.Properties)
{
writer.WritePropertyName(kvp.Key);
if (kvp.Value.RawValue is Instant instant)
{
writer.WriteValue(instant.ToString());
}
else
{
writer.WriteValue(kvp.Value.RawValue);
}
}
writer.WriteEndObject();
}
protected override T ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.StartObject)
{
throw new JsonException($"Expected Object, but got {reader.TokenType}.");
}
var properties = new T();
while (reader.Read())
{
if (reader.TokenType != JsonToken.PropertyName)
{
break;
}
var key = reader.Value.ToString();
reader.Read();
var value = reader.Value;
if (value is DateTime dateTime)
{
properties.Set(key, dateTime.ToInstant());
}
else
{
properties.Set(key, value);
}
}
return properties;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(T);
}
}
}

16
src/Squidex.Infrastructure/Json/Objects/JsonObject.cs

@ -12,9 +12,9 @@ using System.Linq;
namespace Squidex.Infrastructure.Json.Objects
{
public sealed class JsonObject : IReadOnlyDictionary<string, IJsonValue>, IJsonValue, IEquatable<JsonObject>
public class JsonObject : IReadOnlyDictionary<string, IJsonValue>, IJsonValue, IEquatable<JsonObject>
{
private readonly Dictionary<string, IJsonValue> inner = new Dictionary<string, IJsonValue>();
private readonly Dictionary<string, IJsonValue> inner;
public IJsonValue this[string key]
{
@ -51,6 +51,16 @@ namespace Squidex.Infrastructure.Json.Objects
get { return JsonValueType.Array; }
}
public JsonObject()
{
inner = new Dictionary<string, IJsonValue>();
}
public JsonObject(JsonObject obj)
{
inner = new Dictionary<string, IJsonValue>(obj.inner);
}
public JsonObject Add(string key, object value)
{
return Add(key, JsonValue.Create(value));
@ -61,7 +71,7 @@ namespace Squidex.Infrastructure.Json.Objects
Guard.NotNullOrEmpty(key, nameof(key));
Guard.NotNull(value, nameof(value));
inner.Add(key, value);
inner[key] = value;
return this;
}

112
src/Squidex.Infrastructure/PropertiesBag.cs

@ -1,112 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Dynamic;
namespace Squidex.Infrastructure
{
public class PropertiesBag : DynamicObject
{
private static readonly PropertyValue FallbackValue = new PropertyValue(null);
private readonly Dictionary<string, PropertyValue> internalDictionary = new Dictionary<string, PropertyValue>(StringComparer.OrdinalIgnoreCase);
public int Count
{
get { return internalDictionary.Count; }
}
public IReadOnlyDictionary<string, PropertyValue> Properties
{
get { return internalDictionary; }
}
public IEnumerable<string> PropertyNames
{
get { return internalDictionary.Keys; }
}
public PropertyValue this[string propertyName]
{
get
{
Guard.NotNullOrEmpty(propertyName, nameof(propertyName));
return internalDictionary.GetOrDefault(propertyName) ?? FallbackValue;
}
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return internalDictionary.Keys;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = this[binder.Name];
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
internalDictionary[binder.Name] = new PropertyValue(value);
return true;
}
public bool Contains(string propertyName)
{
Guard.NotNullOrEmpty(propertyName, nameof(propertyName));
return internalDictionary.ContainsKey(propertyName);
}
public bool Remove(string propertyName)
{
Guard.NotNullOrEmpty(propertyName, nameof(propertyName));
return internalDictionary.Remove(propertyName);
}
public PropertiesBag Set(string propertyName, object value)
{
Guard.NotNullOrEmpty(propertyName, nameof(propertyName));
internalDictionary[propertyName] = new PropertyValue(value);
return this;
}
public bool Rename(string oldPropertyName, string newPropertyName)
{
Guard.NotNullOrEmpty(oldPropertyName, nameof(oldPropertyName));
Guard.NotNullOrEmpty(newPropertyName, nameof(newPropertyName));
if (internalDictionary.ContainsKey(newPropertyName))
{
throw new ArgumentException($"An property with the key '{newPropertyName}' already exists.", newPropertyName);
}
if (string.Equals(oldPropertyName, newPropertyName, StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException($"The property names '{newPropertyName}' are equal.", newPropertyName);
}
if (!internalDictionary.TryGetValue(oldPropertyName, out var property))
{
return false;
}
internalDictionary[newPropertyName] = property;
internalDictionary.Remove(oldPropertyName);
return true;
}
}
}

235
src/Squidex.Infrastructure/PropertyValue.cs

@ -1,235 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Globalization;
using NodaTime;
using NodaTime.Text;
namespace Squidex.Infrastructure
{
public sealed class PropertyValue : DynamicObject
{
private readonly object rawValue;
private static readonly Dictionary<Type, Func<PropertyValue, object>> Parsers =
new Dictionary<Type, Func<PropertyValue, object>>
{
{ typeof(string), p => p.ToString() },
{ typeof(bool), p => p.ToBoolean() },
{ typeof(bool?), p => p.ToNullableBoolean() },
{ typeof(float), p => p.ToSingle() },
{ typeof(float?), p => p.ToNullableSingle() },
{ typeof(double), p => p.ToDouble() },
{ typeof(double?), p => p.ToNullableDouble() },
{ typeof(int), p => p.ToInt32() },
{ typeof(int?), p => p.ToNullableInt32() },
{ typeof(long), p => p.ToInt64() },
{ typeof(long?), p => p.ToNullableInt64() },
{ typeof(Instant), p => p.ToInstant() },
{ typeof(Instant?), p => p.ToNullableInstant() },
{ typeof(Guid), p => p.ToGuid() },
{ typeof(Guid?), p => p.ToNullableGuid() }
};
public object RawValue
{
get { return rawValue; }
}
internal PropertyValue(object rawValue)
{
if (rawValue != null && !Parsers.ContainsKey(rawValue.GetType()))
{
throw new InvalidOperationException($"The type '{rawValue.GetType()}' is not supported.");
}
this.rawValue = rawValue;
}
public override bool TryConvert(ConvertBinder binder, out object result)
{
result = null;
if (!Parsers.TryGetValue(binder.Type, out var parser))
{
return false;
}
result = parser(this);
return true;
}
public override string ToString()
{
return rawValue?.ToString();
}
public bool ToBoolean()
{
return ToOrParseValue(CultureInfo.InvariantCulture, ParseBoolean);
}
public bool? ToNullableBoolean()
{
return ToNullableOrParseValue(CultureInfo.InvariantCulture, ParseBoolean);
}
public float ToSingle()
{
return ToOrParseValue(CultureInfo.InvariantCulture, x => float.Parse(x, CultureInfo.InvariantCulture));
}
public float? ToNullableSingle()
{
return ToNullableOrParseValue(CultureInfo.InvariantCulture, x => float.Parse(x, CultureInfo.InvariantCulture));
}
public double ToDouble()
{
return ToOrParseValue(CultureInfo.InvariantCulture, x => double.Parse(x, CultureInfo.InvariantCulture));
}
public double? ToNullableDouble()
{
return ToNullableOrParseValue(CultureInfo.InvariantCulture, x => double.Parse(x, CultureInfo.InvariantCulture));
}
public int ToInt32()
{
return ToOrParseValue(CultureInfo.InvariantCulture, x => int.Parse(x, CultureInfo.InvariantCulture));
}
public int? ToNullableInt32()
{
return ToNullableOrParseValue(CultureInfo.InvariantCulture, x => int.Parse(x, CultureInfo.InvariantCulture));
}
public long ToInt64()
{
return ToOrParseValue(CultureInfo.InvariantCulture, x => long.Parse(x, CultureInfo.InvariantCulture));
}
public long? ToNullableInt64()
{
return ToNullableOrParseValue(CultureInfo.InvariantCulture, x => long.Parse(x, CultureInfo.InvariantCulture));
}
public Instant ToInstant()
{
return ToOrParseValue(CultureInfo.InvariantCulture, x => InstantPattern.General.Parse(x).Value);
}
public Instant? ToNullableInstant()
{
return ToNullableOrParseValue(CultureInfo.InvariantCulture, x => InstantPattern.General.Parse(x).Value);
}
public Guid ToGuid()
{
return ToOrParseValue(CultureInfo.InvariantCulture, Guid.Parse);
}
public Guid? ToNullableGuid()
{
return ToNullableOrParseValue(CultureInfo.InvariantCulture, Guid.Parse);
}
private T? ToNullableOrParseValue<T>(IFormatProvider culture, Func<string, T> parser) where T : struct
{
return TryParse(culture, parser, out var result) ? result : (T?)null;
}
private T ToOrParseValue<T>(IFormatProvider culture, Func<string, T> parser)
{
return TryParse(culture, parser, out var result) ? result : default(T);
}
private bool TryParse<T>(IFormatProvider culture, Func<string, T> parser, out T result)
{
var value = rawValue;
if (value != null)
{
var valueType = value.GetType();
if (valueType == typeof(T))
{
result = (T)value;
}
else if (valueType == typeof(string))
{
result = Parse(parser, valueType, value);
}
else
{
result = Convert<T>(culture, value, valueType);
}
return true;
}
result = default(T);
return false;
}
private static T Convert<T>(IFormatProvider culture, object value, Type valueType)
{
var requestedType = typeof(T);
try
{
return (T)System.Convert.ChangeType(value, requestedType, culture);
}
catch (OverflowException)
{
var message = $"The property has type '{valueType}' and cannot be casted to '{requestedType}' because it is either too small or large.";
throw new InvalidCastException(message);
}
catch (InvalidCastException)
{
var message = $"The property has type '{valueType}' and cannot be casted to '{requestedType}'.";
throw new InvalidCastException(message);
}
}
private static T Parse<T>(Func<string, T> parser, Type valueType, object value)
{
var requestedType = typeof(T);
try
{
return parser(value.ToString());
}
catch (Exception ex)
{
var message = $"The property has type '{valueType}' and cannot be casted to '{requestedType}'.";
throw new InvalidCastException(message, ex);
}
}
private static bool ParseBoolean(string value)
{
switch (value)
{
case "1":
return true;
case "0":
return false;
default:
return bool.Parse(value);
}
}
}
}

3
src/Squidex/Config/Domain/EventStoreServices.cs

@ -14,6 +14,7 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.Diagnostics;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.EventSourcing.Grains;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.States;
namespace Squidex.Config.Domain
@ -50,7 +51,7 @@ namespace Squidex.Config.Domain
services.AddSingletonAs(c => new GetEventStoreHealthCheck(connection))
.As<IHealthCheck>();
services.AddSingletonAs(c => new GetEventStore(connection, eventStorePrefix, eventStoreProjectionHost))
services.AddSingletonAs(c => new GetEventStore(connection, c.GetRequiredService<IJsonSerializer>(), eventStorePrefix, eventStoreProjectionHost))
.As<IEventStore>();
}
});

2
src/Squidex/Config/Domain/SerializationServices.cs

@ -56,8 +56,6 @@ namespace Squidex.Config.Domain
new NamedGuidIdConverter(),
new NamedLongIdConverter(),
new NamedStringIdConverter(),
new PropertiesBagConverter<EnvelopeHeaders>(),
new PropertiesBagConverter<PropertiesBag>(),
new RefTokenConverter(),
new RolesConverter(),
new RuleConverter(),

2
tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs

@ -47,8 +47,6 @@ namespace Squidex.Domain.Apps.Core
new NamedGuidIdConverter(),
new NamedLongIdConverter(),
new NamedStringIdConverter(),
new PropertiesBagConverter<EnvelopeHeaders>(),
new PropertiesBagConverter<PropertiesBag>(),
new RefTokenConverter(),
new RolesConverter(),
new RuleConverter(),

12
tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs

@ -82,9 +82,9 @@ namespace Squidex.Domain.Apps.Entities.Backup
var envelope = Envelope.Create<IEvent>(@event);
envelope.Headers.Set(RandomGuid().ToString(), i);
envelope.Headers.Set("Id", RandomGuid());
envelope.Headers.Set("Index", i);
envelope.Headers.Add(RandomGuid().ToString(), i);
envelope.Headers.Add("Id", RandomGuid());
envelope.Headers.Add("Index", i);
sourceEvents.Add(($"My-{RandomGuid()}", envelope));
}
@ -96,7 +96,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
var eventData = formatter.ToEventData(@event.Event, Guid.NewGuid(), true);
var eventStored = new StoredEvent("S", "1", 2, eventData);
var index = @event.Event.Headers["Index"].ToInt32();
var index = int.Parse(@event.Event.Headers["Index"].ToString());
if (index % 17 == 0)
{
@ -124,7 +124,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
await reader.ReadEventsAsync(streamNameResolver, formatter, async @event =>
{
var index = @event.Event.Headers["Index"].ToInt32();
var index = int.Parse(@event.Event.Headers["Index"].ToString());
if (index % 17 == 0)
{
@ -155,7 +155,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
Assert.Equal(rhs.Payload.GuidRaw, reader.OldGuid(lhs.Payload.GuidRaw));
Assert.Equal(rhs.Payload.GuidNamed.Id, reader.OldGuid(lhs.Payload.GuidNamed.Id));
Assert.Equal(rhs.Headers["Id"].ToGuid(), reader.OldGuid(lhs.Headers["Id"].ToGuid()));
Assert.Equal(rhs.Headers.GetGuid("Id"), reader.OldGuid(lhs.Headers.GetGuid("Id")));
}
}
}

4
tests/Squidex.Infrastructure.Tests/EventSourcing/DefaultEventDataFormatterTests.cs

@ -88,9 +88,9 @@ namespace Squidex.Infrastructure.EventSourcing
Assert.Equal(inputEvent.Payload.MyProperty, outputEvent.Payload.MyProperty);
}
private static void AssertHeaders(PropertiesBag lhs, PropertiesBag rhs)
private static void AssertHeaders(EnvelopeHeaders lhs, EnvelopeHeaders rhs)
{
foreach (var key in lhs.PropertyNames.Concat(rhs.PropertyNames).Distinct())
foreach (var key in lhs.Keys.Concat(rhs.Keys).Distinct())
{
Assert.Equal(lhs[key].ToString(), rhs[key].ToString());
}

10
tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeExtensionsTests.cs

@ -23,7 +23,7 @@ namespace Squidex.Infrastructure.EventSourcing
sut.SetTimestamp(timestamp);
Assert.Equal(timestamp, sut.Headers.Timestamp());
Assert.Equal(timestamp, sut.Headers["Timestamp"].ToInstant());
Assert.Equal(timestamp, sut.Headers.GetInstant("Timestamp"));
}
[Fact]
@ -34,7 +34,7 @@ namespace Squidex.Infrastructure.EventSourcing
sut.SetCommitId(commitId);
Assert.Equal(commitId, sut.Headers.CommitId());
Assert.Equal(commitId, sut.Headers["CommitId"].ToGuid());
Assert.Equal(commitId, sut.Headers.GetGuid("CommitId"));
}
[Fact]
@ -45,7 +45,7 @@ namespace Squidex.Infrastructure.EventSourcing
sut.SetEventId(commitId);
Assert.Equal(commitId, sut.Headers.EventId());
Assert.Equal(commitId, sut.Headers["EventId"].ToGuid());
Assert.Equal(commitId, sut.Headers.GetGuid("EventId"));
}
[Fact]
@ -56,7 +56,7 @@ namespace Squidex.Infrastructure.EventSourcing
sut.SetAggregateId(commitId);
Assert.Equal(commitId, sut.Headers.AggregateId());
Assert.Equal(commitId, sut.Headers["AggregateId"].ToGuid());
Assert.Equal(commitId, sut.Headers.GetGuid("AggregateId"));
}
[Fact]
@ -78,7 +78,7 @@ namespace Squidex.Infrastructure.EventSourcing
sut.SetEventStreamNumber(eventStreamNumber);
Assert.Equal(eventStreamNumber, sut.Headers.EventStreamNumber());
Assert.Equal(eventStreamNumber, sut.Headers["EventStreamNumber"].ToInt64());
Assert.Equal(eventStreamNumber, sut.Headers.GetInt64("EventStreamNumber"));
}
}
}

19
tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeHeaderTests.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System.Linq;
using Squidex.Infrastructure.Json.Objects;
using Xunit;
namespace Squidex.Infrastructure.EventSourcing
@ -17,21 +18,13 @@ namespace Squidex.Infrastructure.EventSourcing
{
var headers = new EnvelopeHeaders();
Assert.Equal(0, headers.Count);
}
[Fact]
public void Should_create_headers_with_null_properties()
{
var headers = new EnvelopeHeaders(null);
Assert.Equal(0, headers.Count);
Assert.Empty(headers);
}
[Fact]
public void Should_create_headers_as_copy()
{
var source = new PropertiesBag().Set("Key1", 123);
var source = new JsonObject().Add("Key1", 123);
var headers = new EnvelopeHeaders(source);
CompareHeaders(headers, source);
@ -40,7 +33,7 @@ namespace Squidex.Infrastructure.EventSourcing
[Fact]
public void Should_clone_headers()
{
var source = new PropertiesBag().Set("Key1", 123);
var source = new JsonObject().Add("Key1", 123);
var headers = new EnvelopeHeaders(source);
var clone = headers.Clone();
@ -48,9 +41,9 @@ namespace Squidex.Infrastructure.EventSourcing
CompareHeaders(headers, clone);
}
private static void CompareHeaders(PropertiesBag lhs, PropertiesBag rhs)
private static void CompareHeaders(JsonObject lhs, JsonObject rhs)
{
foreach (var key in lhs.PropertyNames.Concat(rhs.PropertyNames).Distinct())
foreach (var key in lhs.Keys.Concat(rhs.Keys).Distinct())
{
Assert.Equal(lhs[key].ToString(), rhs[key].ToString());
}

2
tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs

@ -49,7 +49,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
private readonly ISemanticLog log = A.Fake<ISemanticLog>();
private readonly IStore<string> store = A.Fake<IStore<string>>();
private readonly IEventDataFormatter formatter = A.Fake<IEventDataFormatter>();
private readonly EventData eventData = new EventData();
private readonly EventData eventData = new EventData("Type", new EnvelopeHeaders(), "Payload");
private readonly Envelope<IEvent> envelope = new Envelope<IEvent>(new MyEvent());
private readonly EventConsumerGrain sut;
private readonly string consumerName;

4
tests/Squidex.Infrastructure.Tests/EventSourcing/RetrySubscriptionTests.cs

@ -90,7 +90,7 @@ namespace Squidex.Infrastructure.EventSourcing
[Fact]
public async Task Should_forward_event_from_inner_subscription()
{
var ev = new StoredEvent("Stream", "1", 2, new EventData());
var ev = new StoredEvent("Stream", "1", 2, new EventData("Type", new EnvelopeHeaders(), "Payload"));
await OnEventAsync(eventSubscription, ev);
await sut.StopAsync();
@ -102,7 +102,7 @@ namespace Squidex.Infrastructure.EventSourcing
[Fact]
public async Task Should_not_forward_event_when_message_is_from_another_subscription()
{
var ev = new StoredEvent("Stream", "1", 2, new EventData());
var ev = new StoredEvent("Stream", "1", 2, new EventData("Type", new EnvelopeHeaders(), "Payload"));
await OnEventAsync(A.Fake<IEventSubscription>(), ev);
await sut.StopAsync();

38
tests/Squidex.Infrastructure.Tests/MongoDb/BsonConverterTests.cs

@ -117,44 +117,6 @@ namespace Squidex.Infrastructure.MongoDb
private readonly TestObject source = TestObject.CreateWithValues();
private readonly JsonSerializer serializer = JsonSerializer.CreateDefault();
[Fact]
public void Should_serialize_and_deserialize_to_bson_with_json()
{
var target = JObject.FromObject(source).ToBson().ToJson().ToObject<TestObject>();
target.Should().BeEquivalentTo(source);
}
[Fact]
public void Should_serialize_datetime_to_iso()
{
source.DateTime = new DateTime(2012, 12, 12, 12, 12, 12, DateTimeKind.Utc);
var target = JObject.FromObject(source).ToBson();
Assert.Equal("2012-12-12T12:12:12Z", target["DateTime"].ToString());
}
[Fact]
public void Should_serialize_datetimeoffset_to_iso_utc()
{
source.DateTimeOffset = new DateTime(2012, 12, 12, 12, 12, 12, DateTimeKind.Utc);
var target = JObject.FromObject(source).ToBson();
Assert.Equal("2012-12-12T12:12:12Z", target["DateTimeOffset"].ToString());
}
[Fact]
public void Should_serialize_datetimeoffset_to_iso_utc_with_offset()
{
source.DateTimeOffset = new DateTimeOffset(2012, 12, 12, 12, 12, 12, TimeSpan.FromHours(2));
var target = JObject.FromObject(source).ToBson();
Assert.Equal("2012-12-12T12:12:12+02:00", target["DateTimeOffset"].ToString());
}
[Fact]
public void Should_write_problematic_object()
{

428
tests/Squidex.Infrastructure.Tests/PropertiesBagTests.cs

@ -1,428 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright () Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using Microsoft.CSharp.RuntimeBinder;
using NodaTime;
using Squidex.Infrastructure.TestHelpers;
using Xunit;
namespace Squidex.Infrastructure
{
public class PropertiesBagTests
{
private readonly PropertiesBag bag = new PropertiesBag();
private readonly dynamic dynamicBag;
public PropertiesBagTests()
{
dynamicBag = bag;
}
[Fact]
public void Should_serialize_and_deserialize_empty_bag()
{
var output = bag.SerializeAndDeserialize();
Assert.Equal(bag.Count, output.Count);
}
[Fact]
public void Should_serialize_and_deserialize()
{
var time = Instant.FromUnixTimeSeconds(SystemClock.Instance.GetCurrentInstant().ToUnixTimeSeconds());
bag.Set("Key1", time);
bag.Set("Key2", "MyString");
bag.Set("Key3", 123L);
bag.Set("Key4", true);
bag.Set("Key5", Guid.NewGuid());
var output = bag.SerializeAndDeserialize();
foreach (var kvp in output.Properties.Take(4))
{
Assert.Equal(kvp.Value.RawValue, bag[kvp.Key].RawValue);
}
Assert.Equal(bag["Key5"].ToGuid(), output["Key5"].ToGuid());
}
[Fact]
public void Should_return_false_when_renaming_unknown_property()
{
Assert.False(bag.Rename("OldKey", "NewKey"));
}
[Fact]
public void Should_throw_when_renaming_to_existing_property()
{
bag.Set("NewKey", 1);
Assert.Throws<ArgumentException>(() => bag.Rename("OldKey", "NewKey"));
}
[Fact]
public void Should_throw_when_renaming_to_same_key()
{
Assert.Throws<ArgumentException>(() => bag.Rename("SameKey", "SameKey"));
}
[Fact]
public void Should_provide_property_with_new_name_after_rename()
{
bag.Set("OldKey", 123);
Assert.True(bag.Rename("OldKey", "NewKey"));
Assert.True(bag.Contains("NewKey"));
Assert.Equal(1, bag.Count);
Assert.Equal(123, bag["NewKey"].ToInt64());
Assert.False(bag.Contains("OldKey"));
}
[Fact]
public void Should_calculate_count_correctly()
{
bag.Set("Key1", 1);
bag.Set("Key2", 1);
Assert.Equal(2, bag.Count);
}
[Fact]
public void Should_calculate_keys_correctly()
{
bag.Set("Key1", 1);
bag.Set("Key2", 1);
Assert.Equal(new[] { "Key1", "Key2" }, bag.PropertyNames.ToArray());
Assert.Equal(new[] { "Key1", "Key2" }, bag.Properties.Keys.ToArray());
Assert.Equal(new[] { "Key1", "Key2" }, bag.GetDynamicMemberNames().ToArray());
}
[Fact]
public void Should_return_correct_value_when_contains_check()
{
Assert.False(bag.Contains("Key"));
bag.Set("Key", 1);
Assert.True(bag.Contains("Key"));
Assert.True(bag.Contains("KEY"));
}
[Fact]
public void Should_returne_false_when_property_to_rename_does_not_exist()
{
Assert.False(bag.Remove("NOTFOUND"));
}
[Fact]
public void Should_ignore_casing_when_returning()
{
bag.Set("Key", 1);
Assert.True(bag.Remove("KEY"));
Assert.False(bag.Contains("KEY"));
}
[Fact]
public void Should_set_value_as_dynamic()
{
dynamicBag.Key = 456;
Assert.Equal(456, (int)dynamicBag.Key);
}
[Fact]
public void Should_throw_when_setting_value_with_invalid_type_dynamically()
{
Assert.Throws<InvalidOperationException>(() => dynamicBag.Key = (byte)123);
}
[Fact]
public void Should_throw_when_setting_value_with_invalid_type()
{
Assert.Throws<InvalidOperationException>(() => bag.Set("Key", (byte)1));
}
[Fact]
public void Should_return_false_when_making_contains_check()
{
Assert.False(dynamicBag.Contains("Key"));
}
[Fact]
public void Should_provide_default_value_if_not_exists()
{
Assert.Equal(0, (int)dynamicBag.Key);
}
[Fact]
public void Should_throw_when_parsing_failed()
{
bag.Set("Key", "abc");
Assert.Throws<InvalidCastException>(() => bag["Key"].ToInt64());
}
[Fact]
public void Should_return_false_when_converter_does_not_exist()
{
bag.Set("Key", "abc");
Assert.Throws<RuntimeBinderException>(() => (TimeSpan)dynamicBag.Key);
}
[Fact]
public void Should_convert_string_to_numbers()
{
bag.Set("Key", 123);
AssertNumber();
}
[Fact]
public void Should_convert_int_to_numbers()
{
bag.Set("Key", 123);
AssertNumber();
}
[Fact]
public void Should_convert_long_to_numbers()
{
bag.Set("Key", 123L);
AssertNumber();
}
[Fact]
public void Should_throw_when_casting_from_large_long()
{
bag.Set("Key", long.MaxValue);
Assert.Throws<InvalidCastException>(() => bag["Key"].ToInt32());
}
[Fact]
public void Should_convert_float_to_number()
{
bag.Set("Key", 123f);
AssertNumber();
}
[Fact]
public void Should_convert_double_to_number()
{
bag.Set("Key", 123d);
AssertNumber();
}
[Fact]
public void Should_throw_when_casting_from_large_doule()
{
bag.Set("Key", double.MaxValue);
Assert.Equal(float.PositiveInfinity, bag["Key"].ToSingle());
}
[Fact]
public void Should_convert_from_instant_value()
{
var time = SystemClock.Instance.GetCurrentInstant();
bag.Set("Key", time);
AssertInstant(time);
}
[Fact]
public void Should_convert_from_instant_string()
{
var time = Instant.FromUnixTimeSeconds(SystemClock.Instance.GetCurrentInstant().ToUnixTimeSeconds());
bag.Set("Key", time.ToString());
AssertInstant(time);
}
[Fact]
public void Should_convert_from_guid_value()
{
var id = Guid.NewGuid();
bag.Set("Key", id);
AssertGuid(id);
}
[Fact]
public void Should_convert_from_guid_string()
{
var id = Guid.NewGuid();
bag.Set("Key", id.ToString());
AssertGuid(id);
}
[Fact]
public void Should_convert_from_boolean_value()
{
bag.Set("Key", true);
AssertBoolean(true);
}
[Fact]
public void Should_convert_from_boolean_string()
{
bag.Set("Key", "true");
AssertBoolean(true);
}
[Fact]
public void Should_convert_boolean_from_number()
{
bag.Set("Key", 1);
AssertBoolean(true);
}
[Fact]
public void Should_convert_boolean_to_truthy_number_string()
{
bag.Set("Key", "1");
AssertBoolean(true);
}
[Fact]
public void Should_convert_boolean_to_falsy_number_string()
{
bag.Set("Key", "0");
AssertBoolean(false);
}
[Fact]
public void Should_provide_value_as_string()
{
bag.Set("Key", "Foo");
AssertString("Foo");
}
[Fact]
public void Should_provide_null()
{
bag.Set("Key", null);
AssertNull();
}
[Fact]
public void Should_throw_when_converting_instant_to_number()
{
bag.Set("Key", SystemClock.Instance.GetCurrentInstant());
Assert.Throws<InvalidCastException>(() => bag["Key"].ToGuid());
}
private void AssertNumber()
{
AssertInt32(123);
AssertInt64(123);
AssertSingle(123);
AssertDouble(123);
}
private void AssertString(string expected)
{
Assert.Equal(expected, bag["Key"].ToString());
Assert.Equal(expected, (string)dynamicBag.Key);
}
private void AssertNull()
{
Assert.Null(bag["Key"].ToString());
Assert.Null(bag["Key"].RawValue);
}
private void AssertBoolean(bool expected)
{
Assert.Equal(expected, bag["Key"].ToBoolean());
Assert.Equal(expected, bag["Key"].ToNullableBoolean());
Assert.Equal(expected, (bool)dynamicBag.Key);
Assert.Equal(expected, (bool?)dynamicBag.Key);
}
private void AssertInstant(Instant expected)
{
Assert.Equal(expected, bag["Key"].ToInstant());
Assert.Equal(expected, bag["Key"].ToNullableInstant().Value);
Assert.Equal(expected, (Instant)dynamicBag.Key);
Assert.Equal(expected, (Instant?)dynamicBag.Key);
}
private void AssertGuid(Guid expected)
{
Assert.Equal(expected, bag["Key"].ToGuid());
Assert.Equal(expected, bag["Key"].ToNullableGuid());
Assert.Equal(expected, (Guid)dynamicBag.Key);
Assert.Equal(expected, (Guid?)dynamicBag.Key);
}
private void AssertDouble(double expected)
{
Assert.Equal(expected, bag["Key"].ToDouble());
Assert.Equal(expected, bag["Key"].ToNullableDouble());
Assert.Equal(expected, (double)dynamicBag.Key);
Assert.Equal(expected, (double?)dynamicBag.Key);
}
private void AssertSingle(float expected)
{
Assert.Equal(expected, bag["Key"].ToSingle());
Assert.Equal(expected, bag["Key"].ToNullableSingle());
Assert.Equal(expected, (float)dynamicBag.Key);
Assert.Equal(expected, (float?)dynamicBag.Key);
}
private void AssertInt32(long expected)
{
Assert.Equal(expected, bag["Key"].ToInt64());
Assert.Equal(expected, bag["Key"].ToNullableInt64());
Assert.Equal(expected, (long)dynamicBag.Key);
Assert.Equal(expected, (long?)dynamicBag.Key);
}
private void AssertInt64(int expected)
{
Assert.Equal(expected, bag["Key"].ToInt64());
Assert.Equal(expected, bag["Key"].ToNullableInt64());
Assert.Equal(expected, (int)dynamicBag.Key);
Assert.Equal(expected, (int?)dynamicBag.Key);
}
}
}

4
tests/Squidex.Infrastructure.Tests/States/PersistenceEventSourcingTests.cs

@ -56,7 +56,7 @@ namespace Squidex.Infrastructure.States
[Fact]
public async Task Should_ignore_old_events()
{
var storedEvent = new StoredEvent("1", "1", 0, new EventData());
var storedEvent = new StoredEvent("1", "1", 0, new EventData("Type", new EnvelopeHeaders(), "Payload"));
A.CallTo(() => eventStore.QueryAsync(key, 0))
.Returns(new List<StoredEvent> { storedEvent });
@ -251,7 +251,7 @@ namespace Squidex.Infrastructure.States
foreach (var @event in events)
{
var eventData = new EventData();
var eventData = new EventData("Type", new EnvelopeHeaders(), "Payload");
var eventStored = new StoredEvent(i.ToString(), i.ToString(), i, eventData);
eventsStored.Add(eventStored);

2
tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs

@ -32,8 +32,6 @@ namespace Squidex.Infrastructure.TestHelpers
new NamedGuidIdConverter(),
new NamedLongIdConverter(),
new NamedStringIdConverter(),
new PropertiesBagConverter<EnvelopeHeaders>(),
new PropertiesBagConverter<PropertiesBag>(),
new RefTokenConverter(),
new StringEnumConverter()),

2
tools/Migrate_01/Rebuilder.cs

@ -108,7 +108,7 @@ namespace Migrate_01
await eventStore.QueryAsync(async storedEvent =>
{
var headers = serializer.Deserialize<EnvelopeHeaders>(storedEvent.Data.Metadata);
var headers = storedEvent.Data.Headers;
var id = headers.AggregateId();

Loading…
Cancel
Save