From a554a2ab9dd56bb70fdc5ae64ecf2f33e30a95e8 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Tue, 23 Jul 2024 13:13:28 +0100 Subject: [PATCH] Create connector tests (#52) * Use shared IdleManager * Fix usage of _hasSubscribed by using Try methods on concurrent dictionaries * use top exception handler in idle manager subscription * Connectors with TopLevel handler tests * fix handler * start idle call manager tests * add remove tests * merge fixes * add a bit more coverage * Fix tests --- .../Bridge/IdleCallManagerTests.cs | 64 +++ .../Bridge/TopLevelExceptionHandlerTests.cs | 125 ++++ .../Speckle.Connectors.DUI.Tests.csproj | 26 + .../packages.lock.json | 540 ++++++++++++++++++ .../Bindings/IBasicConnectorBinding.cs | 2 +- .../Bridge/IdleCallManager.cs | 68 ++- DUI3/Speckle.Connectors.DUI/Bridge/Result.cs | 58 ++ .../Bridge/TopLevelExceptionHandler.cs | 111 ++-- .../ContainerRegistration.cs | 2 +- Sdk/Speckle.Testing/MoqTest.cs | 4 +- Speckle.Connectors.sln | 7 + 11 files changed, 906 insertions(+), 101 deletions(-) create mode 100644 DUI3/Speckle.Connectors.DUI.Tests/Bridge/IdleCallManagerTests.cs create mode 100644 DUI3/Speckle.Connectors.DUI.Tests/Bridge/TopLevelExceptionHandlerTests.cs create mode 100644 DUI3/Speckle.Connectors.DUI.Tests/Speckle.Connectors.DUI.Tests.csproj create mode 100644 DUI3/Speckle.Connectors.DUI.Tests/packages.lock.json create mode 100644 DUI3/Speckle.Connectors.DUI/Bridge/Result.cs diff --git a/DUI3/Speckle.Connectors.DUI.Tests/Bridge/IdleCallManagerTests.cs b/DUI3/Speckle.Connectors.DUI.Tests/Bridge/IdleCallManagerTests.cs new file mode 100644 index 000000000..e0383925c --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI.Tests/Bridge/IdleCallManagerTests.cs @@ -0,0 +1,64 @@ +using FluentAssertions; +using Moq; +using NUnit.Framework; +using Speckle.Connectors.DUI.Bridge; +using Speckle.Testing; + +namespace Speckle.Connectors.DUI.Tests.Bridge; + +public class IdleCallManagerTests : MoqTest +{ + [Test] + public void SubscribeToIdleTest() + { + var handler = Create(); + var sut = new IdleCallManager(handler.Object); + var action = Create(); + var addEvent = Create(); + handler.Setup(x => x.CatchUnhandled(It.IsAny())).Returns(new Result()); + sut.SubscribeToIdle("id", action.Object, addEvent.Object); + } + + [Test] + public void SubscribeInternalTest() + { + var handler = Create(); + var sut = new IdleCallManager(handler.Object); + var action = Create(); + var addEvent = Create(); + addEvent.Setup(x => x.Invoke()); + + sut.IdleSubscriptionCalled.Should().BeFalse(); + sut.SubscribeInternal("id", action.Object, addEvent.Object); + sut.Calls.Count.Should().Be(1); + sut.Calls.Should().ContainKey("id"); + sut.IdleSubscriptionCalled.Should().BeTrue(); + } + + [Test] + public void AppOnIdleTest() + { + var handler = Create(); + var sut = new IdleCallManager(handler.Object); + var removeEvent = Create(); + handler.Setup(x => x.CatchUnhandled(It.IsAny())).Returns(new Result()); + sut.AppOnIdle(removeEvent.Object); + } + + [Test] + public void AppOnIdleInternalTest() + { + var handler = Create(); + var sut = new IdleCallManager(handler.Object); + var removeEvent = Create(); + removeEvent.Setup(x => x.Invoke()); + + sut.SubscribeInternal("Test", () => { }, () => { }); + sut.IdleSubscriptionCalled.Should().BeTrue(); + sut.Calls.Count.Should().Be(1); + + sut.AppOnIdleInternal(removeEvent.Object); + sut.Calls.Count.Should().Be(0); + sut.IdleSubscriptionCalled.Should().BeFalse(); + } +} diff --git a/DUI3/Speckle.Connectors.DUI.Tests/Bridge/TopLevelExceptionHandlerTests.cs b/DUI3/Speckle.Connectors.DUI.Tests/Bridge/TopLevelExceptionHandlerTests.cs new file mode 100644 index 000000000..de397764c --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI.Tests/Bridge/TopLevelExceptionHandlerTests.cs @@ -0,0 +1,125 @@ +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Speckle.Connectors.DUI.Bindings; +using Speckle.Connectors.DUI.Bridge; +using Speckle.Testing; + +namespace Speckle.Connectors.DUI.Tests.Bridge; + +public class TopLevelExceptionHandlerTests : MoqTest +{ + [Test] + public void CatchUnhandledAction_Happy() + { + var logger = Create>(MockBehavior.Loose); + var bridge = Create(); + var sut = new TopLevelExceptionHandler(logger.Object, bridge.Object); + + sut.CatchUnhandled(() => { }); + } + + [Test] + public void CatchUnhandledAction_Exception() + { + var logger = Create>(MockBehavior.Loose); + var bridge = Create(); + + bridge.Setup(x => x.Send(BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, It.IsAny())); + + var sut = new TopLevelExceptionHandler(logger.Object, bridge.Object); + + sut.CatchUnhandled(() => throw new InvalidOperationException()); + } + + [Test] + public void CatchUnhandledFunc_Happy() + { + var val = 2; + var logger = Create>(MockBehavior.Loose); + var bridge = Create(); + var sut = new TopLevelExceptionHandler(logger.Object, bridge.Object); + + var returnVal = sut.CatchUnhandled(() => val); + returnVal.Value.Should().Be(val); + returnVal.Exception.Should().BeNull(); + returnVal.IsSuccess.Should().BeTrue(); + } + + [Test] + public void CatchUnhandledFunc_Exception() + { + var logger = Create>(MockBehavior.Loose); + var bridge = Create(); + + bridge.Setup(x => x.Send(BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, It.IsAny())); + + var sut = new TopLevelExceptionHandler(logger.Object, bridge.Object); + + var returnVal = sut.CatchUnhandled((Func)(() => throw new InvalidOperationException())); + returnVal.Value.Should().BeNull(); + returnVal.Exception.Should().BeOfType(); + returnVal.IsSuccess.Should().BeFalse(); + } + + [Test] + public void CatchUnhandledFunc_Exception_Fatal() + { + var logger = Create>(MockBehavior.Loose); + var bridge = Create(); + var sut = new TopLevelExceptionHandler(logger.Object, bridge.Object); + +#pragma warning disable CA2201 + var exception = Assert.Throws( + () => sut.CatchUnhandled(new Func(() => throw new AppDomainUnloadedException())) + ); +#pragma warning restore CA2201 + exception.InnerExceptions.Single().Should().BeOfType(); + } + + [Test] + public async Task CatchUnhandledFuncAsync_Happy() + { + var val = 2; + var logger = Create>(MockBehavior.Loose); + var bridge = Create(); + var sut = new TopLevelExceptionHandler(logger.Object, bridge.Object); + + var returnVal = await sut.CatchUnhandled(() => Task.FromResult(val)); + returnVal.Value.Should().Be(val); + returnVal.Exception.Should().BeNull(); + returnVal.IsSuccess.Should().BeTrue(); + } + + [Test] + public async Task CatchUnhandledFuncAsync_Exception() + { + var logger = Create>(MockBehavior.Loose); + var bridge = Create(); + + bridge.Setup(x => x.Send(BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, It.IsAny())); + + var sut = new TopLevelExceptionHandler(logger.Object, bridge.Object); + + var returnVal = await sut.CatchUnhandled(new Func>(() => throw new InvalidOperationException())); + returnVal.Value.Should().BeNull(); + returnVal.Exception.Should().BeOfType(); + returnVal.IsSuccess.Should().BeFalse(); + } + + [Test] + public void CatchUnhandledFuncAsync_Exception_Fatal() + { + var logger = Create>(MockBehavior.Loose); + var bridge = Create(); + var sut = new TopLevelExceptionHandler(logger.Object, bridge.Object); + +#pragma warning disable CA2201 + var exception = Assert.ThrowsAsync( + async () => await sut.CatchUnhandled(new Func>(() => throw new AppDomainUnloadedException())) + ); +#pragma warning restore CA2201 + exception.Should().BeOfType(); + } +} diff --git a/DUI3/Speckle.Connectors.DUI.Tests/Speckle.Connectors.DUI.Tests.csproj b/DUI3/Speckle.Connectors.DUI.Tests/Speckle.Connectors.DUI.Tests.csproj new file mode 100644 index 000000000..0619d4149 --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI.Tests/Speckle.Connectors.DUI.Tests.csproj @@ -0,0 +1,26 @@ + + + net8.0 + false + true + Debug;Release;Local + + + CA2007; + $(NoWarn) + + + + + + + + + + + + + + + + diff --git a/DUI3/Speckle.Connectors.DUI.Tests/packages.lock.json b/DUI3/Speckle.Connectors.DUI.Tests/packages.lock.json new file mode 100644 index 000000000..e437d39e5 --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI.Tests/packages.lock.json @@ -0,0 +1,540 @@ +{ + "version": 2, + "dependencies": { + "net8.0": { + "altcover": { + "type": "Direct", + "requested": "[8.8.74, )", + "resolved": "8.8.74", + "contentHash": "e8RZNE0vZnuBk/gOAWu9K5wm3S7dOrOlZje3PHI9PJUHqvP1cxVJD1eXAAmddFVlixowB7C7/zvC16GnunC2LQ==" + }, + "FluentAssertions": { + "type": "Direct", + "requested": "[6.12.0, )", + "resolved": "6.12.0", + "contentHash": "ZXhHT2YwP9lajrwSKbLlFqsmCCvFJMoRSK9t7sImfnCyd0OB3MhgxdoMcVqxbq1iyxD6mD2fiackWmBb7ayiXQ==", + "dependencies": { + "System.Configuration.ConfigurationManager": "4.4.0" + } + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.10.0, )", + "resolved": "17.10.0", + "contentHash": "0/2HeACkaHEYU3wc83YlcD2Fi4LMtECJjqrtvw0lPi9DCEa35zSPt1j4fuvM8NagjDqJuh1Ja35WcRtn1Um6/A==", + "dependencies": { + "Microsoft.CodeCoverage": "17.10.0", + "Microsoft.TestPlatform.TestHost": "17.10.0" + } + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "Moq": { + "type": "Direct", + "requested": "[4.20.70, )", + "resolved": "4.20.70", + "contentHash": "4rNnAwdpXJBuxqrOCzCyICXHSImOTRktCgCWXWykuF1qwoIsVvEnR7PjbMk/eLOxWvhmj5Kwt+kDV3RGUYcNwg==", + "dependencies": { + "Castle.Core": "5.1.1" + } + }, + "NUnit": { + "type": "Direct", + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "MT/DpAhjtiytzhTgTqIhBuWx4y26PKfDepYUHUM+5uv4TsryHC2jwFo5e6NhWkApCm/G6kZ80dRjdJFuAxq3rg==" + }, + "NUnit.Analyzers": { + "type": "Direct", + "requested": "[4.2.0, )", + "resolved": "4.2.0", + "contentHash": "4fJojPkzdoa4nB2+p6U+fITvPnVvwWSnsmiJ/Dl30xqiL3oxNbYvfeSLVd91hOmEjoUqSwN3Z7j1aFedjqWbUA==" + }, + "NUnit3TestAdapter": { + "type": "Direct", + "requested": "[4.5.0, )", + "resolved": "4.5.0", + "contentHash": "s8JpqTe9bI2f49Pfr3dFRfoVSuFQyraTj68c3XXjIS/MRGvvkLnrg6RLqnTjdShX+AdFUCCU/4Xex58AdUfs6A==" + }, + "PolySharp": { + "type": "Direct", + "requested": "[1.14.1, )", + "resolved": "1.14.1", + "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + }, + "Speckle.InterfaceGenerator": { + "type": "Direct", + "requested": "[0.9.5, )", + "resolved": "0.9.5", + "contentHash": "oU/L7pN1R7q8KkbrpQ3WJnHirPHqn+9DEA7asOcUiggV5dzVg1A/VYs7GOSusD24njxXh03tE3a2oTLOjt3cVg==" + }, + "Castle.Core": { + "type": "Transitive", + "resolved": "5.1.1", + "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", + "dependencies": { + "System.Diagnostics.EventLog": "6.0.0" + } + }, + "GraphQL.Client": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==", + "dependencies": { + "GraphQL.Client.Abstractions": "6.0.0", + "GraphQL.Client.Abstractions.Websocket": "6.0.0", + "System.Reactive": "5.0.0" + } + }, + "GraphQL.Client.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==", + "dependencies": { + "GraphQL.Primitives": "6.0.0" + } + }, + "GraphQL.Client.Abstractions.Websocket": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==", + "dependencies": { + "GraphQL.Client.Abstractions": "6.0.0" + } + }, + "GraphQL.Primitives": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA==" + }, + "Microsoft.AspNetCore.Http": { + "type": "Transitive", + "resolved": "2.2.2", + "contentHash": "BAibpoItxI5puk7YJbIGj95arZueM8B8M5xT1fXBn3hb3L2G3ucrZcYXv1gXdaroLbntUs8qeV8iuBrpjQsrKw==", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.AspNetCore.WebUtilities": "2.2.0", + "Microsoft.Extensions.ObjectPool": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0", + "Microsoft.Net.Http.Headers": "2.2.0" + } + }, + "Microsoft.AspNetCore.Http.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==", + "dependencies": { + "Microsoft.AspNetCore.Http.Features": "2.2.0", + "System.Text.Encodings.Web": "4.5.0" + } + }, + "Microsoft.AspNetCore.Http.Features": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0" + } + }, + "Microsoft.AspNetCore.WebUtilities": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "9ErxAAKaDzxXASB/b5uLEkLgUWv1QbeVxyJYEHQwMaxXOeFFVkQxiq8RyfVcifLU7NR0QY0p3acqx4ZpYfhHDg==", + "dependencies": { + "Microsoft.Net.Http.Headers": "2.2.0", + "System.Text.Encodings.Web": "4.5.0" + } + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.10.0", + "contentHash": "yC7oSlnR54XO5kOuHlVOKtxomNNN1BWXX8lK1G2jaPXT9sUok7kCOoA4Pgs0qyFaCtMrNsprztYMeoEGqCm4uA==" + }, + "Microsoft.CSharp": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + }, + "Microsoft.Data.Sqlite": { + "type": "Transitive", + "resolved": "8.0.6", + "contentHash": "YVzVtU1IoGsTiMAe0BNV9ssKyrUm6lBQJP6w2N4W29YrBYYtyCmTFmrNGjxaJtJXVqttu0sYkPxmStj/OnMFdg==", + "dependencies": { + "Microsoft.Data.Sqlite.Core": "8.0.6", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.6" + } + }, + "Microsoft.Data.Sqlite.Core": { + "type": "Transitive", + "resolved": "8.0.6", + "contentHash": "umhZ0ZF2RI81rGFTnYmCxI+Euj4Aqe/6Y4+8CxN9OVJNGDNIqB5laJ3wxQTU8zXCcm2k9F7FL+/6RVoOT4z1Fw==", + "dependencies": { + "SQLitePCLRaw.core": "2.1.6" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "elNeOmkeX3eDVG6pYVeV82p29hr+UKDaBhrZyWvWLw/EVZSYEkZlQdkp0V39k/Xehs2Qa0mvoCvkVj3eQxNQ1Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "h3j/QfmFN4S0w4C2A6X7arXij/M/OVw3uQHSOFxnND4DyAzO1F9eMX7Eti7lU/OkSthEE0WzRsfT/Dmx86jzCw==" + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "7.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Logging.Abstractions": "7.0.0", + "Microsoft.Extensions.Options": "7.0.0" + } + }, + "Microsoft.Extensions.ObjectPool": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "gA8H7uQOnM5gb+L0uTNjViHYr+hRDqCdfugheGo/MxQnuHzmhhzCBTIPm19qL1z1Xe0NEMabfcOBGv9QghlZ8g==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "lP1yBnTTU42cKpMozuafbvNtQ7QcBjr/CcK3bYOGEMH55Fjt+iecXjT6chR7vbgCMqy3PG3aNQSZgo/EuY/9qQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q==" + }, + "Microsoft.Net.Http.Headers": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "iZNkjYqlo8sIOI0bQfpsSoMTmB/kyvmV2h225ihyZT33aTp48ZpF6qYnXxzSXmHt8DpBAwBTX+1s1UFLbYfZKg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0", + "System.Buffers": "4.5.0" + } + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.10.0", + "contentHash": "KkwhjQevuDj0aBRoPLY6OLAhGqbPUEBuKLbaCs0kUVw29qiOYncdORd4mLVJbn9vGZ7/iFGQ/+AoJl0Tu5Umdg==", + "dependencies": { + "System.Reflection.Metadata": "1.6.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.10.0", + "contentHash": "LWpMdfqhHvcUkeMCvNYJO8QlPLlYz9XPPb+ZbaXIKhdmjAV0wqTSrTiW5FLaf7RRZT50AQADDOYMOe0HxDxNgA==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.10.0", + "Newtonsoft.Json": "13.0.1" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, + "Polly": { + "type": "Transitive", + "resolved": "7.2.3", + "contentHash": "DeCY0OFbNdNxsjntr1gTXHJ5pKUwYzp04Er2LLeN3g6pWhffsGuKVfMBLe1lw7x76HrPkLxKEFxBlpRxS2nDEQ==" + }, + "Polly.Contrib.WaitAndRetry": { + "type": "Transitive", + "resolved": "1.1.1", + "contentHash": "1MUQLiSo4KDkQe6nzQRhIU05lm9jlexX5BVsbuw0SL82ynZ+GzAHQxJVDPVBboxV37Po3SG077aX8DuSy8TkaA==" + }, + "Polly.Extensions.Http": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "drrG+hB3pYFY7w1c3BD+lSGYvH2oIclH8GRSehgfyP5kjnFnHKQuuBhuHLv+PWyFuaTDyk/vfRpnxOzd11+J8g==", + "dependencies": { + "Polly": "7.1.0" + } + }, + "Sentry": { + "type": "Transitive", + "resolved": "3.33.0", + "contentHash": "8vbD2o6IR2wrRrkSiRbnodWGWUOqIlwYtzpjvPNOb5raJdOf+zxMwfS8f6nx9bmrTTfDj7KrCB8C/5OuicAc8A==" + }, + "Sentry.Serilog": { + "type": "Transitive", + "resolved": "3.33.0", + "contentHash": "V8BU7QGWg2qLYfNPqtuTBhC1opysny5l+Ifp6J6PhOeAxU0FssR7nYfbJVetrnLIoh2rd3DlJ6hHYYQosQYcUQ==", + "dependencies": { + "Sentry": "3.33.0", + "Serilog": "2.10.0" + } + }, + "Serilog": { + "type": "Transitive", + "resolved": "2.12.0", + "contentHash": "xaiJLIdu6rYMKfQMYUZgTy8YK7SMZjB4Yk50C/u//Z4OsvxkUfSPJy4nknfvwAC34yr13q7kcyh4grbwhSxyZg==" + }, + "Serilog.Enrichers.ClientInfo": { + "type": "Transitive", + "resolved": "1.3.0", + "contentHash": "mTc7PM+wC9Hr7LWSwqt5mmnlAr7RJs+eTb3PGPRhwdOackk95MkhUZognuxXEdlW19HAFNmEBTSBY5DfLwM8jQ==", + "dependencies": { + "Microsoft.AspNetCore.Http": "2.2.2", + "Serilog": "2.9.0" + } + }, + "Serilog.Exceptions": { + "type": "Transitive", + "resolved": "8.4.0", + "contentHash": "nc/+hUw3lsdo0zCj0KMIybAu7perMx79vu72w0za9Nsi6mWyNkGXxYxakAjWB7nEmYL6zdmhEQRB4oJ2ALUeug==", + "dependencies": { + "Serilog": "2.8.0", + "System.Reflection.TypeExtensions": "4.7.0" + } + }, + "Serilog.Formatting.Compact": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "pNroKVjo+rDqlxNG5PXkRLpfSCuDOBY0ri6jp9PLe505ljqwhwZz8ospy2vWhQlFu5GkIesh3FcDs4n7sWZODA==", + "dependencies": { + "Serilog": "2.8.0" + } + }, + "Serilog.Sinks.Console": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "K6N5q+5fetjnJPvCmkWOpJ/V8IEIoMIB1s86OzBrbxwTyHxdx3pmz4H+8+O/Dc/ftUX12DM1aynx/dDowkwzqg==", + "dependencies": { + "Serilog": "2.10.0" + } + }, + "Serilog.Sinks.File": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "uwV5hdhWPwUH1szhO8PJpFiahqXmzPzJT/sOijH/kFgUx+cyoDTMM8MHD0adw9+Iem6itoibbUXHYslzXsLEAg==", + "dependencies": { + "Serilog": "2.10.0" + } + }, + "Serilog.Sinks.PeriodicBatching": { + "type": "Transitive", + "resolved": "3.1.0", + "contentHash": "NDWR7m3PalVlGEq3rzoktrXikjFMLmpwF0HI4sowo8YDdU+gqPlTHlDQiOGxHfB0sTfjPA9JjA7ctKG9zqjGkw==", + "dependencies": { + "Serilog": "2.0.0" + } + }, + "Serilog.Sinks.Seq": { + "type": "Transitive", + "resolved": "5.2.2", + "contentHash": "1Csmo5ua7NKUe0yXUx+zsRefjAniPWcXFhUXxXG8pwo0iMiw2gjn9SOkgYnnxbgWqmlGv236w0N/dHc2v5XwMg==", + "dependencies": { + "Serilog": "2.12.0", + "Serilog.Formatting.Compact": "1.1.0", + "Serilog.Sinks.File": "5.0.0", + "Serilog.Sinks.PeriodicBatching": "3.1.0" + } + }, + "SerilogTimings": { + "type": "Transitive", + "resolved": "3.0.1", + "contentHash": "Zs28eTgszAMwpIrbBnWHBI50yuxL50p/dmAUWmy75+axdZYK/Sjm5/5m1N/CisR8acJUhTVcjPZrsB1P5iv0Uw==", + "dependencies": { + "Serilog": "2.10.0" + } + }, + "Speckle.DoubleNumerics": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "MzEQ1Im0zTja+tEsdRIk/WlPiKqb22NmTOJcR1ZKm/mz46pezyyID3/wRz6vJUELMpSLnG7LhsxBL+nxbr7V0w==" + }, + "Speckle.Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.2", + "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" + }, + "SQLitePCLRaw.bundle_e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.6", + "contentHash": "BmAf6XWt4TqtowmiWe4/5rRot6GerAeklmOPfviOvwLoF5WwgxcJHAxZtySuyW9r9w+HLILnm8VfJFLCUJYW8A==", + "dependencies": { + "SQLitePCLRaw.lib.e_sqlite3": "2.1.6", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.6" + } + }, + "SQLitePCLRaw.core": { + "type": "Transitive", + "resolved": "2.1.6", + "contentHash": "wO6v9GeMx9CUngAet8hbO7xdm+M42p1XeJq47ogyRoYSvNSp0NGLI+MgC0bhrMk9C17MTVFlLiN6ylyExLCc5w==", + "dependencies": { + "System.Memory": "4.5.3" + } + }, + "SQLitePCLRaw.lib.e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.6", + "contentHash": "2ObJJLkIUIxRpOUlZNGuD4rICpBnrBR5anjyfUFQep4hMOIeqW+XGQYzrNmHSVz5xSWZ3klSbh7sFR6UyDj68Q==" + }, + "SQLitePCLRaw.provider.e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.6", + "contentHash": "PQ2Oq3yepLY4P7ll145P3xtx2bX8xF4PzaKPRpw9jZlKvfe4LE/saAV82inND9usn1XRpmxXk7Lal3MTI+6CNg==", + "dependencies": { + "SQLitePCLRaw.core": "2.1.6" + } + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" + }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "gWwQv/Ug1qWJmHCmN17nAbxJYmQBM/E94QxKLksvUiiKB1Ld3Sc/eK1lgmbSjDFxkQhVuayI/cGFZhpBSodLrg==", + "dependencies": { + "System.Security.Cryptography.ProtectedData": "4.4.0" + } + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.3", + "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==" + }, + "System.Reactive": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" + }, + "System.Reflection.TypeExtensions": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "VybpaOQQhqE6siHppMktjfGBw1GCwvCqiufqmP8F1nj7fTUNtW35LOEt3UZTEsECfo+ELAl/9o9nJx3U91i7vA==" + }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "cJV7ScGW7EhatRsjehfvvYVBvtiSMKgN8bOVI0bQhnF5bU7vnHVIsH49Kva7i7GWaWYvmEzkYVk1TC+gZYBEog==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g==" + }, + "speckle.autofac": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "[7.0.0, )" + } + }, + "speckle.connectors.dui": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "[7.0.0, )", + "Speckle.Autofac": "[1.0.0, )", + "Speckle.Connectors.Utils": "[1.0.0, )", + "Speckle.Core": "[3.1.0-dev.75, )", + "System.Threading.Tasks.Dataflow": "[6.0.0, )" + } + }, + "speckle.connectors.utils": { + "type": "Project", + "dependencies": { + "Serilog.Extensions.Logging": "[7.0.0, )", + "Speckle.Autofac": "[1.0.0, )", + "Speckle.Core": "[3.1.0-dev.75, )" + } + }, + "speckle.testing": { + "type": "Project", + "dependencies": { + "Moq": "[4.20.70, )", + "NUnit": "[4.1.0, )" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "CentralTransitive", + "requested": "[7.0.0, )", + "resolved": "7.0.0", + "contentHash": "kmn78+LPVMOWeITUjIlfxUPDsI0R6G0RkeAMBmQxAJ7vBJn4q2dTva7pWi65ceN5vPGjJ9q/Uae2WKgvfktJAw==" + }, + "Serilog.Extensions.Logging": { + "type": "CentralTransitive", + "requested": "[7.0.0, )", + "resolved": "7.0.0", + "contentHash": "9faU0zNQqU7I6soVhLUMYaGNpgWv6cKlKb2S5AnS8gXxzW/em5Ladm/6FMrWTnX41cdbdGPOWNAo6adi4WaJ6A==", + "dependencies": { + "Microsoft.Extensions.Logging": "7.0.0", + "Serilog": "2.12.0" + } + }, + "Speckle.Core": { + "type": "CentralTransitive", + "requested": "[3.1.0-dev.75, )", + "resolved": "3.1.0-dev.75", + "contentHash": "RW+ZNmMoVtJnj+FxwX4pxy9oEFrYvZ6GEVA1AtyQQ3jPFVdApL9GDsfp7iNJDTtzY4sGY+a3EcudslYMfGk8jA==", + "dependencies": { + "GraphQL.Client": "6.0.0", + "Microsoft.CSharp": "4.7.0", + "Microsoft.Data.Sqlite": "8.0.6", + "Polly": "7.2.3", + "Polly.Contrib.WaitAndRetry": "1.1.1", + "Polly.Extensions.Http": "3.0.0", + "Sentry": "3.33.0", + "Sentry.Serilog": "3.33.0", + "Serilog": "2.12.0", + "Serilog.Enrichers.ClientInfo": "1.3.0", + "Serilog.Exceptions": "8.4.0", + "Serilog.Sinks.Console": "4.1.0", + "Serilog.Sinks.Seq": "5.2.2", + "SerilogTimings": "3.0.1", + "Speckle.DoubleNumerics": "4.0.1", + "Speckle.Newtonsoft.Json": "13.0.2" + } + }, + "System.Threading.Tasks.Dataflow": { + "type": "CentralTransitive", + "requested": "[6.0.0, )", + "resolved": "6.0.0", + "contentHash": "+tyDCU3/B1lDdOOAJywHQoFwyXIUghIaP2BxG79uvhfTnO+D9qIgjVlL/JV2NTliYbMHpd6eKDmHp2VHpij7MA==" + } + } + } +} \ No newline at end of file diff --git a/DUI3/Speckle.Connectors.DUI/Bindings/IBasicConnectorBinding.cs b/DUI3/Speckle.Connectors.DUI/Bindings/IBasicConnectorBinding.cs index c88da91a8..73f76340e 100644 --- a/DUI3/Speckle.Connectors.DUI/Bindings/IBasicConnectorBinding.cs +++ b/DUI3/Speckle.Connectors.DUI/Bindings/IBasicConnectorBinding.cs @@ -45,7 +45,7 @@ public class BasicConnectorBindingCommands private const string NOTIFY_DOCUMENT_CHANGED_EVENT_NAME = "documentChanged"; private const string SET_MODEL_PROGRESS_UI_COMMAND_NAME = "setModelProgress"; private const string SET_MODEL_ERROR_UI_COMMAND_NAME = "setModelError"; - internal const string SET_GLOBAL_NOTIFICATION = "setGlobalNotification"; + public const string SET_GLOBAL_NOTIFICATION = "setGlobalNotification"; protected IBridge Bridge { get; } diff --git a/DUI3/Speckle.Connectors.DUI/Bridge/IdleCallManager.cs b/DUI3/Speckle.Connectors.DUI/Bridge/IdleCallManager.cs index 645bba8e5..8e2b19c27 100644 --- a/DUI3/Speckle.Connectors.DUI/Bridge/IdleCallManager.cs +++ b/DUI3/Speckle.Connectors.DUI/Bridge/IdleCallManager.cs @@ -1,52 +1,62 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; -using Speckle.InterfaceGenerator; namespace Speckle.Connectors.DUI.Bridge; +public interface IIdleCallManager +{ + void SubscribeToIdle(string id, Action action, Action addEvent); + void AppOnIdle(Action removeEvent); +} + //should be registered as singleton -[GenerateAutoInterface] [SuppressMessage("ReSharper", "InconsistentlySynchronizedField")] public class IdleCallManager(ITopLevelExceptionHandler topLevelExceptionHandler) : IIdleCallManager { - private readonly ConcurrentDictionary _calls = new(); - private bool _idleSubscriptionCalled; + public ConcurrentDictionary Calls { get; } = new(); + + private readonly object _lock = new(); + public bool IdleSubscriptionCalled { get; private set; } public void SubscribeToIdle(string id, Action action, Action addEvent) => - topLevelExceptionHandler.CatchUnhandled(() => + topLevelExceptionHandler.CatchUnhandled(() => SubscribeInternal(id, action, addEvent)); + + public void SubscribeInternal(string id, Action action, Action addEvent) + { + Calls.TryAdd(id, action); + if (!IdleSubscriptionCalled) { - _calls.TryAdd(id, action); - if (!_idleSubscriptionCalled) + lock (_lock) { - lock (_calls) + if (!IdleSubscriptionCalled) { - if (!_idleSubscriptionCalled) - { - addEvent.Invoke(); - _idleSubscriptionCalled = true; - } + addEvent.Invoke(); + IdleSubscriptionCalled = true; } } - }); + } + } public void AppOnIdle(Action removeEvent) => - topLevelExceptionHandler.CatchUnhandled(() => + topLevelExceptionHandler.CatchUnhandled(() => AppOnIdleInternal(removeEvent)); + + public void AppOnIdleInternal(Action removeEvent) + { + foreach (KeyValuePair kvp in Calls) { - foreach (KeyValuePair kvp in _calls) + kvp.Value.Invoke(); + } + Calls.Clear(); + if (IdleSubscriptionCalled) + { + lock (_lock) { - kvp.Value.Invoke(); - } - _calls.Clear(); - if (_idleSubscriptionCalled) - { - lock (_calls) + if (IdleSubscriptionCalled) { - if (_idleSubscriptionCalled) - { - removeEvent.Invoke(); - _idleSubscriptionCalled = false; - } + removeEvent.Invoke(); + IdleSubscriptionCalled = false; } } - }); + } + } } diff --git a/DUI3/Speckle.Connectors.DUI/Bridge/Result.cs b/DUI3/Speckle.Connectors.DUI/Bridge/Result.cs new file mode 100644 index 000000000..82327a177 --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Bridge/Result.cs @@ -0,0 +1,58 @@ +using System.Diagnostics.CodeAnalysis; +using Speckle.Connectors.Utils; + +namespace Speckle.Connectors.DUI.Bridge; + +/// +/// Result Pattern struct +/// +/// +[ExcludeFromCodeCoverage] +public readonly struct Result +{ + //Don't add new members to this struct, it is perfect. + public T? Value { get; } + public Exception? Exception { get; } + + [MemberNotNullWhen(false, nameof(Exception))] + public bool IsSuccess => Exception is null; + + /// + /// Create a successful result + /// + /// + public Result(T result) + { + Value = result; + } + + /// + /// Create a non-successful result + /// + /// + /// was null + public Result([NotNull] Exception? result) + { + Exception = result.NotNull(); + } +} + +[ExcludeFromCodeCoverage] +public readonly struct Result +{ + //Don't add new members to this struct, it is perfect. + public Exception? Exception { get; } + + [MemberNotNullWhen(false, nameof(Exception))] + public bool IsSuccess => Exception is null; + + /// + /// Create a non-successful result + /// + /// + /// was null + public Result([NotNull] Exception? result) + { + Exception = result.NotNull(); + } +} diff --git a/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs b/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs index 8fa78cc05..870c1d1b5 100644 --- a/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs +++ b/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs @@ -1,46 +1,11 @@ -using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; using Speckle.Connectors.DUI.Bindings; -using Speckle.Connectors.Utils; using Speckle.Core.Logging; using Speckle.Core.Models.Extensions; using Speckle.InterfaceGenerator; namespace Speckle.Connectors.DUI.Bridge; -/// -/// Result Pattern struct -/// -/// -public readonly struct Result -{ - //Don't add new members to this struct, it is perfect. - public T? Value { get; } - public Exception? Exception { get; } - - [MemberNotNullWhen(false, nameof(Exception))] - public bool IsSuccess => Exception is null; - - /// - /// Create a successful result - /// - /// - public Result(T result) - { - Value = result; - } - - /// - /// Create a non-sucessful result - /// - /// - /// was null - public Result([NotNull] Exception? result) - { - Exception = result.NotNull(); - } -} - /// /// The functions provided by this class are designed to be used in all "top level" scenarios (e.g. Plugin, UI, and Event callbacks) /// To provide "last ditch effort" handling of unexpected exceptions that have not been handled. @@ -55,19 +20,11 @@ public readonly struct Result /// Attempting to swallow them may lead to data corruption, deadlocking, or things worse than a managed host app crash. /// [GenerateAutoInterface] -public sealed class TopLevelExceptionHandler : ITopLevelExceptionHandler +public sealed class TopLevelExceptionHandler(ILogger logger, IBridge bridge) + : ITopLevelExceptionHandler { - private readonly ILogger _logger; - private readonly IBridge _bridge; - private const string UNHANDLED_LOGGER_TEMPLATE = "An unhandled Exception occured"; - public TopLevelExceptionHandler(ILogger logger, IBridge bridge) - { - _logger = logger; - _bridge = bridge; - } - /// /// Invokes the given function within a / block, /// and provides exception handling for unexpected exceptions that have not been handled.
@@ -75,35 +32,18 @@ public sealed class TopLevelExceptionHandler : ITopLevelExceptionHandler /// The function to invoke and provide error handling for /// will be rethrown, these should be allowed to bubble up to the host app /// - public void CatchUnhandled(Action function) - { - CatchUnhandled(() => - { - function.Invoke(); - return (object?)null; - }); - } - - /// - /// return type - /// A result pattern struct (where exceptions have been handled) - public Result CatchUnhandled(Func function) - { - return CatchUnhandled(() => Task.FromResult(function.Invoke())).Result; - } - - /// - public async Task> CatchUnhandled(Func> function) + public Result CatchUnhandled(Action function) { try { try { - return new(await function.Invoke().ConfigureAwait(false)); + function.Invoke(); + return new(); } catch (Exception ex) when (!ex.IsFatal()) { - _logger.LogError(ex, UNHANDLED_LOGGER_TEMPLATE); + logger.LogError(ex, UNHANDLED_LOGGER_TEMPLATE); SetGlobalNotification( ToastNotificationType.DANGER, @@ -116,13 +56,48 @@ public sealed class TopLevelExceptionHandler : ITopLevelExceptionHandler } catch (Exception ex) { - _logger.LogCritical(ex, UNHANDLED_LOGGER_TEMPLATE); + logger.LogCritical(ex, UNHANDLED_LOGGER_TEMPLATE); + throw; + } + } + + /// + /// return type + /// A result pattern struct (where exceptions have been handled) + public Result CatchUnhandled(Func function) => + CatchUnhandled(() => Task.FromResult(function.Invoke())).Result; + + /// + public async Task> CatchUnhandled(Func> function) + { + try + { + try + { + return new(await function.Invoke().ConfigureAwait(false)); + } + catch (Exception ex) when (!ex.IsFatal()) + { + logger.LogError(ex, UNHANDLED_LOGGER_TEMPLATE); + + SetGlobalNotification( + ToastNotificationType.DANGER, + "Unhandled Exception Occured", + ex.ToFormattedString(), + false + ); + return new(ex); + } + } + catch (Exception ex) + { + logger.LogCritical(ex, UNHANDLED_LOGGER_TEMPLATE); throw; } } private void SetGlobalNotification(ToastNotificationType type, string title, string message, bool autoClose) => - _bridge.Send( + bridge.Send( BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, //TODO: We could move these constants into a DUI3 constants static class new { diff --git a/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs b/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs index 24a546276..df2b2c62c 100644 --- a/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs +++ b/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs @@ -1,4 +1,4 @@ -using Speckle.Autofac.DependencyInjection; +using Speckle.Autofac.DependencyInjection; using Speckle.Connectors.DUI.Bridge; using Speckle.Connectors.DUI.Models.Card.SendFilter; using Speckle.Connectors.DUI.Utils; diff --git a/Sdk/Speckle.Testing/MoqTest.cs b/Sdk/Speckle.Testing/MoqTest.cs index c9862d620..1b7c80dc6 100644 --- a/Sdk/Speckle.Testing/MoqTest.cs +++ b/Sdk/Speckle.Testing/MoqTest.cs @@ -15,6 +15,6 @@ public abstract class MoqTest protected MockRepository Repository { get; private set; } = new(MockBehavior.Strict); - protected Mock Create() - where T : class => Repository.Create(); + protected Mock Create(MockBehavior behavior = MockBehavior.Strict) + where T : class => Repository.Create(behavior); } diff --git a/Speckle.Connectors.sln b/Speckle.Connectors.sln index 890c23c93..66a66c098 100644 --- a/Speckle.Connectors.sln +++ b/Speckle.Connectors.sln @@ -137,6 +137,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Speckle.Converters.Revit202 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2022", "2022", "{0AF38BA3-65A0-481B-8CBB-B82E406E1575}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Connectors.DUI.Tests", "DUI3\Speckle.Connectors.DUI.Tests\Speckle.Connectors.DUI.Tests.csproj", "{EB83A3A3-F9B6-4281-8EBF-F7289FB5D885}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Speckle.Converters.Revit2022.Tests", "Converters\Revit\Speckle.Converters.Revit2022.Tests\Speckle.Converters.Revit2022.Tests.csproj", "{D8069A23-AD2E-4C9E-8574-7E8C45296A46}" EndProject Global @@ -293,6 +295,10 @@ Global {881D71A3-D276-4108-98C6-0FFD32129B9C}.Debug|Any CPU.Build.0 = Debug|Any CPU {881D71A3-D276-4108-98C6-0FFD32129B9C}.Release|Any CPU.ActiveCfg = Release|Any CPU {881D71A3-D276-4108-98C6-0FFD32129B9C}.Release|Any CPU.Build.0 = Release|Any CPU + {EB83A3A3-F9B6-4281-8EBF-F7289FB5D885}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB83A3A3-F9B6-4281-8EBF-F7289FB5D885}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB83A3A3-F9B6-4281-8EBF-F7289FB5D885}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB83A3A3-F9B6-4281-8EBF-F7289FB5D885}.Release|Any CPU.Build.0 = Release|Any CPU {D8069A23-AD2E-4C9E-8574-7E8C45296A46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D8069A23-AD2E-4C9E-8574-7E8C45296A46}.Debug|Any CPU.Build.0 = Debug|Any CPU {D8069A23-AD2E-4C9E-8574-7E8C45296A46}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -357,6 +363,7 @@ Global {19424B55-058C-4E9C-B86F-700AEF9EAEC3} = {0AF38BA3-65A0-481B-8CBB-B82E406E1575} {881D71A3-D276-4108-98C6-0FFD32129B9C} = {0AF38BA3-65A0-481B-8CBB-B82E406E1575} {0AF38BA3-65A0-481B-8CBB-B82E406E1575} = {D92751C8-1039-4005-90B2-913E55E0B8BD} + {EB83A3A3-F9B6-4281-8EBF-F7289FB5D885} = {FD4D6594-D81E-456F-8F2E-35B09E04A755} {D8069A23-AD2E-4C9E-8574-7E8C45296A46} = {0AF38BA3-65A0-481B-8CBB-B82E406E1575} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution