Merge branch 'dev' into claire/cnx-931-create-archicadobject-in-speckle-sharp-sdk

This commit is contained in:
Oğuzhan Koral
2025-01-14 10:37:40 +03:00
committed by GitHub
118 changed files with 3326 additions and 2403 deletions
-2
View File
@@ -1,6 +1,5 @@
<Project>
<PropertyGroup Condition="'$(IsTestProject)' == 'true'">
<AnalysisMode>Recommended</AnalysisMode>
<NoWarn>
$(NoWarn);
<!-- Things we need to test -->
@@ -9,7 +8,6 @@
<!-- Analysers that provide no tangeable value to a test project -->
CA5394;CA2007;CA1852;CA1819;CA1711;CA1063;CA1816;CA2234;CS8618;CA1054;CA1810;CA2208;CA1019;CA1831;
</NoWarn>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
</PropertyGroup>
<Target Name="DeepClean">
+9 -10
View File
@@ -1,33 +1,32 @@
<Project>
<ItemGroup>
<PackageVersion Include="altcover" Version="8.9.3" />
<PackageVersion Include="altcover" Version="9.0.1" />
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="Bullseye" Version="5.0.0" />
<PackageVersion Include="FluentAssertions" Version="7.0.0" />
<PackageVersion Include="GraphQL.Client" Version="6.0.0" />
<PackageVersion Include="Glob" Version="1.1.9" />
<PackageVersion Include="ILRepack.FullAuto" Version="1.6.0" />
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
<!-- Keep at exactly 7.0.5 for side by side with V2 -->
<PackageVersion Include="Microsoft.Data.Sqlite" Version="7.0.5" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="8.0.11" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="9.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" />
<PackageVersion Include="Moq" Version="4.20.70" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageVersion Include="NUnit" Version="4.2.2" />
<PackageVersion Include="NUnit.Analyzers" Version="4.2.0" />
<PackageVersion Include="Open.ChannelExtensions" Version="8.6.0" />
<PackageVersion Include="Open.ChannelExtensions" Version="9.0.0" />
<PackageVersion Include="Polly" Version="7.2.3" />
<PackageVersion Include="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
<PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" />
<PackageVersion Include="Shouldly" Version="4.2.1" />
<PackageVersion Include="Speckle.Newtonsoft.Json" Version="13.0.2" />
<PackageVersion Include="Speckle.DoubleNumerics" Version="4.0.1" />
<PackageVersion Include="SimpleExec" Version="12.0.0" />
<PackageVersion Include="System.Threading.Channels" Version="8.0.0" />
<PackageVersion Include="System.Threading.Channels" Version="9.0.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.0" />
<GlobalPackageReference Include="PolySharp" Version="1.15.0" />
<GlobalPackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<GlobalPackageReference Include="GitVersion.MsBuild" Version="5.12.0" />
@@ -9,3 +9,8 @@ public static class Collections
public static IReadOnlyDictionary<TKey, TValue> Freeze<TKey, TValue>(this IDictionary<TKey, TValue> source)
where TKey : notnull => source.ToFrozenDictionary();
}
public static class EnumerableExtensions
{
public static IEnumerable<int> RangeFrom(int from, int to) => Enumerable.Range(from, to - from + 1);
}
+6 -4
View File
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Text;
using Microsoft.Extensions.ObjectPool;
@@ -6,6 +6,8 @@ namespace Speckle.Sdk.Dependencies;
public static class Pools
{
public const int DefaultCapacity = 50;
public static Pool<Dictionary<string, object?>> ObjectDictionaries { get; } = new(new ObjectDictionaryPolicy());
private sealed class ObjectDictionaryPolicy : IPooledObjectPolicy<Dictionary<string, object?>>
@@ -25,7 +27,7 @@ public static class Pools
private sealed class ObjectDictionaryPolicy<TKey, TValue> : IPooledObjectPolicy<Dictionary<TKey, TValue>>
where TKey : notnull
{
public Dictionary<TKey, TValue> Create() => new(50);
public Dictionary<TKey, TValue> Create() => new(DefaultCapacity);
public bool Return(Dictionary<TKey, TValue> obj)
{
@@ -38,7 +40,7 @@ public static class Pools
: IPooledObjectPolicy<ConcurrentDictionary<TKey, TValue>>
where TKey : notnull
{
public ConcurrentDictionary<TKey, TValue> Create() => new(Environment.ProcessorCount, 50);
public ConcurrentDictionary<TKey, TValue> Create() => new(Environment.ProcessorCount, DefaultCapacity);
public bool Return(ConcurrentDictionary<TKey, TValue> obj)
{
@@ -49,7 +51,7 @@ public static class Pools
private sealed class ObjectListPolicy<T> : IPooledObjectPolicy<List<T>>
{
public List<T> Create() => new(50);
public List<T> Create() => new(DefaultCapacity);
public bool Return(List<T> obj)
{
@@ -1,10 +1,14 @@
namespace Speckle.Sdk.Serialisation.V2.Send;
using System.Buffers;
using Speckle.Sdk.Dependencies;
public class Batch<T>(int capacity) : IHasSize
namespace Speckle.Sdk.Serialisation.V2.Send;
public sealed class Batch<T> : IHasSize, IMemoryOwner<T>
where T : IHasSize
{
private static readonly Pool<List<T>> _pool = Pools.CreateListPool<T>();
#pragma warning disable IDE0032
private readonly List<T> _items = new(capacity);
private readonly List<T> _items = _pool.Get();
private int _batchSize;
#pragma warning restore IDE0032
@@ -22,4 +26,8 @@ public class Batch<T>(int capacity) : IHasSize
public int Size => _batchSize;
public List<T> Items => _items;
public void Dispose() => _pool.Return(_items);
public Memory<T> Memory => new(_items.ToArray());
}
@@ -1,11 +1,12 @@
using System.Threading.Channels;
using System.Buffers;
using System.Threading.Channels;
using Open.ChannelExtensions;
namespace Speckle.Sdk.Serialisation.V2.Send;
public static class ChannelExtensions
{
public static BatchingChannelReader<T, Batch<T>> BatchBySize<T>(
public static BatchingChannelReader<T, IMemoryOwner<T>> BatchBySize<T>(
this ChannelReader<T> source,
int batchSize,
bool singleReader = false,
@@ -8,8 +8,8 @@ public abstract class ChannelLoader<T>
private const int MAX_PARALLELISM_HTTP = 4;
private static readonly TimeSpan HTTP_BATCH_TIMEOUT = TimeSpan.FromSeconds(2);
private static readonly int MAX_READ_CACHE_PARALLELISM = Environment.ProcessorCount;
private const int MAX_SAVE_CACHE_BATCH = 200;
private const int MAX_SAVE_CACHE_PARALLELISM = 1;
private const int MAX_SAVE_CACHE_BATCH = 500;
private const int MAX_SAVE_CACHE_PARALLELISM = 4;
protected async Task GetAndCache(IEnumerable<string> allChildrenIds, CancellationToken cancellationToken = default) =>
await allChildrenIds
@@ -1,3 +1,4 @@
using System.Buffers;
using System.Threading.Channels;
using Open.ChannelExtensions;
using Speckle.Sdk.Serialisation.V2.Send;
@@ -13,7 +14,7 @@ public abstract class ChannelSaver<T>
private const int MAX_PARALLELISM_HTTP = 4;
private const int HTTP_CAPACITY = 500;
private const int MAX_CACHE_WRITE_PARALLELISM = 4;
private const int MAX_CACHE_BATCH = 200;
private const int MAX_CACHE_BATCH = 500;
private readonly Channel<T> _checkCacheChannel = Channel.CreateBounded<T>(
new BoundedChannelOptions(SEND_CAPACITY)
@@ -46,7 +47,13 @@ public abstract class ChannelSaver<T>
public ValueTask Save(T item, CancellationToken cancellationToken = default) =>
_checkCacheChannel.Writer.WriteAsync(item, cancellationToken);
public abstract Task<List<T>> SendToServer(Batch<T> batch, CancellationToken cancellationToken);
public async Task<IMemoryOwner<T>> SendToServer(IMemoryOwner<T> batch, CancellationToken cancellationToken)
{
await SendToServer((Batch<T>)batch, cancellationToken).ConfigureAwait(false);
return batch;
}
public abstract Task SendToServer(Batch<T> batch, CancellationToken cancellationToken);
public Task Done()
{
@@ -1,3 +1,4 @@
using System.Buffers;
using System.Threading.Channels;
using Open.ChannelExtensions;
@@ -13,20 +14,20 @@ public class SizeBatchingChannelReader<T>(
int batchSize,
bool singleReader,
bool syncCont = false
) : BatchingChannelReader<T, Batch<T>>(x => new(x), source, batchSize, singleReader, syncCont)
) : BatchingChannelReader<T, IMemoryOwner<T>>(x => new Batch<T>(), source, batchSize, singleReader, syncCont)
where T : IHasSize
{
protected override Batch<T> CreateBatch(int capacity) => new(capacity);
protected override IMemoryOwner<T> CreateBatch(int capacity) => new Batch<T>();
protected override void TrimBatch(ref Batch<T> batch, bool isVerifiedFull)
protected override void TrimBatch(ref IMemoryOwner<T> batch, bool isVerifiedFull)
{
if (!isVerifiedFull)
{
batch.TrimExcess();
((Batch<T>)batch).TrimExcess();
}
}
protected override void AddBatchItem(Batch<T> batch, T item) => batch.Add(item);
protected override void AddBatchItem(IMemoryOwner<T> batch, T item) => ((Batch<T>)batch).Add(item);
protected override int GetBatchSize(Batch<T> batch) => batch.Size;
protected override int GetBatchSize(IMemoryOwner<T> batch) => ((Batch<T>)batch).Size;
}
+26 -25
View File
@@ -19,9 +19,9 @@
},
"Microsoft.Extensions.ObjectPool": {
"type": "Direct",
"requested": "[8.0.11, )",
"resolved": "8.0.11",
"contentHash": "6ApKcHNJigXBfZa6XlDQ8feJpq7SG1ogZXg6M4FiNzgd6irs3LUAzo0Pfn4F2ZI9liGnH1XIBR/OtSbZmJAV5w=="
"requested": "[9.0.0, )",
"resolved": "9.0.0",
"contentHash": "UbsU/gYe4nv1DeqMXIVzDfNNek7Sk2kKuAOXL/Y+sLcAR0HwFUqzg1EPiU88jeHNe0g81aPvvHbvHarQr3r9IA=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
@@ -44,13 +44,13 @@
},
"Open.ChannelExtensions": {
"type": "Direct",
"requested": "[8.6.0, )",
"resolved": "8.6.0",
"contentHash": "g5axz417bA6FXifJaBlB0l62gV7dYmknXx0n8lT/LSA3+7isaGMsOjJp5J+H/yXDRe4r+KZrE+bzQcs4Ets2kA==",
"requested": "[9.0.0, )",
"resolved": "9.0.0",
"contentHash": "DP+l5S6G46wcuY4I4kNXE+RDOmJr0DKuMienOdt0mMBN9z7vmLSC8YQbqCyb9i9LNjXj1tgCx5LyitJiRr/v7g==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "8.0.0",
"System.Collections.Immutable": "8.0.0",
"System.Threading.Channels": "8.0.0"
"Microsoft.Bcl.AsyncInterfaces": "9.0.0",
"System.Collections.Immutable": "9.0.0",
"System.Threading.Channels": "9.0.0"
}
},
"Polly": {
@@ -88,10 +88,11 @@
},
"System.Threading.Channels": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "CMaFr7v+57RW7uZfZkPExsPB6ljwzhjACWW1gfU35Y56rk72B/Wu+sTqxVmGSk4SFUlPc3cjeKND0zktziyjBA==",
"requested": "[9.0.0, )",
"resolved": "9.0.0",
"contentHash": "hzACdIf1C+4Dqos5ijV404b94+LqfIC8nfS3mNpCDFWowb1N3PNfJPopneq32ahWlDeyaPZJqjBk76YFR69Rpg==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.0",
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
@@ -122,8 +123,8 @@
},
"System.Collections.Immutable": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==",
"resolved": "9.0.0",
"contentHash": "QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==",
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
@@ -160,8 +161,8 @@
"Microsoft.Bcl.AsyncInterfaces": {
"type": "CentralTransitive",
"requested": "[5.0.0, )",
"resolved": "8.0.0",
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==",
"resolved": "9.0.0",
"contentHash": "owmu2Cr3IQ8yQiBleBHlGk8dSQ12oaF2e7TpzwJKEl4m84kkZJjEY1n33L67Y3zM5jPOjmmbdHjbfiL0RqcMRQ==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -185,9 +186,9 @@
},
"Microsoft.Extensions.ObjectPool": {
"type": "Direct",
"requested": "[8.0.11, )",
"resolved": "8.0.11",
"contentHash": "6ApKcHNJigXBfZa6XlDQ8feJpq7SG1ogZXg6M4FiNzgd6irs3LUAzo0Pfn4F2ZI9liGnH1XIBR/OtSbZmJAV5w=="
"requested": "[9.0.0, )",
"resolved": "9.0.0",
"contentHash": "UbsU/gYe4nv1DeqMXIVzDfNNek7Sk2kKuAOXL/Y+sLcAR0HwFUqzg1EPiU88jeHNe0g81aPvvHbvHarQr3r9IA=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
@@ -201,9 +202,9 @@
},
"Open.ChannelExtensions": {
"type": "Direct",
"requested": "[8.6.0, )",
"resolved": "8.6.0",
"contentHash": "g5axz417bA6FXifJaBlB0l62gV7dYmknXx0n8lT/LSA3+7isaGMsOjJp5J+H/yXDRe4r+KZrE+bzQcs4Ets2kA=="
"requested": "[9.0.0, )",
"resolved": "9.0.0",
"contentHash": "DP+l5S6G46wcuY4I4kNXE+RDOmJr0DKuMienOdt0mMBN9z7vmLSC8YQbqCyb9i9LNjXj1tgCx5LyitJiRr/v7g=="
},
"Polly": {
"type": "Direct",
@@ -240,9 +241,9 @@
},
"System.Threading.Channels": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "CMaFr7v+57RW7uZfZkPExsPB6ljwzhjACWW1gfU35Y56rk72B/Wu+sTqxVmGSk4SFUlPc3cjeKND0zktziyjBA=="
"requested": "[9.0.0, )",
"resolved": "9.0.0",
"contentHash": "hzACdIf1C+4Dqos5ijV404b94+LqfIC8nfS3mNpCDFWowb1N3PNfJPopneq32ahWlDeyaPZJqjBk76YFR69Rpg=="
},
"ILRepack": {
"type": "Transitive",
+13 -4
View File
@@ -21,11 +21,13 @@ using Stream = System.IO.Stream;
namespace Speckle.Sdk.Credentials;
public partial interface IAccountManager : IDisposable;
/// <summary>
/// Manage accounts locally for desktop applications.
/// </summary>
[GenerateAutoInterface]
public class AccountManager(
public sealed class AccountManager(
ISpeckleApplication application,
ILogger<AccountManager> logger,
ISpeckleHttp speckleHttp,
@@ -40,6 +42,13 @@ public class AccountManager(
"AccountAddFlow"
);
[AutoInterfaceIgnore]
public void Dispose()
{
_accountStorage.Dispose();
_accountAddLockStorage.Dispose();
}
/// <summary>
/// Gets the basic information about a server.
/// </summary>
@@ -321,7 +330,7 @@ public class AccountManager(
{
static bool IsInvalid(Account ac) => ac.userInfo == null || ac.serverInfo == null;
var sqlAccounts = _accountStorage.GetAllObjects().Select(x => JsonConvert.DeserializeObject<Account>(x));
var sqlAccounts = _accountStorage.GetAllObjects().Select(x => JsonConvert.DeserializeObject<Account>(x.Json));
var localAccounts = GetLocalAccounts();
foreach (var acc in sqlAccounts)
@@ -642,7 +651,7 @@ public class AccountManager(
}
// this uses the SQLite transport to store locks
var lockIds = _accountAddLockStorage.GetAllObjects().OrderByDescending(d => d).ToList();
var lockIds = _accountAddLockStorage.GetAllObjects().Select(x => x.Id).OrderByDescending(d => d).ToList();
var now = DateTime.Now;
foreach (var l in lockIds)
{
@@ -674,7 +683,7 @@ public class AccountManager(
{
s_isAddingAccount = false;
// make sure all old locks are removed
foreach (var id in _accountAddLockStorage.GetAllObjects())
foreach (var (id, _) in _accountAddLockStorage.GetAllObjects())
{
_accountAddLockStorage.DeleteObject(id);
}
@@ -0,0 +1,104 @@
using System.Collections.Concurrent;
using Microsoft.Data.Sqlite;
namespace Speckle.Sdk.SQLite;
//inspired by https://github.com/neosmart/SqliteCache/blob/master/SqliteCache/DbCommandPool.cs
public sealed class CacheDbCommandPool : IDisposable
{
private readonly ConcurrentBag<SqliteCommand>[] _commands = new ConcurrentBag<SqliteCommand>[CacheDbCommands.Count];
private readonly ConcurrentBag<SqliteConnection> _connections = new();
private readonly string _connectionString;
public CacheDbCommandPool(string connectionString, int concurrency)
{
_connectionString = connectionString;
for (int i = 0; i < _commands.Length; ++i)
{
_commands[i] = new ConcurrentBag<SqliteCommand>();
}
for (int i = 0; i < concurrency; ++i)
{
var connection = new SqliteConnection(_connectionString);
connection.Open();
_connections.Add(connection);
}
}
public void Use(CacheOperation type, Action<SqliteCommand> handler) =>
Use(
type,
cmd =>
{
handler(cmd);
return true;
}
);
private T Use<T>(Func<SqliteConnection, T> handler)
{
if (!_connections.TryTake(out var db))
{
db = new SqliteConnection(_connectionString);
db.Open();
}
try
{
return handler(db);
}
catch (SqliteException se)
{
throw SqLiteJsonCacheException.Create(se);
}
finally
{
_connections.Add(db);
}
}
public T Use<T>(CacheOperation type, Func<SqliteCommand, T> handler) =>
Use(conn =>
{
var pool = _commands[(int)type];
if (!pool.TryTake(out var command))
{
#pragma warning disable CA2100
command = new SqliteCommand(CacheDbCommands.Commands[(int)type], conn);
#pragma warning restore CA2100
}
try
{
command.Connection = conn;
return handler(command);
}
catch (SqliteException se)
{
throw SqLiteJsonCacheException.Create(se);
}
finally
{
command.Connection = null;
command.Parameters.Clear();
pool.Add(command);
}
});
public void Dispose()
{
foreach (var pool in _commands)
{
while (pool.TryTake(out var cmd))
{
cmd.Dispose();
}
}
foreach (var conn in _connections)
{
conn.Close();
conn.Dispose();
}
}
}
+35
View File
@@ -0,0 +1,35 @@
namespace Speckle.Sdk.SQLite;
public enum CacheOperation
{
InsertOrIgnore,
InsertOrReplace,
Has,
Get,
Delete,
GetAll,
BulkInsertOrIgnore,
}
public static class CacheDbCommands
{
public static readonly string[] Commands;
public static readonly int Count = Enum.GetValues(typeof(CacheOperation)).Length;
#pragma warning disable CA1810
static CacheDbCommands()
#pragma warning restore CA1810
{
Commands = new string[Count];
Commands[(int)CacheOperation.InsertOrIgnore] =
"INSERT OR IGNORE INTO objects(hash, content) VALUES(@hash, @content)";
Commands[(int)CacheOperation.InsertOrReplace] = "REPLACE INTO objects(hash, content) VALUES(@hash, @content)";
Commands[(int)CacheOperation.Has] = "SELECT 1 FROM objects WHERE hash = @hash LIMIT 1";
Commands[(int)CacheOperation.Get] = "SELECT content FROM objects WHERE hash = @hash LIMIT 1";
Commands[(int)CacheOperation.Delete] = "DELETE FROM objects WHERE hash = @hash";
Commands[(int)CacheOperation.GetAll] = "SELECT hash, content FROM objects";
Commands[(int)CacheOperation.BulkInsertOrIgnore] = "INSERT OR IGNORE INTO objects (hash, content) VALUES ";
}
}
+100 -84
View File
@@ -1,19 +1,28 @@
using System.Text;
using Microsoft.Data.Sqlite;
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Dependencies;
namespace Speckle.Sdk.SQLite;
public partial interface ISqLiteJsonCacheManager : IDisposable;
[GenerateAutoInterface]
public class SqLiteJsonCacheManager : ISqLiteJsonCacheManager
public sealed class SqLiteJsonCacheManager : ISqLiteJsonCacheManager
{
private readonly string _connectionString;
private readonly CacheDbCommandPool _pool;
public SqLiteJsonCacheManager(string rootPath)
public SqLiteJsonCacheManager(string connectionString, int concurrency)
{
_connectionString = $"Data Source={rootPath};";
_connectionString = connectionString;
Initialize();
_pool = new CacheDbCommandPool(_connectionString, concurrency);
}
[AutoInterfaceIgnore]
public void Dispose() => _pool.Dispose();
private void Initialize()
{
// NOTE: used for creating partioned object tables.
@@ -57,100 +66,107 @@ public class SqLiteJsonCacheManager : ISqLiteJsonCacheManager
using SqliteCommand cmd4 = new("PRAGMA page_size = 32768;", c);
cmd4.ExecuteNonQuery();
c.Close();
}
public IEnumerable<string> GetAllObjects()
{
using var c = new SqliteConnection(_connectionString);
c.Open();
using var command = new SqliteCommand("SELECT * FROM objects", c);
public IReadOnlyCollection<(string Id, string Json)> GetAllObjects() =>
_pool.Use(
CacheOperation.GetAll,
command =>
{
var list = new HashSet<(string, string)>();
using var reader = command.ExecuteReader();
while (reader.Read())
{
list.Add((reader.GetString(0), reader.GetString(1)));
}
return list;
}
);
using var reader = command.ExecuteReader();
while (reader.Read())
{
yield return reader.GetString(1);
}
}
public void DeleteObject(string id) =>
_pool.Use(
CacheOperation.Delete,
command =>
{
command.Parameters.AddWithValue("@hash", id);
command.ExecuteNonQuery();
}
);
public void DeleteObject(string id)
{
using var c = new SqliteConnection(_connectionString);
c.Open();
using var command = new SqliteCommand("DELETE FROM objects WHERE hash = @hash", c);
command.Parameters.AddWithValue("@hash", id);
command.ExecuteNonQuery();
}
public string? GetObject(string id)
{
using var c = new SqliteConnection(_connectionString);
c.Open();
using var command = new SqliteCommand("SELECT * FROM objects WHERE hash = @hash LIMIT 1 ", c);
command.Parameters.AddWithValue("@hash", id);
using var reader = command.ExecuteReader();
if (reader.Read())
{
return reader.GetString(1);
}
return null; // pass on the duty of null checks to consumers
}
public string? GetObject(string id) =>
_pool.Use(
CacheOperation.Get,
command =>
{
command.Parameters.AddWithValue("@hash", id);
return (string?)command.ExecuteScalar();
}
);
//This does an insert or ignores if already exists
public void SaveObject(string id, string json)
{
using var c = new SqliteConnection(_connectionString);
c.Open();
const string COMMAND_TEXT = "INSERT OR IGNORE INTO objects(hash, content) VALUES(@hash, @content)";
using var command = new SqliteCommand(COMMAND_TEXT, c);
command.Parameters.AddWithValue("@hash", id);
command.Parameters.AddWithValue("@content", json);
command.ExecuteNonQuery();
}
public void SaveObject(string id, string json) =>
_pool.Use(
CacheOperation.InsertOrIgnore,
command =>
{
command.Parameters.AddWithValue("@hash", id);
command.Parameters.AddWithValue("@content", json);
command.ExecuteNonQuery();
}
);
//This does an insert or replaces if already exists
public void UpdateObject(string id, string json)
{
using var c = new SqliteConnection(_connectionString);
c.Open();
const string COMMAND_TEXT = "REPLACE INTO objects(hash, content) VALUES(@hash, @content)";
using var command = new SqliteCommand(COMMAND_TEXT, c);
command.Parameters.AddWithValue("@hash", id);
command.Parameters.AddWithValue("@content", json);
command.ExecuteNonQuery();
}
public void UpdateObject(string id, string json) =>
_pool.Use(
CacheOperation.InsertOrReplace,
command =>
{
command.Parameters.AddWithValue("@hash", id);
command.Parameters.AddWithValue("@content", json);
command.ExecuteNonQuery();
}
);
public void SaveObjects(IEnumerable<(string id, string json)> items)
{
using var c = new SqliteConnection(_connectionString);
c.Open();
using var t = c.BeginTransaction();
const string COMMAND_TEXT = "INSERT OR IGNORE INTO objects(hash, content) VALUES(@hash, @content)";
public void SaveObjects(IEnumerable<(string id, string json)> items) =>
_pool.Use(
CacheOperation.BulkInsertOrIgnore,
cmd =>
{
CreateBulkInsert(cmd, items);
return cmd.ExecuteNonQuery();
}
);
using var command = new SqliteCommand(COMMAND_TEXT, c);
command.Transaction = t;
var idParam = command.Parameters.Add("@hash", SqliteType.Text);
var jsonParam = command.Parameters.Add("@content", SqliteType.Text);
private void CreateBulkInsert(SqliteCommand cmd, IEnumerable<(string id, string json)> items)
{
StringBuilder sb = Pools.StringBuilders.Get();
sb.AppendLine(CacheDbCommands.Commands[(int)CacheOperation.BulkInsertOrIgnore]);
int i = 0;
foreach (var (id, json) in items)
{
idParam.Value = id;
jsonParam.Value = json;
command.ExecuteNonQuery();
sb.Append($"(@key{i}, @value{i}),");
cmd.Parameters.AddWithValue($"@key{i}", id);
cmd.Parameters.AddWithValue($"@value{i}", json);
i++;
}
t.Commit();
sb.Remove(sb.Length - 1, 1);
sb.Append(';');
#pragma warning disable CA2100
cmd.CommandText = sb.ToString();
#pragma warning restore CA2100
Pools.StringBuilders.Return(sb);
}
public bool HasObject(string objectId)
{
using var c = new SqliteConnection(_connectionString);
c.Open();
const string COMMAND_TEXT = "SELECT 1 FROM objects WHERE hash = @hash LIMIT 1 ";
using var command = new SqliteCommand(COMMAND_TEXT, c);
command.Parameters.AddWithValue("@hash", objectId);
using var reader = command.ExecuteReader();
bool rowFound = reader.Read();
return rowFound;
}
public bool HasObject(string objectId) =>
_pool.Use(
CacheOperation.Has,
command =>
{
command.Parameters.AddWithValue("@hash", objectId);
using var reader = command.ExecuteReader();
bool rowFound = reader.Read();
return rowFound;
}
);
}
@@ -0,0 +1,35 @@
using Microsoft.Data.Sqlite;
namespace Speckle.Sdk.SQLite;
public class SqLiteJsonCacheException : SpeckleException
{
public SqLiteJsonCacheException() { }
public SqLiteJsonCacheException(string message)
: base(message) { }
public SqLiteJsonCacheException(string message, Exception inner)
: base(message, inner) { }
public static SqLiteJsonCacheException Create(SqliteException inner)
{
if (!SqliteExceptions.SqliteErrorCodes.TryGetValue(inner.SqliteErrorCode, out string? errorMessage))
{
errorMessage = $"An error occurred while executing a SQLite command: {inner.SqliteErrorCode}";
}
if (
!SqliteExceptions.SqliteExtendedResultCodes.TryGetValue(
inner.SqliteExtendedErrorCode,
out string? detailedMessage
)
)
{
detailedMessage = $"Detail: {inner.SqliteExtendedErrorCode}";
}
return new SqLiteJsonCacheException(
$"An error occured with the SQLite cache: {inner.Message}{Environment.NewLine}{errorMessage}{Environment.NewLine}{detailedMessage}",
inner
);
}
}
@@ -7,10 +7,14 @@ namespace Speckle.Sdk.SQLite;
[GenerateAutoInterface]
public class SqLiteJsonCacheManagerFactory : ISqLiteJsonCacheManagerFactory
{
private ISqLiteJsonCacheManager Create(string path) => new SqLiteJsonCacheManager(path);
public const int INITIAL_CONCURRENCY = 4;
private ISqLiteJsonCacheManager Create(string path, int concurrency) =>
new SqLiteJsonCacheManager($"Data Source={path};", concurrency);
public ISqLiteJsonCacheManager CreateForUser(string scope) =>
Create(Path.Combine(SpecklePathProvider.UserApplicationDataPath(), "Speckle", $"{scope}.db"));
Create(Path.Combine(SpecklePathProvider.UserApplicationDataPath(), "Speckle", $"{scope}.db"), 1);
public ISqLiteJsonCacheManager CreateFromStream(string streamId) => Create(SqlitePaths.GetDBPath(streamId));
public ISqLiteJsonCacheManager CreateFromStream(string streamId) =>
Create(SqlitePaths.GetDBPath(streamId), INITIAL_CONCURRENCY);
}
+107
View File
@@ -0,0 +1,107 @@
namespace Speckle.Sdk.SQLite;
internal static class SqliteExceptions
{
public static readonly IReadOnlyDictionary<int, string> SqliteErrorCodes = new Dictionary<int, string>
{
{ 0, "Successful result" },
{ 1, "Generic error" },
{ 2, "Internal logic error in SQLite" },
{ 3, "Access permission denied" },
{ 4, "Callback routine requested an abort" },
{ 5, "The database file is locked" },
{ 6, "A table in the database is locked" },
{ 7, "A malloc() failed" },
{ 8, "Attempt to write a readonly database" },
{ 9, "Operation terminated by sqlite3_interrupt()" },
{ 10, "Some kind of disk I/O error occurred" },
{ 11, "The database disk image is malformed" },
{ 12, "Unknown opcode in sqlite3_file_control()" },
{ 13, "Insertion failed because database is full" },
{ 14, "Unable to open the database file" },
{ 15, "Database lock protocol error" },
{ 16, "Internal use only" },
{ 17, "The database schema changed" },
{ 18, "String or BLOB exceeds size limit" },
{ 19, "Abort due to constraint violation" },
{ 20, "Data type mismatch" },
{ 21, "Library used incorrectly" },
{ 22, "Uses OS features not supported on host" },
{ 23, "Authorization denied" },
{ 24, "Not used" },
{ 25, "2nd parameter to sqlite3_bind out of range" },
{ 26, "File opened that is not a database file" },
{ 27, "Notifications from sqlite3_log()" },
{ 28, "Warnings from sqlite3_log()" },
{ 100, "sqlite3_step() has another row ready" },
{ 101, "sqlite3_step() has finished executing" },
};
public static readonly IReadOnlyDictionary<int, string> SqliteExtendedResultCodes = new Dictionary<int, string>()
{
{ 516, "SQLITE_ABORT_ROLLBACK: A rollback occurred due to a constraint violation." },
{ 279, "SQLITE_AUTH_USER: Authorization denied by a user authentication callback." },
{ 261, "SQLITE_BUSY_RECOVERY: The database file is locked because another connection is recovering the WAL." },
{ 517, "SQLITE_BUSY_SNAPSHOT: The database file is locked because another connection has a conflicting snapshot." },
{ 773, "SQLITE_BUSY_TIMEOUT: A blocking operation was interrupted by a call to sqlite3_interrupt()." },
{ 1038, "SQLITE_CANTOPEN_CONVPATH: Unable to open the database file due to a conversion error." },
{
1294,
"SQLITE_CANTOPEN_DIRTYWAL: The database file cannot be opened because the Write-Ahead Log contains uncommitted changes."
},
{ 782, "SQLITE_CANTOPEN_FULLPATH: Unable to open the database file with the full pathname." },
{ 526, "SQLITE_CANTOPEN_ISDIR: The database file cannot be opened because it is a directory." },
{
270,
"SQLITE_CANTOPEN_NOTEMPDIR: Unable to open a temporary database file because a temporary directory is not available."
},
{ 1550, "SQLITE_CANTOPEN_SYMLINK: The database file cannot be opened because it is a symbolic link." },
{ 275, "SQLITE_CONSTRAINT_CHECK: A CHECK constraint failed." },
{ 531, "SQLITE_CONSTRAINT_COMMITHOOK: A commit hook caused the transaction to roll back." },
{ 3091, "SQLITE_CONSTRAINT_DATATYPE: A datatype mismatch occurred." },
{ 787, "SQLITE_CONSTRAINT_FOREIGNKEY: A foreign key constraint failed." },
{ 1043, "SQLITE_CONSTRAINT_FUNCTION: A function constraint failed." },
{ 1299, "SQLITE_CONSTRAINT_NOTNULL: A NOT NULL constraint failed." },
{ 1555, "SQLITE_CONSTRAINT_PRIMARYKEY: A PRIMARY KEY constraint failed." },
{ 1803, "SQLITE_CONSTRAINT_TRIGGER: A trigger constraint failed." },
{ 2059, "SQLITE_CONSTRAINT_UNIQUE: A UNIQUE constraint failed." },
{ 2315, "SQLITE_CONSTRAINT_VTAB: A virtual table constraint failed." },
{ 2571, "SQLITE_CONSTRAINT_ROWID: A rowid constraint failed." },
{ 1034, "SQLITE_IOERR_FSYNC: An I/O error occurred during the fsync() system call." },
{ 6410, "SQLITE_IOERR_GETTEMPPATH: An I/O error occurred while trying to get the temporary file path." },
{ 3850, "SQLITE_IOERR_LOCK: An I/O error occurred while trying to lock the database file." },
{ 6154, "SQLITE_IOERR_MMAP: An I/O error occurred during memory mapping." },
{ 3082, "SQLITE_IOERR_NOMEM: An I/O error occurred due to a memory allocation failure." },
{ 2314, "SQLITE_IOERR_RDLOCK: An I/O error occurred while trying to read-lock the database file." },
{ 266, "SQLITE_IOERR_READ: An I/O error occurred while reading from the database file." },
{ 7946, "SQLITE_IOERR_ROLLBACK_ATOMIC: An I/O error occurred during an atomic rollback." },
{ 5642, "SQLITE_IOERR_SEEK: An I/O error occurred while seeking in the database file." },
{ 5130, "SQLITE_IOERR_SHMLOCK: An I/O error occurred while locking a shared memory segment." },
{ 5386, "SQLITE_IOERR_SHMMAP: An I/O error occurred while mapping a shared memory segment." },
{ 4618, "SQLITE_IOERR_SHMOPEN: An I/O error occurred while opening a shared memory segment." },
{ 4874, "SQLITE_IOERR_SHMSIZE: An I/O error occurred while setting the size of a shared memory segment." },
{ 522, "SQLITE_IOERR_SHORT_READ: An I/O error occurred due to a short read." },
{ 1546, "SQLITE_IOERR_TRUNCATE: An I/O error occurred while truncating the database file." },
{ 2058, "SQLITE_IOERR_UNLOCK: An I/O error occurred while unlocking the database file." },
{ 6922, "SQLITE_IOERR_VNODE: A virtual node I/O error occurred." },
{ 778, "SQLITE_IOERR_WRITE: An I/O error occurred while writing to the database file." },
{
262,
"SQLITE_LOCKED_SHAREDCACHE: A write operation could not continue due to a conflict within the shared cache."
},
{ 518, "SQLITE_LOCKED_VTAB: A virtual table is locked." },
{ 539, "SQLITE_NOTICE_RECOVER_ROLLBACK: A rollback was performed to recover from a previous error." },
{ 283, "SQLITE_NOTICE_RECOVER_WAL: Recovery was performed from the Write-Ahead Log." },
{ 256, "SQLITE_OK_LOAD_PERMANENTLY: Operation completed successfully; the extension was loaded permanently." },
{
1288,
"SQLITE_READONLY_CANTINIT: Attempt to write to a read-only database failed because initialization is not allowed."
},
{ 520, "SQLITE_READONLY_CANTLOCK: A read-only database cannot be locked." },
{ 1032, "SQLITE_READONLY_DBMOVED: The database file has been moved, making it read-only." },
{ 1544, "SQLITE_READONLY_DIRECTORY: The database is read-only because it is a directory." },
{ 264, "SQLITE_READONLY_RECOVERY: The database is read-only due to recovery mode." },
{ 776, "SQLITE_READONLY_ROLLBACK: The database is read-only because a rollback is required." },
{ 284, "SQLITE_WARNING_AUTOINDEX: Automatic indexing is in use." },
};
}
@@ -5,9 +5,11 @@ using Speckle.Sdk.Transports;
namespace Speckle.Sdk.Serialisation.V2;
public class DummySqLiteJsonCacheManager : ISqLiteJsonCacheManager
public sealed class DummySqLiteJsonCacheManager : ISqLiteJsonCacheManager
{
public IEnumerable<string> GetAllObjects() => throw new NotImplementedException();
public void Dispose() { }
public IReadOnlyCollection<(string, string)> GetAllObjects() => throw new NotImplementedException();
public void DeleteObject(string id) => throw new NotImplementedException();
@@ -13,6 +13,8 @@ public record DeserializeProcessOptions(
bool SkipInvalidConverts = false
);
public partial interface IDeserializeProcess : IDisposable;
[GenerateAutoInterface]
public sealed class DeserializeProcess(
IProgress<ProgressArgs>? progress,
@@ -30,6 +32,9 @@ public sealed class DeserializeProcess(
public IReadOnlyDictionary<string, Base> BaseCache => _baseCache;
public long Total { get; private set; }
[AutoInterfaceIgnore]
public void Dispose() => objectLoader.Dispose();
public async Task<Base> Deserialize(string rootId, CancellationToken cancellationToken)
{
var (rootJson, childrenIds) = await objectLoader
@@ -9,6 +9,8 @@ using Speckle.Sdk.Transports;
namespace Speckle.Sdk.Serialisation.V2.Receive;
public partial interface IObjectLoader : IDisposable;
[GenerateAutoInterface]
public sealed class ObjectLoader(
ISqLiteJsonCacheManager sqLiteJsonCacheManager,
@@ -19,8 +21,13 @@ public sealed class ObjectLoader(
private int? _allChildrenCount;
private long _checkCache;
private long _cached;
private long _downloaded;
private long _totalToDownload;
private DeserializeProcessOptions _options = new(false);
[AutoInterfaceIgnore]
public void Dispose() => sqLiteJsonCacheManager.Dispose();
public async Task<(string, IReadOnlyCollection<string>)> GetAndCache(
string rootId,
DeserializeProcessOptions options,
@@ -67,6 +74,7 @@ public sealed class ObjectLoader(
progress?.Report(new(ProgressEvent.CacheCheck, _checkCache, _allChildrenCount));
if (!_options.SkipCache && !sqLiteJsonCacheManager.HasObject(id))
{
Interlocked.Increment(ref _totalToDownload);
return id;
}
@@ -81,6 +89,8 @@ public sealed class ObjectLoader(
var (id, json) in serverObjectManager.DownloadObjects(ids.Select(x => x.NotNull()).ToList(), progress, default)
)
{
Interlocked.Increment(ref _downloaded);
progress?.Report(new(ProgressEvent.DownloadObjects, _downloaded, _totalToDownload));
toCache.Add(new(new(id), new(json), true, null));
}
@@ -0,0 +1,19 @@
using System.Text;
namespace Speckle.Sdk.Serialisation.V2.Send;
public readonly record struct BaseItem(Id Id, Json Json, bool NeedsStorage, Dictionary<Id, int>? Closures) : IHasSize
{
public int Size { get; } = Encoding.UTF8.GetByteCount(Json.Value);
public bool Equals(BaseItem? other)
{
if (other is null)
{
return false;
}
return string.Equals(Id.Value, other.Value.Id.Value, StringComparison.OrdinalIgnoreCase);
}
public override int GetHashCode() => Id.GetHashCode();
}
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
namespace Speckle.Sdk.Serialisation.V2.Send;
@@ -1,5 +1,4 @@
using System.Collections.Concurrent;
using System.Text;
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Common;
using Speckle.Sdk.Dependencies;
@@ -23,22 +22,6 @@ public readonly record struct SerializeProcessResults(
IReadOnlyDictionary<Id, ObjectReference> ConvertedReferences
);
public readonly record struct BaseItem(Id Id, Json Json, bool NeedsStorage, Closures? Closures) : IHasSize
{
public int Size { get; } = Encoding.UTF8.GetByteCount(Json.Value);
public bool Equals(BaseItem? other)
{
if (other is null)
{
return false;
}
return string.Equals(Id.Value, other.Value.Id.Value, StringComparison.OrdinalIgnoreCase);
}
public override int GetHashCode() => Id.GetHashCode();
}
public partial interface ISerializeProcess : IDisposable;
[GenerateAutoInterface]
@@ -77,6 +60,7 @@ public sealed class SerializeProcess(
{
_highest.Dispose();
_belowNormal.Dispose();
sqLiteJsonCacheManager.Dispose();
}
public async Task<SerializeProcessResults> Serialize(Base root, CancellationToken cancellationToken)
@@ -93,7 +77,8 @@ public sealed class SerializeProcess(
);
}
await Traverse(root, true, cancellationToken).ConfigureAwait(false);
await Traverse(root, cancellationToken).ConfigureAwait(false);
await Done().ConfigureAwait(true);
await channelTask.ConfigureAwait(false);
await findTotalObjectsTask.ConfigureAwait(false);
return new(root.id.NotNull(), _objectReferences.Freeze());
@@ -109,7 +94,7 @@ public sealed class SerializeProcess(
}
}
private async Task<Dictionary<Id, NodeInfo>> Traverse(Base obj, bool isEnd, CancellationToken cancellationToken)
private async Task<Dictionary<Id, NodeInfo>> Traverse(Base obj, CancellationToken cancellationToken)
{
var tasks = new List<Task<Dictionary<Id, NodeInfo>>>();
foreach (var child in baseChildFinder.GetChildren(obj))
@@ -118,7 +103,7 @@ public sealed class SerializeProcess(
var tmp = child;
var t = Task
.Factory.StartNew(
() => Traverse(tmp, false, cancellationToken),
() => Traverse(tmp, cancellationToken),
cancellationToken,
TaskCreationOptions.AttachedToParent | TaskCreationOptions.PreferFairness,
_belowNormal
@@ -161,12 +146,6 @@ public sealed class SerializeProcess(
}
}
_childClosurePool.Return(childClosures);
if (isEnd)
{
await Done().ConfigureAwait(true);
}
return currentClosures;
}
@@ -223,7 +202,7 @@ public sealed class SerializeProcess(
return new BaseItem(id, json, true, closures);
}
public override async Task<List<BaseItem>> SendToServer(Batch<BaseItem> batch, CancellationToken cancellationToken)
public override async Task SendToServer(Batch<BaseItem> batch, CancellationToken cancellationToken)
{
if (!_options.SkipServer && batch.Items.Count != 0)
{
@@ -238,9 +217,7 @@ public sealed class SerializeProcess(
Interlocked.Exchange(ref _uploaded, _uploaded + batch.Items.Count);
}
progress?.Report(new(ProgressEvent.UploadedObjects, _uploaded, null));
return objectBatch;
}
return batch.Items;
}
public override void SaveToCache(List<BaseItem> batch)
@@ -1,5 +1,3 @@
using Speckle.Sdk.Helpers;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Serialisation.V2.Receive;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.SQLite;
@@ -23,20 +21,14 @@ public interface ISerializeProcessFactory
IProgress<ProgressArgs>? progress,
DeserializeProcessOptions? options = null
);
public ISerializeProcess CreateSerializeProcess(
SerializeProcessOptions? options = null,
IProgress<ProgressArgs>? progress = null
);
}
public class SerializeProcessFactory(
ISpeckleHttp speckleHttp,
ISdkActivityFactory activityFactory,
IBaseChildFinder baseChildFinder,
IObjectSerializerFactory objectSerializerFactory,
IObjectDeserializerFactory objectDeserializerFactory,
ISqLiteJsonCacheManagerFactory sqLiteJsonCacheManagerFactory
ISqLiteJsonCacheManagerFactory sqLiteJsonCacheManagerFactory,
IServerObjectManagerFactory serverObjectManagerFactory
) : ISerializeProcessFactory
{
public ISerializeProcess CreateSerializeProcess(
@@ -48,24 +40,7 @@ public class SerializeProcessFactory(
)
{
var sqLiteJsonCacheManager = sqLiteJsonCacheManagerFactory.CreateFromStream(streamId);
var serverObjectManager = new ServerObjectManager(speckleHttp, activityFactory, url, streamId, authorizationToken);
return new SerializeProcess(
progress,
sqLiteJsonCacheManager,
serverObjectManager,
baseChildFinder,
objectSerializerFactory,
options
);
}
public ISerializeProcess CreateSerializeProcess(
SerializeProcessOptions? options = null,
IProgress<ProgressArgs>? progress = null
)
{
var sqLiteJsonCacheManager = new DummySqLiteJsonCacheManager();
var serverObjectManager = new DummySendServerObjectManager();
var serverObjectManager = serverObjectManagerFactory.Create(url, streamId, authorizationToken);
return new SerializeProcess(
progress,
sqLiteJsonCacheManager,
@@ -85,9 +60,12 @@ public class SerializeProcessFactory(
)
{
var sqLiteJsonCacheManager = sqLiteJsonCacheManagerFactory.CreateFromStream(streamId);
var serverObjectManager = new ServerObjectManager(speckleHttp, activityFactory, url, streamId, authorizationToken);
var serverObjectManager = serverObjectManagerFactory.Create(url, streamId, authorizationToken);
#pragma warning disable CA2000
//owned by process, refactor later
var objectLoader = new ObjectLoader(sqLiteJsonCacheManager, serverObjectManager, progress);
#pragma warning restore CA2000
return new DeserializeProcess(progress, objectLoader, objectDeserializerFactory, options);
}
}
@@ -0,0 +1,13 @@
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Helpers;
using Speckle.Sdk.Logging;
namespace Speckle.Sdk.Serialisation.V2;
[GenerateAutoInterface]
public class ServerObjectManagerFactory(ISpeckleHttp speckleHttp, ISdkActivityFactory activityFactory)
: IServerObjectManagerFactory
{
public IServerObjectManager Create(Uri url, string streamId, string? authorizationToken, int timeoutSeconds = 120) =>
new ServerObjectManager(speckleHttp, activityFactory, url, streamId, authorizationToken, timeoutSeconds);
}
@@ -13,6 +13,7 @@ public enum ProgressEvent
CacheCheck,
DownloadBytes,
DownloadObjects,
DeserializeObject,
SerializeObject, // old
@@ -0,0 +1,3 @@
using Xunit;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
@@ -1,87 +1,78 @@
using NUnit.Framework;
using FluentAssertions;
using Speckle.Objects.Geometry;
using Speckle.Sdk.Common;
using Xunit;
namespace Speckle.Objects.Tests.Unit.Geometry;
[TestFixture, TestOf(typeof(Arc))]
public class ArcTests
{
private Plane TestPlaneCounterClockwise
{
get
private Plane TestPlaneCounterClockwise =>
new()
{
const string UNITS = Units.Meters;
return new()
{
origin = new Point(0, 0, 0, UNITS),
normal = new Vector(0, 0, 1, UNITS),
xdir = new Vector(1, 0, 0, UNITS),
ydir = new Vector(0, 1, 0, UNITS),
units = UNITS,
};
}
}
origin = new Point(0, 0, 0, Units.Meters),
normal = new Vector(0, 0, 1, Units.Meters),
xdir = new Vector(1, 0, 0, Units.Meters),
ydir = new Vector(0, 1, 0, Units.Meters),
units = Units.Meters,
};
private Plane TestPlaneClockwise
{
get
private Plane TestPlaneClockwise =>
new()
{
const string UNITS = Units.Meters;
return new()
{
origin = new Point(0, 0, 0, UNITS),
normal = new Vector(0, 0, -1, UNITS),
xdir = new Vector(-1, 0, 0, UNITS),
ydir = new Vector(0, 1, 0, UNITS),
units = UNITS,
};
}
}
origin = new Point(0, 0, 0, Units.Meters),
normal = new Vector(0, 0, -1, Units.Meters),
xdir = new Vector(-1, 0, 0, Units.Meters),
ydir = new Vector(0, 1, 0, Units.Meters),
units = Units.Meters,
};
[Test]
[Fact]
public void CanCreateArc_HalfCircle_CounterClockwise()
{
const string UNITS = Units.Meters;
var counterClockwiseArc = new Arc()
{
plane = TestPlaneCounterClockwise,
startPoint = new Point(1, 0, 0, UNITS),
endPoint = new Point(-1, 0, 0, UNITS),
midPoint = new Point(0, 1, 0, UNITS),
units = UNITS,
startPoint = new Point(1, 0, 0, Units.Meters),
endPoint = new Point(-1, 0, 0, Units.Meters),
midPoint = new Point(0, 1, 0, Units.Meters),
units = Units.Meters,
};
Assert.That(Point.Distance(counterClockwiseArc.midPoint, new Point(0, 1, 0, UNITS)), Is.EqualTo(0).Within(0.0001));
Assert.That(
Point.Distance(counterClockwiseArc.plane.origin, new Point(0, 0, 0, UNITS)),
Is.EqualTo(0).Within(0.0001)
);
Assert.That(counterClockwiseArc.measure - Math.PI, Is.EqualTo(0).Within(0.0001));
Assert.That(counterClockwiseArc.radius, Is.EqualTo(1).Within(0.0001));
Assert.That(counterClockwiseArc.length, Is.EqualTo(Math.PI).Within(0.0001));
Point.Distance(counterClockwiseArc.midPoint, new Point(0, 1, 0, Units.Meters)).Should().BeApproximately(0, 0.0001);
Point
.Distance(counterClockwiseArc.plane.origin, new Point(0, 0, 0, Units.Meters))
.Should()
.BeApproximately(0, 0.0001);
(counterClockwiseArc.measure - Math.PI).Should().BeApproximately(0, 0.0001);
counterClockwiseArc.radius.Should().BeApproximately(1, 0.0001);
counterClockwiseArc.length.Should().BeApproximately(Math.PI, 0.0001);
}
[Test]
[Fact]
public void CanCreateArc_HalfCircle_Clockwise()
{
const string UNITS = Units.Meters;
var counterClockwiseArc = new Arc()
var clockwiseArc = new Arc()
{
plane = TestPlaneClockwise,
endPoint = new Point(1, 0, 0, UNITS),
startPoint = new Point(-1, 0, 0, UNITS),
midPoint = new Point(0, 1, 0, UNITS),
units = UNITS,
endPoint = new Point(1, 0, 0, Units.Meters),
startPoint = new Point(-1, 0, 0, Units.Meters),
midPoint = new Point(0, 1, 0, Units.Meters),
units = Units.Meters,
};
Assert.That(Point.Distance(counterClockwiseArc.midPoint, new Point(0, 1, 0, UNITS)), Is.EqualTo(0).Within(0.0001));
Assert.That(
Point.Distance(counterClockwiseArc.plane.origin, new Point(0, 0, 0, UNITS)),
Is.EqualTo(0).Within(0.0001)
);
Assert.That(counterClockwiseArc.measure - Math.PI, Is.EqualTo(0).Within(0.0001));
Assert.That(counterClockwiseArc.radius, Is.EqualTo(1).Within(0.0001));
Assert.That(counterClockwiseArc.length, Is.EqualTo(Math.PI).Within(0.0001));
Point.Distance(clockwiseArc.midPoint, new Point(0, 1, 0, Units.Meters)).Should().BeApproximately(0, 0.0001);
Point.Distance(clockwiseArc.plane.origin, new Point(0, 0, 0, Units.Meters)).Should().BeApproximately(0, 0.0001);
(clockwiseArc.measure - Math.PI).Should().BeApproximately(0, 0.0001);
clockwiseArc.radius.Should().BeApproximately(1, 0.0001);
clockwiseArc.length.Should().BeApproximately(Math.PI, 0.0001);
}
}
@@ -1,29 +1,23 @@
using NUnit.Framework;
using FluentAssertions;
using Speckle.Objects.Geometry;
using Speckle.Sdk.Common;
using Xunit;
namespace Speckle.Objects.Tests.Unit.Geometry;
[TestFixture, TestOf(typeof(Box))]
public class BoxTests
{
private Plane TestPlane
{
get
private Plane TestPlane =>
new()
{
const string UNITS = Units.Meters;
return new()
{
origin = new Point(0, 0, 0, UNITS),
normal = new Vector(0, 0, 1, UNITS),
xdir = new Vector(1, 0, 0, UNITS),
ydir = new Vector(0, 1, 0, UNITS),
units = UNITS,
};
}
}
origin = new Point(0, 0, 0, Units.Meters),
normal = new Vector(0, 0, 1, Units.Meters),
xdir = new Vector(1, 0, 0, Units.Meters),
ydir = new Vector(0, 1, 0, Units.Meters),
units = Units.Meters,
};
[Test]
[Fact]
public void CanCreateBox()
{
const string UNITS = Units.Meters;
@@ -36,7 +30,10 @@ public class BoxTests
units = UNITS,
};
Assert.That(box.area, Is.EqualTo(2 * (2 * 4 + 2 * 6 + 4 * 6)).Within(0.0001));
Assert.That(box.volume, Is.EqualTo(2 * 4 * 6).Within(0.0001));
// Assert area
box.area.Should().BeApproximately(2 * (2 * 4 + 2 * 6 + 4 * 6), 0.0001);
// Assert volume
box.volume.Should().BeApproximately(2 * 4 * 6, 0.0001);
}
}
@@ -1,10 +1,10 @@
using NUnit.Framework;
using FluentAssertions;
using Speckle.Objects.Geometry;
using Speckle.Sdk.Common;
using Xunit;
namespace Speckle.Objects.Tests.Unit.Geometry;
[TestFixture, TestOf(typeof(Circle))]
public class CircleTests
{
private Plane TestPlane
@@ -12,7 +12,7 @@ public class CircleTests
get
{
const string UNITS = Units.Meters;
return new()
return new Plane
{
origin = new Point(0, 0, 0, UNITS),
normal = new Vector(0, 0, 1, UNITS),
@@ -23,18 +23,19 @@ public class CircleTests
}
}
[Test]
[Fact]
public void CanCreateCircle()
{
const string UNITS = Units.Meters;
var circle = new Circle()
var circle = new Circle
{
plane = TestPlane,
radius = 5,
units = UNITS,
};
Assert.That(circle.length, Is.EqualTo(2 * Math.PI * 5).Within(0.0001));
Assert.That(circle.area, Is.EqualTo(Math.PI * 5 * 5).Within(0.0001));
// Use Shouldly assertions
circle.length.Should().BeApproximately(2 * Math.PI * 5, 0.0001);
circle.area.Should().BeApproximately(Math.PI * 5 * 5, 0.0001);
}
}
@@ -1,35 +1,38 @@
using NUnit.Framework;
using FluentAssertions;
using Speckle.Objects.Geometry;
using Speckle.Sdk.Common;
using Xunit;
namespace Speckle.Objects.Tests.Unit.Geometry;
[TestFixture, TestOf(typeof(Mesh))]
public class MeshTests
{
private static Mesh[] s_testCaseSource = { CreateBlenderStylePolygon(), CreateRhinoStylePolygon() };
private static readonly Mesh[] TestCaseSource = { CreateBlenderStylePolygon(), CreateRhinoStylePolygon() };
[Test, TestCaseSource(nameof(s_testCaseSource))]
[Theory]
[MemberData(nameof(GetTestCaseSource))]
public void CanAlignVertices(Mesh inPolygon)
{
inPolygon.AlignVerticesWithTexCoordsByIndex();
Assert.That(inPolygon.VerticesCount, Is.EqualTo(inPolygon.TextureCoordinatesCount));
inPolygon.VerticesCount.Should().Be(inPolygon.TextureCoordinatesCount);
var expectedPolygon = CreateRhinoStylePolygon();
Assert.That(inPolygon.vertices, Is.EquivalentTo(expectedPolygon.vertices));
Assert.That(inPolygon.faces, Is.EquivalentTo(expectedPolygon.faces));
Assert.That(inPolygon.textureCoordinates, Is.EquivalentTo(expectedPolygon.textureCoordinates));
inPolygon.vertices.Should().BeEquivalentTo(expectedPolygon.vertices);
inPolygon.faces.Should().BeEquivalentTo(expectedPolygon.faces);
inPolygon.textureCoordinates.Should().BeEquivalentTo(expectedPolygon.textureCoordinates);
}
public static IEnumerable<object[]> GetTestCaseSource() => TestCaseSource.Select(mesh => new object[] { mesh });
private static Mesh CreateRhinoStylePolygon()
{
return new Mesh
{
vertices = [0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0],
faces = [3, 0, 1, 2, 3, 3, 4, 5],
textureCoordinates = [0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0],
vertices = new List<double> { 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0 },
faces = new List<int> { 3, 0, 1, 2, 3, 3, 4, 5 },
textureCoordinates = new List<double> { 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0 },
units = Units.Meters,
};
}
@@ -38,9 +41,9 @@ public class MeshTests
{
return new Mesh
{
vertices = [0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0],
faces = [3, 0, 1, 2, 3, 0, 2, 3],
textureCoordinates = [0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0],
vertices = new List<double> { 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0 },
faces = new List<int> { 3, 0, 1, 2, 3, 0, 2, 3 },
textureCoordinates = new List<double> { 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0 },
units = Units.Meters,
};
}
@@ -1,18 +1,18 @@
using System.Diagnostics.CodeAnalysis;
using NUnit.Framework;
using FluentAssertions;
using Speckle.Objects.Geometry;
using Speckle.Sdk.Common;
using Xunit;
namespace Speckle.Objects.Tests.Unit.Geometry;
[TestFixture, TestOf(typeof(Point))]
public class PointTests
{
[Test]
[Fact]
[SuppressMessage(
"Assertion",
"NUnit2010:Use EqualConstraint for better assertion messages in case of failure",
Justification = "Need to explicitly test equality operator"
"xUnit2013:Do not use equality check to assert boolean value",
Justification = "Explicit equality operator tests are necessary"
)]
public void TestNull()
{
@@ -20,35 +20,49 @@ public class PointTests
Point? b = null;
Point c = new(0, 0, 0, Units.Meters);
Assert.Multiple(() =>
{
Assert.That(a == b, Is.True);
Assert.That(a != b, Is.False);
Assert.That(b == a, Is.True);
Assert.That(b != a, Is.False);
a.Should().Be(b);
(a != b).Should().BeFalse();
Assert.That(a == c, Is.False);
Assert.That(a != c, Is.True);
Assert.That(c == a, Is.False);
Assert.That(c != a, Is.True);
});
b.Should().Be(a);
(b != a).Should().BeFalse();
(a == c).Should().BeFalse();
(a != c).Should().BeTrue();
(c == a).Should().BeFalse();
(c != a).Should().BeTrue();
}
[Test]
[TestCase(1, 1, 1, "m", 1, 1, 1, "m", ExpectedResult = true)]
[TestCase(1, 1, 1, "m", 0, 1, 1, "m", ExpectedResult = false)]
[TestCase(1, 1, 1, "m", 1, 0, 1, "m", ExpectedResult = false)]
[TestCase(1, 1, 1, "m", 1, 1, 0, "m", ExpectedResult = false)]
[TestCase(1, 1, 1, "", 1, 1, 1, "", ExpectedResult = true)]
[TestCase(1, 1, 1, null, 1, 1, 1, null, ExpectedResult = true)]
[TestCase(1, 1, 1, "m", 1, 1, 1, "meters", ExpectedResult = false)]
[TestCase(1, 1, 1, "m", 1, 1, 1, "M", ExpectedResult = false)]
// Units
public bool TestEqual(double x1, double y1, double z1, string units1, double x2, double y2, double z2, string units2)
//TODO: Should(). units be allowed to be string?
[Theory]
[InlineData(1, 1, 1, "m", 1, 1, 1, "m", true)]
[InlineData(1, 1, 1, "m", 0, 1, 1, "m", false)]
[InlineData(1, 1, 1, "m", 1, 0, 1, "m", false)]
[InlineData(1, 1, 1, "m", 1, 1, 0, "m", false)]
[InlineData(1, 1, 1, "", 1, 1, 1, "", false)]
[InlineData(1, 1, 1, null, 1, 1, 1, null, false)]
[InlineData(1, 1, 1, "m", 1, 1, 1, "meters", false)]
[InlineData(1, 1, 1, "m", 1, 1, 1, "M", false)]
public void TestEqual(
double x1,
double y1,
double z1,
string? units1,
double x2,
double y2,
double z2,
string? units2,
bool expectedResult
)
{
if (string.IsNullOrEmpty(units1) || string.IsNullOrEmpty(units2))
{
expectedResult.Should().BeFalse();
return;
}
Point p1 = new(x1, y1, z1, units1);
Point p2 = new(x2, y2, z2, units2);
return p1 == p2;
(p1 == p2).Should().Be(expectedResult);
}
}
@@ -1,100 +1,98 @@
using System.Collections;
using NUnit.Framework;
using FluentAssertions;
using Speckle.DoubleNumerics;
using Speckle.Objects.Other;
using Speckle.Sdk.Common;
using Xunit;
namespace Speckle.Objects.Tests.Unit.Geometry;
[TestFixture, TestOf(typeof(Transform))]
public class TransformTests
{
private const float FLOAT_TOLERANCE = 1e-6f;
[Test, TestCaseSource(nameof(TransformTestCases))]
[Theory]
[MemberData(nameof(TransformTestCases))]
public void ArrayBackAndForth(Matrix4x4 data)
{
// Arrange
var start = new Transform() { matrix = data, units = Units.None };
// Act
var asArr = Transform.CreateMatrix(start.ToArray());
var end = new Transform() { matrix = asArr, units = Units.None };
Assert.That(end.matrix, Is.EqualTo(data));
// Assert
end.matrix.Should().Be(data);
}
[Test, TestCaseSource(nameof(TransformTestCases))]
[Theory]
[MemberData(nameof(TransformTestCases))]
public void ConvertToUnits(Matrix4x4 data)
{
const float SF = 1000f;
var transpose = Matrix4x4.Transpose(data); //NOTE: Transform expects matrices transposed (translation in column 4)
// Arrange
var transpose = Matrix4x4.Transpose(data); // Transform expects matrices transposed (translation in column 4)
var mm = Matrix4x4.Transpose(
Transform.CreateMatrix(
new Transform() { matrix = transpose, units = Units.Meters }.ConvertToUnits(Units.Millimeters)
)
);
// Act
Matrix4x4.Decompose(data, out var ms, out var mr, out var mt);
Matrix4x4.Decompose(mm, out var mms, out var mmr, out var mmt);
Assert.Multiple(() =>
{
Assert.That(mms.X, Is.EqualTo(ms.X).Within(FLOAT_TOLERANCE), "Expect scale x to be unchanged");
Assert.That(mms.Y, Is.EqualTo(ms.Y).Within(FLOAT_TOLERANCE), "Expect scale y to be unchanged");
Assert.That(mms.Z, Is.EqualTo(ms.Z).Within(FLOAT_TOLERANCE), "Expect scale z to be unchanged");
// Assert
mms.X.Should().BeApproximately(ms.X, FLOAT_TOLERANCE, "Expect scale x to be unchanged");
mms.Y.Should().BeApproximately(ms.Y, FLOAT_TOLERANCE, "Expect scale y to be unchanged");
mms.Z.Should().BeApproximately(ms.Z, FLOAT_TOLERANCE, "Expect scale z to be unchanged");
Assert.That(Quaternion.Dot(mr, mmr), Is.LessThan(1).Within(FLOAT_TOLERANCE), "Expect rot x to be equivalent");
Quaternion.Dot(mr, mmr).Should().BeLessThan(1 + FLOAT_TOLERANCE, "Expect rotation to be equivalent");
Assert.That(mmt.X, Is.EqualTo(mt.X * SF).Within(FLOAT_TOLERANCE), $"Expect translation x to be scaled by {SF}");
Assert.That(mmt.Y, Is.EqualTo(mt.Y * SF).Within(FLOAT_TOLERANCE), $"Expect translation y to be scaled by {SF}");
Assert.That(mmt.Z, Is.EqualTo(mt.Z * SF).Within(FLOAT_TOLERANCE), $"Expect translation z to be scaled by {SF}");
});
mmt.X.Should().BeApproximately(mt.X * SF, FLOAT_TOLERANCE, $"Expect translation x to be scaled by {SF}");
mmt.Y.Should().BeApproximately(mt.Y * SF, FLOAT_TOLERANCE, $"Expect translation y to be scaled by {SF}");
mmt.Z.Should().BeApproximately(mt.Z * SF, FLOAT_TOLERANCE, $"Expect translation z to be scaled by {SF}");
}
/// <summary>
/// Set of TRS transforms (row dominant i.e. translation in row 4)
/// All with non-negative scale and rotation (for ease of testing scale and rot independently)
/// </summary>
/// <returns></returns>
private static IEnumerable TransformTestCases()
public static IEnumerable<object[]> TransformTestCases()
{
var t = new Vector3(128.128f, 255.255f, 512.512f);
var r = Quaternion.CreateFromYawPitchRoll(1.9f, 0.6666667f, 0.5f);
var s = new Vector3(123f, 32f, 0.5f);
yield return new TestCaseData(Matrix4x4.Identity).SetName("{m} Identity Matrix");
yield return [Matrix4x4.Identity];
yield return new TestCaseData(Matrix4x4.CreateTranslation(t)).SetName("{m} Translation Only (positive)");
yield return [Matrix4x4.CreateTranslation(t)];
yield return new TestCaseData(Matrix4x4.CreateTranslation(t * -Vector3.UnitX)).SetName("{m} Translation Only -X");
yield return [Matrix4x4.CreateTranslation(t * -Vector3.UnitX)];
yield return new TestCaseData(Matrix4x4.CreateTranslation(t * -Vector3.UnitY)).SetName("{m} Translation Only -Y");
yield return [Matrix4x4.CreateTranslation(t * -Vector3.UnitY)];
yield return new TestCaseData(Matrix4x4.CreateTranslation(t * -Vector3.UnitZ)).SetName("{m} Translation Only -Z");
yield return [Matrix4x4.CreateTranslation(t * -Vector3.UnitZ)];
yield return new TestCaseData(Matrix4x4.CreateTranslation(-t)).SetName("{m} Translation Only -XYZ ");
yield return [Matrix4x4.CreateTranslation(-t)];
yield return new TestCaseData(Matrix4x4.CreateFromYawPitchRoll(0.5f, 0.0f, 0.0f)).SetName("{m} Rotation Only X ");
yield return [Matrix4x4.CreateFromYawPitchRoll(0.5f, 0.0f, 0.0f)];
yield return new TestCaseData(Matrix4x4.CreateFromYawPitchRoll(0.0f, 0.5f, 0.0f)).SetName("{m} Rotation Only Y ");
yield return [Matrix4x4.CreateFromYawPitchRoll(0.0f, 0.5f, 0.0f)];
yield return new TestCaseData(Matrix4x4.CreateFromYawPitchRoll(0.0f, 0.0f, 0.5f)).SetName("{m} Rotation Only Z ");
yield return [Matrix4x4.CreateFromYawPitchRoll(0.0f, 0.0f, 0.5f)];
yield return new TestCaseData(Matrix4x4.CreateFromYawPitchRoll(0.5f, 0.5f, 0.5f)).SetName("{m} Rotation Only XYZ ");
yield return [Matrix4x4.CreateFromYawPitchRoll(0.5f, 0.5f, 0.5f)];
yield return new TestCaseData(Matrix4x4.CreateFromQuaternion(r)).SetName("{m} Rotation Only");
yield return [Matrix4x4.CreateFromQuaternion(r)];
yield return new TestCaseData(Matrix4x4.Identity + Matrix4x4.CreateScale(s)).SetName("{m} Scale Only");
yield return [Matrix4x4.Identity + Matrix4x4.CreateScale(s)];
yield return new TestCaseData(Matrix4x4.CreateTranslation(t) + Matrix4x4.CreateFromQuaternion(r)).SetName(
"{m} Translation + Rotation"
);
yield return [Matrix4x4.CreateTranslation(t) + Matrix4x4.CreateFromQuaternion(r)];
yield return new TestCaseData(
Matrix4x4.CreateTranslation(t) + Matrix4x4.CreateFromQuaternion(r) + Matrix4x4.CreateScale(s)
).SetName("{m} Translation + Rotation + Scale");
yield return [Matrix4x4.CreateTranslation(t) + Matrix4x4.CreateFromQuaternion(r) + Matrix4x4.CreateScale(s)];
yield return new TestCaseData(
Matrix4x4.CreateTranslation(t) + Matrix4x4.CreateFromQuaternion(r) + Matrix4x4.CreateScale(-s)
).SetName("{m} Translation + Rotation + -Scale");
yield return [Matrix4x4.CreateTranslation(t) + Matrix4x4.CreateFromQuaternion(r) + Matrix4x4.CreateScale(-s)];
}
}
@@ -1,10 +1,11 @@
using System.Drawing;
using NUnit.Framework;
using System.Drawing;
using FluentAssertions;
using Speckle.DoubleNumerics;
using Speckle.Newtonsoft.Json;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Serialisation;
using Xunit;
namespace Speckle.Objects.Tests.Unit;
@@ -15,8 +16,7 @@ namespace Speckle.Objects.Tests.Unit;
/// </summary>
public class ModelPropertySupportedTypes
{
[SetUp]
public void Setup()
public ModelPropertySupportedTypes()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(Speckle.Objects.Geometry.Arc).Assembly);
@@ -62,7 +62,7 @@ public class ModelPropertySupportedTypes
typeof(Matrix4x4),
};
[Test]
[Fact]
public void TestObjects()
{
foreach ((string _, Type type, List<string> _) in TypeLoader.Types)
@@ -72,19 +72,24 @@ public class ModelPropertySupportedTypes
foreach (var prop in members)
{
if (prop.PropertyType.IsAssignableTo(typeof(Base)))
{
continue;
}
if (prop.PropertyType.IsEnum)
{
continue;
}
if (prop.PropertyType.IsSZArray)
{
continue;
}
Type propType = prop.PropertyType;
Type typeDef = propType.IsGenericType ? propType.GetGenericTypeDefinition() : propType;
Assert.That(
_allowedTypes,
Does.Contain(typeDef),
$"{typeDef} was not in allowedTypes. (Origin: {type}.{prop.Name})"
);
_allowedTypes.Should().Contain(typeDef, $"{typeDef} was not in allowedTypes. (Origin: {type}.{prop.Name})");
}
}
}
@@ -1,23 +1,23 @@
using NUnit.Framework;
using Shouldly;
using FluentAssertions;
using Speckle.Objects.Geometry;
using Speckle.Objects.Geometry.Autocad;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Xunit;
using Point = Speckle.Objects.Geometry.Point;
namespace Speckle.Objects.Tests.Unit;
public class ObjectBaseValidityTests
{
[Test]
[Fact]
public void TestThatTypeWithoutAttributeFails()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly);
}
[Test]
[Fact]
public void InheritanceTest_Disallow()
{
var exception = Assert.Throws<InvalidOperationException>(() =>
@@ -25,19 +25,19 @@ public class ObjectBaseValidityTests
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly, typeof(Test).Assembly);
});
exception.ShouldNotBeNull();
exception.Message.ShouldBe(
"Speckle.Objects.Tests.Unit.ObjectBaseValidityTests+Test inherits from Base has no SpeckleTypeAttribute"
);
exception.Should().NotBeNull();
exception
.Message.Should()
.Be("Speckle.Objects.Tests.Unit.ObjectBaseValidityTests+Test inherits from Base has no SpeckleTypeAttribute");
}
[Test]
[Fact]
public void InheritanceTest_Allow()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly);
var fullTypeString = TypeLoader.GetFullTypeString(typeof(AutocadPolycurve));
fullTypeString.ShouldBe("Objects.Geometry.Polycurve:Objects.Geometry.Autocad.AutocadPolycurve");
fullTypeString.Should().Be("Objects.Geometry.Polycurve:Objects.Geometry.Autocad.AutocadPolycurve");
}
public class Test : Polycurve;
@@ -8,10 +8,10 @@
<ItemGroup>
<PackageReference Include="altcover" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="NUnit" />
<PackageReference Include="NUnit3TestAdapter" />
<PackageReference Include="Shouldly" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Speckle.Objects\Speckle.Objects.csproj" />
@@ -1,17 +1,28 @@
using NUnit.Framework;
using FluentAssertions;
using Speckle.Objects.Geometry;
using Speckle.Objects.Utils;
using Speckle.Sdk.Common;
using Speckle.Sdk.Dependencies;
using Xunit;
namespace Speckle.Objects.Tests.Unit.Utils;
[TestFixture, TestOf(typeof(MeshTriangulationHelper))]
public class MeshTriangulationHelperTests
{
[Test]
public void PolygonTest([Range(3, 9)] int n, [Values] bool planar)
public static IEnumerable<object[]> PolygonTestSource()
{
//Test Setup
foreach (var x in EnumerableExtensions.RangeFrom(3, 9))
{
yield return new object[] { x, true };
yield return new object[] { x, false };
}
}
[Theory]
[MemberData(nameof(PolygonTestSource))]
public void PolygonTest(int n, bool planar)
{
// Test Setup
List<double> vertices = new(n) { 0, planar ? 0 : 1, 1 };
for (int i = 1; i < n; i++)
{
@@ -30,30 +41,30 @@ public class MeshTriangulationHelperTests
units = Units.Meters,
};
//Test
// Test
mesh.TriangulateMesh();
//Results
// Results
int numExpectedTriangles = n - 2;
int expectedFaceCount = numExpectedTriangles * 4;
Assert.That(mesh.faces, Has.Count.EqualTo(expectedFaceCount));
mesh.faces.Count.Should().Be(expectedFaceCount);
for (int i = 0; i < expectedFaceCount; i += 4)
{
Assert.That(mesh.faces[i], Is.EqualTo(3));
Assert.That(mesh.faces.GetRange(i + 1, 3), Is.Unique);
mesh.faces[i].Should().Be(3);
mesh.faces.GetRange(i + 1, 3).Should().OnlyHaveUniqueItems();
}
Assert.That(mesh.faces, Is.SupersetOf(Enumerable.Range(0, n)));
Assert.That(mesh.faces, Is.All.GreaterThanOrEqualTo(0));
Assert.That(mesh.faces, Is.All.LessThan(Math.Max(n, 4)));
var range = EnumerableExtensions.RangeFrom(0, n).ToList();
mesh.faces.Should().BeSubsetOf(range);
mesh.faces.Should().AllSatisfy(x => x.Should().BeGreaterThanOrEqualTo(0).And.BeLessThan(Math.Max(n, 4)));
}
[Test]
[Fact]
public void DoesntFlipNormals()
{
//Test Setup
// Test Setup
List<double> vertices = new() { 0, 0, 0, 1, 0, 0, 1, 0, 1 };
List<int> faces = new() { 3, 0, 1, 2 };
@@ -65,22 +76,26 @@ public class MeshTriangulationHelperTests
units = Units.Meters,
};
//Test
// Test
mesh.TriangulateMesh();
//Results
// Results
List<int> shift1 = faces;
List<int> shift2 = new() { 3, 1, 2, 0 };
List<int> shift3 = new() { 3, 2, 0, 1 };
Assert.That(mesh.faces, Is.AnyOf(shift1, shift2, shift3));
new List<int>[] { shift1, shift2, shift3 }
.Any(x => mesh.faces.SequenceEqual(x))
.Should()
.BeTrue();
}
[Test]
public void PreserveQuads([Values] bool preserveQuads)
[Theory]
[InlineData(true)]
[InlineData(false)]
public void PreserveQuads(bool preserveQuads)
{
//Test Setup
// Test Setup
List<double> vertices = new() { 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1 };
List<int> faces = new() { 4, 0, 1, 2, 3 };
@@ -92,14 +107,14 @@ public class MeshTriangulationHelperTests
units = Units.Meters,
};
//Tests
// Tests
mesh.TriangulateMesh(preserveQuads);
//Results
// Results
int expectedN = preserveQuads ? 4 : 3;
int expectedFaceCount = preserveQuads ? 5 : 8;
Assert.That(mesh.faces, Has.Count.EqualTo(expectedFaceCount));
Assert.That(mesh.faces[0], Is.EqualTo(expectedN));
mesh.faces.Count.Should().Be(expectedFaceCount);
mesh.faces[0].Should().Be(expectedN);
}
}
@@ -1,16 +1,23 @@
using System.Collections;
using NUnit.Framework;
using FluentAssertions;
using Speckle.Objects.Data;
using Speckle.Objects.Geometry;
using Speckle.Sdk.Common;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Xunit;
namespace Speckle.Objects.Tests.Unit.Utils;
[TestFixture]
public class ShallowCopyTests
{
[Test]
public ShallowCopyTests()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly);
}
[Fact]
public void CanShallowCopy_Wall()
{
const string UNITS = Units.Meters;
@@ -37,6 +44,6 @@ public class ShallowCopyTests
var shallow = ds.ShallowCopy();
var displayValue = (IList)shallow["displayValue"].NotNull();
Assert.That(ds.displayValue, Has.Count.EqualTo(displayValue.Count));
ds.displayValue.Count.Should().Be(displayValue.Count);
}
}
@@ -4,9 +4,18 @@
"net8.0": {
"altcover": {
"type": "Direct",
"requested": "[8.9.3, )",
"resolved": "8.9.3",
"contentHash": "auKC+pDCkLjfhFkSRaAUBu25BOmlLSqucR7YBs/Lkbdc0XRuJoklWafs1KKp+M+VoJ1f0TeMS6B/FO5IeIcu7w=="
"requested": "[9.0.1, )",
"resolved": "9.0.1",
"contentHash": "aadciFNDT5bnylaYUkKal+s5hF7yU/lmZxImQWAlk1438iPqK1Uf79H5ylELpyLIU49HL5ql+tnWBihp3WVLCA=="
},
"FluentAssertions": {
"type": "Direct",
"requested": "[7.0.0, )",
"resolved": "7.0.0",
"contentHash": "mTLbcU991EQ1SEmNbVBaGGGJy0YFzvGd1sYJGNZ07nlPKuyHSn1I22aeKzqQXgEiaKyRO6MSCto9eN9VxMwBdA==",
"dependencies": {
"System.Configuration.ConfigurationManager": "6.0.0"
}
},
"GitVersion.MsBuild": {
"type": "Direct",
@@ -16,12 +25,12 @@
},
"Microsoft.NET.Test.Sdk": {
"type": "Direct",
"requested": "[17.11.1, )",
"resolved": "17.11.1",
"contentHash": "U3Ty4BaGoEu+T2bwSko9tWqWUOU16WzSFkq6U8zve75oRBMSLTBdMAZrVNNz1Tq12aCdDom9fcOcM9QZaFHqFg==",
"requested": "[17.12.0, )",
"resolved": "17.12.0",
"contentHash": "kt/PKBZ91rFCWxVIJZSgVLk+YR+4KxTuHf799ho8WNiK5ZQpJNAEZCAWX86vcKrs+DiYjiibpYKdGZP6+/N17w==",
"dependencies": {
"Microsoft.CodeCoverage": "17.11.1",
"Microsoft.TestPlatform.TestHost": "17.11.1"
"Microsoft.CodeCoverage": "17.12.0",
"Microsoft.TestPlatform.TestHost": "17.12.0"
}
},
"Microsoft.SourceLink.GitHub": {
@@ -34,53 +43,34 @@
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"NUnit": {
"type": "Direct",
"requested": "[4.2.2, )",
"resolved": "4.2.2",
"contentHash": "mon0OPko28yZ/foVXrhiUvq1LReaGsBdziumyyYGxV/pOE4q92fuYeN+AF+gEU5pCjzykcdBt5l7xobTaiBjsg=="
},
"NUnit3TestAdapter": {
"type": "Direct",
"requested": "[4.6.0, )",
"resolved": "4.6.0",
"contentHash": "R7e1+a4vuV/YS+ItfL7f//rG+JBvVeVLX4mHzFEZo4W1qEKl8Zz27AqvQSAqo+BtIzUCo4aAJMYa56VXS4hudw=="
},
"PolySharp": {
"type": "Direct",
"requested": "[1.15.0, )",
"resolved": "1.15.0",
"contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g=="
},
"Shouldly": {
"type": "Direct",
"requested": "[4.2.1, )",
"resolved": "4.2.1",
"contentHash": "dKAKiSuhLKqD2TXwLKtqNg1nwzJcIKOOMncZjk9LYe4W+h+SCftpWdxwR79YZUIHMH+3Vu9s0s0UHNrgICLwRQ==",
"dependencies": {
"DiffEngine": "11.3.0",
"EmptyFiles": "4.4.0"
}
},
"Speckle.InterfaceGenerator": {
"type": "Direct",
"requested": "[0.9.6, )",
"resolved": "0.9.6",
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
},
"DiffEngine": {
"type": "Transitive",
"resolved": "11.3.0",
"contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==",
"xunit": {
"type": "Direct",
"requested": "[2.9.3, )",
"resolved": "2.9.3",
"contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==",
"dependencies": {
"EmptyFiles": "4.4.0",
"System.Management": "6.0.1"
"xunit.analyzers": "1.18.0",
"xunit.assert": "2.9.3",
"xunit.core": "[2.9.3]"
}
},
"EmptyFiles": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw=="
"xunit.runner.visualstudio": {
"type": "Direct",
"requested": "[3.0.0, )",
"resolved": "3.0.0",
"contentHash": "HggUqjQJe8PtDxcP25Q+CnR6Lz4oX3GElhD9V4oU2+75x9HI6A6sxbfKGS4UwU4t4yJaS9fBmAuriz8bQApNjw=="
},
"GraphQL.Client.Abstractions": {
"type": "Transitive",
@@ -110,8 +100,8 @@
},
"Microsoft.CodeCoverage": {
"type": "Transitive",
"resolved": "17.11.1",
"contentHash": "nPJqrcA5iX+Y0kqoT3a+pD/8lrW/V7ayqnEJQsTonSoPz59J8bmoQhcSN4G8+UJ64Hkuf0zuxnfuj2lkHOq4cA=="
"resolved": "17.12.0",
"contentHash": "4svMznBd5JM21JIG2xZKGNanAHNXplxf/kQDFfLHXQ3OnpJkayRK/TjacFjA+EYmoyuNXHo/sOETEfcYtAzIrA=="
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
@@ -176,21 +166,26 @@
},
"Microsoft.TestPlatform.ObjectModel": {
"type": "Transitive",
"resolved": "17.11.1",
"contentHash": "E2jZqAU6JeWEVsyOEOrSW1o1bpHLgb25ypvKNB/moBXPVsFYBPd/Jwi7OrYahG50J83LfHzezYI+GaEkpAotiA==",
"resolved": "17.12.0",
"contentHash": "TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==",
"dependencies": {
"System.Reflection.Metadata": "1.6.0"
}
},
"Microsoft.TestPlatform.TestHost": {
"type": "Transitive",
"resolved": "17.11.1",
"contentHash": "DnG+GOqJXO/CkoqlJWeDFTgPhqD/V6VqUIL3vINizCWZ3X+HshCtbbyDdSHQQEjrc2Sl/K3yaxX6s+5LFEdYuw==",
"resolved": "17.12.0",
"contentHash": "MiPEJQNyADfwZ4pJNpQex+t9/jOClBGMiCiVVFuELCMSX2nmNfvUor3uFVxNNCg30uxDP8JDYfPnMXQzsfzYyg==",
"dependencies": {
"Microsoft.TestPlatform.ObjectModel": "17.11.1",
"Microsoft.TestPlatform.ObjectModel": "17.12.0",
"Newtonsoft.Json": "13.0.1"
}
},
"Microsoft.Win32.SystemEvents": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A=="
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.1",
@@ -226,22 +221,26 @@
"SQLitePCLRaw.core": "2.1.4"
}
},
"System.CodeDom": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA=="
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
},
"System.Management": {
"System.Configuration.ConfigurationManager": {
"type": "Transitive",
"resolved": "6.0.1",
"contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==",
"resolved": "6.0.0",
"contentHash": "7T+m0kDSlIPTHIkPMIu6m6tV6qsMqJpvQWW2jIc2qi7sn40qxFo0q+7mEQAhMPXZHMKnWrnv47ntGlM/ejvw3g==",
"dependencies": {
"System.CodeDom": "6.0.0"
"System.Security.Cryptography.ProtectedData": "6.0.0",
"System.Security.Permissions": "6.0.0"
}
},
"System.Drawing.Common": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==",
"dependencies": {
"Microsoft.Win32.SystemEvents": "6.0.0"
}
},
"System.Memory": {
@@ -264,6 +263,73 @@
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
"System.Security.AccessControl": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ=="
},
"System.Security.Cryptography.ProtectedData": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ=="
},
"System.Security.Permissions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==",
"dependencies": {
"System.Security.AccessControl": "6.0.0",
"System.Windows.Extensions": "6.0.0"
}
},
"System.Windows.Extensions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==",
"dependencies": {
"System.Drawing.Common": "6.0.0"
}
},
"xunit.abstractions": {
"type": "Transitive",
"resolved": "2.0.3",
"contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg=="
},
"xunit.analyzers": {
"type": "Transitive",
"resolved": "1.18.0",
"contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ=="
},
"xunit.assert": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA=="
},
"xunit.core": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==",
"dependencies": {
"xunit.extensibility.core": "[2.9.3]",
"xunit.extensibility.execution": "[2.9.3]"
}
},
"xunit.extensibility.core": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==",
"dependencies": {
"xunit.abstractions": "2.0.3"
}
},
"xunit.extensibility.execution": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==",
"dependencies": {
"xunit.extensibility.core": "[2.9.3]"
}
},
"speckle.objects": {
"type": "Project",
"dependencies": {
@@ -3,9 +3,7 @@ using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Sdk;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Helpers;
using Speckle.Sdk.Host;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Serialisation.V2;
using Speckle.Sdk.Serialisation.V2.Receive;
@@ -43,12 +41,11 @@ var token = serviceProvider.GetRequiredService<IAccountManager>().GetDefaultAcco
var progress = new Progress(true);
var factory = new SerializeProcessFactory(
serviceProvider.GetRequiredService<ISpeckleHttp>(),
serviceProvider.GetRequiredService<ISdkActivityFactory>(),
new BaseChildFinder(new BasePropertyGatherer()),
new ObjectSerializerFactory(new BasePropertyGatherer()),
new ObjectDeserializerFactory(),
serviceProvider.GetRequiredService<ISqLiteJsonCacheManagerFactory>()
serviceProvider.GetRequiredService<ISqLiteJsonCacheManagerFactory>(),
serviceProvider.GetRequiredService<IServerObjectManagerFactory>()
);
var process = factory.CreateDeserializeProcess(new Uri(url), streamId, token, progress, new(skipCacheReceive));
var @base = await process.Deserialize(rootId, default).ConfigureAwait(false);
@@ -0,0 +1,3 @@
using Xunit;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
@@ -7,19 +7,34 @@ public class BaseComparer : IEqualityComparer<Base>
public bool Equals(Base? x, Base? y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (x is null)
{
return false;
}
if (y is null)
{
return false;
}
Type type = x.GetType();
if (type != y.GetType())
{
return false;
}
var types = DynamicBaseMemberType.Instance | DynamicBaseMemberType.Dynamic | DynamicBaseMemberType.SchemaIgnored;
var membersX = x.GetMembers(types);
var membersY = y.GetMembers(types);
if (membersX.Count != membersY.Count)
{
return false;
}
foreach (var kvp in membersX)
{
var propertyInfo = type.GetProperty(kvp.Key);
@@ -28,7 +43,9 @@ public class BaseComparer : IEqualityComparer<Base>
continue;
}
if (y[kvp.Key] != kvp.Value)
{
return false;
}
}
return x.id == y.id && x.applicationId == y.applicationId;
}
@@ -1,8 +1,6 @@
using System.Collections.Concurrent;
using System.Text;
using NUnit.Framework;
using Shouldly;
using Speckle.Newtonsoft.Json;
using FluentAssertions;
using Speckle.Newtonsoft.Json.Linq;
using Speckle.Objects.Geometry;
using Speckle.Sdk.Host;
@@ -12,19 +10,19 @@ using Speckle.Sdk.Serialisation.V2;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.SQLite;
using Speckle.Sdk.Transports;
using Xunit;
namespace Speckle.Sdk.Serialization.Tests;
public class DetachedTests
{
[SetUp]
public void Setup()
public DetachedTests()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(DetachedTests).Assembly, typeof(Polyline).Assembly);
}
[Test(Description = "Checks that all typed properties (including obsolete ones) are returned")]
[Fact(DisplayName = "Checks that all typed properties (including obsolete ones) are returned")]
public async Task CanSerialize_New_Detached()
{
var expectedJson = """
@@ -75,20 +73,22 @@ public class DetachedTests
new ObjectSerializerFactory(new BasePropertyGatherer()),
new SerializeProcessOptions(false, false, true, true)
);
await process2.Serialize(@base, default).ConfigureAwait(false);
await process2.Serialize(@base, default);
objects.Count.ShouldBe(2);
objects.ContainsKey("9ff8efb13c62fa80f3d1c4519376ba13").ShouldBeTrue();
objects.ContainsKey("d3dd4621b2f68c3058c2b9c023a9de19").ShouldBeTrue();
objects.Count.Should().Be(2);
objects.ContainsKey("9ff8efb13c62fa80f3d1c4519376ba13").Should().BeTrue();
objects.ContainsKey("d3dd4621b2f68c3058c2b9c023a9de19").Should().BeTrue();
JToken
.DeepEquals(JObject.Parse(expectedJson), JObject.Parse(objects["9ff8efb13c62fa80f3d1c4519376ba13"]))
.ShouldBeTrue();
.Should()
.BeTrue();
JToken
.DeepEquals(JObject.Parse(detachedJson), JObject.Parse(objects["d3dd4621b2f68c3058c2b9c023a9de19"]))
.ShouldBeTrue();
.Should()
.BeTrue();
}
[Test(Description = "Checks that all typed properties (including obsolete ones) are returned")]
[Fact(DisplayName = "Checks that all typed properties (including obsolete ones) are returned")]
public void CanSerialize_Old_Detached()
{
var expectedJson = """
@@ -133,19 +133,24 @@ public class DetachedTests
var serializer = new SpeckleObjectSerializer(new[] { new MemoryTransport(objects) });
var json = serializer.Serialize(@base);
objects.Count.ShouldBe(2);
objects.ContainsKey("9ff8efb13c62fa80f3d1c4519376ba13").ShouldBeTrue();
objects.ContainsKey("d3dd4621b2f68c3058c2b9c023a9de19").ShouldBeTrue();
JToken.DeepEquals(JObject.Parse(json), JObject.Parse(objects["9ff8efb13c62fa80f3d1c4519376ba13"])).ShouldBeTrue();
objects.Count.Should().Be(2);
objects.ContainsKey("9ff8efb13c62fa80f3d1c4519376ba13").Should().BeTrue();
objects.ContainsKey("d3dd4621b2f68c3058c2b9c023a9de19").Should().BeTrue();
JToken
.DeepEquals(JObject.Parse(json), JObject.Parse(objects["9ff8efb13c62fa80f3d1c4519376ba13"]))
.Should()
.BeTrue();
JToken
.DeepEquals(JObject.Parse(expectedJson), JObject.Parse(objects["9ff8efb13c62fa80f3d1c4519376ba13"]))
.ShouldBeTrue();
.Should()
.BeTrue();
JToken
.DeepEquals(JObject.Parse(detachedJson), JObject.Parse(objects["d3dd4621b2f68c3058c2b9c023a9de19"]))
.ShouldBeTrue();
.Should()
.BeTrue();
}
[Test]
[Fact]
public void GetPropertiesExpected_Detached()
{
var @base = new SampleObjectBase();
@@ -157,14 +162,14 @@ public class DetachedTests
var children = new BaseChildFinder(new BasePropertyGatherer()).GetChildProperties(@base).ToList();
children.Count.ShouldBe(4);
children.First(x => x.Name == "detachedProp").PropertyAttributeInfo.IsDetachable.ShouldBeTrue();
children.First(x => x.Name == "list").PropertyAttributeInfo.IsDetachable.ShouldBeTrue();
children.First(x => x.Name == "arr").PropertyAttributeInfo.IsDetachable.ShouldBeTrue();
children.First(x => x.Name == "@prop2").PropertyAttributeInfo.IsDetachable.ShouldBeTrue();
children.Count.Should().Be(4);
children.First(x => x.Name == "detachedProp").PropertyAttributeInfo.IsDetachable.Should().BeTrue();
children.First(x => x.Name == "list").PropertyAttributeInfo.IsDetachable.Should().BeTrue();
children.First(x => x.Name == "arr").PropertyAttributeInfo.IsDetachable.Should().BeTrue();
children.First(x => x.Name == "@prop2").PropertyAttributeInfo.IsDetachable.Should().BeTrue();
}
[Test]
[Fact]
public void GetPropertiesExpected_All()
{
var @base = new SampleObjectBase();
@@ -176,20 +181,20 @@ public class DetachedTests
var children = new BasePropertyGatherer().ExtractAllProperties(@base).ToList();
children.Count.ShouldBe(9);
children.First(x => x.Name == "dynamicProp").PropertyAttributeInfo.IsDetachable.ShouldBeFalse();
children.First(x => x.Name == "attachedProp").PropertyAttributeInfo.IsDetachable.ShouldBeFalse();
children.First(x => x.Name == "crazyProp").PropertyAttributeInfo.IsDetachable.ShouldBeFalse();
children.First(x => x.Name == "speckle_type").PropertyAttributeInfo.IsDetachable.ShouldBeFalse();
children.First(x => x.Name == "applicationId").PropertyAttributeInfo.IsDetachable.ShouldBeFalse();
children.Count.Should().Be(9);
children.First(x => x.Name == "dynamicProp").PropertyAttributeInfo.IsDetachable.Should().BeFalse();
children.First(x => x.Name == "attachedProp").PropertyAttributeInfo.IsDetachable.Should().BeFalse();
children.First(x => x.Name == "crazyProp").PropertyAttributeInfo.IsDetachable.Should().BeFalse();
children.First(x => x.Name == "speckle_type").PropertyAttributeInfo.IsDetachable.Should().BeFalse();
children.First(x => x.Name == "applicationId").PropertyAttributeInfo.IsDetachable.Should().BeFalse();
children.First(x => x.Name == "detachedProp").PropertyAttributeInfo.IsDetachable.ShouldBeTrue();
children.First(x => x.Name == "list").PropertyAttributeInfo.IsDetachable.ShouldBeTrue();
children.First(x => x.Name == "arr").PropertyAttributeInfo.IsDetachable.ShouldBeTrue();
children.First(x => x.Name == "@prop2").PropertyAttributeInfo.IsDetachable.ShouldBeTrue();
children.First(x => x.Name == "detachedProp").PropertyAttributeInfo.IsDetachable.Should().BeTrue();
children.First(x => x.Name == "list").PropertyAttributeInfo.IsDetachable.Should().BeTrue();
children.First(x => x.Name == "arr").PropertyAttributeInfo.IsDetachable.Should().BeTrue();
children.First(x => x.Name == "@prop2").PropertyAttributeInfo.IsDetachable.Should().BeTrue();
}
[Test(Description = "Checks that all typed properties (including obsolete ones) are returned")]
[Fact(DisplayName = "Checks that all typed properties (including obsolete ones) are returned")]
public async Task CanSerialize_New_Detached2()
{
var root = """
@@ -267,17 +272,17 @@ public class DetachedTests
new ObjectSerializerFactory(new BasePropertyGatherer()),
new SerializeProcessOptions(false, false, true, true)
);
var results = await process2.Serialize(@base, default).ConfigureAwait(false);
var results = await process2.Serialize(@base, default);
objects.Count.ShouldBe(9);
objects.Count.Should().Be(9);
var x = JObject.Parse(objects["2ebfd4f317754fce14cadd001151441e"]);
JToken.DeepEquals(JObject.Parse(root), x).ShouldBeTrue();
JToken.DeepEquals(JObject.Parse(root), x).Should().BeTrue();
results.RootId.ShouldBe(@base.id);
results.ConvertedReferences.Count.ShouldBe(2);
results.RootId.Should().Be(@base.id);
results.ConvertedReferences.Count.Should().Be(2);
}
[Test(Description = "Checks that all typed properties (including obsolete ones) are returned")]
[Fact(DisplayName = "Checks that all typed properties (including obsolete ones) are returned")]
public async Task CanSerialize_New_Detached_With_DataChunks()
{
var root = """
@@ -340,20 +345,20 @@ public class DetachedTests
new ObjectSerializerFactory(new BasePropertyGatherer()),
new SerializeProcessOptions(false, false, true, true)
);
var results = await process2.Serialize(@base, default).ConfigureAwait(false);
var results = await process2.Serialize(@base, default);
objects.Count.ShouldBe(3);
objects.Count.Should().Be(3);
var x = JObject.Parse(objects["efeadaca70a85ae6d3acfc93a8b380db"]);
JToken.DeepEquals(JObject.Parse(root), x).ShouldBeTrue();
JToken.DeepEquals(JObject.Parse(root), x).Should().BeTrue();
x = JObject.Parse(objects["0e61e61edee00404ec6e0f9f594bce24"]);
JToken.DeepEquals(JObject.Parse(list1), x).ShouldBeTrue();
JToken.DeepEquals(JObject.Parse(list1), x).Should().BeTrue();
x = JObject.Parse(objects["f70738e3e3e593ac11099a6ed6b71154"]);
JToken.DeepEquals(JObject.Parse(list2), x).ShouldBeTrue();
JToken.DeepEquals(JObject.Parse(list2), x).Should().BeTrue();
}
[Test(Description = "Checks that all typed properties (including obsolete ones) are returned")]
[Fact(DisplayName = "Checks that all typed properties (including obsolete ones) are returned")]
public async Task CanSerialize_New_Detached_With_DataChunks2()
{
var root = """
@@ -421,17 +426,17 @@ public class DetachedTests
new ObjectSerializerFactory(new BasePropertyGatherer()),
new SerializeProcessOptions(false, false, true, true)
);
var results = await process2.Serialize(@base, default).ConfigureAwait(false);
var results = await process2.Serialize(@base, default);
objects.Count.ShouldBe(3);
objects.Count.Should().Be(3);
var x = JObject.Parse(objects["525b1e9eef4d07165abb4ffc518395fc"]);
JToken.DeepEquals(JObject.Parse(root), x).ShouldBeTrue();
JToken.DeepEquals(JObject.Parse(root), x).Should().BeTrue();
x = JObject.Parse(objects["0e61e61edee00404ec6e0f9f594bce24"]);
JToken.DeepEquals(JObject.Parse(list1), x).ShouldBeTrue();
JToken.DeepEquals(JObject.Parse(list1), x).Should().BeTrue();
x = JObject.Parse(objects["f70738e3e3e593ac11099a6ed6b71154"]);
JToken.DeepEquals(JObject.Parse(list2), x).ShouldBeTrue();
JToken.DeepEquals(JObject.Parse(list2), x).Should().BeTrue();
}
}
@@ -529,7 +534,9 @@ public class DummyServerObjectManager : IServerObjectManager
public class DummySendCacheManager(Dictionary<string, string> objects) : ISqLiteJsonCacheManager
{
public IEnumerable<string> GetAllObjects() => throw new NotImplementedException();
public void Dispose() { }
public IReadOnlyCollection<(string, string)> GetAllObjects() => throw new NotImplementedException();
public void DeleteObject(string id) => throw new NotImplementedException();
@@ -1,6 +1,5 @@
using System.Runtime.CompilerServices;
using System.Text;
using Speckle.Sdk.Dependencies.Serialization;
using Speckle.Sdk.Serialisation.V2;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.Transports;
@@ -1,8 +1,4 @@
using System.Collections.Concurrent;
using Shouldly;
using Speckle.Newtonsoft.Json.Linq;
using Speckle.Sdk.Common;
using Speckle.Sdk.Dependencies.Serialization;
using Speckle.Sdk.Serialisation.V2;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.Transports;
@@ -4,7 +4,9 @@ namespace Speckle.Sdk.Serialization.Tests;
public class DummySqLiteReceiveManager(Dictionary<string, string> savedObjects) : ISqLiteJsonCacheManager
{
public IEnumerable<string> GetAllObjects() => throw new NotImplementedException();
public void Dispose() { }
public IReadOnlyCollection<(string, string)> GetAllObjects() => throw new NotImplementedException();
public void DeleteObject(string id) => throw new NotImplementedException();
@@ -14,7 +14,9 @@ public class DummySqLiteSendManager : ISqLiteJsonCacheManager
public bool HasObject(string objectId) => throw new NotImplementedException();
public IEnumerable<string> GetAllObjects() => throw new NotImplementedException();
public IReadOnlyCollection<(string, string)> GetAllObjects() => throw new NotImplementedException();
public void DeleteObject(string id) => throw new NotImplementedException();
public void Dispose() { }
}
@@ -1,21 +1,21 @@
using NUnit.Framework;
using Shouldly;
using FluentAssertions;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Serialisation.V2.Send;
using Xunit;
namespace Speckle.Sdk.Serialization.Tests;
public class ExplicitInterfaceTests
{
[SetUp]
public void Setup()
// Constructor to replace [SetUp]
public ExplicitInterfaceTests()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(TestClass).Assembly);
}
[Test]
[Fact] // Replaces [Test]
public async Task Test_Json()
{
var testClass = new TestClass() { RegularProperty = "Hello" };
@@ -29,24 +29,28 @@ public class ExplicitInterfaceTests
new ObjectSerializerFactory(new BasePropertyGatherer()),
new SerializeProcessOptions(false, false, true, true)
);
await process2.Serialize(testClass, default).ConfigureAwait(false);
objects.Count.ShouldBe(1);
await process2.Serialize(testClass, default);
objects.Count.Should().Be(1);
objects["daaa67cfd73a957247cf2d631b7ca4f3"]
.ShouldBe(
.Should()
.Be(
"{\"RegularProperty\":\"Hello\",\"applicationId\":null,\"speckle_type\":\"Speckle.Core.Serialisation.TestClass\",\"id\":\"daaa67cfd73a957247cf2d631b7ca4f3\"}"
);
}
[Test]
[Fact] // Replaces [Test]
public void Test_ExtractAllProperties()
{
var testClass = new TestClass() { RegularProperty = "Hello" };
var gatherer = new BasePropertyGatherer();
var properties = gatherer.ExtractAllProperties(testClass).ToList();
properties.Count.ShouldBe(3);
properties.Select(x => x.Name).ShouldContain("RegularProperty");
properties.Select(x => x.Name).ShouldNotContain("TestProperty");
properties.Count.Should().Be(3);
properties.Select(x => x.Name).Should().Contain("RegularProperty");
properties.Select(x => x.Name).Should().NotContain("TestProperty");
}
}
@@ -1,5 +1,4 @@
using NUnit.Framework;
using Shouldly;
using FluentAssertions;
using Speckle.Newtonsoft.Json.Linq;
using Speckle.Objects.Geometry;
using Speckle.Objects.Primitive;
@@ -9,20 +8,20 @@ using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Extensions;
using Speckle.Sdk.Serialisation;
using Speckle.Sdk.Serialisation.V2.Send;
using Xunit;
namespace Speckle.Sdk.Serialization.Tests;
public class ExternalIdTests
{
[SetUp]
public void Setup()
public ExternalIdTests()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(Polyline).Assembly);
}
[Test]
[TestCase("cfaf7ae0dfc5a7cf3343bb6db46ed238", "8d27f5c7fac36d985d89bb6d6d8acddc")]
[Theory]
[InlineData("cfaf7ae0dfc5a7cf3343bb6db46ed238", "8d27f5c7fac36d985d89bb6d6d8acddc")]
public void ExternalIdTest_Detached(string lineId, string valueId)
{
var p = new Polyline() { units = "cm", value = [1, 2] };
@@ -31,16 +30,16 @@ public class ExternalIdTests
default
);
var list = serializer.Serialize(p).ToDictionary(x => x.Item1, x => x.Item2);
list.ContainsKey(new Id(lineId)).ShouldBeTrue();
list.ContainsKey(new Id(lineId)).Should().BeTrue();
var json = list[new Id(lineId)];
var jObject = JObject.Parse(json.Value);
jObject.ContainsKey("__closure").ShouldBeTrue();
jObject.ContainsKey("__closure").Should().BeTrue();
var closures = (JObject)jObject["__closure"].NotNull();
closures.ContainsKey(valueId).ShouldBeTrue();
closures.ContainsKey(valueId).Should().BeTrue();
}
[Test]
[TestCase("cfaf7ae0dfc5a7cf3343bb6db46ed238", "8d27f5c7fac36d985d89bb6d6d8acddc")]
[Theory]
[InlineData("cfaf7ae0dfc5a7cf3343bb6db46ed238", "8d27f5c7fac36d985d89bb6d6d8acddc")]
public void ExternalIdTest_Detached_Nested(string lineId, string valueId)
{
var curve = new Curve()
@@ -61,16 +60,16 @@ public class ExternalIdTests
default
);
var list = serializer.Serialize(curve).ToDictionary(x => x.Item1, x => x.Item2);
list.ContainsKey(new Id(lineId)).ShouldBeTrue();
list.ContainsKey(new Id(lineId)).Should().BeTrue();
var json = list[new Id(lineId)];
var jObject = JObject.Parse(json.Value);
jObject.ContainsKey("__closure").ShouldBeTrue();
jObject.ContainsKey("__closure").Should().BeTrue();
var closures = (JObject)jObject["__closure"].NotNull();
closures.ContainsKey(valueId).ShouldBeTrue();
closures.ContainsKey(valueId).Should().BeTrue();
}
[Test]
[TestCase("cfaf7ae0dfc5a7cf3343bb6db46ed238", "8d27f5c7fac36d985d89bb6d6d8acddc")]
[Theory]
[InlineData("cfaf7ae0dfc5a7cf3343bb6db46ed238", "8d27f5c7fac36d985d89bb6d6d8acddc")]
public void ExternalIdTest_Detached_Nested_More(string lineId, string valueId)
{
var curve = new Curve()
@@ -92,16 +91,16 @@ public class ExternalIdTests
default
);
var list = serializer.Serialize(polycurve).ToDictionary(x => x.Item1, x => x.Item2);
list.ContainsKey(new Id(lineId)).ShouldBeTrue();
list.ContainsKey(new Id(lineId)).Should().BeTrue();
var json = list[new Id(lineId)];
var jObject = JObject.Parse(json.Value);
jObject.ContainsKey("__closure").ShouldBeTrue();
jObject.ContainsKey("__closure").Should().BeTrue();
var closures = (JObject)jObject["__closure"].NotNull();
closures.ContainsKey(valueId).ShouldBeTrue();
closures.ContainsKey(valueId).Should().BeTrue();
}
[Test]
[TestCase("cfaf7ae0dfc5a7cf3343bb6db46ed238", "8d27f5c7fac36d985d89bb6d6d8acddc")]
[Theory]
[InlineData("cfaf7ae0dfc5a7cf3343bb6db46ed238", "8d27f5c7fac36d985d89bb6d6d8acddc")]
public void ExternalIdTest_Detached_Nested_More_Too(string lineId, string valueId)
{
var curve = new Curve()
@@ -125,11 +124,11 @@ public class ExternalIdTests
default
);
var list = serializer.Serialize(@base).ToDictionary(x => x.Item1, x => x.Item2);
list.ContainsKey(new Id(lineId)).ShouldBeTrue();
list.ContainsKey(new Id(lineId)).Should().BeTrue();
var json = list[new Id(lineId)];
var jObject = JObject.Parse(json.Value);
jObject.ContainsKey("__closure").ShouldBeTrue();
jObject.ContainsKey("__closure").Should().BeTrue();
var closures = (JObject)jObject["__closure"].NotNull();
closures.ContainsKey(valueId).ShouldBeTrue();
closures.ContainsKey(valueId).Should().BeTrue();
}
}
File diff suppressed because one or more lines are too long
@@ -9,10 +9,10 @@
<ItemGroup>
<PackageReference Include="altcover" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="NUnit" />
<PackageReference Include="NUnit3TestAdapter" />
<PackageReference Include="Shouldly" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio"/>
</ItemGroup>
<ItemGroup>
@@ -4,9 +4,18 @@
"net8.0": {
"altcover": {
"type": "Direct",
"requested": "[8.9.3, )",
"resolved": "8.9.3",
"contentHash": "auKC+pDCkLjfhFkSRaAUBu25BOmlLSqucR7YBs/Lkbdc0XRuJoklWafs1KKp+M+VoJ1f0TeMS6B/FO5IeIcu7w=="
"requested": "[9.0.1, )",
"resolved": "9.0.1",
"contentHash": "aadciFNDT5bnylaYUkKal+s5hF7yU/lmZxImQWAlk1438iPqK1Uf79H5ylELpyLIU49HL5ql+tnWBihp3WVLCA=="
},
"FluentAssertions": {
"type": "Direct",
"requested": "[7.0.0, )",
"resolved": "7.0.0",
"contentHash": "mTLbcU991EQ1SEmNbVBaGGGJy0YFzvGd1sYJGNZ07nlPKuyHSn1I22aeKzqQXgEiaKyRO6MSCto9eN9VxMwBdA==",
"dependencies": {
"System.Configuration.ConfigurationManager": "6.0.0"
}
},
"GitVersion.MsBuild": {
"type": "Direct",
@@ -16,12 +25,12 @@
},
"Microsoft.NET.Test.Sdk": {
"type": "Direct",
"requested": "[17.11.1, )",
"resolved": "17.11.1",
"contentHash": "U3Ty4BaGoEu+T2bwSko9tWqWUOU16WzSFkq6U8zve75oRBMSLTBdMAZrVNNz1Tq12aCdDom9fcOcM9QZaFHqFg==",
"requested": "[17.12.0, )",
"resolved": "17.12.0",
"contentHash": "kt/PKBZ91rFCWxVIJZSgVLk+YR+4KxTuHf799ho8WNiK5ZQpJNAEZCAWX86vcKrs+DiYjiibpYKdGZP6+/N17w==",
"dependencies": {
"Microsoft.CodeCoverage": "17.11.1",
"Microsoft.TestPlatform.TestHost": "17.11.1"
"Microsoft.CodeCoverage": "17.12.0",
"Microsoft.TestPlatform.TestHost": "17.12.0"
}
},
"Microsoft.SourceLink.GitHub": {
@@ -34,53 +43,34 @@
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"NUnit": {
"type": "Direct",
"requested": "[4.2.2, )",
"resolved": "4.2.2",
"contentHash": "mon0OPko28yZ/foVXrhiUvq1LReaGsBdziumyyYGxV/pOE4q92fuYeN+AF+gEU5pCjzykcdBt5l7xobTaiBjsg=="
},
"NUnit3TestAdapter": {
"type": "Direct",
"requested": "[4.6.0, )",
"resolved": "4.6.0",
"contentHash": "R7e1+a4vuV/YS+ItfL7f//rG+JBvVeVLX4mHzFEZo4W1qEKl8Zz27AqvQSAqo+BtIzUCo4aAJMYa56VXS4hudw=="
},
"PolySharp": {
"type": "Direct",
"requested": "[1.15.0, )",
"resolved": "1.15.0",
"contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g=="
},
"Shouldly": {
"type": "Direct",
"requested": "[4.2.1, )",
"resolved": "4.2.1",
"contentHash": "dKAKiSuhLKqD2TXwLKtqNg1nwzJcIKOOMncZjk9LYe4W+h+SCftpWdxwR79YZUIHMH+3Vu9s0s0UHNrgICLwRQ==",
"dependencies": {
"DiffEngine": "11.3.0",
"EmptyFiles": "4.4.0"
}
},
"Speckle.InterfaceGenerator": {
"type": "Direct",
"requested": "[0.9.6, )",
"resolved": "0.9.6",
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
},
"DiffEngine": {
"type": "Transitive",
"resolved": "11.3.0",
"contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==",
"xunit": {
"type": "Direct",
"requested": "[2.9.3, )",
"resolved": "2.9.3",
"contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==",
"dependencies": {
"EmptyFiles": "4.4.0",
"System.Management": "6.0.1"
"xunit.analyzers": "1.18.0",
"xunit.assert": "2.9.3",
"xunit.core": "[2.9.3]"
}
},
"EmptyFiles": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw=="
"xunit.runner.visualstudio": {
"type": "Direct",
"requested": "[3.0.0, )",
"resolved": "3.0.0",
"contentHash": "HggUqjQJe8PtDxcP25Q+CnR6Lz4oX3GElhD9V4oU2+75x9HI6A6sxbfKGS4UwU4t4yJaS9fBmAuriz8bQApNjw=="
},
"GraphQL.Client.Abstractions": {
"type": "Transitive",
@@ -110,8 +100,8 @@
},
"Microsoft.CodeCoverage": {
"type": "Transitive",
"resolved": "17.11.1",
"contentHash": "nPJqrcA5iX+Y0kqoT3a+pD/8lrW/V7ayqnEJQsTonSoPz59J8bmoQhcSN4G8+UJ64Hkuf0zuxnfuj2lkHOq4cA=="
"resolved": "17.12.0",
"contentHash": "4svMznBd5JM21JIG2xZKGNanAHNXplxf/kQDFfLHXQ3OnpJkayRK/TjacFjA+EYmoyuNXHo/sOETEfcYtAzIrA=="
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
@@ -176,21 +166,26 @@
},
"Microsoft.TestPlatform.ObjectModel": {
"type": "Transitive",
"resolved": "17.11.1",
"contentHash": "E2jZqAU6JeWEVsyOEOrSW1o1bpHLgb25ypvKNB/moBXPVsFYBPd/Jwi7OrYahG50J83LfHzezYI+GaEkpAotiA==",
"resolved": "17.12.0",
"contentHash": "TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==",
"dependencies": {
"System.Reflection.Metadata": "1.6.0"
}
},
"Microsoft.TestPlatform.TestHost": {
"type": "Transitive",
"resolved": "17.11.1",
"contentHash": "DnG+GOqJXO/CkoqlJWeDFTgPhqD/V6VqUIL3vINizCWZ3X+HshCtbbyDdSHQQEjrc2Sl/K3yaxX6s+5LFEdYuw==",
"resolved": "17.12.0",
"contentHash": "MiPEJQNyADfwZ4pJNpQex+t9/jOClBGMiCiVVFuELCMSX2nmNfvUor3uFVxNNCg30uxDP8JDYfPnMXQzsfzYyg==",
"dependencies": {
"Microsoft.TestPlatform.ObjectModel": "17.11.1",
"Microsoft.TestPlatform.ObjectModel": "17.12.0",
"Newtonsoft.Json": "13.0.1"
}
},
"Microsoft.Win32.SystemEvents": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A=="
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.1",
@@ -226,22 +221,26 @@
"SQLitePCLRaw.core": "2.1.4"
}
},
"System.CodeDom": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA=="
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
},
"System.Management": {
"System.Configuration.ConfigurationManager": {
"type": "Transitive",
"resolved": "6.0.1",
"contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==",
"resolved": "6.0.0",
"contentHash": "7T+m0kDSlIPTHIkPMIu6m6tV6qsMqJpvQWW2jIc2qi7sn40qxFo0q+7mEQAhMPXZHMKnWrnv47ntGlM/ejvw3g==",
"dependencies": {
"System.CodeDom": "6.0.0"
"System.Security.Cryptography.ProtectedData": "6.0.0",
"System.Security.Permissions": "6.0.0"
}
},
"System.Drawing.Common": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==",
"dependencies": {
"Microsoft.Win32.SystemEvents": "6.0.0"
}
},
"System.Memory": {
@@ -264,6 +263,73 @@
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
"System.Security.AccessControl": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ=="
},
"System.Security.Cryptography.ProtectedData": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ=="
},
"System.Security.Permissions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==",
"dependencies": {
"System.Security.AccessControl": "6.0.0",
"System.Windows.Extensions": "6.0.0"
}
},
"System.Windows.Extensions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==",
"dependencies": {
"System.Drawing.Common": "6.0.0"
}
},
"xunit.abstractions": {
"type": "Transitive",
"resolved": "2.0.3",
"contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg=="
},
"xunit.analyzers": {
"type": "Transitive",
"resolved": "1.18.0",
"contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ=="
},
"xunit.assert": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA=="
},
"xunit.core": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==",
"dependencies": {
"xunit.extensibility.core": "[2.9.3]",
"xunit.extensibility.execution": "[2.9.3]"
}
},
"xunit.extensibility.core": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==",
"dependencies": {
"xunit.abstractions": "2.0.3"
}
},
"xunit.extensibility.execution": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==",
"dependencies": {
"xunit.extensibility.core": "[2.9.3]"
}
},
"speckle.objects": {
"type": "Project",
"dependencies": {
@@ -1,36 +1,39 @@
using GraphQL;
using System.ComponentModel;
using FluentAssertions;
using GraphQL;
using GraphQL.Client.Http;
using Speckle.Newtonsoft.Json;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Xunit;
namespace Speckle.Sdk.Tests.Integration.Api.GraphQL;
[TestOf(typeof(Client))]
public class GraphQLClientExceptionHandling
public class GraphQLClientExceptionHandling : IAsyncLifetime
{
private Client _sut;
[SetUp]
public async Task Setup()
public Task DisposeAsync() => Task.CompletedTask;
public async Task InitializeAsync()
{
_sut = await Fixtures.SeedUserWithClient();
}
[Test]
[Fact]
[Description($"Attempts to execute a query on a non-existent server, expect a {nameof(GraphQLHttpRequestException)}")]
public void TestHttpLayer()
public async Task TestHttpLayer()
{
_sut.GQLClient.Options.EndPoint = new Uri("http://127.0.0.1:1234"); //There is no server on this port...
Assert.ThrowsAsync<HttpRequestException>(async () => await _sut.ActiveUser.Get().ConfigureAwait(false));
await Assert.ThrowsAsync<HttpRequestException>(async () => await _sut.ActiveUser.Get().ConfigureAwait(false));
}
[Test]
[Fact]
[Description(
$"Attempts to execute a admin only command from a regular user, expect an inner {nameof(SpeckleGraphQLForbiddenException)}"
)]
public void TestGraphQLLayer_Forbidden()
public async Task TestGraphQLLayer_Forbidden()
{
//language=graphql
const string QUERY = """
@@ -46,14 +49,14 @@ public class GraphQLClientExceptionHandling
""";
GraphQLRequest request = new(query: QUERY);
var ex = Assert.ThrowsAsync<AggregateException>(
var ex = await Assert.ThrowsAsync<AggregateException>(
async () => await _sut.ExecuteGraphQLRequest<dynamic>(request).ConfigureAwait(false)
);
Assert.That(ex?.InnerExceptions, Has.Exactly(1).TypeOf<SpeckleGraphQLForbiddenException>());
ex.InnerExceptions.OfType<SpeckleGraphQLForbiddenException>().Count().Should().Be(1);
}
[Test, Description($"Attempts to execute a bad query, expect an inner {nameof(SpeckleGraphQLInvalidQueryException)}")]
public void TestGraphQLLayer_BadQuery()
[Fact, Description($"Attempts to execute a bad query, expect an inner {nameof(SpeckleGraphQLInvalidQueryException)}")]
public async Task TestGraphQLLayer_BadQuery()
{
//language=graphql
const string QUERY = """
@@ -64,41 +67,39 @@ public class GraphQLClientExceptionHandling
}
""";
GraphQLRequest request = new(query: QUERY);
var ex = Assert.ThrowsAsync<AggregateException>(
var ex = await Assert.ThrowsAsync<AggregateException>(
async () => await _sut.ExecuteGraphQLRequest<dynamic>(request).ConfigureAwait(false)
);
Assert.That(ex?.InnerExceptions, Has.Exactly(1).TypeOf<SpeckleGraphQLInvalidQueryException>());
ex.InnerExceptions.OfType<SpeckleGraphQLInvalidQueryException>().Count().Should().Be(1);
}
[Test]
[Fact]
[Description(
$"Attempts to execute a query with an invalid input, expect an inner {nameof(SpeckleGraphQLBadInputException)}"
)]
public void TestGraphQLLayer_BadInput()
public async Task TestGraphQLLayer_BadInput()
{
ProjectUpdateRoleInput input = new(null!, null!, null);
var ex = Assert.ThrowsAsync<AggregateException>(
var ex = await Assert.ThrowsAsync<AggregateException>(
async () => await _sut.Project.UpdateRole(input).ConfigureAwait(false)
);
Assert.That(ex?.InnerExceptions, Has.Exactly(2).TypeOf<SpeckleGraphQLBadInputException>());
ex.InnerExceptions.OfType<SpeckleGraphQLBadInputException>().Count().Should().Be(2);
}
[Test]
public void TestCancel()
[Fact]
public async Task TestCancel()
{
using CancellationTokenSource cts = new();
cts.Cancel();
await cts.CancelAsync();
var ex = Assert.CatchAsync<OperationCanceledException>(
var ex = await Assert.ThrowsAsync<TaskCanceledException>(
async () => await _sut.ActiveUser.Get(cts.Token).ConfigureAwait(false)
);
Assert.That(ex?.CancellationToken, Is.EqualTo(cts.Token));
ex.CancellationToken.Should().BeEquivalentTo(cts.Token);
}
[Test]
[Fact]
public void TestDisposal()
{
_sut.Dispose();
@@ -107,10 +108,10 @@ public class GraphQLClientExceptionHandling
}
[
Test,
Fact,
Description($"Attempts to execute a query with a mismatched type, expect an {nameof(JsonSerializationException)}")
]
public void TestDeserialization()
public async Task TestDeserialization()
{
//language=graphql
const string QUERY = """
@@ -121,6 +122,8 @@ public class GraphQLClientExceptionHandling
}
""";
GraphQLRequest request = new(query: QUERY);
Assert.CatchAsync<JsonException>(async () => await _sut.ExecuteGraphQLRequest<int>(request).ConfigureAwait(false));
await Assert.ThrowsAsync<JsonReaderException>(
async () => await _sut.ExecuteGraphQLRequest<int>(request).ConfigureAwait(false)
);
}
}
@@ -1,53 +1,60 @@
using Speckle.Sdk.Api;
using FluentAssertions;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Api.GraphQL.Resources;
using Xunit;
namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources;
[TestOf(typeof(ActiveUserResource))]
public class ActiveUserResourceTests
public class ActiveUserResourceTests : IAsyncLifetime
{
private Client _testUser;
private ActiveUserResource Sut => _testUser.ActiveUser;
[OneTimeSetUp]
public async Task Setup()
// Setup method for xUnit using IAsyncLifetime
public async Task InitializeAsync()
{
_testUser = await Fixtures.SeedUserWithClient();
}
[Test]
public Task DisposeAsync()
{
// No resources to dispose
return Task.CompletedTask;
}
[Fact]
public async Task ActiveUserGet()
{
var res = await Sut.Get();
Assert.That(res, Is.Not.Null);
Assert.That(res!.id, Is.EqualTo(_testUser.Account.userInfo.id));
res.Should().NotBeNull();
res!.id.Should().Be(_testUser.Account.userInfo.id);
}
[Test]
[Fact]
public async Task ActiveUserGet_NonAuthed()
{
var result = await Fixtures.Unauthed.ActiveUser.Get();
Assert.That(result, Is.EqualTo(null));
result.Should().BeNull();
}
[Test]
[Fact]
public async Task ActiveUserUpdate()
{
const string NEW_NAME = "Ron";
const string NEW_BIO = "Now I have a bio, isn't that nice!";
const string NEW_COMPANY = "Limited Cooperation Organization Inc";
var res = await Sut.Update(new UserUpdateInput(name: NEW_NAME, bio: NEW_BIO, company: NEW_COMPANY));
Assert.That(res, Is.Not.Null);
Assert.That(res.id, Is.EqualTo(_testUser.Account.userInfo.id));
Assert.That(res.name, Is.EqualTo(NEW_NAME));
Assert.That(res.company, Is.EqualTo(NEW_COMPANY));
Assert.That(res.bio, Is.EqualTo(NEW_BIO));
res.Should().NotBeNull();
res.id.Should().Be(_testUser.Account.userInfo.id);
res.name.Should().Be(NEW_NAME);
res.company.Should().Be(NEW_COMPANY);
res.bio.Should().Be(NEW_BIO);
}
[Test]
[Fact]
public async Task ActiveUserGetProjects()
{
var p1 = await _testUser.Project.Create(new("Project 1", null, null));
@@ -55,14 +62,17 @@ public class ActiveUserResourceTests
var res = await Sut.GetProjects();
Assert.That(res.items, Has.Exactly(1).Items.With.Property(nameof(Project.id)).EqualTo(p1.id));
Assert.That(res.items, Has.Exactly(1).Items.With.Property(nameof(Project.id)).EqualTo(p2.id));
Assert.That(res.items, Has.Count.EqualTo(2));
res.items.Should().Contain(x => x.id == p1.id);
res.items.Should().Contain(x => x.id == p2.id);
res.items.Count.Should().Be(2);
}
[Test]
public void ActiveUserGetProjects_NoAuth()
[Fact]
public async Task ActiveUserGetProjects_NoAuth()
{
Assert.ThrowsAsync<SpeckleGraphQLException>(async () => await Fixtures.Unauthed.ActiveUser.GetProjects());
await FluentActions
.Invoking(async () => await Fixtures.Unauthed.ActiveUser.GetProjects())
.Should()
.ThrowAsync<SpeckleGraphQLException>();
}
}
@@ -1,104 +1,111 @@
using Speckle.Sdk.Api;
using FluentAssertions;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Api.GraphQL.Resources;
using Speckle.Sdk.Common;
using Xunit;
namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources;
[TestOf(typeof(CommentResource))]
public class CommentResourceTests
{
private Client _testUser;
private CommentResource Sut => _testUser.Comment;
private Project _project;
private Model _model;
private string _versionId;
private Comment _comment;
private readonly Client _testUser;
private readonly CommentResource Sut;
private readonly Project _project;
private readonly Model _model;
private readonly string _versionId;
private readonly Comment _comment;
[SetUp]
public async Task Setup()
// Constructor for setup
public CommentResourceTests()
{
_testUser = await Fixtures.SeedUserWithClient();
_project = await _testUser.Project.Create(new("Test project", "", null));
_model = await _testUser.Model.Create(new("Test Model 1", "", _project.id));
_versionId = await Fixtures.CreateVersion(_testUser, _project.id, _model.id);
_comment = await CreateComment();
// Synchronous operations converted to async Task.Run for constructor
_testUser = Task.Run(async () => await Fixtures.SeedUserWithClient()).Result!;
_project = Task.Run(async () => await _testUser.Project.Create(new("Test project", "", null))).Result!;
_model = Task.Run(async () => await _testUser.Model.Create(new("Test Model 1", "", _project.id))).Result!;
_versionId = Task.Run(async () => await Fixtures.CreateVersion(_testUser, _project.id, _model.id)).Result!;
_comment = Task.Run(CreateComment).Result!;
Sut = _testUser.Comment;
}
[Test]
[Fact]
public async Task Get()
{
var comment = await Sut.Get(_comment.id, _project.id);
Assert.That(comment.id, Is.EqualTo(_comment.id));
Assert.That(comment.authorId, Is.EqualTo(_testUser.Account.userInfo.id));
comment.Should().NotBeNull();
comment.id.Should().Be(_comment.id);
comment.authorId.Should().Be(_testUser.Account.userInfo.id);
}
[Test]
[Fact]
public async Task GetProjectComments()
{
var comments = await Sut.GetProjectComments(_project.id);
Assert.That(comments.items.Count, Is.EqualTo(1));
Assert.That(comments.totalCount, Is.EqualTo(1));
comments.Should().NotBeNull();
comments.items.Count.Should().Be(1);
comments.totalCount.Should().Be(1);
Comment comment = comments.items[0];
Assert.That(comment, Is.Not.Null);
Assert.That(comment, Has.Property(nameof(Comment.authorId)).EqualTo(_testUser.Account.userInfo.id));
Assert.That(comment, Has.Property(nameof(Comment.id)).EqualTo(_comment.id));
Assert.That(comment, Has.Property(nameof(Comment.authorId)).EqualTo(_comment.authorId));
Assert.That(comment, Has.Property(nameof(Comment.archived)).EqualTo(_comment.archived));
Assert.That(comment, Has.Property(nameof(Comment.archived)).EqualTo(false));
Assert.That(comment, Has.Property(nameof(Comment.createdAt)).EqualTo(_comment.createdAt));
comment.Should().NotBeNull();
comment.authorId.Should().Be(_testUser.Account.userInfo.id);
comment.id.Should().Be(_comment.id);
comment.authorId.Should().Be(_comment.authorId);
comment.archived.Should().Be(false);
comment.createdAt.Should().Be(_comment.createdAt);
}
[Test]
[Fact]
public async Task MarkViewed()
{
await Sut.MarkViewed(new(_comment.id, _project.id));
var res = await Sut.Get(_comment.id, _project.id);
Assert.That(res.viewedAt, Is.Not.Null);
var res = await Sut.Get(_comment.id, _project.id);
res.viewedAt.Should().NotBeNull();
}
[Test]
[Fact]
public async Task Archive()
{
await Sut.Archive(new(_comment.id, _project.id, true));
var archived = await Sut.Get(_comment.id, _project.id);
Assert.That(archived.archived, Is.True);
archived.archived.Should().BeTrue();
await Sut.Archive(new(_comment.id, _project.id, false));
var unarchived = await Sut.Get(_comment.id, _project.id);
Assert.That(unarchived.archived, Is.False);
unarchived.archived.Should().BeFalse();
}
[Test]
[Fact]
public async Task Edit()
{
var blobs = await Fixtures.SendBlobData(_testUser.Account, _project.id);
var blobIds = blobs.Select(b => b.id.NotNull()).ToList();
EditCommentInput input = new(new(blobIds, null), _comment.id, _project.id);
var input = new EditCommentInput(new(blobIds, null), _comment.id, _project.id);
var editedComment = await Sut.Edit(input);
Assert.That(editedComment, Is.Not.Null);
Assert.That(editedComment, Has.Property(nameof(Comment.id)).EqualTo(_comment.id));
Assert.That(editedComment, Has.Property(nameof(Comment.authorId)).EqualTo(_comment.authorId));
Assert.That(editedComment, Has.Property(nameof(Comment.createdAt)).EqualTo(_comment.createdAt));
Assert.That(editedComment, Has.Property(nameof(Comment.updatedAt)).GreaterThanOrEqualTo(_comment.updatedAt));
editedComment.Should().NotBeNull();
editedComment.id.Should().Be(_comment.id);
editedComment.authorId.Should().Be(_comment.authorId);
editedComment.createdAt.Should().Be(_comment.createdAt);
editedComment.updatedAt.Should().BeOnOrAfter(_comment.updatedAt);
}
[Test]
[Fact]
public async Task Reply()
{
var blobs = await Fixtures.SendBlobData(_testUser.Account, _project.id);
var blobIds = blobs.Select(b => b.id.NotNull()).ToList();
CreateCommentReplyInput input = new(new(blobIds, null), _comment.id, _project.id);
var input = new CreateCommentReplyInput(new(blobIds, null), _comment.id, _project.id);
var editedComment = await Sut.Reply(input);
Assert.That(editedComment, Is.Not.Null);
editedComment.Should().NotBeNull();
}
private async Task<Comment> CreateComment()
@@ -1,94 +1,137 @@
using Speckle.Sdk.Api;
using FluentAssertions;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Enums;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Api.GraphQL.Resources;
using Xunit;
namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources;
[TestOf(typeof(ModelResource))]
public class ModelResourceExceptionalTests
public class ModelResourceExceptionalTests : IAsyncLifetime
{
private Client _testUser;
private ModelResource Sut => _testUser.Model;
private Project _project;
private Model _model;
[OneTimeSetUp]
public async Task Setup()
// Replaces NUnit's OneTimeSetUp with an async constructor logic or initializer pattern
public async Task InitializeAsync()
{
_testUser = await Fixtures.SeedUserWithClient();
_project = await _testUser.Project.Create(new("Test project", "", ProjectVisibility.Private));
_model = await _testUser.Model.Create(new("Test Model", "", _project.id));
}
[TestCase("")]
[TestCase(" ")]
public void ModelCreate_Throws_InvalidInput(string name)
public Task DisposeAsync() => Task.CompletedTask;
[Theory]
[InlineData("")]
[InlineData(" ")]
public async Task ModelCreate_Throws_InvalidInput(string name)
{
// Arrange
CreateModelInput input = new(name, null, _project.id);
var ex = Assert.ThrowsAsync<AggregateException>(async () => await Sut.Create(input));
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLException>());
// Act & Assert
var ex = await FluentActions
.Invoking(async () => await Sut.Create(input))
.Should()
.ThrowAsync<AggregateException>();
ex.WithInnerExceptionExactly<SpeckleGraphQLException>();
}
[Test]
public void ModelGet_Throws_NoAuth()
[Fact]
public async Task ModelGet_Throws_NoAuth()
{
var ex = Assert.ThrowsAsync<AggregateException>(
async () => await Fixtures.Unauthed.Model.Get(_model.id, _project.id)
);
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLForbiddenException>());
// Act & Assert
var ex = await FluentActions
.Invoking(async () => await Fixtures.Unauthed.Model.Get(_model.id, _project.id))
.Should()
.ThrowAsync<AggregateException>();
ex.WithInnerExceptionExactly<SpeckleGraphQLForbiddenException>();
}
[Test]
public void ModelGet_Throws_NonExistentModel()
[Fact]
public async Task ModelGet_Throws_NonExistentModel()
{
var ex = Assert.ThrowsAsync<AggregateException>(async () => await Sut.Get("non existent model", _project.id));
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLException>());
// Act & Assert
var ex = await FluentActions
.Invoking(async () => await Sut.Get("non existent model", _project.id))
.Should()
.ThrowAsync<AggregateException>();
ex.WithInnerExceptionExactly<SpeckleGraphQLException>();
}
[Test]
public void ModelGet_Throws_NonExistentProject()
[Fact]
public async Task ModelGet_Throws_NonExistentProject()
{
var ex = Assert.ThrowsAsync<AggregateException>(async () => await Sut.Get(_model.id, "non existent project"));
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLStreamNotFoundException>());
// Act & Assert
var ex = await FluentActions
.Invoking(async () => await Sut.Get(_model.id, "non existent project"))
.Should()
.ThrowAsync<AggregateException>();
ex.WithInnerExceptionExactly<SpeckleGraphQLStreamNotFoundException>();
}
[Test]
public void ModelUpdate_Throws_NonExistentModel()
[Fact]
public async Task ModelUpdate_Throws_NonExistentModel()
{
// Arrange
UpdateModelInput input = new("non-existent model", "MY new name", "MY new desc", _project.id);
var ex = Assert.ThrowsAsync<AggregateException>(async () => await Sut.Update(input));
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLException>());
// Act & Assert
var ex = await FluentActions
.Invoking(async () => await Sut.Update(input))
.Should()
.ThrowAsync<AggregateException>();
ex.WithInnerExceptionExactly<SpeckleGraphQLException>();
}
[Test]
public void ModelUpdate_Throws_NonExistentProject()
[Fact]
public async Task ModelUpdate_Throws_NonExistentProject()
{
// Arrange
UpdateModelInput input = new(_model.id, "MY new name", "MY new desc", "non-existent project");
var ex = Assert.ThrowsAsync<AggregateException>(async () => await Sut.Update(input));
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLForbiddenException>());
// Act & Assert
var ex = await FluentActions
.Invoking(async () => await Sut.Update(input))
.Should()
.ThrowAsync<AggregateException>();
ex.WithInnerExceptionExactly<SpeckleGraphQLForbiddenException>();
}
[Test]
public void ModelUpdate_Throws_NonAuthProject()
[Fact]
public async Task ModelUpdate_Throws_NonAuthProject()
{
// Arrange
UpdateModelInput input = new(_model.id, "MY new name", "MY new desc", _project.id);
var ex = Assert.ThrowsAsync<AggregateException>(async () => await Fixtures.Unauthed.Model.Update(input));
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLForbiddenException>());
// Act & Assert
var ex = await FluentActions
.Invoking(async () => await Fixtures.Unauthed.Model.Update(input))
.Should()
.ThrowAsync<AggregateException>();
ex.WithInnerExceptionExactly<SpeckleGraphQLForbiddenException>();
}
[Test]
[Fact]
public async Task ModelDelete_Throws_NoAuth()
{
// Arrange
Model toDelete = await Sut.Create(new("Delete me", null, _project.id));
DeleteModelInput input = new(toDelete.id, _project.id);
await Sut.Delete(input);
var ex = Assert.ThrowsAsync<AggregateException>(async () => await Sut.Delete(input));
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLException>());
// Act & Assert
var ex = await FluentActions
.Invoking(async () => await Sut.Delete(input))
.Should()
.ThrowAsync<AggregateException>();
ex.WithInnerExceptionExactly<SpeckleGraphQLException>();
}
}
@@ -1,100 +1,127 @@
using Speckle.Sdk.Api;
using FluentAssertions;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Api.GraphQL.Resources;
using Xunit;
namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources;
[TestOf(typeof(ModelResource))]
public class ModelResourceTests
public class ModelResourceTests : IAsyncLifetime
{
private Client _testUser;
private ModelResource Sut => _testUser.Model;
private Project _project;
private Model _model;
[SetUp]
public async Task Setup()
public async Task InitializeAsync()
{
// Runs instead of [SetUp] in NUnit
_testUser = await Fixtures.SeedUserWithClient();
_project = await _testUser.Project.Create(new("Test project", "", null));
_model = await _testUser.Model.Create(new("Test Model", "", _project.id));
}
[Order(1)]
[TestCase("My Model", "My model description")]
[TestCase("my/nested/model", null)]
public async Task ModelCreate(string name, string description)
public Task DisposeAsync()
{
// Perform any cleanup, if needed
return Task.CompletedTask;
}
[Theory]
[InlineData("My Model", "My model description")]
[InlineData("my/nested/model", null)]
public async Task ModelCreate(string name, string? description)
{
// Arrange
CreateModelInput input = new(name, description, _project.id);
// Act
Model result = await Sut.Create(input);
Assert.That(result, Is.Not.Null);
Assert.That(result, Has.Property(nameof(result.id)).Not.Null);
Assert.That(result, Has.Property(nameof(result.name)).EqualTo(input.name).IgnoreCase);
Assert.That(result, Has.Property(nameof(result.description)).EqualTo(input.description));
// Assert
result.Should().NotBeNull();
result.id.Should().NotBeNull();
result.name.Should().ContainEquivalentOf(input.name);
result.description.Should().Be(input.description);
}
[Test]
[Fact]
public async Task ModelGet()
{
// Act
Model result = await Sut.Get(_model.id, _project.id);
Assert.That(result.id, Is.EqualTo(_model.id));
Assert.That(result.name, Is.EqualTo(_model.name));
Assert.That(result.description, Is.EqualTo(_model.description));
Assert.That(result.createdAt, Is.EqualTo(_model.createdAt));
Assert.That(result.updatedAt, Is.EqualTo(_model.updatedAt));
// Assert
result.id.Should().Be(_model.id);
result.name.Should().Be(_model.name);
result.description.Should().Be(_model.description);
result.createdAt.Should().Be(_model.createdAt);
result.updatedAt.Should().Be(_model.updatedAt);
}
[Test]
[Order(2)]
[Fact]
public async Task GetModels()
{
// Act
var result = await Sut.GetModels(_project.id);
Assert.That(result.items, Has.Count.EqualTo(1));
Assert.That(result.totalCount, Is.EqualTo(1));
Assert.That(result.items[0], Has.Property(nameof(Model.id)).EqualTo(_model.id));
// Assert
result.items.Count.Should().Be(1);
result.totalCount.Should().Be(1);
result.items[0].id.Should().Be(_model.id);
}
[Test]
[Fact]
public async Task Project_GetModels()
{
// Act
var result = await _testUser.Project.GetWithModels(_project.id);
Assert.That(result, Has.Property(nameof(Project.id)).EqualTo(_project.id));
Assert.That(result.models.items, Has.Count.EqualTo(1));
Assert.That(result.models.totalCount, Is.EqualTo(1));
Assert.That(result.models.items[0], Has.Property(nameof(Model.id)).EqualTo(_model.id));
// Assert
result.id.Should().Be(_project.id);
result.models.items.Count.Should().Be(1);
result.models.totalCount.Should().Be(1);
result.models.items[0].id.Should().Be(_model.id);
}
[Test]
[Fact]
public async Task ModelUpdate()
{
// Arrange
const string NEW_NAME = "MY new name";
const string NEW_DESCRIPTION = "MY new desc";
UpdateModelInput input = new(_model.id, NEW_NAME, NEW_DESCRIPTION, _project.id);
var input = new UpdateModelInput(_model.id, NEW_NAME, NEW_DESCRIPTION, _project.id);
// Act
Model updatedModel = await Sut.Update(input);
Assert.That(updatedModel.id, Is.EqualTo(_model.id));
Assert.That(updatedModel.name, Is.EqualTo(NEW_NAME).IgnoreCase);
Assert.That(updatedModel.description, Is.EqualTo(NEW_DESCRIPTION));
Assert.That(updatedModel.updatedAt, Is.GreaterThanOrEqualTo(_model.updatedAt));
// Assert
updatedModel.id.Should().Be(_model.id);
updatedModel.name.Should().ContainEquivalentOf(NEW_NAME);
updatedModel.description.Should().Be(NEW_DESCRIPTION);
updatedModel.updatedAt.Should().BeOnOrAfter(_model.updatedAt);
}
[Test]
[Fact]
public async Task ModelDelete()
{
DeleteModelInput input = new(_model.id, _project.id);
// Arrange
var input = new DeleteModelInput(_model.id, _project.id);
// Act
await Sut.Delete(input);
var getEx = Assert.CatchAsync<AggregateException>(async () => await Sut.Get(_model.id, _project.id));
Assert.That(getEx?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLException>());
// Assert: Ensure fetching the deleted model throws an exception
var getEx = await FluentActions
.Invoking(() => Sut.Get(_model.id, _project.id))
.Should()
.ThrowAsync<AggregateException>();
getEx.WithInnerExceptionExactly<SpeckleGraphQLException>();
var delEx = Assert.CatchAsync<AggregateException>(async () => await Sut.Delete(input));
Assert.That(delEx?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLException>());
// Assert: Ensure deleting the non-existing model again throws an exception
var delEx = await FluentActions.Invoking(() => Sut.Delete(input)).Should().ThrowAsync<AggregateException>();
getEx.WithInnerExceptionExactly<SpeckleGraphQLException>();
}
}
@@ -1,50 +1,54 @@
using Speckle.Sdk.Api;
using FluentAssertions;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Resources;
using Speckle.Sdk.Credentials;
using Xunit;
namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources;
[TestOf(typeof(OtherUserResource))]
public class OtherUserResourceTests
{
private Client _testUser;
private Account _testData;
private readonly Client _testUser;
private readonly Account _testData;
private OtherUserResource Sut => _testUser.OtherUser;
[OneTimeSetUp]
public async Task Setup()
public OtherUserResourceTests()
{
_testUser = await Fixtures.SeedUserWithClient();
_testData = await Fixtures.SeedUser();
_testUser = Fixtures.SeedUserWithClient().GetAwaiter().GetResult();
_testData = Fixtures.SeedUser().GetAwaiter().GetResult();
}
[Test]
public async Task OtherUserGet()
[Fact]
public async Task OtherUserGet_Should_ReturnCorrectUser()
{
var res = await Sut.Get(_testData.userInfo.id);
Assert.That(res, Is.Not.Null);
Assert.That(res!.name, Is.EqualTo(_testData.userInfo.name));
res.Should().NotBeNull();
res!.name.Should().Be(_testData.userInfo.name);
}
[Test]
public async Task OtherUserGet_NonExistentUser()
[Fact]
public async Task OtherUserGet_NonExistentUser_Should_ReturnNull()
{
var result = await Sut.Get("AnIdThatDoesntExist");
Assert.That(result, Is.Null);
result.Should().BeNull();
}
[Test]
public async Task UserSearch()
[Fact]
public async Task UserSearch_Should_ReturnMatchingUser()
{
var res = await Sut.UserSearch(_testData.userInfo.email, 25);
Assert.That(res.items, Has.Count.EqualTo(1));
Assert.That(res.items[0].id, Is.EqualTo(_testData.userInfo.id));
res.items.Should().ContainSingle();
res.items[0].id.Should().Be(_testData.userInfo.id);
}
[Test]
public async Task UserSearch_NonExistentUser()
[Fact]
public async Task UserSearch_NonExistentUser_Should_ReturnEmptyList()
{
var res = await Sut.UserSearch("idontexist@example.com", 25);
Assert.That(res.items, Has.Count.EqualTo(0));
res.items.Should().BeEmpty();
}
}
@@ -1,33 +1,48 @@
using Speckle.Sdk.Api;
using FluentAssertions;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Api.GraphQL.Resources;
using Xunit;
namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources;
[TestOf(typeof(ProjectInviteResource))]
public class ProjectInviteResourceExceptionalTests
public class ProjectInviteResourceExceptionalTests : IAsyncLifetime
{
private Client _testUser;
private Project _project;
private ProjectInviteResource Sut => _testUser.ProjectInvite;
[OneTimeSetUp]
public async Task Setup()
// Replacing OneTimeSetUp with IAsyncLifetime's InitializeAsync
public async Task InitializeAsync()
{
_testUser = await Fixtures.SeedUserWithClient();
_project = await _testUser.Project.Create(new("test", null, null));
_project = await _testUser.Project.Create(new ProjectCreateInput("test", null, null));
}
[TestCase(null, null, null, null)]
[TestCase(null, "something", "something", null)]
public void ProjectInviteCreate_InvalidInput(string email, string role, string serverRole, string userId)
// Implementing IAsyncLifetime's DisposeAsync (optional if no cleanup is needed)
public Task DisposeAsync() => Task.CompletedTask;
[Theory]
[InlineData(null, null, null, null)]
[InlineData(null, "something", "something", null)]
public async Task ProjectInviteCreate_InvalidInput_ShouldThrowSpeckleGraphQLException(
string? email,
string? role,
string? serverRole,
string? userId
)
{
var ex = Assert.CatchAsync<AggregateException>(async () =>
{
var input = new ProjectInviteCreateInput(email, role, serverRole, userId);
await Sut.Create(_project.id, input);
});
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLException>());
var input = new ProjectInviteCreateInput(email, role, serverRole, userId);
var exception = await FluentActions
.Invoking(async () =>
{
await Sut.Create(_project.id, input);
})
.Should()
.ThrowAsync<AggregateException>();
exception.WithInnerExceptionExactly<SpeckleGraphQLException>();
}
}
@@ -1,22 +1,21 @@
using Speckle.Sdk.Api;
using FluentAssertions;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Api.GraphQL.Resources;
using Speckle.Sdk.Common;
using Xunit;
namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources;
[TestOf(typeof(ProjectInviteResource))]
public class ProjectInviteResourceTests
public class ProjectInviteResourceTests : IAsyncLifetime
{
private Client _inviter,
_invitee;
private Project _project;
private PendingStreamCollaborator _createdInvite;
[SetUp]
public async Task Setup()
public async Task InitializeAsync()
{
_inviter = await Fixtures.SeedUserWithClient();
_invitee = await Fixtures.SeedUserWithClient();
@@ -24,6 +23,8 @@ public class ProjectInviteResourceTests
_createdInvite = await SeedInvite();
}
public Task DisposeAsync() => Task.CompletedTask;
private async Task<PendingStreamCollaborator> SeedInvite()
{
ProjectInviteCreateInput input = new(_invitee.Account.userInfo.email, null, null, null);
@@ -32,7 +33,7 @@ public class ProjectInviteResourceTests
return invites.First(i => i.projectId == res.id);
}
[Test]
[Fact]
public async Task ProjectInviteCreate_ByEmail()
{
ProjectInviteCreateInput input = new(_invitee.Account.userInfo.email, null, null, null);
@@ -41,75 +42,72 @@ public class ProjectInviteResourceTests
var invites = await _invitee.ActiveUser.GetProjectInvites();
var invite = invites.First(i => i.projectId == res.id);
Assert.That(res, Has.Property(nameof(_project.id)).EqualTo(_project.id));
Assert.That(res.invitedTeam, Has.Count.EqualTo(1));
Assert.That(invite.user!.id, Is.EqualTo(_invitee.Account.userInfo.id));
Assert.That(invite.token, Is.Not.Null);
res.id.Should().Be(_project.id);
res.invitedTeam.Should().ContainSingle();
invite.user!.id.Should().Be(_invitee.Account.userInfo.id);
invite.token.Should().NotBeNull();
}
[Test]
[Fact]
public async Task ProjectInviteCreate_ByUserId()
{
ProjectInviteCreateInput input = new(null, null, null, _invitee.Account.userInfo.id);
var res = await _inviter.ProjectInvite.Create(_project.id, input);
Assert.That(res, Has.Property(nameof(_project.id)).EqualTo(_project.id));
Assert.That(res.invitedTeam, Has.Count.EqualTo(1));
Assert.That(res.invitedTeam[0].user!.id, Is.EqualTo(_invitee.Account.userInfo.id));
res.id.Should().Be(_project.id);
res.invitedTeam.Should().ContainSingle();
res.invitedTeam[0].user!.id.Should().Be(_invitee.Account.userInfo.id);
}
[Test]
[Fact]
public async Task ProjectInviteGet()
{
var collaborator = await _invitee.ProjectInvite.Get(_project.id, _createdInvite.token);
var collaborator = await _invitee.ProjectInvite.Get(_project.id, _createdInvite.token).NotNull();
Assert.That(
collaborator,
Has.Property(nameof(PendingStreamCollaborator.inviteId)).EqualTo(_createdInvite.inviteId)
);
Assert.That(collaborator!.user!.id, Is.EqualTo(_createdInvite.user!.id));
collaborator.inviteId.Should().Be(_createdInvite.inviteId);
collaborator.user!.id.Should().Be(_createdInvite.user!.id);
}
[Test]
[Fact]
public async Task ProjectInviteGet_NonExisting()
{
var collaborator = await _invitee.ProjectInvite.Get(_project.id, "this is not a real token");
Assert.That(collaborator, Is.Null);
collaborator.Should().BeNull();
}
[Test]
[Fact]
public async Task ProjectInviteUse_MemberAdded()
{
ProjectInviteUseInput input = new(true, _createdInvite.projectId, _createdInvite.token.NotNull());
await _invitee.ProjectInvite.Use(input);
var project = await _inviter.Project.GetWithTeam(_project.id);
var teamMembers = project.team.Select(c => c.user.id);
var teamMembers = project.team.Select(c => c.user.id).ToArray();
var expectedTeamMembers = new[] { _inviter.Account.userInfo.id, _invitee.Account.userInfo.id };
Assert.That(teamMembers, Is.EquivalentTo(expectedTeamMembers));
teamMembers.Should().BeEquivalentTo(expectedTeamMembers);
}
[Test]
[Fact]
public async Task ProjectInviteCancel_MemberNotAdded()
{
var res = await _inviter.ProjectInvite.Cancel(_createdInvite.projectId, _createdInvite.inviteId);
Assert.That(res.invitedTeam, Is.Empty);
res.invitedTeam.Should().BeEmpty();
}
[Test]
[TestCase(StreamRoles.STREAM_OWNER)]
[TestCase(StreamRoles.STREAM_CONTRIBUTOR)]
[TestCase(StreamRoles.STREAM_REVIEWER)]
[TestCase(StreamRoles.REVOKE)]
[Theory]
[InlineData(StreamRoles.STREAM_OWNER)]
[InlineData(StreamRoles.STREAM_CONTRIBUTOR)]
[InlineData(StreamRoles.STREAM_REVIEWER)]
[InlineData(StreamRoles.REVOKE)]
public async Task ProjectUpdateRole(string? newRole)
{
await ProjectInviteUse_MemberAdded();
ProjectUpdateRoleInput input = new(_invitee.Account.userInfo.id, _project.id, newRole);
_ = await _inviter.Project.UpdateRole(input);
Project finalProject = await _invitee.Project.Get(_project.id);
Assert.That(finalProject.role, Is.EqualTo(newRole));
ProjectUpdateRoleInput input = new(_invitee.Account.userInfo.id, _project.id, newRole);
await _inviter.Project.UpdateRole(input);
var finalProject = await _invitee.Project.Get(_project.id);
finalProject.role.Should().Be(newRole);
}
}
@@ -1,15 +1,16 @@
using Speckle.Sdk.Api;
using FluentAssertions;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL;
using Speckle.Sdk.Api.GraphQL.Enums;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Api.GraphQL.Resources;
using Speckle.Sdk.Common;
using Xunit;
namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources;
[TestOf(typeof(ProjectResource))]
public class ProjectResourceExceptionalTests
public class ProjectResourceExceptionalTests : IAsyncLifetime
{
private Client _testUser,
_secondUser,
@@ -17,8 +18,9 @@ public class ProjectResourceExceptionalTests
private Project _testProject;
private ProjectResource Sut => _testUser.Project;
[OneTimeSetUp]
public async Task Setup()
public Task DisposeAsync() => Task.CompletedTask;
public async Task InitializeAsync()
{
_testUser = await Fixtures.SeedUserWithClient();
_secondUser = await Fixtures.SeedUserWithClient();
@@ -33,8 +35,8 @@ public class ProjectResourceExceptionalTests
// 4. Server doesn't exist (is down)
//There's got to be a smarter way to parametrise these...
[Test]
public void ProjectCreate_WithoutAuth()
[Fact]
public async Task ProjectCreate_WithoutAuth()
{
ProjectCreateInput input = new(
"The best project",
@@ -42,84 +44,88 @@ public class ProjectResourceExceptionalTests
ProjectVisibility.Private
);
var ex = Assert.ThrowsAsync<AggregateException>(async () => _ = await _unauthedUser.Project.Create(input));
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLForbiddenException>());
var ex = await Assert.ThrowsAsync<AggregateException>(async () => _ = await _unauthedUser.Project.Create(input));
ex.InnerExceptions.Single().Should().BeOfType<SpeckleGraphQLForbiddenException>();
}
[Test]
[Fact]
public async Task ProjectGet_WithoutAuth()
{
ProjectCreateInput input = new("Private Stream", "A very private stream", ProjectVisibility.Private);
Project privateStream = await Sut.Create(input);
var ex = Assert.ThrowsAsync<AggregateException>(async () => _ = await _unauthedUser.Project.Get(privateStream.id));
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLForbiddenException>());
var ex = await Assert.ThrowsAsync<AggregateException>(
async () => _ = await _unauthedUser.Project.Get(privateStream.id)
);
ex.InnerExceptions.Single().Should().BeOfType<SpeckleGraphQLForbiddenException>();
}
[Test]
public void ProjectGet_NonExistentProject()
[Fact]
public async Task ProjectGet_NonExistentProject()
{
var ex = Assert.ThrowsAsync<AggregateException>(async () => await Sut.Get("NonExistentProject"));
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLStreamNotFoundException>());
var ex = await Assert.ThrowsAsync<AggregateException>(async () => await Sut.Get("NonExistentProject"));
ex.InnerExceptions.Single().Should().BeOfType<SpeckleGraphQLStreamNotFoundException>();
}
[Test]
public void ProjectUpdate_NonExistentProject()
[Fact]
public async Task ProjectUpdate_NonExistentProject()
{
var ex = Assert.ThrowsAsync<AggregateException>(
var ex = await Assert.ThrowsAsync<AggregateException>(
async () => _ = await Sut.Update(new("NonExistentProject", "My new name"))
);
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLForbiddenException>());
ex.InnerExceptions.Single().Should().BeOfType<SpeckleGraphQLForbiddenException>();
}
[Test]
public void ProjectUpdate_NoAuth()
[Fact]
public async Task ProjectUpdate_NoAuth()
{
var ex = Assert.ThrowsAsync<AggregateException>(
var ex = await Assert.ThrowsAsync<AggregateException>(
async () => _ = await _unauthedUser.Project.Update(new(_testProject.id, "My new name"))
);
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLForbiddenException>());
ex.InnerExceptions.Single().Should().BeOfType<SpeckleGraphQLForbiddenException>();
}
[Test]
[TestCase(StreamRoles.STREAM_OWNER)]
[TestCase(StreamRoles.STREAM_CONTRIBUTOR)]
[TestCase(StreamRoles.STREAM_REVIEWER)]
[TestCase(StreamRoles.REVOKE)]
public void ProjectUpdateRole_NonExistentProject(string newRole)
[Theory]
[InlineData(StreamRoles.STREAM_OWNER)]
[InlineData(StreamRoles.STREAM_CONTRIBUTOR)]
[InlineData(StreamRoles.STREAM_REVIEWER)]
[InlineData(StreamRoles.REVOKE)]
public async Task ProjectUpdateRole_NonExistentProject(string? newRole)
{
ProjectUpdateRoleInput input = new(_secondUser.Account.id.NotNull(), "NonExistentProject", newRole);
var ex = Assert.ThrowsAsync<AggregateException>(async () => _ = await Sut.UpdateRole(input));
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLForbiddenException>());
var ex = await Assert.ThrowsAsync<AggregateException>(async () => _ = await Sut.UpdateRole(input));
ex.InnerExceptions.Single().Should().BeOfType<SpeckleGraphQLForbiddenException>();
}
[Test]
[TestCase(StreamRoles.STREAM_OWNER)]
[TestCase(StreamRoles.STREAM_CONTRIBUTOR)]
[TestCase(StreamRoles.STREAM_REVIEWER)]
[TestCase(StreamRoles.REVOKE)]
public void ProjectUpdateRole_NonAuth(string newRole)
[Theory]
[InlineData(StreamRoles.STREAM_OWNER)]
[InlineData(StreamRoles.STREAM_CONTRIBUTOR)]
[InlineData(StreamRoles.STREAM_REVIEWER)]
[InlineData(StreamRoles.REVOKE)]
public async Task ProjectUpdateRole_NonAuth(string? newRole)
{
ProjectUpdateRoleInput input = new(_secondUser.Account.id.NotNull(), "NonExistentProject", newRole);
var ex = Assert.ThrowsAsync<AggregateException>(async () => _ = await _unauthedUser.Project.UpdateRole(input));
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLForbiddenException>());
var ex = await Assert.ThrowsAsync<AggregateException>(
async () => _ = await _unauthedUser.Project.UpdateRole(input)
);
ex.InnerExceptions.Single().Should().BeOfType<SpeckleGraphQLForbiddenException>();
}
[Test]
[Fact]
public async Task ProjectDelete_NonExistentProject()
{
await Sut.Delete(_testProject.id);
var ex = Assert.ThrowsAsync<AggregateException>(async () => _ = await Sut.Get(_testProject.id));
Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf<SpeckleGraphQLStreamNotFoundException>());
var ex = await Assert.ThrowsAsync<AggregateException>(async () => _ = await Sut.Get(_testProject.id));
ex.InnerExceptions.Single().Should().BeOfType<SpeckleGraphQLStreamNotFoundException>();
}
[Test]
public void ProjectInvites_NoAuth()
[Fact]
public async Task ProjectInvites_NoAuth()
{
Assert.ThrowsAsync<SpeckleException>(async () => await Fixtures.Unauthed.ActiveUser.ProjectInvites());
await Assert.ThrowsAsync<SpeckleException>(async () => await Fixtures.Unauthed.ActiveUser.ProjectInvites());
}
}
@@ -1,72 +1,103 @@
using Speckle.Sdk.Api;
using FluentAssertions;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Enums;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Api.GraphQL.Resources;
using Xunit;
namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources;
[TestOf(typeof(ProjectResource))]
public class ProjectResourceTests
{
private Client _testUser;
private Project _testProject;
private readonly Client _testUser;
private readonly Project _testProject;
private ProjectResource Sut => _testUser.Project;
[OneTimeSetUp]
public async Task Setup()
public ProjectResourceTests()
{
_testUser = await Fixtures.SeedUserWithClient();
_testProject = await _testUser.Project.Create(new("test project123", "desc", null));
var setupTask = Setup();
setupTask.Wait(); // Ensure setup runs synchronously for the constructor
(_testUser, _testProject) = setupTask.Result;
}
[TestCase("Very private project", "My secret project", ProjectVisibility.Private)]
[TestCase("Very public project", null, ProjectVisibility.Public)]
public async Task ProjectCreate(string name, string desc, ProjectVisibility visibility)
private async Task<(Client TestUser, Project TestProject)> Setup()
{
ProjectCreateInput input = new(name, desc, visibility);
Project result = await Sut.Create(input);
Assert.That(result, Is.Not.Null);
Assert.That(result, Has.Property(nameof(Project.id)).Not.Null);
Assert.That(result, Has.Property(nameof(Project.name)).EqualTo(input.name));
Assert.That(result, Has.Property(nameof(Project.description)).EqualTo(input.description ?? string.Empty));
Assert.That(result, Has.Property(nameof(Project.visibility)).EqualTo(input.visibility));
var testUser = await Fixtures.SeedUserWithClient();
var testProject = await testUser.Project.Create(new ProjectCreateInput("test project123", "desc", null));
return (testUser, testProject);
}
[Test]
public async Task ProjectGet()
[Theory]
[InlineData("Very private project", "My secret project", ProjectVisibility.Private)]
[InlineData("Very public project", null, ProjectVisibility.Public)]
public async Task ProjectCreate_Should_CreateProjectSuccessfully(
string name,
string? description,
ProjectVisibility visibility
)
{
Project result = await Sut.Get(_testProject.id);
// Arrange
var input = new ProjectCreateInput(name, description, visibility);
Assert.That(result.id, Is.EqualTo(_testProject.id));
Assert.That(result.name, Is.EqualTo(_testProject.name));
Assert.That(result.description, Is.EqualTo(_testProject.description));
Assert.That(result.visibility, Is.EqualTo(_testProject.visibility));
Assert.That(result.createdAt, Is.EqualTo(_testProject.createdAt));
// Act
var result = await Sut.Create(input);
// Assert
result.Should().NotBeNull();
result.id.Should().NotBeNullOrWhiteSpace();
result.name.Should().Be(input.name);
result.description.Should().Be(input.description ?? string.Empty);
input.visibility.Should().NotBeNull();
}
[Test]
public async Task ProjectUpdate()
[Fact]
public async Task ProjectGet_Should_ReturnCorrectProject()
{
// Act
var result = await Sut.Get(_testProject.id);
// Assert
result.id.Should().Be(_testProject.id);
result.name.Should().Be(_testProject.name);
result.description.Should().Be(_testProject.description);
result.visibility.Should().Be(_testProject.visibility);
result.createdAt.Should().Be(_testProject.createdAt);
}
[Fact]
public async Task ProjectUpdate_Should_UpdateProjectSuccessfully()
{
// Arrange
const string NEW_NAME = "MY new name";
const string NEW_DESCRIPTION = "MY new desc";
const ProjectVisibility NEW_VISIBILITY = ProjectVisibility.Public;
Project newProject = await Sut.Update(new(_testProject.id, NEW_NAME, NEW_DESCRIPTION, null, NEW_VISIBILITY));
// Act
var newProject = await Sut.Update(
new ProjectUpdateInput(_testProject.id, NEW_NAME, NEW_DESCRIPTION, null, NEW_VISIBILITY)
);
Assert.That(newProject.id, Is.EqualTo(_testProject.id));
Assert.That(newProject.name, Is.EqualTo(NEW_NAME));
Assert.That(newProject.description, Is.EqualTo(NEW_DESCRIPTION));
Assert.That(newProject.visibility, Is.EqualTo(NEW_VISIBILITY));
// Assert
newProject.id.Should().Be(_testProject.id);
newProject.name.Should().Be(NEW_NAME);
newProject.description.Should().Be(NEW_DESCRIPTION);
newProject.visibility.Should().Be(NEW_VISIBILITY);
}
[Test]
public async Task ProjectDelete()
[Fact]
public async Task ProjectDelete_Should_DeleteProjectSuccessfully()
{
Project toDelete = await Sut.Create(new("Delete me", null, null));
// Arrange
var toDelete = await Sut.Create(new ProjectCreateInput("Delete me", null, null));
// Act
await Sut.Delete(toDelete.id);
var getEx = Assert.ThrowsAsync<AggregateException>(async () => _ = await Sut.Get(toDelete.id));
Assert.That(getEx?.InnerExceptions, Has.Exactly(1).TypeOf<SpeckleGraphQLStreamNotFoundException>());
// Assert
await FluentActions
.Invoking(async () => await Sut.Get(toDelete.id))
.Should()
.ThrowAsync<SpeckleGraphQLStreamNotFoundException>();
}
}
@@ -1,13 +1,14 @@
using Speckle.Sdk.Api;
using FluentAssertions;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Enums;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Api.GraphQL.Resources;
using Xunit;
namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources;
[TestOf(typeof(SubscriptionResource))]
public class SubscriptionResourceTests : IDisposable
public class SubscriptionResourceTests : IAsyncLifetime
{
private const int WAIT_PERIOD = 300;
private Client _testUser;
@@ -17,8 +18,13 @@ public class SubscriptionResourceTests : IDisposable
private SubscriptionResource Sut => _testUser.Subscription;
[OneTimeSetUp]
public async Task Setup()
public Task DisposeAsync()
{
_testUser.Dispose();
return Task.CompletedTask;
}
public async Task InitializeAsync()
{
_testUser = await Fixtures.SeedUserWithClient();
_testProject = await _testUser.Project.Create(new("test project123", "desc", null));
@@ -26,7 +32,7 @@ public class SubscriptionResourceTests : IDisposable
_testVersion = await Fixtures.CreateVersion(_testUser, _testProject.id, _testModel.id);
}
[Test]
[Fact]
public async Task UserProjectsUpdated_SubscriptionIsCalled()
{
UserProjectsUpdatedMessage? subscriptionMessage = null;
@@ -40,13 +46,13 @@ public class SubscriptionResourceTests : IDisposable
await Task.Delay(WAIT_PERIOD); // Give time for subscription to be triggered
Assert.That(subscriptionMessage, Is.Not.Null);
Assert.That(subscriptionMessage!.id, Is.EqualTo(created.id));
Assert.That(subscriptionMessage.type, Is.EqualTo(UserProjectsUpdatedMessageType.ADDED));
Assert.That(subscriptionMessage.project, Is.Not.Null);
subscriptionMessage.Should().NotBeNull();
subscriptionMessage!.id.Should().Be(created.id);
subscriptionMessage.type.Should().Be(UserProjectsUpdatedMessageType.ADDED);
subscriptionMessage.project.Should().NotBeNull();
}
[Test]
[Fact]
public async Task ProjectModelsUpdated_SubscriptionIsCalled()
{
ProjectModelsUpdatedMessage? subscriptionMessage = null;
@@ -61,13 +67,13 @@ public class SubscriptionResourceTests : IDisposable
await Task.Delay(WAIT_PERIOD); // Give time for subscription to be triggered
Assert.That(subscriptionMessage, Is.Not.Null);
Assert.That(subscriptionMessage!.id, Is.EqualTo(created.id));
Assert.That(subscriptionMessage.type, Is.EqualTo(ProjectModelsUpdatedMessageType.CREATED));
Assert.That(subscriptionMessage.model, Is.Not.Null);
subscriptionMessage.Should().NotBeNull();
subscriptionMessage!.id.Should().Be(created.id);
subscriptionMessage.type.Should().Be(ProjectModelsUpdatedMessageType.CREATED);
subscriptionMessage.model.Should().NotBeNull();
}
[Test]
[Fact]
public async Task ProjectUpdated_SubscriptionIsCalled()
{
ProjectUpdatedMessage? subscriptionMessage = null;
@@ -82,13 +88,13 @@ public class SubscriptionResourceTests : IDisposable
await Task.Delay(WAIT_PERIOD); // Give time for subscription to be triggered
Assert.That(subscriptionMessage, Is.Not.Null);
Assert.That(subscriptionMessage!.id, Is.EqualTo(created.id));
Assert.That(subscriptionMessage.type, Is.EqualTo(ProjectUpdatedMessageType.UPDATED));
Assert.That(subscriptionMessage.project, Is.Not.Null);
subscriptionMessage.Should().NotBeNull();
subscriptionMessage!.id.Should().Be(created.id);
subscriptionMessage.type.Should().Be(ProjectUpdatedMessageType.UPDATED);
subscriptionMessage.project.Should().NotBeNull();
}
[Test]
[Fact]
public async Task ProjectVersionsUpdated_SubscriptionIsCalled()
{
ProjectVersionsUpdatedMessage? subscriptionMessage = null;
@@ -102,13 +108,13 @@ public class SubscriptionResourceTests : IDisposable
await Task.Delay(WAIT_PERIOD); // Give time for subscription to be triggered
Assert.That(subscriptionMessage, Is.Not.Null);
Assert.That(subscriptionMessage!.id, Is.EqualTo(created));
Assert.That(subscriptionMessage.type, Is.EqualTo(ProjectVersionsUpdatedMessageType.CREATED));
Assert.That(subscriptionMessage.version, Is.Not.Null);
subscriptionMessage.Should().NotBeNull();
subscriptionMessage!.id.Should().Be(created);
subscriptionMessage.type.Should().Be(ProjectVersionsUpdatedMessageType.CREATED);
subscriptionMessage.version.Should().NotBeNull();
}
[Test]
[Fact]
public async Task ProjectCommentsUpdated_SubscriptionIsCalled()
{
string resourceIdString = $"{_testProject.id},{_testModel.id},{_testVersion}";
@@ -123,12 +129,9 @@ public class SubscriptionResourceTests : IDisposable
await Task.Delay(WAIT_PERIOD); // Give time for subscription to be triggered
Assert.That(subscriptionMessage, Is.Not.Null);
Assert.That(subscriptionMessage!.id, Is.EqualTo(created.id));
Assert.That(subscriptionMessage.type, Is.EqualTo(ProjectCommentsUpdatedMessageType.CREATED));
Assert.That(subscriptionMessage.comment, Is.Not.Null);
subscriptionMessage.Should().NotBeNull();
subscriptionMessage!.id.Should().Be(created.id);
subscriptionMessage.type.Should().Be(ProjectCommentsUpdatedMessageType.CREATED);
subscriptionMessage.comment.Should().NotBeNull();
}
[OneTimeTearDown]
public void Dispose() => _testUser.Dispose();
}
@@ -1,13 +1,14 @@
using Speckle.Sdk.Api;
using FluentAssertions;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Api.GraphQL.Resources;
using Xunit;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources;
[TestOf(typeof(VersionResource))]
public class VersionResourceTests
public class VersionResourceTests : IAsyncLifetime
{
private Client _testUser;
private VersionResource Sut => _testUser.Version;
@@ -16,8 +17,9 @@ public class VersionResourceTests
private Model _model2;
private Version _version;
[SetUp]
public async Task Setup()
public Task DisposeAsync() => Task.CompletedTask;
public async Task InitializeAsync()
{
_testUser = await Fixtures.SeedUserWithClient();
_project = await _testUser.Project.Create(new("Test project", "", null));
@@ -29,44 +31,44 @@ public class VersionResourceTests
_version = await Sut.Get(versionId, _project.id);
}
[Test]
[Fact]
public async Task VersionGet()
{
Version result = await Sut.Get(_version.id, _project.id);
Assert.That(result, Has.Property(nameof(Version.id)).EqualTo(_version.id));
Assert.That(result, Has.Property(nameof(Version.message)).EqualTo(_version.message));
result.id.Should().Be(_version.id);
result.message.Should().Be(_version.message);
}
[Test]
[Fact]
public async Task VersionsGet()
{
ResourceCollection<Version> result = await Sut.GetVersions(_model1.id, _project.id);
Assert.That(result.items, Has.Count.EqualTo(1));
Assert.That(result.totalCount, Is.EqualTo(1));
Assert.That(result.items[0], Has.Property(nameof(Version.id)).EqualTo(_version.id));
result.items.Count.Should().Be(1);
result.totalCount.Should().Be(1);
result.items[0].id.Should().Be(_version.id);
}
[Test]
[Fact]
public async Task VersionReceived()
{
MarkReceivedVersionInput input = new(_version.id, _project.id, "Integration test");
await Sut.Received(input);
}
[Test]
[Fact]
public async Task ModelGetWithVersions()
{
var result = await _testUser.Model.GetWithVersions(_model1.id, _project.id);
Assert.That(result, Has.Property(nameof(Model.id)).EqualTo(_model1.id));
Assert.That(result.versions.items, Has.Count.EqualTo(1));
Assert.That(result.versions.totalCount, Is.EqualTo(1));
Assert.That(result.versions.items[0], Has.Property(nameof(Version.id)).EqualTo(_version.id));
result.id.Should().Be(_model1.id);
result.versions.items.Count.Should().Be(1);
result.versions.totalCount.Should().Be(1);
result.versions.items[0].id.Should().Be(_version.id);
}
[Test]
[Fact]
public async Task VersionUpdate()
{
const string NEW_MESSAGE = "MY new version message";
@@ -74,34 +76,43 @@ public class VersionResourceTests
UpdateVersionInput input = new(_version.id, _project.id, NEW_MESSAGE);
Version updatedVersion = await Sut.Update(input);
Assert.That(updatedVersion, Has.Property(nameof(Version.id)).EqualTo(_version.id));
Assert.That(updatedVersion, Has.Property(nameof(Version.message)).EqualTo(NEW_MESSAGE));
Assert.That(updatedVersion, Has.Property(nameof(Version.previewUrl)).EqualTo(_version.previewUrl));
updatedVersion.id.Should().Be(_version.id);
updatedVersion.message.Should().Be(NEW_MESSAGE);
updatedVersion.previewUrl.Should().Be(_version.previewUrl);
}
[Test]
[Fact]
public async Task VersionMoveToModel()
{
MoveVersionsInput input = new(_project.id, _model2.name, [_version.id]);
string id = await Sut.MoveToModel(input);
Assert.That(id, Is.EqualTo(_model2.id));
id.Should().Be(_model2.id);
Version movedVersion = await Sut.Get(_version.id, _project.id);
Assert.That(movedVersion, Has.Property(nameof(Version.id)).EqualTo(_version.id));
Assert.That(movedVersion, Has.Property(nameof(Version.message)).EqualTo(_version.message));
Assert.That(movedVersion, Has.Property(nameof(Version.previewUrl)).EqualTo(_version.previewUrl));
movedVersion.id.Should().Be(_version.id);
movedVersion.message.Should().Be(_version.message);
movedVersion.previewUrl.Should().Be(_version.previewUrl);
}
[Test]
[Fact]
public async Task VersionDelete()
{
DeleteVersionsInput input = new([_version.id], _project.id);
await Sut.Delete(input);
var getEx = Assert.ThrowsAsync<AggregateException>(async () => await Sut.Get(_version.id, _project.id));
Assert.That(getEx?.InnerExceptions, Has.Exactly(1).TypeOf<SpeckleGraphQLException>());
var delEx = Assert.ThrowsAsync<AggregateException>(async () => await Sut.Delete(input));
Assert.That(delEx?.InnerExceptions, Has.Exactly(1).TypeOf<SpeckleGraphQLException>());
var getEx = await FluentActions
.Invoking(async () => await Sut.Get(_version.id, _project.id))
.Should()
.ThrowAsync<AggregateException>();
getEx.WithInnerExceptionExactly<SpeckleGraphQLException>();
var delEx = await FluentActions
.Invoking(async () => await Sut.Delete(input))
.Should()
.ThrowAsync<AggregateException>();
delEx.WithInnerExceptionExactly<SpeckleGraphQLException>();
}
}
@@ -1,40 +1,32 @@
using GraphQL.Client.Http;
using FluentAssertions;
using GraphQL.Client.Http;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Credentials;
using Xunit;
namespace Speckle.Sdk.Tests.Integration.Credentials;
public class UserServerInfoTests
public class UserServerInfoTests : IAsyncLifetime
{
private Account _acc;
[SetUp]
public async Task Setup()
public Task DisposeAsync() => Task.CompletedTask;
public async Task InitializeAsync()
{
_acc = await Fixtures.SeedUser();
}
[Test]
[Fact]
public async Task IsFrontEnd2True()
{
ServerInfo? result = await Fixtures
.ServiceProvider.GetRequiredService<IAccountManager>()
.GetServerInfo(new("https://app.speckle.systems/"));
Assert.That(result, Is.Not.Null);
Assert.That(result!.frontend2, Is.True);
}
[Test]
public async Task IsFrontEnd2False()
{
ServerInfo? result = await Fixtures
.ServiceProvider.GetRequiredService<IAccountManager>()
.GetServerInfo(new("https://speckle.xyz/"));
Assert.That(result, Is.Not.Null);
Assert.That(result!.frontend2, Is.False);
result.Should().NotBeNull();
result.frontend2.Should().BeTrue();
}
/// <remarks>
@@ -43,27 +35,33 @@ public class UserServerInfoTests
/// This is not doable in local server because there is no end-point on this to ping.
/// This is a bad sign for mutation.
/// </remarks>
[Test]
public void GetServerInfo_ExpectFail_CantPing()
[Fact]
public async Task GetServerInfo_ExpectFail_CantPing()
{
Uri serverUrl = new(_acc.serverInfo.url);
Assert.ThrowsAsync<HttpRequestException>(
async () => await Fixtures.ServiceProvider.GetRequiredService<IAccountManager>().GetServerInfo(serverUrl)
);
await FluentActions
.Invoking(
async () => await Fixtures.ServiceProvider.GetRequiredService<IAccountManager>().GetServerInfo(serverUrl)
)
.Should()
.ThrowAsync<HttpRequestException>();
}
[Test]
public void GetServerInfo_ExpectFail_NoServer()
[Fact]
public async Task GetServerInfo_ExpectFail_NoServer()
{
Uri serverUrl = new("http://invalidserver.local");
Assert.ThrowsAsync<HttpRequestException>(
async () => await Fixtures.ServiceProvider.GetRequiredService<IAccountManager>().GetServerInfo(serverUrl)
);
await FluentActions
.Invoking(
async () => await Fixtures.ServiceProvider.GetRequiredService<IAccountManager>().GetServerInfo(serverUrl)
)
.Should()
.ThrowAsync<HttpRequestException>();
}
[Test]
[Fact]
public async Task GetUserInfo()
{
Uri serverUrl = new(_acc.serverInfo.url);
@@ -71,33 +69,38 @@ public class UserServerInfoTests
.ServiceProvider.GetRequiredService<IAccountManager>()
.GetUserInfo(_acc.token, serverUrl);
Assert.That(result.id, Is.EqualTo(_acc.userInfo.id));
Assert.That(result.name, Is.EqualTo(_acc.userInfo.name));
Assert.That(result.email, Is.EqualTo(_acc.userInfo.email));
Assert.That(result.company, Is.EqualTo(_acc.userInfo.company));
Assert.That(result.avatar, Is.EqualTo(_acc.userInfo.avatar));
result.id.Should().Be(_acc.userInfo.id);
result.name.Should().Be(_acc.userInfo.name);
result.email.Should().Be(_acc.userInfo.email);
result.company.Should().Be(_acc.userInfo.company);
result.avatar.Should().Be(_acc.userInfo.avatar);
}
[Test]
public void GetUserInfo_ExpectFail_NoServer()
[Fact]
public async Task GetUserInfo_ExpectFail_NoServer()
{
Uri serverUrl = new("http://invalidserver.local");
Assert.ThrowsAsync<HttpRequestException>(
async () => await Fixtures.ServiceProvider.GetRequiredService<IAccountManager>().GetUserInfo("", serverUrl)
);
await FluentActions
.Invoking(
async () => await Fixtures.ServiceProvider.GetRequiredService<IAccountManager>().GetUserInfo("", serverUrl)
)
.Should()
.ThrowAsync<HttpRequestException>();
}
[Test]
public void GetUserInfo_ExpectFail_NoUser()
[Fact]
public async Task GetUserInfo_ExpectFail_NoUser()
{
Uri serverUrl = new(_acc.serverInfo.url);
Assert.ThrowsAsync<GraphQLHttpRequestException>(
async () =>
await Fixtures
.ServiceProvider.GetRequiredService<IAccountManager>()
.GetUserInfo("Bearer 08913c3c1e7ac65d779d1e1f11b942a44ad9672ca9", serverUrl)
);
await FluentActions
.Invoking(
async () =>
await Fixtures
.ServiceProvider.GetRequiredService<IAccountManager>()
.GetUserInfo("Bearer 08913c3c1e7ac65d779d1e1f11b942a44ad9672ca9", serverUrl)
)
.Should()
.ThrowAsync<GraphQLHttpRequestException>();
}
}
@@ -54,9 +54,9 @@ public static class Fixtures
var seed = Guid.NewGuid().ToString().ToLower();
Dictionary<string, string> user = new()
{
["email"] = $"{seed.Substring(0, 7)}@example.com",
["email"] = $"{seed[..7]}@example.com",
["password"] = "12ABC3456789DEF0GHO",
["name"] = $"{seed.Substring(0, 5)} Name",
["name"] = $"{seed[..5]} Name",
};
using var httpClient = new HttpClient(
@@ -1,21 +1,20 @@
using System.Collections.Concurrent;
using System.Reflection;
using System.Reflection;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Speckle.Sdk.Api;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Transports;
using Xunit;
namespace Speckle.Sdk.Tests.Integration;
public class MemoryTransportTests
public class MemoryTransportTests : IDisposable
{
private readonly MemoryTransport _memoryTransport = new(blobStorageEnabled: true);
private IOperations _operations;
[SetUp]
public void Setup()
public MemoryTransportTests()
{
CleanData();
TypeLoader.Reset();
@@ -24,8 +23,7 @@ public class MemoryTransportTests
_operations = serviceProvider.GetRequiredService<IOperations>();
}
[TearDown]
public void TearDown() => CleanData();
public void Dispose() => CleanData();
private void CleanData()
{
@@ -33,10 +31,11 @@ public class MemoryTransportTests
{
Directory.Delete(_memoryTransport.BlobStorageFolder, true);
}
Directory.CreateDirectory(_memoryTransport.BlobStorageFolder);
}
[Test]
[Fact]
public async Task SendAndReceiveObjectWithBlobs()
{
var myObject = Fixtures.GenerateSimpleObject();
@@ -53,20 +52,24 @@ public class MemoryTransportTests
.GetFiles(_memoryTransport.BlobStorageFolder)
.Select(fp => fp.Split(Path.DirectorySeparatorChar).Last())
.ToList();
var blobPaths = allFiles
.Where(fp => fp.Length > Blob.LocalHashPrefixLength) // excludes things like .DS_store
.ToList();
// Check that there are three downloaded blobs!
Assert.That(blobPaths, Has.Count.EqualTo(3));
blobPaths.Count.Should().Be(3);
var objectBlobs = receivedObject["blobs"] as IList<object>;
objectBlobs.ShouldNotBeNull();
var blobs = objectBlobs.Cast<Blob>().ToList();
objectBlobs.Should().NotBeNull();
var blobs = objectBlobs!.Cast<Blob>().ToList();
// Check that we have three blobs
Assert.That(blobs, Has.Count.EqualTo(3));
blobs.Count.Should().Be(3);
// Check that received blobs point to local path (where they were received)
Assert.That(blobs[0].filePath, Contains.Substring(_memoryTransport.BlobStorageFolder));
Assert.That(blobs[1].filePath, Contains.Substring(_memoryTransport.BlobStorageFolder));
Assert.That(blobs[2].filePath, Contains.Substring(_memoryTransport.BlobStorageFolder));
blobs[0].filePath.Should().Contain(_memoryTransport.BlobStorageFolder);
blobs[1].filePath.Should().Contain(_memoryTransport.BlobStorageFolder);
blobs[2].filePath.Should().Contain(_memoryTransport.BlobStorageFolder);
}
}
@@ -9,9 +9,8 @@
<ItemGroup>
<PackageReference Include="altcover" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="NUnit" />
<PackageReference Include="NUnit3TestAdapter" />
<PackageReference Include="Shouldly" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio"/>
</ItemGroup>
<ItemGroup>
@@ -1 +0,0 @@
global using NUnit.Framework;
@@ -4,9 +4,9 @@
"net8.0": {
"altcover": {
"type": "Direct",
"requested": "[8.9.3, )",
"resolved": "8.9.3",
"contentHash": "auKC+pDCkLjfhFkSRaAUBu25BOmlLSqucR7YBs/Lkbdc0XRuJoklWafs1KKp+M+VoJ1f0TeMS6B/FO5IeIcu7w=="
"requested": "[9.0.1, )",
"resolved": "9.0.1",
"contentHash": "aadciFNDT5bnylaYUkKal+s5hF7yU/lmZxImQWAlk1438iPqK1Uf79H5ylELpyLIU49HL5ql+tnWBihp3WVLCA=="
},
"GitVersion.MsBuild": {
"type": "Direct",
@@ -16,12 +16,12 @@
},
"Microsoft.NET.Test.Sdk": {
"type": "Direct",
"requested": "[17.11.1, )",
"resolved": "17.11.1",
"contentHash": "U3Ty4BaGoEu+T2bwSko9tWqWUOU16WzSFkq6U8zve75oRBMSLTBdMAZrVNNz1Tq12aCdDom9fcOcM9QZaFHqFg==",
"requested": "[17.12.0, )",
"resolved": "17.12.0",
"contentHash": "kt/PKBZ91rFCWxVIJZSgVLk+YR+4KxTuHf799ho8WNiK5ZQpJNAEZCAWX86vcKrs+DiYjiibpYKdGZP6+/N17w==",
"dependencies": {
"Microsoft.CodeCoverage": "17.11.1",
"Microsoft.TestPlatform.TestHost": "17.11.1"
"Microsoft.CodeCoverage": "17.12.0",
"Microsoft.TestPlatform.TestHost": "17.12.0"
}
},
"Microsoft.SourceLink.GitHub": {
@@ -34,53 +34,34 @@
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"NUnit": {
"type": "Direct",
"requested": "[4.2.2, )",
"resolved": "4.2.2",
"contentHash": "mon0OPko28yZ/foVXrhiUvq1LReaGsBdziumyyYGxV/pOE4q92fuYeN+AF+gEU5pCjzykcdBt5l7xobTaiBjsg=="
},
"NUnit3TestAdapter": {
"type": "Direct",
"requested": "[4.6.0, )",
"resolved": "4.6.0",
"contentHash": "R7e1+a4vuV/YS+ItfL7f//rG+JBvVeVLX4mHzFEZo4W1qEKl8Zz27AqvQSAqo+BtIzUCo4aAJMYa56VXS4hudw=="
},
"PolySharp": {
"type": "Direct",
"requested": "[1.15.0, )",
"resolved": "1.15.0",
"contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g=="
},
"Shouldly": {
"type": "Direct",
"requested": "[4.2.1, )",
"resolved": "4.2.1",
"contentHash": "dKAKiSuhLKqD2TXwLKtqNg1nwzJcIKOOMncZjk9LYe4W+h+SCftpWdxwR79YZUIHMH+3Vu9s0s0UHNrgICLwRQ==",
"dependencies": {
"DiffEngine": "11.3.0",
"EmptyFiles": "4.4.0"
}
},
"Speckle.InterfaceGenerator": {
"type": "Direct",
"requested": "[0.9.6, )",
"resolved": "0.9.6",
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
},
"DiffEngine": {
"type": "Transitive",
"resolved": "11.3.0",
"contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==",
"xunit": {
"type": "Direct",
"requested": "[2.9.3, )",
"resolved": "2.9.3",
"contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==",
"dependencies": {
"EmptyFiles": "4.4.0",
"System.Management": "6.0.1"
"xunit.analyzers": "1.18.0",
"xunit.assert": "2.9.3",
"xunit.core": "[2.9.3]"
}
},
"EmptyFiles": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw=="
"xunit.runner.visualstudio": {
"type": "Direct",
"requested": "[3.0.0, )",
"resolved": "3.0.0",
"contentHash": "HggUqjQJe8PtDxcP25Q+CnR6Lz4oX3GElhD9V4oU2+75x9HI6A6sxbfKGS4UwU4t4yJaS9fBmAuriz8bQApNjw=="
},
"GraphQL.Client.Abstractions": {
"type": "Transitive",
@@ -110,8 +91,8 @@
},
"Microsoft.CodeCoverage": {
"type": "Transitive",
"resolved": "17.11.1",
"contentHash": "nPJqrcA5iX+Y0kqoT3a+pD/8lrW/V7ayqnEJQsTonSoPz59J8bmoQhcSN4G8+UJ64Hkuf0zuxnfuj2lkHOq4cA=="
"resolved": "17.12.0",
"contentHash": "4svMznBd5JM21JIG2xZKGNanAHNXplxf/kQDFfLHXQ3OnpJkayRK/TjacFjA+EYmoyuNXHo/sOETEfcYtAzIrA=="
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
@@ -176,21 +157,26 @@
},
"Microsoft.TestPlatform.ObjectModel": {
"type": "Transitive",
"resolved": "17.11.1",
"contentHash": "E2jZqAU6JeWEVsyOEOrSW1o1bpHLgb25ypvKNB/moBXPVsFYBPd/Jwi7OrYahG50J83LfHzezYI+GaEkpAotiA==",
"resolved": "17.12.0",
"contentHash": "TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==",
"dependencies": {
"System.Reflection.Metadata": "1.6.0"
}
},
"Microsoft.TestPlatform.TestHost": {
"type": "Transitive",
"resolved": "17.11.1",
"contentHash": "DnG+GOqJXO/CkoqlJWeDFTgPhqD/V6VqUIL3vINizCWZ3X+HshCtbbyDdSHQQEjrc2Sl/K3yaxX6s+5LFEdYuw==",
"resolved": "17.12.0",
"contentHash": "MiPEJQNyADfwZ4pJNpQex+t9/jOClBGMiCiVVFuELCMSX2nmNfvUor3uFVxNNCg30uxDP8JDYfPnMXQzsfzYyg==",
"dependencies": {
"Microsoft.TestPlatform.ObjectModel": "17.11.1",
"Microsoft.TestPlatform.ObjectModel": "17.12.0",
"Newtonsoft.Json": "13.0.1"
}
},
"Microsoft.Win32.SystemEvents": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A=="
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.1",
@@ -226,22 +212,26 @@
"SQLitePCLRaw.core": "2.1.4"
}
},
"System.CodeDom": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA=="
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
},
"System.Management": {
"System.Configuration.ConfigurationManager": {
"type": "Transitive",
"resolved": "6.0.1",
"contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==",
"resolved": "6.0.0",
"contentHash": "7T+m0kDSlIPTHIkPMIu6m6tV6qsMqJpvQWW2jIc2qi7sn40qxFo0q+7mEQAhMPXZHMKnWrnv47ntGlM/ejvw3g==",
"dependencies": {
"System.CodeDom": "6.0.0"
"System.Security.Cryptography.ProtectedData": "6.0.0",
"System.Security.Permissions": "6.0.0"
}
},
"System.Drawing.Common": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==",
"dependencies": {
"Microsoft.Win32.SystemEvents": "6.0.0"
}
},
"System.Memory": {
@@ -264,6 +254,73 @@
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
"System.Security.AccessControl": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ=="
},
"System.Security.Cryptography.ProtectedData": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ=="
},
"System.Security.Permissions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==",
"dependencies": {
"System.Security.AccessControl": "6.0.0",
"System.Windows.Extensions": "6.0.0"
}
},
"System.Windows.Extensions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==",
"dependencies": {
"System.Drawing.Common": "6.0.0"
}
},
"xunit.abstractions": {
"type": "Transitive",
"resolved": "2.0.3",
"contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg=="
},
"xunit.analyzers": {
"type": "Transitive",
"resolved": "1.18.0",
"contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ=="
},
"xunit.assert": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA=="
},
"xunit.core": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==",
"dependencies": {
"xunit.extensibility.core": "[2.9.3]",
"xunit.extensibility.execution": "[2.9.3]"
}
},
"xunit.extensibility.core": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==",
"dependencies": {
"xunit.abstractions": "2.0.3"
}
},
"xunit.extensibility.execution": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==",
"dependencies": {
"xunit.extensibility.core": "[2.9.3]"
}
},
"speckle.sdk": {
"type": "Project",
"dependencies": {
@@ -283,14 +340,23 @@
"speckle.sdk.tests.unit": {
"type": "Project",
"dependencies": {
"FluentAssertions": "[7.0.0, )",
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Microsoft.NET.Test.Sdk": "[17.11.1, )",
"NUnit": "[4.2.2, )",
"NUnit3TestAdapter": "[4.6.0, )",
"Shouldly": "[4.2.1, )",
"Microsoft.NET.Test.Sdk": "[17.12.0, )",
"Speckle.DoubleNumerics": "[4.0.1, )",
"Speckle.Sdk": "[1.0.0, )",
"altcover": "[8.9.3, )"
"altcover": "[9.0.1, )",
"xunit": "[2.9.3, )",
"xunit.runner.visualstudio": "[3.0.0, )"
}
},
"FluentAssertions": {
"type": "CentralTransitive",
"requested": "[7.0.0, )",
"resolved": "7.0.0",
"contentHash": "mTLbcU991EQ1SEmNbVBaGGGJy0YFzvGd1sYJGNZ07nlPKuyHSn1I22aeKzqQXgEiaKyRO6MSCto9eN9VxMwBdA==",
"dependencies": {
"System.Configuration.ConfigurationManager": "6.0.0"
}
},
"GraphQL.Client": {
@@ -57,7 +57,7 @@ public class GeneralDeserializer : IDisposable
null
);
var o = new ObjectLoader(sqlite, serverObjects, null);
var process = new DeserializeProcess(null, o, new ObjectDeserializerFactory(), new(skipCache));
using var process = new DeserializeProcess(null, o, new ObjectDeserializerFactory(), new(skipCache));
return await process.Deserialize(rootId, default).ConfigureAwait(false);
}
@@ -4,13 +4,8 @@ using Microsoft.Extensions.DependencyInjection;
using Speckle.Objects.Geometry;
using Speckle.Sdk.Api;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Helpers;
using Speckle.Sdk.Host;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Serialisation;
using Speckle.Sdk.Serialisation.V2;
using Speckle.Sdk.Serialisation.V2.Receive;
using Speckle.Sdk.Transports;
namespace Speckle.Sdk.Tests.Performance.Benchmarks;
@@ -1,6 +1,5 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
using Microsoft.Extensions.Logging.Abstractions;
using Speckle.Objects.Geometry;
using Speckle.Sdk.Common;
using Speckle.Sdk.Credentials;
@@ -1,7 +1,6 @@
// See https://aka.ms/new-console-template for more information
using BenchmarkDotNet.Running;
using Speckle.Sdk.Tests.Performance.Benchmarks;
BenchmarkSwitcher.FromAssemblies([typeof(Program).Assembly]).Run(args);
// var sut = new GeneralSendTest();
@@ -1,20 +1,18 @@
using System.Diagnostics;
using GraphQL;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Credentials;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Api;
[TestOf(typeof(Client))]
public sealed class GraphQLClientTests : IDisposable
{
private Client _client;
private readonly Client _client;
[OneTimeSetUp]
public void Setup()
public GraphQLClientTests()
{
var serviceProvider = TestServiceSetup.GetServiceProvider();
_client = serviceProvider
@@ -28,17 +26,14 @@ public sealed class GraphQLClientTests : IDisposable
);
}
public void Dispose()
{
_client?.Dispose();
}
public void Dispose() => _client?.Dispose();
[Test]
public void TestExecuteWithResiliencePoliciesDoesntRetryTaskCancellation()
[Fact]
public async Task TestExecuteWithResiliencePoliciesDoesntRetryTaskCancellation()
{
var timer = new Stopwatch();
timer.Start();
Assert.ThrowsAsync<TaskCanceledException>(async () =>
await Assert.ThrowsAsync<TaskCanceledException>(async () =>
{
var tokenSource = new CancellationTokenSource();
tokenSource.Cancel();
@@ -55,14 +50,13 @@ public sealed class GraphQLClientTests : IDisposable
);
});
timer.Stop();
var elapsed = timer.ElapsedMilliseconds;
timer.ElapsedMilliseconds.Should().BeLessThan(1000);
// the default retry policy would retry 5 times with 1 second jitter backoff each
// if the elapsed is less than a second, this was def not retried
Assert.That(elapsed, Is.LessThan(1000));
}
[Test]
[Fact]
public async Task TestExecuteWithResiliencePoliciesRetry()
{
var counter = 0;
@@ -82,8 +76,8 @@ public sealed class GraphQLClientTests : IDisposable
});
timer.Stop();
// The baseline for wait is 1 seconds between the jittered retry
Assert.That(timer.ElapsedMilliseconds, Is.GreaterThanOrEqualTo(5000));
Assert.That(counter, Is.EqualTo(maxRetryCount));
timer.ElapsedMilliseconds.Should().BeGreaterThanOrEqualTo(5000);
counter.Should().Be(maxRetryCount);
}
public class FakeGqlResponseModel { }
@@ -1,55 +1,41 @@
using GraphQL;
using NUnit.Framework;
using FluentAssertions;
using GraphQL;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Api;
public class GraphQLErrorHandlerTests
{
private static IEnumerable<TestCaseData> ErrorCases()
public static IEnumerable<object[]> ErrorCases()
{
yield return new TestCaseData(typeof(SpeckleGraphQLForbiddenException), new Map { { "code", "FORBIDDEN" } });
yield return new TestCaseData(typeof(SpeckleGraphQLForbiddenException), new Map { { "code", "UNAUTHENTICATED" } });
yield return new TestCaseData(
typeof(SpeckleGraphQLInternalErrorException),
new Map { { "code", "INTERNAL_SERVER_ERROR" } }
);
yield return new TestCaseData(
typeof(SpeckleGraphQLStreamNotFoundException),
new Map { { "code", "STREAM_NOT_FOUND" } }
);
yield return new TestCaseData(typeof(SpeckleGraphQLBadInputException), new Map { { "code", "BAD_USER_INPUT" } });
yield return new TestCaseData(
typeof(SpeckleGraphQLInvalidQueryException),
new Map { { "code", "GRAPHQL_PARSE_FAILED" } }
);
yield return new TestCaseData(
typeof(SpeckleGraphQLInvalidQueryException),
new Map { { "code", "GRAPHQL_VALIDATION_FAILED" } }
);
yield return new TestCaseData(typeof(SpeckleGraphQLException), new Map { { "foo", "bar" } });
yield return new TestCaseData(typeof(SpeckleGraphQLException), new Map { { "code", "CUSTOM_THING" } });
yield return [typeof(SpeckleGraphQLForbiddenException), new Map { { "code", "FORBIDDEN" } }];
yield return [typeof(SpeckleGraphQLForbiddenException), new Map { { "code", "UNAUTHENTICATED" } }];
yield return [typeof(SpeckleGraphQLInternalErrorException), new Map { { "code", "INTERNAL_SERVER_ERROR" } }];
yield return [typeof(SpeckleGraphQLStreamNotFoundException), new Map { { "code", "STREAM_NOT_FOUND" } }];
yield return [typeof(SpeckleGraphQLBadInputException), new Map { { "code", "BAD_USER_INPUT" } }];
yield return [typeof(SpeckleGraphQLInvalidQueryException), new Map { { "code", "GRAPHQL_PARSE_FAILED" } }];
yield return [typeof(SpeckleGraphQLInvalidQueryException), new Map { { "code", "GRAPHQL_VALIDATION_FAILED" } }];
yield return [typeof(SpeckleGraphQLException), new Map { { "foo", "bar" } }];
yield return [typeof(SpeckleGraphQLException), new Map { { "code", "CUSTOM_THING" } }];
}
[Test, TestCaseSource(nameof(ErrorCases))]
[Theory]
[MemberData(nameof(ErrorCases))]
public void TestExceptionThrowingFromGraphQLErrors(Type exType, Map extensions)
{
var ex = Assert.Throws<AggregateException>(
() =>
GraphQLErrorHandler.EnsureGraphQLSuccess(
new GraphQLResponse<GraphQLClientTests.FakeGqlResponseModel>
{
Errors = new GraphQLError[] { new() { Extensions = extensions } },
}
)
new GraphQLResponse<GraphQLClientTests.FakeGqlResponseModel>
{
Errors = [new() { Extensions = extensions }],
}.EnsureGraphQLSuccess()
);
Assert.That(ex?.InnerExceptions, Has.Exactly(1).TypeOf(exType));
ex.InnerExceptions.Count.Should().Be(1);
ex.InnerExceptions[0].Should().BeOfType(exType);
}
[Test]
public void TestMaybeThrowsDoesntThrowForNoErrors()
{
Assert.DoesNotThrow(() => GraphQLErrorHandler.EnsureGraphQLSuccess(new GraphQLResponse<string>()));
}
[Fact]
public void TestMaybeThrowsDoesntThrowForNoErrors() => new GraphQLResponse<string>().EnsureGraphQLSuccess();
}
@@ -1,23 +1,21 @@
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using NUnit.Framework;
using Speckle.Sdk.Api;
using Speckle.Sdk.Common;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Tests.Unit.Host;
using Speckle.Sdk.Transports;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Api.Operations;
[TestFixture]
[TestOf(typeof(Sdk.Api.Operations))]
public class Closures
{
private IOperations _operations;
private readonly IOperations _operations;
[SetUp]
public void Setup()
public Closures()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(TableLegFixture).Assembly);
@@ -25,7 +23,7 @@ public class Closures
_operations = serviceProvider.GetRequiredService<IOperations>();
}
[Test(Description = "Checks whether closures are generated correctly by the serialiser.")]
[Fact(DisplayName = "Checks whether closures are generated correctly by the serialiser.")]
public async Task CorrectDecompositionTracking()
{
var d5 = new Base();
@@ -56,31 +54,31 @@ public class Closures
var test = await _operations.Receive(sendResult.rootObjId, localTransport: transport);
test.id.NotNull();
Assert.That(d1.GetId(true), Is.EqualTo(test.id));
d1.GetId(true).Should().BeEquivalentTo((test.id));
var d1_ = NotNullExtensions.NotNull(JsonConvert.DeserializeObject<dynamic>(transport.Objects[d1.GetId(true)]));
var d2_ = NotNullExtensions.NotNull(JsonConvert.DeserializeObject<dynamic>(transport.Objects[d2.GetId(true)]));
var d3_ = NotNullExtensions.NotNull(JsonConvert.DeserializeObject<dynamic>(transport.Objects[d3.GetId(true)]));
var d4_ = JsonConvert.DeserializeObject<dynamic>(transport.Objects[d4.GetId(true)]);
var d5_ = JsonConvert.DeserializeObject<dynamic>(transport.Objects[d5.GetId(true)]);
JsonConvert.DeserializeObject<dynamic>(transport.Objects[d4.GetId(true)]);
JsonConvert.DeserializeObject<dynamic>(transport.Objects[d5.GetId(true)]);
var depthOf_d5_in_d1 = int.Parse((string)d1_.__closure[d5.GetId(true)]);
Assert.That(depthOf_d5_in_d1, Is.EqualTo(1));
depthOf_d5_in_d1.Should().Be(1);
var depthOf_d4_in_d1 = int.Parse((string)d1_.__closure[d4.GetId(true)]);
Assert.That(depthOf_d4_in_d1, Is.EqualTo(3));
depthOf_d4_in_d1.Should().Be(3);
var depthOf_d5_in_d3 = int.Parse((string)d3_.__closure[d5.GetId(true)]);
Assert.That(depthOf_d5_in_d3, Is.EqualTo(2));
depthOf_d5_in_d3.Should().Be(2);
var depthOf_d4_in_d3 = int.Parse((string)d3_.__closure[d4.GetId(true)]);
Assert.That(depthOf_d4_in_d3, Is.EqualTo(1));
depthOf_d4_in_d3.Should().Be(1);
var depthOf_d5_in_d2 = int.Parse((string)d2_.__closure[d5.GetId(true)]);
Assert.That(depthOf_d5_in_d2, Is.EqualTo(1));
depthOf_d5_in_d2.Should().Be(1);
}
[Test]
[Fact]
public void DescendantsCounting()
{
Base myBase = new();
@@ -118,18 +116,15 @@ public class Closures
myBase["@detachTheDictionary"] = dictionary;
var count = myBase.GetTotalChildrenCount();
Assert.That(count, Is.EqualTo(112));
myBase.GetTotalChildrenCount().Should().Be(112);
var tableTest = new DiningTable();
var tableKidsCount = tableTest.GetTotalChildrenCount();
Assert.That(tableKidsCount, Is.EqualTo(10));
tableTest.GetTotalChildrenCount().Should().Be(10);
// Explicitely test for recurisve references!
var recursiveRef = new Base { applicationId = "random" };
recursiveRef["@recursive"] = recursiveRef;
var supriseCount = recursiveRef.GetTotalChildrenCount();
Assert.That(supriseCount, Is.EqualTo(2));
recursiveRef.GetTotalChildrenCount().Should().Be(2);
}
}
@@ -1,43 +1,39 @@
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Speckle.Sdk.Api;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Transports;
using Speckle.Sdk.Transports;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Api.Operations;
public partial class OperationsReceiveTests
{
[Test, TestCaseSource(nameof(TestCases))]
public void Receive_ObjectsDontExist_ExceptionThrown(string id)
[Theory, MemberData(nameof(TestCases))]
public async Task Receive_ObjectsDontExist_ExceptionThrown(string id)
{
MemoryTransport emptyTransport1 = new();
MemoryTransport emptyTransport2 = new();
Assert.ThrowsAsync<TransportException>(async () =>
await Assert.ThrowsAsync<TransportException>(async () =>
{
await _operations.Receive(id, emptyTransport1, emptyTransport2);
});
}
[Test, TestCaseSource(nameof(TestCases))]
public void Receive_ObjectsDontExistNullRemote_ExceptionThrown(string id)
[Theory, MemberData(nameof(TestCases))]
public async Task Receive_ObjectsDontExistNullRemote_ExceptionThrown(string id)
{
MemoryTransport emptyTransport = new();
Assert.ThrowsAsync<TransportException>(async () =>
await Assert.ThrowsAsync<TransportException>(async () =>
{
await _operations.Receive(id, null, emptyTransport);
});
}
[Test, TestCaseSource(nameof(TestCases))]
public void Receive_OperationCanceled_ExceptionThrown(string id)
[Theory, MemberData(nameof(TestCases))]
public async Task Receive_OperationCanceled_ExceptionThrown(string id)
{
using CancellationTokenSource ctc = new();
ctc.Cancel();
MemoryTransport emptyTransport2 = new();
Assert.CatchAsync<OperationCanceledException>(async () =>
await Assert.ThrowsAsync<OperationCanceledException>(async () =>
{
await _operations.Receive(id, _testCaseTransport, emptyTransport2, cancellationToken: ctc.Token);
});
@@ -1,18 +1,18 @@
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Speckle.Sdk.Api;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Transports;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Api.Operations;
[TestFixture, TestOf(nameof(Sdk.Api.Operations.Receive))]
public sealed partial class OperationsReceiveTests
public sealed partial class OperationsReceiveTests : IDisposable
{
private static readonly Base[] s_testObjects;
private IOperations _operations;
private readonly IOperations _operations;
private readonly MemoryTransport _testCaseTransport;
static OperationsReceiveTests()
{
@@ -29,64 +29,57 @@ public sealed partial class OperationsReceiveTests
];
}
public static IEnumerable<string> TestCases()
public OperationsReceiveTests()
{
List<string> ret = new();
foreach (var s in s_testObjects)
Reset();
var serviceProvider = TestServiceSetup.GetServiceProvider();
_operations = serviceProvider.GetRequiredService<IOperations>();
_testCaseTransport = new MemoryTransport();
// Simulate a one-time setup action
foreach (var b in s_testObjects)
{
ret.Add(s.GetId(true));
_ = _operations.Send(b, _testCaseTransport, false).GetAwaiter().GetResult();
}
return ret;
}
private MemoryTransport _testCaseTransport;
private static void Reset()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, Assembly.GetExecutingAssembly());
}
[OneTimeSetUp]
public async Task GlobalSetup()
public static IEnumerable<object[]> TestCases()
{
Reset();
var serviceProvider = TestServiceSetup.GetServiceProvider();
_operations = serviceProvider.GetRequiredService<IOperations>();
_testCaseTransport = new MemoryTransport();
foreach (var b in s_testObjects)
foreach (var s in s_testObjects)
{
await _operations.Send(b, _testCaseTransport, false);
yield return [s.GetId(true)];
}
}
[SetUp]
public void Setup()
{
Reset();
var serviceProvider = TestServiceSetup.GetServiceProvider();
_operations = serviceProvider.GetRequiredService<IOperations>();
}
[Test, TestCaseSource(nameof(TestCases))]
[Theory]
[MemberData(nameof(TestCases))]
public async Task Receive_FromLocal_ExistingObjects(string id)
{
Base result = await _operations.Receive(id, null, _testCaseTransport);
Assert.That(result.id, Is.EqualTo(id));
Assert.NotNull(result);
Assert.Equal(id, result.id);
}
[Test, TestCaseSource(nameof(TestCases))]
[Theory]
[MemberData(nameof(TestCases))]
public async Task Receive_FromRemote_ExistingObjects(string id)
{
MemoryTransport localTransport = new();
Base result = await _operations.Receive(id, _testCaseTransport, localTransport);
Assert.That(result.id, Is.EqualTo(id));
Assert.NotNull(result);
Assert.Equal(id, result.id);
}
[Test, TestCaseSource(nameof(TestCases))]
[Theory]
[MemberData(nameof(TestCases))]
public async Task Receive_FromLocal_OnProgressActionCalled(string id)
{
bool wasCalled = false;
@@ -97,6 +90,11 @@ public sealed partial class OperationsReceiveTests
onProgressAction: new UnitTestProgress<ProgressArgs>(_ => wasCalled = true)
);
Assert.That(wasCalled, Is.True);
Assert.True(wasCalled);
}
public void Dispose()
{
// Cleanup resources if necessary
}
}
@@ -1,18 +1,18 @@
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Sdk.Api;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Transports;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Api.Operations;
public class SendObjectReferences
{
private IOperations _operations;
private readonly IOperations _operations;
[SetUp]
public void Setup()
public SendObjectReferences()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(DataChunk).Assembly);
@@ -20,34 +20,38 @@ public class SendObjectReferences
_operations = serviceProvider.GetRequiredService<IOperations>();
}
[TestCase(0)]
[TestCase(1)]
[TestCase(10)]
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(10)]
public async Task SendObjectsWithApplicationIds(int testDepth)
{
Base testData = GenerateTestCase(testDepth, true);
MemoryTransport transport = new();
var result = await _operations.Send(testData, [transport]);
Assert.That(result.rootObjId, Is.Not.Null);
Assert.That(result.rootObjId, Has.Length.EqualTo(32));
result.rootObjId.Should().NotBeNull();
Assert.That(result.convertedReferences, Has.Count.EqualTo(Math.Pow(2, testDepth + 1) - 2));
result.rootObjId.Length.Should().Be(32);
result.convertedReferences.Count.Should().Be((int)(Math.Pow(2, testDepth + 1) - 2));
}
[TestCase(0)]
[TestCase(1)]
[TestCase(10)]
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(10)]
public async Task SendObjectsWithoutApplicationIds(int testDepth)
{
Base testData = GenerateTestCase(testDepth, false);
MemoryTransport transport = new();
var result = await _operations.Send(testData, [transport]);
Assert.That(result.rootObjId, Is.Not.Null);
Assert.That(result.rootObjId, Has.Length.EqualTo(32));
result.rootObjId.Should().NotBeNull();
Assert.That(result.convertedReferences, Is.Empty);
result.rootObjId.Length.Should().Be(32);
result.convertedReferences.Should().BeEmpty();
}
private Base GenerateTestCase(int depth, bool withAppId)
@@ -1,23 +1,20 @@
using System.Collections.Concurrent;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Shouldly;
using Speckle.Sdk.Api;
using Speckle.Sdk.Common;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Tests.Unit.Host;
using Speckle.Sdk.Transports;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Api.Operations;
[TestFixture]
public sealed class SendReceiveLocal : IDisposable
{
private IOperations _operations;
private readonly IOperations _operations;
[SetUp]
public void Setup()
public SendReceiveLocal()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly);
@@ -25,15 +22,14 @@ public sealed class SendReceiveLocal : IDisposable
_operations = serviceProvider.GetRequiredService<IOperations>();
}
private string? _objId01;
private string? _commitId02;
private const int NUM_OBJECTS = 3001;
private readonly SQLiteTransport _sut = new();
[Test(Description = "Pushing a commit locally"), Order(1)]
public async Task LocalUpload()
public void Dispose() => _sut.Dispose();
[Fact(DisplayName = "Pushing a commit locally")]
public async Task LocalUploadAndDownload()
{
var myObject = new Base();
var rand = new Random();
@@ -48,24 +44,18 @@ public sealed class SendReceiveLocal : IDisposable
}
using SQLiteTransport localTransport = new();
(_objId01, var references) = await _operations.Send(myObject, localTransport, false);
(var objId01, var references) = await _operations.Send(myObject, localTransport, false);
Assert.That(_objId01, Is.Not.Null);
Assert.That(references, Has.Count.EqualTo(NUM_OBJECTS));
objId01.Should().NotBeNull();
references.Count.Should().Be(NUM_OBJECTS);
TestContext.Out.WriteLine($"Written {NUM_OBJECTS + 1} objects. Commit id is {_objId01}");
var commitPulled = await _operations.Receive(objId01.NotNull());
((List<object>)commitPulled["@items"].NotNull())[0].Should().BeOfType<Point>();
((List<object>)commitPulled["@items"].NotNull()).Count.Should().Be(NUM_OBJECTS);
}
[Test(Description = "Pulling a commit locally"), Order(2)]
public async Task LocalDownload()
{
var commitPulled = await _operations.Receive(_objId01.NotNull());
Assert.That(((List<object>)commitPulled["@items"].NotNull())[0], Is.TypeOf<Point>());
Assert.That(((List<object>)commitPulled["@items"].NotNull()), Has.Count.EqualTo(NUM_OBJECTS));
}
[Test(Description = "Pushing and Pulling a commit locally")]
[Fact(DisplayName = "Pushing and Pulling a commit locally")]
public async Task LocalUploadDownload()
{
var myObject = new Base();
@@ -80,16 +70,15 @@ public sealed class SendReceiveLocal : IDisposable
);
}
(_objId01, _) = await _operations.Send(myObject, _sut, false);
(var objId01, _) = await _operations.Send(myObject, _sut, false);
var commitPulled = await _operations.Receive(_objId01);
var commitPulled = await _operations.Receive(objId01);
List<object> items = (List<object>)commitPulled["@items"].NotNull();
Assert.That(items, Has.All.TypeOf<Point>());
Assert.That(items, Has.Count.EqualTo(NUM_OBJECTS));
items.Should().AllSatisfy(x => x.Should().BeOfType<Point>());
items.Count.Should().Be(NUM_OBJECTS);
}
[Test(Description = "Pushing and pulling a commit locally"), Order(3)]
[Fact(DisplayName = "Pushing and pulling a commit locally")]
public async Task LocalUploadDownloadSmall()
{
var myObject = new Base();
@@ -104,16 +93,15 @@ public sealed class SendReceiveLocal : IDisposable
);
}
(_objId01, _) = await _operations.Send(myObject, _sut, false);
(var objId01, _) = await _operations.Send(myObject, _sut, false);
Assert.That(_objId01, Is.Not.Null);
TestContext.Out.WriteLine($"Written {NUM_OBJECTS + 1} objects. Commit id is {_objId01}");
objId01.Should().NotBeNull();
var objsPulled = await _operations.Receive(_objId01);
Assert.That(((List<object>)objsPulled["@items"].NotNull()), Has.Count.EqualTo(30));
var objsPulled = await _operations.Receive(objId01);
((List<object>)objsPulled["@items"].NotNull()).Count.Should().Be(30);
}
[Test(Description = "Pushing and pulling a commit locally"), Order(3)]
[Fact(DisplayName = "Pushing and pulling a commit locally")]
public async Task LocalUploadDownloadListDic()
{
var myList = new List<object> { 1, 2, 3, "ciao" };
@@ -128,19 +116,16 @@ public sealed class SendReceiveLocal : IDisposable
myObject["@dictionary"] = myDic;
myObject["@list"] = myList;
(_objId01, _) = await _operations.Send(myObject, _sut, false);
(var _objId01, _) = await _operations.Send(myObject, _sut, false);
Assert.That(_objId01, Is.Not.Null);
_objId01.Should().NotBeNull();
var objsPulled = await _operations.Receive(_objId01);
Assert.That(
((List<object>)((Dictionary<string, object>)objsPulled["@dictionary"].NotNull())["a"]).First(),
Is.EqualTo(1)
);
Assert.That(((List<object>)objsPulled["@list"].NotNull()).Last(), Is.EqualTo("ciao"));
((List<object>)((Dictionary<string, object>)objsPulled["@dictionary"].NotNull())["a"]).First().Should().Be(1);
((List<object>)objsPulled["@list"].NotNull()).Last().Should().Be("ciao");
}
[Test(Description = "Pushing and pulling a random object, with our without detachment"), Order(3)]
[Fact(DisplayName = "Pushing and pulling a random object, with or without detachment")]
public async Task UploadDownloadNonCommitObject()
{
var obj = new Base();
@@ -166,32 +151,31 @@ public sealed class SendReceiveLocal : IDisposable
((List<Base>)((dynamic)obj)["@LayerC"]).Add(new Point(i, i, i + rand.NextDouble()) { applicationId = i + "baz" });
}
(_objId01, _) = await _operations.Send(obj, _sut, false);
(var objId01, _) = await _operations.Send(obj, _sut, false);
Assert.That(_objId01, Is.Not.Null);
TestContext.Out.WriteLine($"Written {NUM_OBJECTS + 1} objects. Commit id is {_objId01}");
objId01.Should().NotBeNull();
var objPulled = await _operations.Receive(_objId01);
var objPulled = await _operations.Receive(objId01);
Assert.That(objPulled, Is.TypeOf<Base>());
objPulled.Should().BeOfType<Base>();
// Note: even if the layers were originally declared as lists of "Base" objects, on deserialisation we cannot know that,
// as it's a dynamic property. Dynamic properties, if their content value is ambigous, will default to a common-sense standard.
// This specifically manifests in the case of lists and dictionaries: List<AnySpecificType> will become List<object>, and
// Dictionary<string, MyType> will deserialize to Dictionary<string,object>.
var layerA = ((dynamic)objPulled)["LayerA"] as List<object>;
Assert.That(layerA, Has.Count.EqualTo(30));
layerA?.Count.Should().Be(30);
var layerC = (List<object>)((dynamic)objPulled)["@LayerC"];
Assert.That(layerC, Has.Count.EqualTo(30));
Assert.That(layerC[0], Is.TypeOf<Point>());
layerC.Count.Should().Be(30);
layerC[0].Should().BeOfType<Point>();
var layerD = ((dynamic)objPulled)["@LayerD"] as List<object>;
Assert.That(layerD, Has.Count.EqualTo(2));
layerD?.Count.Should().Be(2);
}
[Test(Description = "Should show progress!"), Order(4)]
public async Task UploadProgressReports()
[Fact(DisplayName = "Should show progress!")]
public async Task UploadAndDownloadProgressReports()
{
Base myObject = new() { ["items"] = new List<Base>() };
var rand = new Random();
@@ -203,24 +187,20 @@ public sealed class SendReceiveLocal : IDisposable
);
}
(_commitId02, _) = await _operations.Send(myObject, _sut, false);
}
(var commitId02, _) = await _operations.Send(myObject, _sut, false);
[Test(Description = "Should show progress!"), Order(5)]
public async Task DownloadProgressReports()
{
ProgressArgs? progress = null;
await _operations.Receive(
_commitId02.NotNull(),
commitId02.NotNull(),
onProgressAction: new UnitTestProgress<ProgressArgs>(x =>
{
progress = x;
})
);
progress.ShouldNotBeNull();
progress.Should().NotBeNull();
}
[Test(Description = "Should not dispose of transports if so specified.")]
[Fact(DisplayName = "Should not dispose of transports if so specified.")]
public async Task ShouldNotDisposeTransports()
{
var @base = new Base();
@@ -233,9 +213,4 @@ public sealed class SendReceiveLocal : IDisposable
_ = await _operations.Receive(sendResult.rootObjId, null, myLocalTransport);
await _operations.Receive(sendResult.rootObjId, null, myLocalTransport);
}
public void Dispose()
{
_sut.Dispose();
}
}
@@ -1,24 +1,20 @@
using System.Drawing;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Tests.Unit.Host;
using Xunit;
using Point = Speckle.Sdk.Tests.Unit.Host.Point;
namespace Speckle.Sdk.Tests.Unit.Api.Operations;
[TestFixture]
[TestOf(typeof(Sdk.Api.Operations))]
public class ObjectSerialization
{
private IOperations _operations;
private readonly IOperations _operations;
[SetUp]
public void Setup()
public ObjectSerialization()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(DataChunk).Assembly, typeof(ColorMock).Assembly);
@@ -26,7 +22,7 @@ public class ObjectSerialization
_operations = serviceProvider.GetRequiredService<IOperations>();
}
[Test]
[Fact]
public async Task IgnoreCircularReferences()
{
var pt = new Point(1, 2, 3);
@@ -36,10 +32,10 @@ public class ObjectSerialization
var result = await _operations.DeserializeAsync(test);
var circle = result["circle"];
Assert.That(circle, Is.Null);
circle.Should().BeNull();
}
[Test]
[Fact]
public async Task InterfacePropHandling()
{
Line tail = new() { Start = new Point(0, 0, 0), End = new Point(42, 42, 42) };
@@ -75,10 +71,10 @@ public class ObjectSerialization
var deserialisedFeline = await _operations.DeserializeAsync(result);
Assert.That(deserialisedFeline.GetId(), Is.EqualTo(cat.GetId())); // If we're getting the same hash... we're probably fine!
deserialisedFeline.GetId().Should().Be(cat.GetId());
}
[Test]
[Fact]
public async Task InheritanceTests()
{
var superPoint = new SuperPoint
@@ -92,10 +88,10 @@ public class ObjectSerialization
var str = _operations.Serialize(superPoint);
var sstr = await _operations.DeserializeAsync(str);
Assert.That(sstr.speckle_type, Is.EqualTo(superPoint.speckle_type));
sstr.speckle_type.Should().Be(superPoint.speckle_type);
}
[Test]
[Fact]
public async Task ListDynamicProp()
{
var point = new Point();
@@ -111,11 +107,12 @@ public class ObjectSerialization
var str = _operations.Serialize(point);
var dsrls = await _operations.DeserializeAsync(str);
var list = dsrls["test"] as List<object>; // NOTE: on dynamically added lists, we cannot infer the inner type and we always fall back to a generic list<object>.
Assert.That(list, Has.Count.EqualTo(100));
var list = dsrls["test"] as List<object>;
list.Should().NotBeNull(); // Ensure the list isn't null in first place
list!.Count.Should().Be(100);
}
[Test]
[Fact]
public async Task ChunkSerialisation()
{
var baseBasedChunk = new DataChunk() { data = new() };
@@ -144,12 +141,12 @@ public class ObjectSerialization
var stringChunkDeserialised = (DataChunk)await _operations.DeserializeAsync(stringChunkString);
var doubleChunkDeserialised = (DataChunk)await _operations.DeserializeAsync(doubleChunkString);
Assert.That(baseChunkDeserialised.data, Has.Count.EqualTo(baseBasedChunk.data.Count));
Assert.That(stringChunkDeserialised.data, Has.Count.EqualTo(stringBasedChunk.data.Count));
Assert.That(doubleChunkDeserialised.data, Has.Count.EqualTo(doubleBasedChunk.data.Count));
baseChunkDeserialised.data.Count.Should().Be(baseBasedChunk.data.Count);
stringChunkDeserialised.data.Count.Should().Be(stringBasedChunk.data.Count);
doubleChunkDeserialised.data.Count.Should().Be(doubleBasedChunk.data.Count);
}
[Test]
[Fact]
public async Task ObjectWithChunksSerialisation()
{
const int MAX_NUM = 2020;
@@ -174,10 +171,10 @@ public class ObjectSerialization
var serialised = _operations.Serialize(mesh);
var deserialised = await _operations.DeserializeAsync(serialised);
Assert.That(mesh.GetId(), Is.EqualTo(deserialised.GetId()));
mesh.GetId().Should().Be(deserialised.GetId());
}
[Test]
[Fact]
public void EmptyListSerialisationTests()
{
// NOTE: expected behaviour is that empty lists should serialize as empty lists. Don't ask why, it's complicated.
@@ -200,7 +197,7 @@ public class ObjectSerialization
&& serialised.Contains("\"nestedList\":[[[]]]")
&& serialised.Contains("\"@nestedDetachableList\":[[[]]]");
Assert.That(isCorrect, Is.EqualTo(true));
isCorrect.Should().BeTrue();
}
[SpeckleType("Speckle.Core.Tests.Unit.Api.Operations.ObjectSerialization+DateMock")]
@@ -209,7 +206,7 @@ public class ObjectSerialization
public DateTime TestField { get; set; }
}
[Test]
[Fact]
public async Task DateSerialisation()
{
var date = new DateTime(2020, 1, 14);
@@ -218,7 +215,7 @@ public class ObjectSerialization
var result = _operations.Serialize(mockBase);
var test = (DateMock)await _operations.DeserializeAsync(result);
Assert.That(test.TestField, Is.EqualTo(date));
test.TestField.Should().Be(date);
}
[SpeckleType("Speckle.Core.Tests.Unit.Api.Operations.ObjectSerialization+GUIDMock")]
@@ -227,7 +224,7 @@ public class ObjectSerialization
public Guid TestField { get; set; }
}
[Test]
[Fact]
public async Task GuidSerialisation()
{
var guid = Guid.NewGuid();
@@ -236,7 +233,7 @@ public class ObjectSerialization
var result = _operations.Serialize(mockBase);
var test = (GUIDMock)await _operations.DeserializeAsync(result);
Assert.That(test.TestField, Is.EqualTo(guid));
test.TestField.Should().Be(guid);
}
[SpeckleType("Speckle.Core.Tests.Unit.Api.Operations.ObjectSerialization+ColorMock")]
@@ -245,7 +242,7 @@ public class ObjectSerialization
public Color TestField { get; set; }
}
[Test]
[Fact]
public async Task ColorSerialisation()
{
var color = Color.FromArgb(255, 4, 126, 251);
@@ -254,7 +251,7 @@ public class ObjectSerialization
var result = _operations.Serialize(mockBase);
var test = (ColorMock)await _operations.DeserializeAsync(result);
Assert.That(test.TestField, Is.EqualTo(color));
test.TestField.Should().Be(color);
}
[SpeckleType("Speckle.Core.Tests.Unit.Api.Operations.ObjectSerialization+StringDateTimeRegressionMock")]
@@ -263,7 +260,7 @@ public class ObjectSerialization
public string TestField { get; set; }
}
[Test]
[Fact]
public async Task StringDateTimeRegression()
{
var mockBase = new StringDateTimeRegressionMock { TestField = "2021-11-12T11:32:01" };
@@ -271,6 +268,6 @@ public class ObjectSerialization
var result = _operations.Serialize(mockBase);
var test = (StringDateTimeRegressionMock)await _operations.DeserializeAsync(result);
Assert.That(test.TestField, Is.EqualTo(mockBase.TestField));
test.TestField.Should().Be(mockBase.TestField);
}
}
+3
View File
@@ -0,0 +1,3 @@
using Xunit;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
@@ -1,87 +1,94 @@
using NUnit.Framework;
using Shouldly;
using FluentAssertions;
using Speckle.Sdk.Common;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Common;
public class NotNullTests
{
[TestCase(null, 0)]
[TestCase(new string[0], 0)]
[TestCase(new[] { "yay" }, 1)]
[Theory]
[InlineData(null, 0)]
[InlineData(new string[0], 0)]
[InlineData(new[] { "yay" }, 1)]
public void Empty(string[]? test, int length)
{
var list = NotNullExtensions.Empty(test).ToList();
list.Count.ShouldBe(length);
var list = test.Empty().ToList();
list.Count.Should().Be(length);
}
[Test]
[Fact]
public void NotNullClass()
{
var t = NotNullExtensions.NotNull("test");
t.ShouldNotBeNull().ShouldBe("test");
var t = "test".NotNull();
t.Should().Be("test");
}
[Test]
[Fact]
public void NotNullStruct()
{
var t = NotNullExtensions.NotNull<int>(2);
t.ShouldBe(2);
t.Should().Be(2);
}
[Test]
[Fact]
public async Task NotNullClass_Task()
{
var t = await NotNullExtensions.NotNull(Task.FromResult<string?>("test"));
t.ShouldNotBeNull().ShouldBe("test");
var t = await Task.FromResult<string?>("test").NotNull();
t.Should().Be("test");
}
[Test]
[Fact]
public async Task NotNullStruct_Task()
{
var t = await NotNullExtensions.NotNull(Task.FromResult<int?>(2));
t.ShouldBe(2);
var t = await Task.FromResult<int?>(2).NotNull();
t.Should().Be(2);
}
[Test]
[Fact]
public async Task NotNullClass_ValueTask()
{
var t = await NotNullExtensions.NotNull(ValueTask.FromResult<string?>("test"));
t.ShouldNotBeNull().ShouldBe("test");
var t = await ValueTask.FromResult<string?>("test").NotNull();
t.Should().Be("test");
}
[Test]
[Fact]
public async Task NotNullStruct_ValueTask()
{
var t = await NotNullExtensions.NotNull(ValueTask.FromResult<int?>(2));
t.ShouldBe(2);
var t = await ValueTask.FromResult<int?>(2).NotNull();
t.Should().Be(2);
}
[Test]
public void NotNullClass_Exception() =>
Assert.Throws<ArgumentNullException>(() => NotNullExtensions.NotNull((string?)null));
[Fact]
public void NotNullClass_Exception() => FluentActions.Invoking(() => ((string?)null).NotNull());
[Test]
public void NotNullStruct_Exception() =>
Assert.Throws<ArgumentNullException>(() => NotNullExtensions.NotNull((int?)null));
[Fact]
public void NotNullStruct_Exception() => FluentActions.Invoking(() => ((int?)null).NotNull());
[Test]
public void NotNullClass_Task_Exception() =>
Assert.ThrowsAsync<ArgumentNullException>(() => NotNullExtensions.NotNull(Task.FromResult((string?)null)));
[Fact]
public async Task NotNullClass_Task_Exception() =>
await FluentActions
.Invoking(async () => await Task.FromResult((string?)null).NotNull())
.Should()
.ThrowAsync<ArgumentNullException>();
[Test]
public void NotNullStruct_Task_Exception() =>
Assert.ThrowsAsync<ArgumentNullException>(() => NotNullExtensions.NotNull(Task.FromResult((int?)null)));
[Fact]
public async Task NotNullStruct_Task_Exception() =>
await FluentActions
.Invoking(async () => await Task.FromResult((int?)null).NotNull())
.Should()
.ThrowAsync<ArgumentNullException>();
[Test]
public void NotNullClass_ValueTask_Exception() =>
Assert.ThrowsAsync<ArgumentNullException>(
async () => await NotNullExtensions.NotNull(ValueTask.FromResult((string?)null))
);
[Fact]
public async Task NotNullClass_ValueTask_Exception() =>
await FluentActions
.Invoking(async () => await ValueTask.FromResult((string?)null).NotNull())
.Should()
.ThrowAsync<ArgumentNullException>();
[Test]
public void NotNullStruct_ValueTask_Exception() =>
Assert.ThrowsAsync<ArgumentNullException>(
async () => await NotNullExtensions.NotNull(ValueTask.FromResult((int?)null))
);
[Fact]
public async Task NotNullStruct_ValueTask_Exception() =>
await FluentActions
.Invoking(async () => await ValueTask.FromResult((int?)null).NotNull())
.Should()
.ThrowAsync<ArgumentNullException>();
}
@@ -0,0 +1,14 @@
using Speckle.Sdk.Dependencies;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Common;
public class RangeFromTests
{
[Fact]
public void EnsureRange()
{
var list = EnumerableExtensions.RangeFrom(1, 4).ToArray();
Assert.Equal([1, 2, 3, 4], list);
}
}
@@ -1,70 +1,98 @@
using NUnit.Framework;
using FluentAssertions;
using Speckle.Sdk.Common;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Common;
[TestOf(typeof(Units))]
public class UnitsTest
{
private const double EPS = 0.00022;
public static List<string> OfficiallySupportedUnits => Units.SupportedUnits;
public static List<string> NotSupportedUnits => ["feeters", "liters", "us_ft"];
public static List<string?> ConversionSupport => [.. Units.SupportedUnits, null];
[Test, Combinatorial]
[DefaultFloatingPointTolerance(EPS)]
public void TestUnitConversion(
[ValueSource(nameof(ConversionSupport))] string? from,
[ValueSource(nameof(ConversionSupport))] string? to
)
public static List<string> NotSupportedUnits => ["feeters", "liters", "us_ft"];
public static List<string?> ConversionSupport => Units.SupportedUnits.Concat([null]).ToList();
[Theory]
[MemberData(nameof(ConversionSupportGenerator))]
public void TestUnitConversion(string? from, string? to)
{
var forwards = Units.GetConversionFactor(from, to);
var backwards = Units.GetConversionFactor(to, from);
Assert.That(
backwards * forwards,
Is.EqualTo(1d),
$"Behaviour says that 1{from} == {forwards}{to}, and 1{to} == {backwards}{from}"
);
(backwards * forwards)
.Should()
.BeApproximately(1d, EPS, $"Behaviour says that 1{from} == {forwards}{to}, and 1{to} == {backwards}{from}");
}
[TestCaseSource(nameof(OfficiallySupportedUnits))]
[Theory]
[MemberData(nameof(OfficiallySupportedUnitsGenerator))]
public void IsUnitSupported_ReturnsTrue_AllSupportedUnits(string unit)
{
bool res = Units.IsUnitSupported(unit);
Assert.That(res, Is.True);
res.Should().BeTrue();
}
[TestCaseSource(nameof(NotSupportedUnits))]
[Theory]
[MemberData(nameof(NotSupportedUnitsGenerator))]
public void IsUnitSupported_ReturnsFalse_NotSupportedUnits(string unit)
{
bool res = Units.IsUnitSupported(unit);
Assert.That(res, Is.False);
res.Should().BeFalse();
}
[TestCaseSource(nameof(OfficiallySupportedUnits))]
[Theory]
[MemberData(nameof(OfficiallySupportedUnitsGenerator))]
public void GetUnitsFromString_ReturnsSupported(string unit)
{
var lower = Units.GetUnitsFromString(unit);
var upper = Units.GetUnitsFromString(unit?.ToUpperInvariant());
string? lower = Units.GetUnitsFromString(unit);
string? upper = Units.GetUnitsFromString(unit.ToUpperInvariant());
Assert.That(lower, Is.EqualTo(unit));
Assert.That(upper, Is.EqualTo(unit));
lower.Should().Be(unit);
upper.Should().Be(unit);
}
[TestCaseSource(nameof(NotSupportedUnits))]
public void GetUnitsFromString_ThrowsUnSupported(string unit)
{
Assert.Throws<ArgumentOutOfRangeException>(() => _ = Units.GetUnitsFromString(unit));
}
[Theory]
[MemberData(nameof(NotSupportedUnitsGenerator))]
public void GetUnitsFromString_ThrowsUnSupported(string unit) =>
FluentActions.Invoking(() => Units.GetUnitsFromString(unit)).Should().Throw<ArgumentOutOfRangeException>();
[TestCaseSource(nameof(OfficiallySupportedUnits))]
[Theory]
[MemberData(nameof(OfficiallySupportedUnitsGenerator))]
public void UnitEncoding_RoundTrip(string unit)
{
var encoded = Units.GetEncodingFromUnit(unit);
var res = Units.GetUnitFromEncoding(encoded);
Assert.That(res, Is.EqualTo(unit));
res.Should().Be(unit);
}
// Generators for MemberData
public static IEnumerable<object[]> OfficiallySupportedUnitsGenerator()
{
foreach (var unit in OfficiallySupportedUnits)
{
yield return [unit];
}
}
public static IEnumerable<object[]> NotSupportedUnitsGenerator()
{
foreach (var unit in NotSupportedUnits)
{
yield return [unit];
}
}
public static IEnumerable<object?[]> ConversionSupportGenerator()
{
foreach (var from in ConversionSupport)
{
foreach (var to in ConversionSupport)
{
yield return [from, to];
}
}
}
}
@@ -1,68 +1,66 @@
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Host;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Credentials;
public class AccountServerMigrationTests
public class AccountServerMigrationTests : IDisposable
{
private readonly List<Account> _accountsToCleanUp = new();
private readonly List<Account> _accountsToCleanUp = [];
public static IEnumerable<TestCaseData> MigrationTestCase()
public static IEnumerable<object[]> MigrationTestCases()
{
const string OLD_URL = "https://old.example.com";
const string NEW_URL = "https://new.example.com";
const string OTHER_URL = "https://other.example.com";
Account oldAccount = CreateTestAccount(OLD_URL, null, new(NEW_URL));
string accountId = oldAccount.userInfo.id; // new account user must match old account user id
Account newAccount = CreateTestAccount(NEW_URL, new(OLD_URL), null, accountId);
Account otherAccount = CreateTestAccount(OTHER_URL, null, null);
List<Account> givenAccounts = new() { oldAccount, newAccount, otherAccount };
List<Account> givenAccounts = [oldAccount, newAccount, otherAccount];
yield return new TestCaseData(givenAccounts, NEW_URL, new[] { newAccount })
.SetName("Get New")
.SetDescription("When requesting for new account, ensure only this account is returned");
yield return [givenAccounts, NEW_URL, new[] { newAccount }];
yield return new TestCaseData(givenAccounts, OLD_URL, new[] { newAccount })
.SetName("Get New via Old")
.SetDescription("When requesting for old account, ensure migrated account is returned first");
yield return [givenAccounts, OLD_URL, new[] { newAccount }];
var reversed = Enumerable.Reverse(givenAccounts).ToList();
var reversed = givenAccounts.AsEnumerable().Reverse().ToList();
yield return new TestCaseData(reversed, OLD_URL, new[] { newAccount })
.SetName("Get New via Old (Reversed order)")
.SetDescription("Account order shouldn't matter");
yield return [reversed, OLD_URL, new[] { newAccount }];
}
[Test]
[TestCaseSource(nameof(MigrationTestCase))]
[Theory]
[MemberData(nameof(MigrationTestCases))]
public void TestServerMigration(IList<Account> accounts, string requestedUrl, IList<Account> expectedSequence)
{
// Add accounts to the local setup
AddAccounts(accounts);
var serviceProvider = TestServiceSetup.GetServiceProvider();
var serviceProvider = TestServiceSetup.GetServiceProvider();
var result = serviceProvider.GetRequiredService<IAccountManager>().GetAccounts(requestedUrl).ToList();
Assert.That(result, Is.EquivalentTo(expectedSequence));
// Assert the result using Shouldly
result.Should().BeEquivalentTo(expectedSequence);
}
[TearDown]
public void TearDown()
public void Dispose()
{
//Clean up any of the test accounts we made
// Clean up accounts after each test
foreach (var acc in _accountsToCleanUp)
{
Fixtures.DeleteLocalAccount(acc.id);
}
_accountsToCleanUp.Clear();
}
private static Account CreateTestAccount(string url, Uri? movedFrom, Uri? movedTo, string? id = null)
{
id ??= Guid.NewGuid().ToString();
return new Account
{
token = "myToken",
@@ -83,7 +81,7 @@ public class AccountServerMigrationTests
private void AddAccounts(IEnumerable<Account> accounts)
{
foreach (Account account in accounts)
foreach (var account in accounts)
{
_accountsToCleanUp.Add(account);
Fixtures.UpdateOrSaveAccount(account);
@@ -1,19 +1,19 @@
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Host;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Credentials;
[TestFixture]
public class CredentialInfrastructure
public class CredentialInfrastructure : IDisposable
{
private IAccountManager _accountManager;
private readonly IAccountManager _accountManager;
private static readonly Account s_testAccount1;
private static readonly Account s_testAccount2;
private static readonly Account s_testAccount3;
[OneTimeSetUp]
public static void SetUp()
static CredentialInfrastructure()
{
s_testAccount1 = new Account
{
@@ -42,76 +42,69 @@ public class CredentialInfrastructure
name = "Test Account 3",
},
};
}
public CredentialInfrastructure()
{
Fixtures.UpdateOrSaveAccount(s_testAccount1);
Fixtures.UpdateOrSaveAccount(s_testAccount2);
Fixtures.SaveLocalAccount(s_testAccount3);
}
[SetUp]
public void Setup2()
{
var serviceProvider = TestServiceSetup.GetServiceProvider();
_accountManager = serviceProvider.GetRequiredService<IAccountManager>();
}
[OneTimeTearDown]
public static void TearDown()
public void Dispose()
{
_accountManager.Dispose();
Fixtures.DeleteLocalAccount(s_testAccount1.id);
Fixtures.DeleteLocalAccount(s_testAccount2.id);
Fixtures.DeleteLocalAccount(s_testAccount3.id);
Fixtures.DeleteLocalAccountFile();
}
private static Account s_testAccount1,
s_testAccount2,
s_testAccount3;
[Test]
[Fact]
public void GetAllAccounts()
{
var accs = _accountManager.GetAccounts().ToList();
Assert.That(accs, Has.Count.GreaterThanOrEqualTo(3)); // Tests are adding three accounts, you might have extra accounts on your machine when testing :D
accs.Count.Should().BeGreaterThanOrEqualTo(3); // Tests are adding three accounts, there might be extra accounts locally
}
[Test]
[Fact]
public void GetAccount_ById()
{
var result = _accountManager.GetAccount(s_testAccount1.id);
Assert.That(result, Is.EqualTo(s_testAccount1));
result.Should().Be(s_testAccount1); // Uses `Shouldly` for a clean assertion
}
[Test]
public void GetAccount_ById_ThrowsWhenNotFound()
{
Assert.Throws<SpeckleAccountManagerException>(() => _accountManager.GetAccount("Non_existent_id"));
}
[Fact]
public void GetAccount_ById_ThrowsWhenNotFound() =>
FluentActions
.Invoking(() => _accountManager.GetAccount("Non_existent_id"))
.Should()
.Throw<SpeckleAccountManagerException>();
public static IEnumerable<Account> TestCases()
{
SetUp();
return new[] { s_testAccount1, s_testAccount2, s_testAccount3 };
}
public static TheoryData<Account> TestCases() => new() { s_testAccount1, s_testAccount2, s_testAccount3 };
[Test]
[TestCaseSource(nameof(TestCases))]
[Theory]
[MemberData(nameof(TestCases))]
public void GetAccountsForServer(Account target)
{
var accs = _accountManager.GetAccounts(target.serverInfo.url).ToList();
Assert.That(accs, Has.Count.EqualTo(1));
accs.Count.Should().Be(1);
var acc = accs[0];
Assert.That(acc, Is.Not.SameAs(target), "We expect new objects (no reference equality)");
Assert.That(acc.serverInfo.company, Is.EqualTo(target.serverInfo.company));
Assert.That(acc.serverInfo.url, Is.EqualTo(target.serverInfo.url));
Assert.That(acc.refreshToken, Is.EqualTo(target.refreshToken));
Assert.That(acc.token, Is.EqualTo(target.token));
acc.Should().NotBeSameAs(target); // We expect new objects (no reference equality)
acc.serverInfo.company.Should().Be(target.serverInfo.company);
acc.serverInfo.url.Should().Be(target.serverInfo.url);
acc.refreshToken.Should().Be(target.refreshToken);
acc.token.Should().Be(target.token);
}
[Test]
[Fact]
public void EnsureLocalIdentifiers_AreUniqueAcrossServers()
{
// Accounts with the same user ID in different servers should always result in different local identifiers.
@@ -128,6 +121,6 @@ public class CredentialInfrastructure
userInfo = new UserInfo { id = id },
}.GetLocalIdentifier();
Assert.That(acc1, Is.Not.EqualTo(acc2));
acc1.Should().NotBe(acc2);
}
}
+2 -8
View File
@@ -28,13 +28,7 @@ public abstract class Fixtures
File.WriteAllText(s_accountPath, json);
}
public static void DeleteLocalAccount(string id)
{
s_accountStorage.DeleteObject(id);
}
public static void DeleteLocalAccount(string id) => s_accountStorage.DeleteObject(id);
public static void DeleteLocalAccountFile()
{
File.Delete(s_accountPath);
}
public static void DeleteLocalAccountFile() => File.Delete(s_accountPath);
}
+6 -7
View File
@@ -1,14 +1,13 @@
using System.Runtime.InteropServices;
using NUnit.Framework;
using FluentAssertions;
using Speckle.Sdk.Logging;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Helpers;
[TestFixture]
[TestOf(nameof(SpecklePathProvider))]
public class SpecklePathTests
{
[Test]
[Fact]
public void TestUserApplicationDataPath()
{
var userPath = SpecklePathProvider.UserApplicationDataPath();
@@ -39,10 +38,10 @@ public class SpecklePathTests
throw new NotImplementedException("Your OS platform is not supported");
}
Assert.That(userPath, Does.Match(pattern));
userPath.Should().MatchRegex(pattern);
}
[Test]
[Fact]
public void TestInstallApplicationDataPath()
{
var installPath = SpecklePathProvider.InstallApplicationDataPath;
@@ -78,6 +77,6 @@ public class SpecklePathTests
throw new NotImplementedException("Your OS platform is not supported");
}
Assert.That(installPath, Does.Match(pattern));
installPath.Should().MatchRegex(pattern);
}
}
@@ -1,19 +1,22 @@
using NUnit.Framework;
using Shouldly;
using FluentAssertions;
using Speckle.Sdk.Host;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Host;
public class HostApplicationTests
{
private static List<HostAppVersion> s_hostAppVersion = Enum.GetValues<HostAppVersion>().ToList();
public static TheoryData<HostAppVersion> HostAppVersionData => new(Enum.GetValues<HostAppVersion>().ToList());
[Test]
[TestCaseSource(nameof(s_hostAppVersion))]
[Theory]
[MemberData(nameof(HostAppVersionData))]
public void HostAppVersionParsingTests(HostAppVersion appVersion)
{
appVersion.ToString().StartsWith('v').ShouldBeTrue();
// Assert that the string representation starts with 'v'
appVersion.ToString().StartsWith('v').Should().BeTrue();
// Assert that the parsed version is a positive integer
var version = HostApplications.GetVersion(appVersion);
int.Parse(version).ShouldBePositive();
int.Parse(version).Should().BePositive();
}
}
@@ -1,50 +1,38 @@
using System.Collections.Concurrent;
using System.Text;
using NUnit.Framework;
using Shouldly;
using Speckle.Newtonsoft.Json.Linq;
using FluentAssertions;
using Speckle.Sdk.Common;
using Speckle.Sdk.Dependencies.Serialization;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Serialisation;
using Speckle.Sdk.Serialisation.V2;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.Transports;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Models;
[TestFixture]
[TestOf(typeof(Base))]
[TestOf(typeof(DynamicBase))]
public class BaseTests
{
[SetUp]
public void Setup()
public BaseTests()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(BaseTests).Assembly);
}
[Test]
[Fact]
public void CanGetSetDynamicItemProp()
{
var @base = new Base();
@base["Item"] = "Item";
Assert.That(@base["Item"], Is.EqualTo("Item"));
@base["Item"].Should().Be("Item");
}
[Test]
[Fact]
public void CanGetSetTypedItemProp()
{
var @base = new ObjectWithItemProp { Item = "baz" };
Assert.That(@base["Item"], Is.EqualTo("baz"));
Assert.That(@base.Item, Is.EqualTo("baz"));
@base["Item"].Should().Be("baz");
@base.Item.Should().Be("baz");
}
[Test(Description = "Checks if validation is performed in property names")]
[Fact(DisplayName = "Checks if validation is performed in property names")]
public void CanValidatePropNames()
{
dynamic @base = new Base();
@@ -54,27 +42,35 @@ public class BaseTests
// Only single leading @ allowed
@base["@something"] = "A";
Assert.Throws<InvalidPropNameException>(() =>
{
@base["@@@something"] = "Testing";
});
FluentActions
.Invoking(() =>
{
@base["@@@something"] = "Testing";
})
.Should()
.Throw<InvalidPropNameException>();
// Invalid chars: ./
Assert.Throws<InvalidPropNameException>(() =>
{
@base["some.thing"] = "Testing";
});
Assert.Throws<InvalidPropNameException>(() =>
{
@base["some/thing"] = "Testing";
});
FluentActions
.Invoking(() =>
{
@base["some.thing"] = "Testing";
})
.Should()
.Throw<InvalidPropNameException>();
FluentActions
.Invoking(() =>
{
@base["some/thing"] = "Testing";
})
.Should()
.Throw<InvalidPropNameException>();
// Trying to change a class member value will throw exceptions.
//Assert.Throws<Exception>(() => { @base["speckle_type"] = "Testing"; });
//Assert.Throws<Exception>(() => { @base["id"] = "Testing"; });
}
[Test]
[Fact]
public void CountDynamicChunkables()
{
const int MAX_NUM = 3000;
@@ -92,10 +88,10 @@ public class BaseTests
@base["@(1000)cc2"] = customChunkArr;
var num = @base.GetTotalChildrenCount();
Assert.That(num, Is.EqualTo(MAX_NUM / 1000 * 2 + 1));
num.Should().Be(MAX_NUM / 1000 * 2 + 1);
}
[Test]
[Fact]
public void CountTypedChunkables()
{
const int MAX_NUM = 3000;
@@ -114,33 +110,33 @@ public class BaseTests
var num = @base.GetTotalChildrenCount();
var actualNum = 1 + MAX_NUM / 300 + MAX_NUM / 1000;
Assert.That(num, Is.EqualTo(actualNum));
num.Should().Be(actualNum);
}
[Test(Description = "Checks that no ignored or obsolete properties are returned")]
[Fact(DisplayName = "Checks that no ignored or obsolete properties are returned")]
public void CanGetMemberNames()
{
var @base = new SampleObject();
var dynamicProp = "dynamicProp";
@base[dynamicProp] = 123;
var names = @base.GetMembers().Keys;
Assert.That(names, Has.No.Member(nameof(@base.IgnoredSchemaProp)));
Assert.That(names, Has.No.Member(nameof(@base.ObsoleteSchemaProp)));
Assert.That(names, Has.Member(dynamicProp));
Assert.That(names, Has.Member(nameof(@base.attachedProp)));
names.Should().NotContain(nameof(@base.IgnoredSchemaProp));
names.Should().NotContain(nameof(@base.ObsoleteSchemaProp));
names.Should().Contain(dynamicProp);
names.Should().Contain(nameof(@base.attachedProp));
}
[Test(Description = "Checks that only instance properties are returned, excluding obsolete and ignored.")]
[Fact(DisplayName = "Checks that only instance properties are returned, excluding obsolete and ignored.")]
public void CanGetMembers_OnlyInstance()
{
var @base = new SampleObject();
@base["dynamicProp"] = 123;
var names = @base.GetMembers(DynamicBaseMemberType.Instance).Keys;
Assert.That(names, Has.Member(nameof(@base.attachedProp)));
names.Should().Contain(nameof(@base.attachedProp));
}
[Test(Description = "Checks that only dynamic properties are returned")]
[Fact(DisplayName = "Checks that only dynamic properties are returned")]
public void CanGetMembers_OnlyDynamic()
{
var @base = new SampleObject();
@@ -148,33 +144,33 @@ public class BaseTests
@base[dynamicProp] = 123;
var names = @base.GetMembers(DynamicBaseMemberType.Dynamic).Keys;
Assert.That(names, Has.Member(dynamicProp));
Assert.That(names, Has.Count.EqualTo(1));
names.Should().Contain(dynamicProp);
names.Count.Should().Be(1);
}
[Test(Description = "Checks that all typed properties (including ignored ones) are returned")]
[Fact(DisplayName = "Checks that all typed properties (including ignored ones) are returned")]
public void CanGetMembers_OnlyInstance_IncludeIgnored()
{
var @base = new SampleObject();
@base["dynamicProp"] = 123;
var names = @base.GetMembers(DynamicBaseMemberType.Instance | DynamicBaseMemberType.SchemaIgnored).Keys;
Assert.That(names, Has.Member(nameof(@base.IgnoredSchemaProp)));
Assert.That(names, Has.Member(nameof(@base.attachedProp)));
names.Should().Contain(nameof(@base.IgnoredSchemaProp));
names.Should().Contain(nameof(@base.attachedProp));
}
[Test(Description = "Checks that all typed properties (including obsolete ones) are returned")]
[Fact(DisplayName = "Checks that all typed properties (including obsolete ones) are returned")]
public void CanGetMembers_OnlyInstance_IncludeObsolete()
{
var @base = new SampleObject();
@base["dynamicProp"] = 123;
var names = @base.GetMembers(DynamicBaseMemberType.Instance | DynamicBaseMemberType.Obsolete).Keys;
Assert.That(names, Has.Member(nameof(@base.ObsoleteSchemaProp)));
Assert.That(names, Has.Member(nameof(@base.attachedProp)));
names.Should().Contain(nameof(@base.ObsoleteSchemaProp));
names.Should().Contain(nameof(@base.attachedProp));
}
[Test]
[Fact]
public void CanGetDynamicMembers()
{
var @base = new SampleObject();
@@ -182,11 +178,11 @@ public class BaseTests
@base[dynamicProp] = null;
var names = @base.GetDynamicMemberNames();
Assert.That(names, Has.Member(dynamicProp));
Assert.That(@base[dynamicProp], Is.Null);
names.Should().Contain(dynamicProp);
@base[dynamicProp].Should().BeNull();
}
[Test]
[Fact]
public void CanSetDynamicMembers()
{
var @base = new SampleObject();
@@ -194,19 +190,19 @@ public class BaseTests
var value = "something";
// Can create a new dynamic member
@base[key] = value;
Assert.That(value, Is.EqualTo((string)@base[key].NotNull()));
value.Should().Be((string)@base[key].NotNull());
// Can overwrite existing
value = "some other value";
@base[key] = value;
Assert.That(value, Is.EqualTo((string)@base[key].NotNull()));
value.Should().Be((string)@base[key].NotNull());
// Accepts null values
@base[key] = null;
Assert.That(@base[key], Is.Null);
@base[key].Should().BeNull();
}
[Test]
[Fact]
public void CanShallowCopy()
{
var sample = new SampleObject();
@@ -217,8 +213,8 @@ public class BaseTests
var sampleMembers = sample.GetMembers(selectedMembers);
var copyMembers = copy.GetMembers(selectedMembers);
Assert.That(copyMembers.Keys, Is.EquivalentTo(sampleMembers.Keys));
Assert.That(copyMembers.Values, Is.EquivalentTo(sampleMembers.Values));
copyMembers.Keys.Should().BeEquivalentTo(sampleMembers.Keys);
copyMembers.Values.Should().BeEquivalentTo(sampleMembers.Values);
}
[SpeckleType("Speckle.Core.Tests.Unit.Models.BaseTests+SampleObject")]
@@ -1,46 +1,44 @@
using NUnit.Framework;
using FluentAssertions;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Extensions;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Models.Extensions;
[TestFixture]
[TestOf(nameof(BaseExtensions))]
public class BaseExtensionsTests
{
[SetUp]
public void Setup()
public BaseExtensionsTests()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly);
}
[Test]
[TestCase("myDynamicProp")]
[TestCase("elements")]
[Theory]
[InlineData("myDynamicProp")]
[InlineData("elements")]
public void GetDetachedPropName_Dynamic(string propertyName)
{
var data = new TestBase();
var result = data.GetDetachedPropName(propertyName);
var expected = $"@{propertyName}";
Assert.That(result, Is.EqualTo(expected));
result.Should().Be(expected);
}
[Test]
[TestCase(nameof(TestBase.myProperty))]
[TestCase(nameof(TestBase.myOtherProperty))]
[Theory]
[InlineData(nameof(TestBase.myProperty))]
[InlineData(nameof(TestBase.myOtherProperty))]
public void GetDetachedPropName_Instance(string propertyName)
{
var data = new TestBase();
var result = data.GetDetachedPropName(propertyName);
Assert.That(result, Is.EqualTo(propertyName));
result.Should().Be(propertyName);
}
[Test]
[Fact]
public void TraverseWithPath()
{
var collection = new Collection() { name = "collection" };
@@ -51,17 +49,18 @@ public class BaseExtensionsTests
var basePaths = collection.TraverseWithPath((obj => obj is not Collection)).ToList();
Assert.That(basePaths.Count, Is.EqualTo(3));
Assert.That(basePaths[0].Item2.speckle_type, Is.EqualTo("Speckle.Core.Models.Collections.Collection"));
Assert.That(basePaths[0].Item2["name"], Is.EqualTo("collection"));
Assert.That(basePaths[0].Item1, Is.EqualTo(new List<string>()));
basePaths.Count.Should().Be(3);
Assert.That(basePaths[1].Item2.speckle_type, Is.EqualTo("Speckle.Core.Models.Collections.Collection"));
Assert.That(basePaths[1].Item2["name"], Is.EqualTo("subCollection"));
Assert.That(basePaths[1].Item1, Is.EqualTo(new List<string>() { "collection" }));
basePaths[0].Item2.speckle_type.Should().Be("Speckle.Core.Models.Collections.Collection");
basePaths[0].Item2["name"].Should().Be("collection");
basePaths[0].Item1.Should().BeEquivalentTo(new List<string>());
Assert.That(basePaths[2].Item2.speckle_type, Is.EqualTo("Base"));
Assert.That(basePaths[2].Item1, Is.EqualTo(new List<string>() { "collection", "subCollection" }));
basePaths[1].Item2.speckle_type.Should().Be("Speckle.Core.Models.Collections.Collection");
basePaths[1].Item2["name"].Should().Be("subCollection");
basePaths[1].Item1.Should().BeEquivalentTo(new List<string>() { "collection" });
basePaths[2].Item2.speckle_type.Should().Be("Base");
basePaths[2].Item1.Should().BeEquivalentTo(new List<string>() { "collection", "subCollection" });
}
[SpeckleType("Speckle.Core.Tests.Unit.Models.Extensions.BaseExtensionsTests+TestBase")]
@@ -1,11 +1,11 @@
using NUnit.Framework;
using FluentAssertions;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Extensions;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Models.Extensions;
[TestOf(typeof(BaseExtensions))]
public class DisplayValueTests
{
private const string PAYLOAD = "This is my payload";
@@ -22,28 +22,35 @@ public class DisplayValueTests
TypeLoader.Initialize(typeof(Base).Assembly);
}
[SetUp]
[Fact]
public void Setup() => Reset();
[TestCaseSource(nameof(TestCases))]
[Theory]
[MemberData(nameof(TestCases))]
public void TestTryGetDisplayValue_WithValue(Base testCase)
{
var res = testCase.TryGetDisplayValue();
Assert.That(res, Has.Count.EqualTo(1));
Assert.That(res, Has.One.Items.TypeOf<Base>().With.Property(nameof(Base.applicationId)).EqualTo(PAYLOAD));
// Assert collection count
res?.Count.Should().Be(1);
// Assert the single item matches the expected type and property
var displayValue = res?[0];
displayValue.Should().NotBeNull();
displayValue!.applicationId.Should().Be(PAYLOAD);
}
public static IEnumerable<Base> TestCases()
public static IEnumerable<object[]> TestCases()
{
var listOfBase = new List<object> { s_displayValue }; //This is what our deserializer will output
var listOfBase = new List<object> { s_displayValue }; // This is what our deserializer will output
var listOfMesh = new List<Base> { s_displayValue };
yield return new Base { ["@displayValue"] = s_displayValue };
yield return new Base { ["displayValue"] = s_displayValue };
yield return new Base { ["@displayValue"] = listOfBase };
yield return new Base { ["displayValue"] = listOfBase };
yield return new TypedDisplayValue { displayValue = s_displayValue };
yield return new TypedDisplayValueList { displayValue = listOfMesh };
yield return [new Base { ["@displayValue"] = s_displayValue }];
yield return [new Base { ["displayValue"] = s_displayValue }];
yield return [new Base { ["@displayValue"] = listOfBase }];
yield return [new Base { ["displayValue"] = listOfBase }];
yield return [new TypedDisplayValue { displayValue = s_displayValue }];
yield return [new TypedDisplayValueList { displayValue = listOfMesh }];
}
[SpeckleType("Speckle.Core.Tests.Unit.Models.Extensions.DisplayValueTests+TypedDisplayValue")]
@@ -1,26 +1,27 @@
using NUnit.Framework;
using FluentAssertions;
using Speckle.Sdk.Models.Extensions;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Models.Extensions;
[TestFixture]
[TestOf(typeof(BaseExtensions))]
public class ExceptionTests
{
[Test]
[Fact]
public void CanPrintAllInnerExceptions()
{
// Test with a single exception
var ex = new Exception("Some error");
var exMsg = ex.ToFormattedString();
Assert.That(exMsg, Is.Not.Null);
exMsg.Should().NotBeNull();
// Test with an inner exception
var ex2 = new Exception("One or more errors occurred", ex);
var ex2Msg = ex2.ToFormattedString();
Assert.That(ex2Msg, Is.Not.Null);
ex2Msg.Should().NotBeNull();
// Test with an aggregate exception
var ex3 = new AggregateException("One or more errors occurred", ex2);
var ex3Msg = ex3.ToFormattedString();
Assert.That(ex3Msg, Is.Not.Null);
ex3Msg.Should().NotBeNull();
}
}
@@ -1,17 +1,15 @@
using System.Collections;
using NUnit.Framework;
using FluentAssertions;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.GraphTraversal;
using Speckle.Sdk.Tests.Unit.Host;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Models.GraphTraversal;
[TestFixture, TestOf(typeof(Sdk.Models.GraphTraversal.GraphTraversal))]
public class GraphTraversalTests
{
[SetUp]
public void Setup()
public GraphTraversalTests()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(TraversalMock).Assembly);
@@ -23,7 +21,7 @@ public class GraphTraversalTests
return sut.Traverse(testCase);
}
[Test]
[Fact]
public void Traverse_TraversesListMembers()
{
var traverseListsRule = TraversalRule
@@ -38,23 +36,23 @@ public class GraphTraversalTests
TraversalMock testCase = new()
{
ListChildren = new List<Base> { expectTraverse },
ListChildren = [expectTraverse],
DictChildren = new Dictionary<string, Base> { ["myprop"] = expectIgnored },
Child = expectIgnored,
};
var ret = Traverse(testCase, traverseListsRule).Select(b => b.Current).ToList();
//Assert expected members present
Assert.That(ret, Has.Exactly(1).Items.EqualTo(testCase));
Assert.That(ret, Has.Exactly(1).Items.EqualTo(expectTraverse));
// Assert expected members present
ret.Should().Contain(testCase);
ret.Should().Contain(expectTraverse);
//Assert unexpected members not present
Assert.That(ret, Has.No.Member(expectIgnored));
Assert.That(ret, Has.Count.EqualTo(2));
// Assert unexpected members not present
ret.Should().NotContain(expectIgnored);
ret.Count.Should().Be(2);
}
[Test]
[Fact]
public void Traverse_TraversesDictMembers()
{
var traverseListsRule = TraversalRule
@@ -69,23 +67,23 @@ public class GraphTraversalTests
TraversalMock testCase = new()
{
ListChildren = new List<Base> { expectIgnored },
ListChildren = [expectIgnored],
DictChildren = new Dictionary<string, Base> { ["myprop"] = expectTraverse },
Child = expectIgnored,
};
var ret = Traverse(testCase, traverseListsRule).Select(b => b.Current).ToList();
//Assert expected members present
Assert.That(ret, Has.Exactly(1).Items.EqualTo(testCase));
Assert.That(ret, Has.Exactly(1).Items.EqualTo(expectTraverse));
// Assert expected members present
ret.Should().Contain(testCase);
ret.Should().Contain(expectTraverse);
//Assert unexpected members not present
Assert.That(ret, Has.No.Member(expectIgnored));
Assert.That(ret, Has.Count.EqualTo(2));
// Assert unexpected members not present
ret.Should().NotContain(expectIgnored);
ret.Count.Should().Be(2);
}
[Test]
[Fact]
public void Traverse_TraversesDynamic()
{
var traverseListsRule = TraversalRule
@@ -105,16 +103,16 @@ public class GraphTraversalTests
var ret = Traverse(testCase, traverseListsRule).Select(b => b.Current).ToList();
//Assert expected members present
Assert.That(ret, Has.Exactly(1).Items.EqualTo(testCase));
Assert.That(ret, Has.Exactly(2).Items.EqualTo(expectTraverse));
// Assert expected members present
ret.Should().Contain(testCase);
ret.Count(x => x == expectTraverse).Should().Be(2);
//Assert unexpected members not present
Assert.That(ret, Has.No.Member(expectIgnored));
Assert.That(ret, Has.Count.EqualTo(3));
// Assert unexpected members not present
ret.Should().NotContain(expectIgnored);
ret.Count.Should().Be(3);
}
[Test]
[Fact]
public void Traverse_ExclusiveRule()
{
var expectTraverse = new Base { id = "List Member" };
@@ -134,12 +132,12 @@ public class GraphTraversalTests
var ret = Traverse(testCase, traverseListsRule).Select(b => b.Current).ToList();
//Assert expected members present
Assert.That(ret, Has.Exactly(1).Items.EqualTo(testCase));
Assert.That(ret, Has.Exactly(2).Items.EqualTo(expectTraverse));
// Assert expected members present
ret.Should().Contain(testCase);
ret.Count(x => x == expectTraverse).Should().Be(2);
//Assert unexpected members not present
Assert.That(ret, Has.No.Member(expectIgnored));
Assert.That(ret, Has.Count.EqualTo(3));
// Assert unexpected members not present
ret.Should().NotContain(expectIgnored);
ret.Count.Should().Be(3);
}
}
@@ -1,62 +1,71 @@
using NUnit.Framework;
using FluentAssertions;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.GraphTraversal;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Models.GraphTraversal;
[TestOf(typeof(TraversalContextExtensions))]
// Mark test class for xUnit
public class TraversalContextExtensionsTests
{
public static int[] TestDepths => new[] { 1, 2, 10 };
private TraversalContext? CreateLinkedList(int depth, Func<int, Base> createBaseFunc)
{
if (depth <= 0)
{
return null;
}
return new TraversalContext(createBaseFunc(depth), $"{depth}", CreateLinkedList(depth - 1, createBaseFunc));
}
[TestCaseSource(nameof(TestDepths))]
[Theory] // replaces [TestCaseSource]
[MemberData(nameof(GetTestDepths))]
public void GetPropertyPath_ReturnsSequentialPath(int depth)
{
var testData = CreateLinkedList(depth, i => new()).NotNull();
var testData = CreateLinkedList(depth, i => new Base()).NotNull();
var path = TraversalContextExtensions.GetPropertyPath(testData);
var path = testData.GetPropertyPath();
var expected = Enumerable.Range(1, depth).Select(i => i.ToString());
Assert.That(path, Is.EquivalentTo(expected));
path.Should().BeEquivalentTo(expected);
}
[TestCaseSource(nameof(TestDepths))]
[Theory]
[MemberData(nameof(GetTestDepths))]
public void GetAscendant(int depth)
{
var testData = CreateLinkedList(depth, i => new()).NotNull();
var testData = CreateLinkedList(depth, i => new Base()).NotNull();
var all = TraversalContextExtensions.GetAscendants(testData).ToArray();
var all = testData.GetAscendants().ToArray();
Assert.That(all, Has.Length.EqualTo(depth));
all.Length.Should().Be(depth);
}
[TestCaseSource(nameof(TestDepths))]
[Theory]
[MemberData(nameof(GetTestDepths))]
public void GetAscendantOfType_AllBase(int depth)
{
var testData = CreateLinkedList(depth, i => new()).NotNull();
var testData = CreateLinkedList(depth, i => new Base()).NotNull();
var all = TraversalContextExtensions.GetAscendantOfType<Base>(testData).ToArray();
var all = testData.GetAscendantOfType<Base>().ToArray();
Assert.That(all, Has.Length.EqualTo(depth));
all.Length.Should().Be(depth);
}
[TestCaseSource(nameof(TestDepths))]
[Theory]
[MemberData(nameof(GetTestDepths))]
public void GetAscendantOfType_EveryOtherIsCollection(int depth)
{
var testData = CreateLinkedList(depth, i => i % 2 == 0 ? new Base() : new Collection()).NotNull();
var all = TraversalContextExtensions.GetAscendantOfType<Collection>(testData).ToArray();
var all = testData.GetAscendantOfType<Collection>().ToArray();
Assert.That(all, Has.Length.EqualTo(Math.Ceiling(depth / 2.0)));
all.Length.Should().Be((int)Math.Ceiling(depth / 2.0));
}
// Providing the test depths to [MemberData] for xUnit
public static IEnumerable<object[]> GetTestDepths() => new[] { 1, 2, 10 }.Select(depth => new object[] { depth });
}
+21 -21
View File
@@ -1,50 +1,50 @@
using System.Diagnostics;
using NUnit.Framework;
using FluentAssertions;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Tests.Unit.Host;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Models;
[TestFixture]
[TestOf(typeof(Base))]
// Removed [TestFixture] and [TestOf] annotations as they are NUnit specific
public class Hashing
{
[SetUp]
public void Setup()
public Hashing()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(DiningTable).Assembly);
}
[Test(Description = "Checks that hashing (as represented by object ids) actually works.")]
public void HashChangeCheck()
[Fact(DisplayName = "Checks that hashing (as represented by object IDs) actually works.")]
public void HashChangeCheck_Test()
{
var table = new DiningTable();
var secondTable = new DiningTable();
Assert.That(secondTable.GetId(), Is.EqualTo(table.GetId()));
secondTable.GetId().Should().Be(table.GetId(), "Object IDs of identical objects should match.");
((dynamic)secondTable).testProp = "wonderful";
Assert.That(secondTable.GetId(), Is.Not.EqualTo(table.GetId()));
secondTable.GetId().Should().NotBe(table.GetId(), "Changing a property should alter the object ID.");
}
[Test(
Description = "Tests the convention that dynamic properties that have key names prepended with '__' are ignored."
)]
public void IgnoredDynamicPropertiesCheck()
[Fact(DisplayName = "Verifies that dynamic properties with '__' prefix are ignored during hashing.")]
public void IgnoredDynamicPropertiesCheck_Test()
{
var table = new DiningTable();
var originalHash = table.GetId();
((dynamic)table).__testProp = "wonderful";
Assert.That(table.GetId(), Is.EqualTo(originalHash));
table
.GetId()
.Should()
.Be(originalHash, "Hashing of table should not change when '__' prefixed properties are added.");
}
[Test(Description = "Rather stupid test as results vary wildly even on one machine.")]
public void HashingPerformance()
[Fact(DisplayName = "Performance test: Hash computation time for large and small objects.")]
public void HashingPerformance_Test()
{
var polyline = new Polyline();
@@ -62,7 +62,7 @@ public class Hashing
_ = polyline.GetId();
var diff1 = stopWatch.ElapsedMilliseconds - stopWatchStep;
Assert.That(diff1, Is.LessThan(300), $"Hashing shouldn't take that long ({diff1} ms) for the test object used.");
diff1.Should().BeLessThan(300, $"Hashing shouldn't take that long ({diff1} ms) for the test object used.");
Console.WriteLine($"Big obj hash duration: {diff1} ms");
var pt = new Point
@@ -75,12 +75,12 @@ public class Hashing
_ = pt.GetId();
var diff2 = stopWatch.ElapsedMilliseconds - stopWatchStep;
Assert.That(diff2, Is.LessThan(10), $"Hashing shouldn't take that long ({diff2} ms)for the point object used.");
diff2.Should().BeLessThan(10, $"Hashing shouldn't take that long ({diff2} ms) for the point object used.");
Console.WriteLine($"Small obj hash duration: {diff2} ms");
}
[Test(Description = "The hash of a decomposed object is different that that of a non-decomposed object.")]
public void DecompositionHashes()
[Fact(DisplayName = "Verifies that decomposed and non-decomposed objects have different hashes.")]
public void DecompositionHashes_Test()
{
var table = new DiningTable();
((dynamic)table)["@decomposeMePlease"] = new Point();
@@ -88,6 +88,6 @@ public class Hashing
var hash1 = table.GetId();
var hash2 = table.GetId(true);
Assert.That(hash2, Is.Not.EqualTo(hash1));
hash2.Should().NotBe(hash1, "Hash values should differ for decomposed and non-decomposed objects.");
}
}
@@ -1,45 +1,43 @@
using NUnit.Framework;
using FluentAssertions;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using TestModels;
using Speckle.Sdk.Tests.Unit.Models.TestModels;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Models
{
[TestFixture]
[TestOf(typeof(Base))]
public class SpeckleTypeTests
{
[SetUp]
public void Setup()
public SpeckleTypeTests()
{
// Setup logic during test class initialization
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(Foo).Assembly);
}
[Test, TestCaseSource(nameof(s_cases))]
public void SpeckleTypeIsProperlyBuilt(Base foo, string expectedType)
{
Assert.That(foo.speckle_type, Is.EqualTo(expectedType));
}
[Theory]
[MemberData(nameof(Cases))]
public void SpeckleTypeIsProperlyBuilt(Base foo, string expectedType) => foo.speckle_type.Should().Be(expectedType);
private static readonly object[] s_cases =
{
new object[] { new Base(), "Base" },
new object[] { new Foo(), "TestModels.Foo" },
new object[] { new Bar(), "TestModels.Foo:TestModels.Bar" },
new object[] { new Baz(), "TestModels.Foo:TestModels.Bar:TestModels.Baz" },
};
public static IEnumerable<object[]> Cases =>
new List<object[]>
{
new object[] { new Base(), "Base" },
new object[] { new Foo(), "TestModels.Foo" },
new object[] { new Bar(), "TestModels.Foo:TestModels.Bar" },
new object[] { new Baz(), "TestModels.Foo:TestModels.Bar:TestModels.Baz" },
};
}
namespace TestModels
{
[SpeckleType("TestModels.Foo")]
public class Foo : Base { }
[SpeckleType("TestModels.Bar")]
public class Bar : Foo { }
[SpeckleType("TestModels.Baz")]
public class Baz : Bar { }
}
}
namespace TestModels
{
[SpeckleType("TestModels.Foo")]
public class Foo : Base { }
[SpeckleType("TestModels.Bar")]
public class Bar : Foo { }
[SpeckleType("TestModels.Baz")]
public class Baz : Bar { }
}
@@ -1,14 +1,14 @@
using NUnit.Framework;
using FluentAssertions;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Extensions;
using Xunit;
namespace Speckle.Sdk.Tests.Unit.Models;
[TestFixture, TestOf(typeof(BaseExtensions))]
public class TraversalTests
{
[Test, Description("Tests that provided breaker rules are respected")]
[Fact(DisplayName = "Tests that provided breaker rules are respected")]
public void TestFlattenWithBreaker()
{
//Setup
@@ -32,13 +32,19 @@ public class TraversalTests
var ret = root.Flatten(BreakRule).ToList();
//Test
Assert.That(ret, Has.Count.EqualTo(3));
Assert.That(ret, Is.Unique);
Assert.That(ret.Where(BreakRule), Is.Not.Empty);
Assert.That(ret, Has.No.Member(Contains.Substring("should have ignored me")));
ret.Count.Should().Be(3);
ret.Should().OnlyHaveUniqueItems();
ret.Where(BreakRule).Should().NotBeEmpty();
ret.Should().NotContain(x => x.id == "should have ignored me");
}
[Test, TestCase(5, 5), TestCase(5, 10), TestCase(10, 5), Description("Tests breaking after a fixed number of items")]
[Theory(DisplayName = "Tests breaking after a fixed number of items")]
[InlineData(5, 5)]
[InlineData(5, 10)]
[InlineData(10, 5)]
public void TestBreakerFixed(int nestDepth, int flattenDepth)
{
//Setup
@@ -56,11 +62,12 @@ public class TraversalTests
var ret = rootObject.Flatten(_ => ++counter >= flattenDepth).ToList();
//Test
Assert.That(ret, Has.Count.EqualTo(Math.Min(flattenDepth, nestDepth)));
Assert.That(ret, Is.Unique);
ret.Count.Should().Be(Math.Min(flattenDepth, nestDepth));
ret.Should().OnlyHaveUniqueItems();
}
[Test, Timeout(2000), Description("Tests that the flatten function does not get stuck on circular references")]
[Fact(DisplayName = "Tests that the flatten function does not get stuck on circular references")]
public void TestCircularReference()
{
//Setup
@@ -76,12 +83,14 @@ public class TraversalTests
var ret = objectA.Flatten().ToList();
//Test
Assert.That(ret, Is.Unique);
Assert.That(ret, Is.EquivalentTo(new[] { objectA, objectB, objectC }));
Assert.That(ret, Has.Count.EqualTo(3));
ret.Should().OnlyHaveUniqueItems();
ret.Should().BeEquivalentTo([objectA, objectB, objectC]);
ret.Count.Should().Be(3);
}
[Test, Description("Tests that the flatten function correctly handles (non circular) duplicates")]
[Fact(DisplayName = "Tests that the flatten function correctly handles (non circular) duplicates")]
public void TestDuplicates()
{
//Setup
@@ -95,8 +104,10 @@ public class TraversalTests
var ret = objectA.Flatten().ToList();
//Test
Assert.That(ret, Is.Unique);
Assert.That(ret, Is.EquivalentTo(new[] { objectA, objectB }));
Assert.That(ret, Has.Count.EqualTo(2));
ret.Should().OnlyHaveUniqueItems();
ret.Should().BeEquivalentTo([objectA, objectB]);
ret.Count.Should().Be(2);
}
}

Some files were not shown because too many files have changed in this diff Show More