Skip to content

Commit

Permalink
Switch to use serializer in containermock (#82)
Browse files Browse the repository at this point in the history
* Switch to use serializer in ContainerMock

* Verify the serialization options match in query
  • Loading branch information
PeterStephenson authored Apr 18, 2023
1 parent 07de964 commit 486b73f
Show file tree
Hide file tree
Showing 21 changed files with 528 additions and 268 deletions.
57 changes: 25 additions & 32 deletions src/LogOtter.CosmosDb.ContainerMock/ContainerMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using LogOtter.CosmosDb.ContainerMock.TransactionalBatch;
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.Cosmos.Scripts;
using Newtonsoft.Json;

#pragma warning disable CS1998

Expand All @@ -19,6 +18,9 @@ public class ContainerMock : Container

private readonly string _partitionKeyPath;

private readonly StringSerializationHelper _serializationHelper;
private CosmosSerializationOptions _cosmosSerializationOptions;

public override string Id { get; }
public override Conflicts? Conflicts => null;
public override Scripts? Scripts => null;
Expand All @@ -28,10 +30,13 @@ public ContainerMock(
string partitionKeyPath = "/partitionKey",
UniqueKeyPolicy? uniqueKeyPolicy = null,
string containerName = "TestContainer",
int defaultDocumentTimeToLive = -1
int defaultDocumentTimeToLive = -1,
CosmosSerializationOptions? cosmosSerializationOptions = null
)
{
_containerData = new ContainerData(uniqueKeyPolicy, defaultDocumentTimeToLive);
_cosmosSerializationOptions = cosmosSerializationOptions ?? new CosmosSerializationOptions();
_serializationHelper = new StringSerializationHelper(_cosmosSerializationOptions);
_containerData = new ContainerData(uniqueKeyPolicy, defaultDocumentTimeToLive, _serializationHelper);
_exceptionsToThrow = new ConcurrentQueue<(CosmosException, Func<InvocationInformation, bool> condition)>();

_partitionKeyPath = partitionKeyPath;
Expand Down Expand Up @@ -67,7 +72,7 @@ public override async Task<ResponseMessage> CreateItemStreamAsync(

streamPayload.Position = 0;
var streamReader = new StreamReader(streamPayload);
var json = await streamReader.ReadToEndAsync();
var json = await streamReader.ReadToEndAsync(cancellationToken);

if (JsonHelpers.GetIdFromJson(json) == string.Empty)
{
Expand Down Expand Up @@ -106,7 +111,7 @@ public override async Task<ItemResponse<T>> CreateItemAsync<T>(
);
}

var json = JsonConvert.SerializeObject(item);
var json = _serializationHelper.SerializeObject(item);

if (JsonHelpers.GetIdFromJson(json) == string.Empty)
{
Expand Down Expand Up @@ -207,7 +212,7 @@ public override async Task<ResponseMessage> UpsertItemStreamAsync(

streamPayload.Position = 0;
var streamReader = new StreamReader(streamPayload);
var json = await streamReader.ReadToEndAsync();
var json = await streamReader.ReadToEndAsync(cancellationToken);

try
{
Expand All @@ -229,7 +234,7 @@ public override async Task<ItemResponse<T>> UpsertItemAsync<T>(
{
ThrowNextExceptionIfPresent(new InvocationInformation(nameof(UpsertItemAsync)));

var json = JsonConvert.SerializeObject(item);
var json = _serializationHelper.SerializeObject(item);

try
{
Expand All @@ -252,7 +257,7 @@ public override async Task<ItemResponse<T>> ReplaceItemAsync<T>(
{
ThrowNextExceptionIfPresent(new InvocationInformation(nameof(ReplaceItemAsync)));

var json = JsonConvert.SerializeObject(item);
var json = _serializationHelper.SerializeObject(item);

try
{
Expand Down Expand Up @@ -317,37 +322,19 @@ public override IOrderedQueryable<T> GetItemLinqQueryable<T>(

var items = _containerData.GetItemsInPartition(requestOptions?.PartitionKey);

var itemLinqQueryable = new CosmosQueryableMock<T>(items.OrderBy(i => i.Id).Select(i => i.Deserialize<T>()).AsQueryable());

return itemLinqQueryable;
}

public async IAsyncEnumerable<TResult> QueryAsync<TModel, TResult>(string? partitionKey, Func<IQueryable<TModel>, IQueryable<TResult>> applyQuery)
{
if (applyQuery == null)
var asQueryable = items.OrderBy(i => i.Id).Select(i => i.Deserialize<T>()).AsQueryable();
if (SerializationSettingsDoNotMatch(linqSerializerOptions))
{
throw new ArgumentNullException(nameof(applyQuery));
throw new ArgumentException("Linq serialization options do not match the base serializer options", nameof(linqSerializerOptions));
}

ThrowNextExceptionIfPresent(new InvocationInformation(nameof(QueryAsync)));

var partition = partitionKey == null ? (PartitionKey?)null : new PartitionKey(partitionKey);

var items = _containerData.GetItemsInPartition(partition);

var itemLinqQueryable = new CosmosQueryableMock<TModel>(items.OrderBy(i => i.Id).Select(i => i.Deserialize<TModel>()).AsQueryable());

var results = applyQuery(itemLinqQueryable).ToList();

foreach (var result in results)
{
yield return result;
}
var itemLinqQueryable = new CosmosQueryableMock<T>(asQueryable);
return itemLinqQueryable;
}

public override Microsoft.Azure.Cosmos.TransactionalBatch CreateTransactionalBatch(PartitionKey partitionKey)
{
return new TestTransactionalBatch(partitionKey, this);
return new TestTransactionalBatch(partitionKey, this, _serializationHelper);
}

public void Reset()
Expand Down Expand Up @@ -398,6 +385,12 @@ private static ResponseMessage ToCosmosResponseMessage(Response response, Stream
return responseMessage;
}

private bool SerializationSettingsDoNotMatch(CosmosLinqSerializerOptions? linqSerializerOptions)
{
linqSerializerOptions ??= new CosmosLinqSerializerOptions();
return linqSerializerOptions.PropertyNamingPolicy != _cosmosSerializationOptions.PropertyNamingPolicy;
}

private static ItemResponse<T> ToCosmosItemResponse<T>(Response response)
{
return new MockItemResponse<T>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,19 @@ internal class ContainerData
private readonly ConcurrentDictionary<PartitionKey, ConcurrentDictionary<string, ContainerItem>> _data;
private readonly int _defaultDocumentTimeToLive;
private readonly UniqueKeyPolicy? _uniqueKeyPolicy;
private readonly StringSerializationHelper _serializationHelper;

private readonly SemaphoreSlim _updateSemaphore = new(1, 1);

private int _currentTimer;

public ContainerData(UniqueKeyPolicy? uniqueKeyPolicy, int defaultDocumentTimeToLive)
public ContainerData(UniqueKeyPolicy? uniqueKeyPolicy, int defaultDocumentTimeToLive, StringSerializationHelper serializationHelper)
{
GuardAgainstInvalidUniqueKeyPolicy(uniqueKeyPolicy);

_uniqueKeyPolicy = uniqueKeyPolicy;
_defaultDocumentTimeToLive = defaultDocumentTimeToLive;
_serializationHelper = serializationHelper;

_data = new ConcurrentDictionary<PartitionKey, ConcurrentDictionary<string, ContainerItem>>();
_currentTimer = 0;
Expand Down Expand Up @@ -112,11 +114,11 @@ public Task<Response> UpsertItem(string json, PartitionKey partitionKey, ItemReq
throw new ETagMismatchException();
}

var newItem = new ContainerItem(id, json, partitionKey, GetExpiryTime(ttl, _currentTimer));
var newItem = new ContainerItem(id, json, partitionKey, GetExpiryTime(ttl, _currentTimer), _serializationHelper);

partition[id] = newItem;

DataChanged?.Invoke(this, new DataChangedEventArgs(isUpdate ? Operation.Updated : Operation.Created, newItem.Json));
DataChanged?.Invoke(this, new DataChangedEventArgs(isUpdate ? Operation.Updated : Operation.Created, newItem.Json, _serializationHelper));

return Task.FromResult(new Response(newItem, isUpdate));
}
Expand Down Expand Up @@ -165,7 +167,7 @@ public ContainerItem RemoveItem(string id, PartitionKey partitionKey, ItemReques

if (removedItem != null)
{
DataChanged?.Invoke(this, new DataChangedEventArgs(Operation.Deleted, removedItem.Json));
DataChanged?.Invoke(this, new DataChangedEventArgs(Operation.Deleted, removedItem.Json, _serializationHelper));
}

return removedItem;
Expand Down Expand Up @@ -358,7 +360,8 @@ public void RestoreSnapshot(ContainerDataSnapshot snapshot)
item.Value<int?>("ExpiryTime"),
item.Value<string>("ETag"),
item.Value<bool>("RequireETagOnNextUpdate"),
item.Value<bool>("HasScheduledETagMismatch")
item.Value<bool>("HasScheduledETagMismatch"),
_serializationHelper
);

partitionKeyDictionary[containerItem.Id] = containerItem;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Microsoft.Azure.Cosmos;
using Newtonsoft.Json;

namespace LogOtter.CosmosDb.ContainerMock.ContainerMockData;

Expand All @@ -14,10 +13,13 @@ internal class ContainerItem
public bool RequireETagOnNextUpdate { get; private set; }
public bool HasScheduledETagMismatch { get; private set; }

public ContainerItem(string id, string json, PartitionKey partitionKey, int? expiryTime)
private readonly StringSerializationHelper _serializationHelper;

public ContainerItem(string id, string json, PartitionKey partitionKey, int? expiryTime, StringSerializationHelper serializationHelper)
{
Id = id;
ExpiryTime = expiryTime;
_serializationHelper = serializationHelper;
Json = json;
PartitionKey = partitionKey;
ETag = GenerateETag();
Expand All @@ -30,11 +32,13 @@ internal ContainerItem(
int? expiryTime,
string eTag,
bool requireETagOnNextUpdate,
bool hasScheduledETagMismatch
bool hasScheduledETagMismatch,
StringSerializationHelper serializationHelper
)
{
Id = id;
ExpiryTime = expiryTime;
_serializationHelper = serializationHelper;
Json = json;
PartitionKey = partitionKey;
ETag = eTag;
Expand All @@ -44,7 +48,7 @@ bool hasScheduledETagMismatch

public T Deserialize<T>()
{
return JsonConvert.DeserializeObject<T>(Json);
return _serializationHelper.DeserializeObject<T>(Json);
}

public void ScheduleMismatchETagOnNextUpdate()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ namespace LogOtter.CosmosDb.ContainerMock.ContainerMockData;

public class DataChangedEventArgs : EventArgs
{
private readonly StringSerializationHelper _serializationHelper;

public string Json { get; }

public Operation Operation { get; set; }

internal DataChangedEventArgs(Operation operation, string json)
internal DataChangedEventArgs(Operation operation, string json, StringSerializationHelper serializationHelper)
{
Operation = operation;
Json = json;
_serializationHelper = serializationHelper;
}

public T Deserialize<T>()
{
return JsonConvert.DeserializeObject<T>(Json);
return _serializationHelper.DeserializeObject<T>(Json);
}
}

Expand Down
Loading

0 comments on commit 486b73f

Please sign in to comment.