diff --git a/src/Speckle.Sdk/Api/Helpers.cs b/src/Speckle.Sdk/Api/Helpers.cs index 6f6ec154..30e1df2f 100644 --- a/src/Speckle.Sdk/Api/Helpers.cs +++ b/src/Speckle.Sdk/Api/Helpers.cs @@ -157,7 +157,7 @@ public static async Task Send( using var transport = serverTransportFactory.Create(client.Account, sw.StreamId); var branchName = string.IsNullOrEmpty(sw.BranchName) ? "main" : sw.BranchName; - var objectId = await Operations.Send(data, transport, useDefaultCache, onProgressAction).ConfigureAwait(false); + var (objectId, _) = await Operations.Send(data, transport, useDefaultCache, onProgressAction).ConfigureAwait(false); Analytics.TrackEvent(client.Account, Analytics.Events.Send); diff --git a/src/Speckle.Sdk/Api/Operations/Operations.Send.cs b/src/Speckle.Sdk/Api/Operations/Operations.Send.cs index ddc32638..5dc876a3 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.Send.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.Send.cs @@ -19,9 +19,9 @@ public static partial class Operations /// The or was /// /// using ServerTransport destination = new(account, streamId); - /// string objectId = await Send(mySpeckleObject, destination, true); + /// var (objectId, references) = await Send(mySpeckleObject, destination, true); /// - public static async Task Send( + public static async Task<(string rootObjId, IReadOnlyDictionary convertedReferences)> Send( Base value, ITransport transport, bool useDefaultCache, @@ -58,7 +58,7 @@ public static async Task Send( /// Serialization or Send operation was unsuccessful /// One or more failed to send /// The requested cancellation - public static async Task Send( + public static async Task<(string rootObjId, IReadOnlyDictionary convertedReferences)> Send( Base value, IReadOnlyCollection transports, Action>? onProgressAction = null, @@ -87,7 +87,7 @@ public static async Task Send( var internalProgressAction = GetInternalProgressAction(onProgressAction); - BaseObjectSerializerV2 serializerV2 = new(transports, internalProgressAction, false, cancellationToken); + BaseObjectSerializerV2 serializerV2 = new(transports, internalProgressAction, true, cancellationToken); foreach (var t in transports) { @@ -96,10 +96,25 @@ public static async Task Send( t.BeginWrite(); } - string hash; try { - hash = await SerializerSend(value, serializerV2, cancellationToken).ConfigureAwait(false); + var rootObjectId = await SerializerSend(value, serializerV2, cancellationToken).ConfigureAwait(false); + + sendTimer.Stop(); + activity?.SetTag("transportElapsedBreakdown", transports.ToDictionary(t => t.TransportName, t => t.Elapsed)); + activity?.SetTag( + "note", + "the elapsed summary doesn't need to add up to the total elapsed... Threading magic..." + ); + activity?.SetTag("serializerElapsed", serializerV2.Elapsed); + SpeckleLog.Logger.Information( + "Finished sending {objectCount} objects after {elapsed}, result {objectId}", + transports.Max(t => t.SavedObjectCount), + sendTimer.Elapsed.TotalSeconds, + rootObjectId + ); + + return (rootObjectId, serializerV2.ObjectReferences); } catch (Exception ex) when (!ex.IsFatal()) { @@ -122,18 +137,6 @@ public static async Task Send( t.EndWrite(); } } - - sendTimer.Stop(); - activity?.SetTag("transportElapsedBreakdown", transports.ToDictionary(t => t.TransportName, t => t.Elapsed)); - activity?.SetTag("note", "the elapsed summary doesn't need to add up to the total elapsed... Threading magic..."); - activity?.SetTag("serializerElapsed", serializerV2.Elapsed); - SpeckleLog.Logger.Information( - "Finished sending {objectCount} objects after {elapsed}, result {objectId}", - transports.Max(t => t.SavedObjectCount), - sendTimer.Elapsed.TotalSeconds, - hash - ); - return hash; } } diff --git a/src/Speckle.Sdk/Models/Extras.cs b/src/Speckle.Sdk/Models/Extras.cs index b734f896..6d5844ba 100644 --- a/src/Speckle.Sdk/Models/Extras.cs +++ b/src/Speckle.Sdk/Models/Extras.cs @@ -7,13 +7,13 @@ namespace Speckle.Sdk.Models; /// See the following reference. /// [SpeckleType("Speckle.Core.Models.DataChunk")] -public class DataChunk : Base +public sealed class DataChunk : Base { public List data { get; set; } = new(); } [SpeckleType("Speckle.Core.Models.ObjectReference")] -public class ObjectReference : Base +public sealed class ObjectReference : Base { public new string speckle_type = "reference"; diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Legacy/LegacyAPITests.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Legacy/LegacyAPITests.cs index 91a71776..08590e1d 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Legacy/LegacyAPITests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Legacy/LegacyAPITests.cs @@ -341,7 +341,7 @@ public async Task CommitCreate() myObject["@Points"] = ptsList; - _objectId = await Operations.Send(myObject, new List { _myServerTransport }); + (_objectId, _) = await Operations.Send(myObject, new List { _myServerTransport }); Assert.That(_objectId, Is.Not.Null); diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Legacy/Subscriptions/Commits.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Legacy/Subscriptions/Commits.cs index b2c7f1cc..f0981829 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Legacy/Subscriptions/Commits.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Legacy/Subscriptions/Commits.cs @@ -73,13 +73,13 @@ public async Task SubscribeCommitCreated() myObject["Points"] = ptsList; - var objectId = await Operations.Send(myObject, _myServerTransport, false); + var sendResult = await Operations.Send(myObject, _myServerTransport, false); var commitInput = new CommitCreateInput { streamId = _streamId, branchName = "awesome-features", - objectId = objectId, + objectId = sendResult.rootObjId, message = "sending some test points", sourceApplication = "Tests", totalChildrenCount = 20 diff --git a/tests/Speckle.Sdk.Tests.Integration/Fixtures.cs b/tests/Speckle.Sdk.Tests.Integration/Fixtures.cs index 20a731f1..45dacf31 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Fixtures.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Fixtures.cs @@ -27,7 +27,7 @@ public static async Task SeedUserWithClient() public static async Task CreateVersion(Client client, string projectId, string modelId) { using ServerTransport remote = new(client.Account, projectId); - var objectId = await Operations.Send(new() { applicationId = "ASDF" }, remote, false); + var (objectId, _) = await Operations.Send(new() { applicationId = "ASDF" }, remote, false); CreateVersionInput input = new(objectId, modelId, projectId); return await client.Version.Create(input); } diff --git a/tests/Speckle.Sdk.Tests.Integration/ServerTransportTests.cs b/tests/Speckle.Sdk.Tests.Integration/ServerTransportTests.cs index 664e29d9..f105dcfb 100644 --- a/tests/Speckle.Sdk.Tests.Integration/ServerTransportTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/ServerTransportTests.cs @@ -73,12 +73,12 @@ public async Task SendAndReceiveObjectWithBlobs() var myObject = Fixtures.GenerateSimpleObject(); myObject["blobs"] = Fixtures.GenerateThreeBlobs(); - var sentObjectId = await Operations.Send(myObject, _transport, false); + var sendResult = await Operations.Send(myObject, _transport, false); // NOTE: used to debug diffing // await Operations.Send(myObject, new List { transport }); - var receivedObject = await Operations.Receive(sentObjectId, _transport, new MemoryTransport()); + var receivedObject = await Operations.Receive(sendResult.rootObjId, _transport, new MemoryTransport()); var allFiles = Directory .GetFiles(_transport.BlobStorageFolder) @@ -108,9 +108,9 @@ public async Task SendWithBlobsWithoutSQLiteSendCache() myObject["blobs"] = Fixtures.GenerateThreeBlobs(); var memTransport = new MemoryTransport(); - var sentObjectId = await Operations.Send(myObject, new List { _transport, memTransport }); + var sendResult = await Operations.Send(myObject, new List { _transport, memTransport }); - var receivedObject = await Operations.Receive(sentObjectId, _transport, new MemoryTransport()); + var receivedObject = await Operations.Receive(sendResult.rootObjId, _transport, new MemoryTransport()); var allFiles = Directory .GetFiles(_transport.BlobStorageFolder) @@ -141,10 +141,10 @@ public async Task SendReceiveWithCleanedMemoryCache() myObject["blobs"] = Fixtures.GenerateThreeBlobs(); var memTransport = new MemoryTransport(); - var sentObjectId = await Operations.Send(myObject, new ITransport[] { _transport, memTransport }); + var sendResult = await Operations.Send(myObject, new ITransport[] { _transport, memTransport }); memTransport = new MemoryTransport(); - Base receivedObject = await Operations.Receive(sentObjectId, _transport, memTransport); + Base receivedObject = await Operations.Receive(sendResult.rootObjId, _transport, memTransport); Assert.That(receivedObject, Is.Not.Null); var allFiles = Directory diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/ClosureTests.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/ClosureTests.cs index 11d8f282..a8a3e98b 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/ClosureTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/ClosureTests.cs @@ -20,7 +20,7 @@ public void Setup() } [Test(Description = "Checks whether closures are generated correctly by the serialiser.")] - public void CorrectDecompositionTracking() + public async Task CorrectDecompositionTracking() { var d5 = new Base(); ((dynamic)d5).name = "depth five"; // end v @@ -45,9 +45,9 @@ public void CorrectDecompositionTracking() var transport = new MemoryTransport(); - var result = Sdk.Api.Operations.Send(d1, transport, false).Result; + var sendResult = await Sdk.Api.Operations.Send(d1, transport, false); - var test = Sdk.Api.Operations.Receive(result, localTransport: transport).Result; + var test = await Sdk.Api.Operations.Receive(sendResult.rootObjId, localTransport: transport); test.id.NotNull(); Assert.That(d1.GetId(true), Is.EqualTo(test.id)); diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendObjectReferences.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendObjectReferences.cs new file mode 100644 index 00000000..77e0b63e --- /dev/null +++ b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendObjectReferences.cs @@ -0,0 +1,54 @@ +using NUnit.Framework; +using Speckle.Sdk.Models; +using Speckle.Sdk.Transports; + +namespace Speckle.Sdk.Tests.Unit.Api.Operations; + +public class SendObjectReferences +{ + [TestCase(0)] + [TestCase(1)] + [TestCase(10)] + public async Task SendObjectsWithApplicationIds(int testDepth) + { + Base testData = GenerateTestCase(testDepth, true); + MemoryTransport transport = new(); + var result = await Speckle.Sdk.Api.Operations.Send(testData, [transport]); + + Assert.That(result.rootObjId, Is.Not.Null); + Assert.That(result.rootObjId, Has.Length.EqualTo(32)); + + Assert.That(result.convertedReferences, Has.Count.EqualTo(Math.Pow(2, testDepth + 1) - 2)); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(10)] + public async Task SendObjectsWithoutApplicationIds(int testDepth) + { + Base testData = GenerateTestCase(testDepth, false); + MemoryTransport transport = new(); + var result = await Speckle.Sdk.Api.Operations.Send(testData, [transport]); + + Assert.That(result.rootObjId, Is.Not.Null); + Assert.That(result.rootObjId, Has.Length.EqualTo(32)); + + Assert.That(result.convertedReferences, Is.Empty); + } + + private Base GenerateTestCase(int depth, bool withAppId) + { + var appId = withAppId ? $"{Guid.NewGuid()}" : null; + var ret = new Base() { applicationId = appId, }; + if (depth > 0) + { + ret["@elements"] = new List + { + GenerateTestCase(depth - 1, withAppId), + GenerateTestCase(depth - 1, withAppId) + }; + } + + return ret; + } +} diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendReceiveLocal.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendReceiveLocal.cs index eaa074ef..fca3683c 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendReceiveLocal.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendReceiveLocal.cs @@ -41,9 +41,11 @@ public async Task LocalUpload() } using SQLiteTransport localTransport = new(); - _objId01 = await Sdk.Api.Operations.Send(myObject, localTransport, false); + (_objId01, var references) = await Sdk.Api.Operations.Send(myObject, localTransport, false); Assert.That(_objId01, Is.Not.Null); + Assert.That(references, Has.Count.EqualTo(NUM_OBJECTS)); + TestContext.Out.WriteLine($"Written {NUM_OBJECTS + 1} objects. Commit id is {_objId01}"); } @@ -71,7 +73,7 @@ public async Task LocalUploadDownload() ); } - _objId01 = await Sdk.Api.Operations.Send(myObject, _sut, false); + (_objId01, _) = await Sdk.Api.Operations.Send(myObject, _sut, false); var commitPulled = await Sdk.Api.Operations.Receive(_objId01); List items = (List)commitPulled["@items"].NotNull(); @@ -95,7 +97,7 @@ public async Task LocalUploadDownloadSmall() ); } - _objId01 = await Sdk.Api.Operations.Send(myObject, _sut, false); + (_objId01, _) = await Sdk.Api.Operations.Send(myObject, _sut, false); Assert.That(_objId01, Is.Not.Null); TestContext.Out.WriteLine($"Written {NUM_OBJECTS + 1} objects. Commit id is {_objId01}"); @@ -119,7 +121,7 @@ public async Task LocalUploadDownloadListDic() myObject["@dictionary"] = myDic; myObject["@list"] = myList; - _objId01 = await Sdk.Api.Operations.Send(myObject, _sut, false); + (_objId01, _) = await Sdk.Api.Operations.Send(myObject, _sut, false); Assert.That(_objId01, Is.Not.Null); @@ -157,7 +159,7 @@ public async Task UploadDownloadNonCommitObject() ((List)((dynamic)obj)["@LayerC"]).Add(new Point(i, i, i + rand.NextDouble()) { applicationId = i + "baz" }); } - _objId01 = await Sdk.Api.Operations.Send(obj, _sut, false); + (_objId01, _) = await Sdk.Api.Operations.Send(obj, _sut, false); Assert.That(_objId01, Is.Not.Null); TestContext.Out.WriteLine($"Written {NUM_OBJECTS + 1} objects. Commit id is {_objId01}"); @@ -195,7 +197,7 @@ public async Task UploadProgressReports() } ConcurrentDictionary? progress = null; - _commitId02 = await Sdk.Api.Operations.Send( + (_commitId02, _) = await Sdk.Api.Operations.Send( myObject, _sut, false, @@ -230,37 +232,13 @@ public async Task ShouldNotDisposeTransports() @base["test"] = "the best"; SQLiteTransport myLocalTransport = new(); - var id = await Sdk.Api.Operations.Send(@base, myLocalTransport, false); + var sendResult = await Sdk.Api.Operations.Send(@base, myLocalTransport, false); await Sdk.Api.Operations.Send(@base, myLocalTransport, false); - _ = await Sdk.Api.Operations.Receive(id, null, myLocalTransport); - await Sdk.Api.Operations.Receive(id, null, myLocalTransport); + _ = await Sdk.Api.Operations.Receive(sendResult.rootObjId, null, myLocalTransport); + await Sdk.Api.Operations.Receive(sendResult.rootObjId, null, myLocalTransport); } - //[Test] - //public async Task DiskTransportTest() - //{ - // var myObject = new Base(); - // myObject["@items"] = new List(); - // myObject["test"] = "random"; - - // var rand = new Random(); - - // for (int i = 0; i < 100; i++) - // { - // ((List)myObject["@items"]).Add(new Point(i, i, i) { applicationId = i + "-___/---" }); - // } - - // var dt = new Speckle.Sdk.Transports.Speckle.Speckle.Sdk.Transports(); - // var id = await Operations.Send(myObject, new List() { dt }, false); - - // Assert.IsNotNull(id); - - // var rebase = await Operations.Receive(id, dt); - - // Assert.AreEqual(rebase.GetId(true), id); - //} - public void Dispose() { _sut.Dispose();