Skip to content

Commit

Permalink
Merge pull request #606 from Particular/emtpy-lists-2.0
Browse files Browse the repository at this point in the history
Add support for empty collections in saga data (release-2.0)
  • Loading branch information
DavidBoike committed May 3, 2024
2 parents 9ff47d8 + 4f0d845 commit 4c392b5
Show file tree
Hide file tree
Showing 15 changed files with 791 additions and 29 deletions.
Expand Up @@ -12,7 +12,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.301.23" />
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.302.25" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="NServiceBus.AcceptanceTests.Sources" Version="9.0.0" GeneratePathProperty="true" />
Expand Down
Expand Up @@ -11,7 +11,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.301.23" />
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.302.25" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="NServiceBus.PersistenceTests.Sources" Version="9.0.0" />
Expand Down
@@ -0,0 +1,190 @@
namespace NServiceBus.PersistenceTesting;

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using Sagas;

public class When_saving_saga_with_empty_list : SagaPersisterTests
{
[Test]
public async Task ShouldSave()
{
var sagaData = new EmptyCollectionsSagaData { SomeId = Guid.NewGuid().ToString() };
await SaveSaga(sagaData);

var read = await GetById<EmptyCollectionsSagaData>(sagaData.Id);
Assert.That(read, Is.Not.Null);
Assert.That(read.SomeId, Is.EqualTo(sagaData.SomeId));

AssertEverythingEmpty(read);
}

[Test]
public async Task ShouldUpdate()
{
var memStreams = Enumerable.Range(0, 5)
.Select(i => new MemoryStream(Encoding.UTF8.GetBytes($"Hello world {i}")))
.ToArray();

var sagaData = new EmptyCollectionsSagaData
{
SomeId = Guid.NewGuid().ToString(),
StringList = ["a", "b"],
StringArray = ["c", "d"],
RecordList = [new("e", 1.2), new("f", 3.4)],
RecordArray = [new("g", 5.6), new("h", 7.8)],
SimpleDict = new Dictionary<string, int>
{
["i"] = 9,
["j"] = 10,
},
Ints = [11, 12],
Doubles = [13.4, 15.6],
Floats = [1.234f, 5.678f],
Bytes = [0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64],
Shorts = [1, 2, 3, 4],
UShorts = [1, 2, 3, 4],
Longs = [3147483647, 4147483647, 5147483647],
ULongs = [3147483647, 4147483647, 5147483647, 18446744073709551615],
UInts = [2147483647, 4294967295],
SBytes = [0x0F, 0x10],
Decimals = [1.234m, 5.678m],
HashSetOfMemoryStreams = new HashSet<MemoryStream>(memStreams),
ImmutableHashSetOfStreams = new HashSet<MemoryStream>(memStreams).ToImmutableHashSet(),
HashSetOfString = ["a", "b", "c", "d"],
SortedSetOfString = ["a", "b", "c", "d"],
ImmutableHashSetOfString = ["a", "b", "c", "d"],
ImmutableSortedSetOfString = ["a", "b", "c", "d"],

};
await SaveSaga(sagaData);

var context = configuration.GetContextBagForSagaStorage();
using (var session = configuration.CreateStorageSession())
{
await session.Open(context);
var read = await configuration.SagaStorage.Get<EmptyCollectionsSagaData>("SomeId", sagaData.SomeId, session, context);

Assert.That(read, Is.Not.Null);
Assert.That(read.SomeId, Is.EqualTo(sagaData.SomeId));

read.StringList = [];
read.StringArray = [];
read.RecordList = [];
read.RecordArray = [];
read.SimpleDict = [];
read.Ints = [];
read.Doubles = [];
read.Floats = [];
read.Bytes = [];
read.Shorts = [];
read.UShorts = [];
read.Longs = [];
read.ULongs = [];
read.UInts = [];
read.SBytes = [];
read.Decimals = [];
read.HashSetOfMemoryStreams = [];
read.ImmutableHashSetOfStreams = [];
read.HashSetOfString = [];
read.SortedSetOfString = [];
read.ImmutableHashSetOfString = [];
read.ImmutableSortedSetOfString = [];

await configuration.SagaStorage.Update(read, session, context);
await session.CompleteAsync();
}

var read2 = await GetById<EmptyCollectionsSagaData>(sagaData.Id);
Assert.That(read2, Is.Not.Null);
Assert.That(read2.SomeId, Is.EqualTo(sagaData.SomeId));

AssertEverythingEmpty(read2);
}

void AssertEverythingEmpty(EmptyCollectionsSagaData data) => Assert.Multiple(() =>
{
Assert.That(data.StringList, Is.Empty);
Assert.That(data.StringArray, Is.Empty);
Assert.That(data.RecordList, Is.Empty);
Assert.That(data.RecordArray, Is.Empty);
Assert.That(data.SimpleDict, Is.Empty);
Assert.That(data.Ints, Is.Empty);
Assert.That(data.Doubles, Is.Empty);
Assert.That(data.Floats, Is.Empty);
Assert.That(data.Bytes, Is.Empty);
Assert.That(data.Shorts, Is.Empty);
Assert.That(data.UShorts, Is.Empty);
Assert.That(data.Longs, Is.Empty);
Assert.That(data.ULongs, Is.Empty);
Assert.That(data.UInts, Is.Empty);
Assert.That(data.SBytes, Is.Empty);
Assert.That(data.Decimals, Is.Empty);
Assert.That(data.HashSetOfMemoryStreams, Is.Empty);
Assert.That(data.ImmutableHashSetOfStreams, Is.Empty);
Assert.That(data.HashSetOfString, Is.Empty);
Assert.That(data.SortedSetOfString, Is.Empty);
Assert.That(data.ImmutableHashSetOfString, Is.Empty);
Assert.That(data.ImmutableSortedSetOfString, Is.Empty);
});

// Even though not used, need this class so saga data will get picked up by mapper
public class EmptyCollectionsSaga : Saga<EmptyCollectionsSagaData>,
IAmStartedByMessages<TestMessage>
{
public Task Handle(TestMessage message, IMessageHandlerContext context) => throw new NotImplementedException();

protected override void ConfigureHowToFindSaga(SagaPropertyMapper<EmptyCollectionsSagaData> mapper)
{
mapper.MapSaga(s => s.SomeId)
.ToMessage<TestMessage>(m => m.SomeId);
}
}

public class EmptyCollectionsSagaData : ContainSagaData
{
public string SomeId { get; set; } = "Test";

public List<string> StringList { get; set; } = [];
public string[] StringArray { get; set; } = [];
public List<SimpleType> RecordList { get; set; } = [];
public SimpleType[] RecordArray { get; set; } = [];
public Dictionary<string, int> SimpleDict { get; set; } = [];
public HashSet<int> Ints { get; set; } = [];
public SortedSet<double> Doubles { get; set; } = [];
public ImmutableHashSet<float> Floats { get; set; } = [];
public ImmutableSortedSet<byte> Bytes { get; set; } = [];
public HashSet<short> Shorts { get; set; } = [];
public SortedSet<ushort> UShorts { get; set; } = [];
public ImmutableHashSet<long> Longs { get; set; } = [];
public ImmutableSortedSet<ulong> ULongs { get; set; } = [];
public HashSet<uint> UInts { get; set; } = [];
public SortedSet<sbyte> SBytes { get; set; } = [];
public ImmutableHashSet<decimal> Decimals { get; set; } = [];
#pragma warning disable PS0025 // It is a test
public HashSet<MemoryStream> HashSetOfMemoryStreams { get; set; } = [];
public ImmutableHashSet<MemoryStream> ImmutableHashSetOfStreams { get; set; } = [];
#pragma warning restore PS0025
public HashSet<string> HashSetOfString { get; set; } = [];
public SortedSet<string> SortedSetOfString { get; set; } = [];
public ImmutableHashSet<string> ImmutableHashSetOfString { get; set; } = [];
public ImmutableSortedSet<string> ImmutableSortedSetOfString { get; set; } = [];
}

public record SimpleType(string Id, double Value);

public class TestMessage : ICommand
{
public string SomeId { get; set; }
}

public When_saving_saga_with_empty_list(TestVariant param) : base(param)
{
}
}
Expand Up @@ -11,7 +11,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.301.23" />
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.302.25" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="NServiceBus.AcceptanceTests.Sources" Version="9.0.0" GeneratePathProperty="true" />
Expand Down
Expand Up @@ -14,105 +14,126 @@
"BOOL": false,
"IsBOOLSet": false,
"BS": [],
"IsBSSet": false,
"L": [],
"IsLSet": false,
"M": {},
"IsMSet": false,
"N": null,
"NS": [],
"IsNSSet": false,
"NULL": false,
"S": "OUTBOX#SchemaVersionTest#FFC8A2FD-0335-47C8-A29D-9EEA6C8445D8",
"SS": []
"SS": [],
"IsSSSet": false
},
"SK": {
"B": null,
"BOOL": false,
"IsBOOLSet": false,
"BS": [],
"IsBSSet": false,
"L": [],
"IsLSet": false,
"M": {},
"IsMSet": false,
"N": null,
"NS": [],
"IsNSSet": false,
"NULL": false,
"S": "OUTBOX#METADATA#FFC8A2FD-0335-47C8-A29D-9EEA6C8445D8",
"SS": []
"SS": [],
"IsSSSet": false
},
"OperationsCount": {
"B": null,
"BOOL": false,
"IsBOOLSet": false,
"BS": [],
"IsBSSet": false,
"L": [],
"IsLSet": false,
"M": {},
"IsMSet": false,
"N": "0",
"NS": [],
"IsNSSet": false,
"NULL": false,
"S": null,
"SS": []
"SS": [],
"IsSSSet": false
},
"Dispatched": {
"B": null,
"BOOL": false,
"IsBOOLSet": true,
"BS": [],
"IsBSSet": false,
"L": [],
"IsLSet": false,
"M": {},
"IsMSet": false,
"N": null,
"NS": [],
"IsNSSet": false,
"NULL": false,
"S": null,
"SS": []
"SS": [],
"IsSSSet": false
},
"DispatchedAt": {
"B": null,
"BOOL": false,
"IsBOOLSet": false,
"BS": [],
"IsBSSet": false,
"L": [],
"IsLSet": false,
"M": {},
"IsMSet": false,
"N": null,
"NS": [],
"IsNSSet": false,
"NULL": true,
"S": null,
"SS": []
"SS": [],
"IsSSSet": false
},
"SchemaVersion": {
"B": null,
"BOOL": false,
"IsBOOLSet": false,
"BS": [],
"IsBSSet": false,
"L": [],
"IsLSet": false,
"M": {},
"IsMSet": false,
"N": null,
"NS": [],
"IsNSSet": false,
"NULL": false,
"S": "1.0",
"SS": []
"SS": [],
"IsSSSet": false
},
"ExpiresAt": {
"B": null,
"BOOL": false,
"IsBOOLSet": false,
"BS": [],
"IsBSSet": false,
"L": [],
"IsLSet": false,
"M": {},
"IsMSet": false,
"N": null,
"NS": [],
"IsNSSet": false,
"NULL": true,
"S": null,
"SS": []
"SS": [],
"IsSSSet": false
}
},
"ReturnValuesOnConditionCheckFailure": null,
Expand Down

0 comments on commit 4c392b5

Please sign in to comment.