about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2024-09-17 13:54:51 +0200
committerRory& <root@rory.gay>2024-09-17 13:54:51 +0200
commit1713936ce7a0811de8b2c3022cf08a63fc62b966 (patch)
tree49d9cfe2f0abadb7508b07cb2b2f7e3a7796f626
parentFix room joining, power levels (diff)
downloadLibMatrix-1713936ce7a0811de8b2c3022cf08a63fc62b966.tar.xz
Fix unit tests, add authenticated media github/dev/authenticated-media dev/authenticated-media
-rw-r--r--LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs97
-rw-r--r--LibMatrix/RoomTypes/GenericRoom.cs2
-rw-r--r--Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs16
-rw-r--r--Tests/LibMatrix.Tests/Tests/AuthMediaTests.cs56
-rw-r--r--Tests/LibMatrix.Tests/Tests/AuthTests.cs19
-rw-r--r--Tests/LibMatrix.Tests/Tests/TestCleanup.cs148
6 files changed, 240 insertions, 98 deletions
diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
index 40fdef3..6be49b9 100644
--- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
+++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
@@ -61,18 +61,15 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
         return rooms;
     }
 
-    [Obsolete("Use UploadMedia instead, as this method is deprecated.")]
     public virtual async Task<string> UploadFile(string fileName, IEnumerable<byte> data, string contentType = "application/octet-stream") {
         return await UploadFile(fileName, data.ToArray(), contentType);
     }
 
-    [Obsolete("Use UploadMedia instead, as this method is deprecated.")]
     public virtual async Task<string> UploadFile(string fileName, byte[] data, string contentType = "application/octet-stream") {
         await using var ms = new MemoryStream(data);
         return await UploadFile(fileName, ms, contentType);
     }
 
-    [Obsolete("Use UploadMedia instead, as this method is deprecated.")]
     public virtual async Task<string> UploadFile(string fileName, Stream fileStream, string contentType = "application/octet-stream") {
         var req = new HttpRequestMessage(HttpMethod.Post, $"/_matrix/media/v3/upload?filename={fileName}");
         req.Content = new StreamContent(fileStream);
@@ -420,22 +417,100 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
         if (parts.Length != 2) throw new ArgumentException($"Invalid Matrix Content URI '{mxcUri}' passed! Matrix Content URIs must exist of only 2 parts!", nameof(mxcUri));
         return (parts[0], parts[1]);
     }
-    
-    public async Task<Stream> GetMediaStreamAsync(string mxcUri, int timeout = 0) {
+
+    public async Task<Stream> GetMediaStreamAsync(string mxcUri, string? filename = null, int? timeout = null) {
         var (serverName, mediaId) = ParseMxcUri(mxcUri);
         try {
-            var res = await ClientHttpClient.GetAsync($"/_matrix/client/v1/media/download/{serverName}/{mediaId}");
+            var uri = $"/_matrix/client/v1/media/download/{serverName}/{mediaId}";
+            if (!string.IsNullOrWhiteSpace(filename)) uri += $"/{HttpUtility.UrlEncode(filename)}";
+            if (timeout is not null) uri += $"?timeout_ms={timeout}";
+            var res = await ClientHttpClient.GetAsync(uri);
             return await res.Content.ReadAsStreamAsync();
         }
-        catch (LibMatrixException e) {
-            Console.WriteLine($"Failed to get media stream: {e.Message}");
-            throw;
+        catch (MatrixException e) {
+            if (e is not { ErrorCode: "M_UNKNOWN" }) throw;
         }
 
+        //fallback to legacy media
+        try {
+            var uri = $"/_matrix/media/v1/download/{serverName}/{mediaId}";
+            if (!string.IsNullOrWhiteSpace(filename)) uri += $"/{HttpUtility.UrlEncode(filename)}";
+            if (timeout is not null) uri += $"?timeout_ms={timeout}";
+            var res = await ClientHttpClient.GetAsync(uri);
+            return await res.Content.ReadAsStreamAsync();
+        }
+        catch (MatrixException e) {
+            if (e is not { ErrorCode: "M_UNKNOWN" }) throw;
+        }
+
+        throw new LibMatrixException() {
+            ErrorCode = LibMatrixException.ErrorCodes.M_UNSUPPORTED,
+            Error = "Failed to download media"
+        };
         // return default;
     }
-    
-    
+
+    public async Task<Stream> GetThumbnailStreamAsync(string mxcUri, int width, int height, string? method = null, int? timeout = null) {
+        var (serverName, mediaId) = ParseMxcUri(mxcUri);
+        try {
+            var uri = new Uri($"/_matrix/client/v1/thumbnail/{serverName}/{mediaId}");
+            uri = uri.AddQuery("width", width.ToString());
+            uri = uri.AddQuery("height", height.ToString());
+            if (!string.IsNullOrWhiteSpace(method)) uri = uri.AddQuery("method", method);
+            if (timeout is not null) uri = uri.AddQuery("timeout_ms", timeout.ToString());
+
+            var res = await ClientHttpClient.GetAsync(uri.ToString());
+            return await res.Content.ReadAsStreamAsync();
+        }
+        catch (MatrixException e) {
+            if (e is not { ErrorCode: "M_UNKNOWN" }) throw;
+        }
+
+        //fallback to legacy media
+        try {
+            var uri = new Uri($"/_matrix/media/v1/thumbnail/{serverName}/{mediaId}");
+            uri = uri.AddQuery("width", width.ToString());
+            uri = uri.AddQuery("height", height.ToString());
+            if (!string.IsNullOrWhiteSpace(method)) uri = uri.AddQuery("method", method);
+            if (timeout is not null) uri = uri.AddQuery("timeout_ms", timeout.ToString());
+
+            var res = await ClientHttpClient.GetAsync(uri.ToString());
+            return await res.Content.ReadAsStreamAsync();
+        }
+        catch (MatrixException e) {
+            if (e is not { ErrorCode: "M_UNKNOWN" }) throw;
+        }
+
+        throw new LibMatrixException() {
+            ErrorCode = LibMatrixException.ErrorCodes.M_UNSUPPORTED,
+            Error = "Failed to download media"
+        };
+        // return default;
+    }
+
+    public async Task<Dictionary<string, JsonValue>?> GetUrlPreviewAsync(string url) {
+        try {
+            var res = await ClientHttpClient.GetAsync($"/_matrix/client/v1/media/preview_url?url={HttpUtility.UrlEncode(url)}");
+            return await res.Content.ReadFromJsonAsync<Dictionary<string, JsonValue>>();
+        }
+        catch (MatrixException e) {
+            if (e is not { ErrorCode: "M_UNRECOGNIZED" }) throw;
+        }
+        
+        //fallback to legacy media
+        try {
+            var res = await ClientHttpClient.GetAsync($"/_matrix/media/v1/preview_url?url={HttpUtility.UrlEncode(url)}");
+            return await res.Content.ReadFromJsonAsync<Dictionary<string, JsonValue>>();
+        }
+        catch (MatrixException e) {
+            if (e is not { ErrorCode: "M_UNRECOGNIZED" }) throw;
+        }
+        
+        throw new LibMatrixException() {
+            ErrorCode = LibMatrixException.ErrorCodes.M_UNSUPPORTED,
+            Error = "Failed to download URL preview"
+        };
+    }
 
 #endregion
 }
\ No newline at end of file
diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs
index 4641349..349ccb5 100644
--- a/LibMatrix/RoomTypes/GenericRoom.cs
+++ b/LibMatrix/RoomTypes/GenericRoom.cs
@@ -379,7 +379,7 @@ public class GenericRoom {
             new UserIdAndReason { UserId = userId });
 
     public async Task InviteUserAsync(string userId, string? reason = null, bool skipExisting = true) {
-        if (skipExisting && await GetStateAsync<RoomMemberEventContent>("m.room.member", userId) is not null)
+        if (skipExisting && await GetStateOrNullAsync<RoomMemberEventContent>("m.room.member", userId) is not null)
             return;
         await Homeserver.ClientHttpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/invite", new UserIdAndReason(userId, reason));
     }
diff --git a/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs b/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs
index c9727d6..13b5c1b 100644
--- a/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs
+++ b/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs
@@ -1,14 +1,17 @@
 using ArcaneLibs.Extensions;
 using LibMatrix.Homeservers;
 using LibMatrix.Responses;
+using LibMatrix.Services;
+using Microsoft.Extensions.Logging.Abstractions;
 
 namespace LibMatrix.Tests.Abstractions;
 
 public static class HomeserverAbstraction {
+    private static HomeserverResolverService _hsResolver = new HomeserverResolverService(NullLogger<HomeserverResolverService>.Instance);
+    private static HomeserverProviderService _hsProvider = new HomeserverProviderService(NullLogger<HomeserverProviderService>.Instance, _hsResolver);
+
     public static async Task<AuthenticatedHomeserverGeneric> GetHomeserver() {
-        var rhs = await RemoteHomeserver.Create("https://matrixunittests.rory.gay");
-        // string username = Guid.NewGuid().ToString();
-        // string password = Guid.NewGuid().ToString();
+        var rhs = await _hsProvider.GetRemoteHomeserver("matrixunittests.rory.gay");
         var username = "@f1a2d2d6-1924-421b-91d0-893b347b2a49:matrixunittests.rory.gay";
         var password = "d6d782d6-8bc9-4fac-9cd8-78e101b4298b";
         LoginResponse reg;
@@ -23,8 +26,7 @@ public static class HomeserverAbstraction {
             else throw new Exception("Failed to register", e);
         }
 
-        var hs = await reg.GetAuthenticatedHomeserver("https://matrixunittests.rory.gay");
-
+        var hs = await _hsProvider.GetAuthenticatedWithToken(reg.Homeserver, reg.AccessToken);
         //var rooms = await hs.GetJoinedRooms();
 
         // var disbandRoomTasks = rooms.Select(async room => {
@@ -45,9 +47,9 @@ public static class HomeserverAbstraction {
     }
 
     public static async Task<AuthenticatedHomeserverGeneric> GetRandomHomeserver() {
-        var rhs = await RemoteHomeserver.Create("https://matrixunittests.rory.gay");
+        var rhs = await _hsProvider.GetRemoteHomeserver("matrixunittests.rory.gay");
         var reg = await rhs.RegisterAsync(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "Unit tests!");
-        var hs = await reg.GetAuthenticatedHomeserver("https://matrixunittests.rory.gay");
+        var hs = await _hsProvider.GetAuthenticatedWithToken(reg.Homeserver, reg.AccessToken);
 
         // var rooms = await hs.GetJoinedRooms();
         //
diff --git a/Tests/LibMatrix.Tests/Tests/AuthMediaTests.cs b/Tests/LibMatrix.Tests/Tests/AuthMediaTests.cs
new file mode 100644
index 0000000..b2f5627
--- /dev/null
+++ b/Tests/LibMatrix.Tests/Tests/AuthMediaTests.cs
@@ -0,0 +1,56 @@
+using ArcaneLibs.Extensions;
+using ArcaneLibs.Extensions.Streams;
+using LibMatrix.Homeservers;
+using LibMatrix.Services;
+using LibMatrix.Tests.Abstractions;
+using LibMatrix.Tests.Fixtures;
+using Xunit.Abstractions;
+using Xunit.Microsoft.DependencyInjection.Abstracts;
+
+namespace LibMatrix.Tests.Tests;
+
+public class AuthMediaTests : TestBed<TestFixture> {
+    private readonly TestFixture _fixture;
+    private readonly HomeserverResolverService _resolver;
+    private readonly Config _config;
+    private readonly HomeserverProviderService _provider;
+
+    public AuthMediaTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
+        _fixture = fixture;
+        _resolver = _fixture.GetService<HomeserverResolverService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverResolverService)}");
+        _config = _fixture.GetService<Config>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}");
+        _provider = _fixture.GetService<HomeserverProviderService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverProviderService)}");
+    }
+
+    private async Task<AuthenticatedHomeserverGeneric> GetHomeserver() => await HomeserverAbstraction.GetHomeserver();
+
+    [Fact]
+    public async Task UploadFileAsync() {
+        var hs = await HomeserverAbstraction.GetHomeserver();
+
+        var mxcUri = await hs.UploadFile("test", "LibMatrix test file".AsBytes());
+        Assert.NotNull(mxcUri);
+    }
+    
+    [Fact]
+    public async Task DownloadFileAsync() {
+        var hs = await HomeserverAbstraction.GetHomeserver();
+
+        var mxcUri = await hs.UploadFile("test", "LibMatrix test file".AsBytes());
+        Assert.NotNull(mxcUri);
+        
+        var file = await hs.GetMediaStreamAsync(mxcUri);
+        Assert.NotNull(file);
+        
+        var data = file!.ReadToEnd().AsString();
+        Assert.Equal("LibMatrix test file", data);
+    }
+    
+    [SkippableFact(typeof(LibMatrixException))] // This test will fail if the homeserver does not support URL previews
+    public async Task GetUrlPreviewAsync() {
+        var hs = await HomeserverAbstraction.GetHomeserver();
+        var preview = await hs.GetUrlPreviewAsync("https://matrix.org");
+        
+        Assert.NotNull(preview);
+    }
+}
\ No newline at end of file
diff --git a/Tests/LibMatrix.Tests/Tests/AuthTests.cs b/Tests/LibMatrix.Tests/Tests/AuthTests.cs
index f331dd0..3ffadf0 100644
--- a/Tests/LibMatrix.Tests/Tests/AuthTests.cs
+++ b/Tests/LibMatrix.Tests/Tests/AuthTests.cs
@@ -26,7 +26,13 @@ public class AuthTests : TestBed<TestFixture> {
         Assert.False(string.IsNullOrWhiteSpace(_config.TestPassword), $"{nameof(_config.TestPassword)} must be set in appsettings!");
 
         // var server = await _resolver.ResolveHomeserverFromWellKnown(_config.TestHomeserver!);
-        var login = await _provider.Login(_config.TestHomeserver!, _config.TestUsername!, _config.TestPassword!);
+        var rhs = await _provider.GetRemoteHomeserver(_config.TestHomeserver);
+        var username = Guid.NewGuid().ToString();
+        var password = Guid.NewGuid().ToString();
+        
+        var reg = await rhs.RegisterAsync(username, password, "Unit tests!");
+        
+        var login = await _provider.Login(_config.TestHomeserver!, username, password);
         Assert.NotNull(login);
         var hs = await _provider.GetAuthenticatedWithToken(_config.TestHomeserver!, login.AccessToken);
         Assert.NotNull(hs);
@@ -40,10 +46,13 @@ public class AuthTests : TestBed<TestFixture> {
         Assert.False(string.IsNullOrWhiteSpace(_config.TestPassword), $"{nameof(_config.TestPassword)} must be set in appsettings!");
 
         // var server = await _resolver.ResolveHomeserverFromWellKnown(_config.TestHomeserver!);
-        var login = await _provider.Login(_config.TestHomeserver!, _config.TestUsername!, _config.TestPassword!);
-        Assert.NotNull(login);
-
-        var hs = await _provider.GetAuthenticatedWithToken(_config.TestHomeserver!, login.AccessToken);
+        var rhs = await _provider.GetRemoteHomeserver(_config.TestHomeserver);
+        var username = Guid.NewGuid().ToString();
+        var password = Guid.NewGuid().ToString();
+        
+        var reg = await rhs.RegisterAsync(username, password, "Unit tests!");
+        
+        var hs = await _provider.GetAuthenticatedWithToken(_config.TestHomeserver!, reg.AccessToken);
         Assert.NotNull(hs);
         Assert.NotNull(hs.WhoAmI);
         hs.WhoAmI.VerifyRequiredFields();
diff --git a/Tests/LibMatrix.Tests/Tests/TestCleanup.cs b/Tests/LibMatrix.Tests/Tests/TestCleanup.cs
index 7fc7c64..d9bea94 100644
--- a/Tests/LibMatrix.Tests/Tests/TestCleanup.cs
+++ b/Tests/LibMatrix.Tests/Tests/TestCleanup.cs
@@ -1,74 +1,74 @@
-using System.Diagnostics;
-using LibMatrix.Helpers;
-using LibMatrix.Services;
-using LibMatrix.Tests.Abstractions;
-using LibMatrix.Tests.Fixtures;
-using Microsoft.Extensions.Logging;
-using Xunit.Abstractions;
-using Xunit.Microsoft.DependencyInjection.Abstracts;
-
-namespace LibMatrix.Tests.Tests;
-
-public class TestCleanup : TestBed<TestFixture> {
-    // private readonly TestFixture _fixture;
-    private readonly HomeserverResolverService _resolver;
-    private readonly Config _config;
-    private readonly HomeserverProviderService _provider;
-    private readonly ILogger<TestCleanup> _logger;
-
-    public TestCleanup(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
-        // _fixture = fixture;
-        _resolver = _fixture.GetService<HomeserverResolverService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverResolverService)}");
-        _config = _fixture.GetService<Config>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}");
-        _provider = _fixture.GetService<HomeserverProviderService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverProviderService)}");
-        _logger = _fixture.GetService<ILogger<TestCleanup>>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(ILogger<TestCleanup>)}");
-    }
-
-    [Fact]
-    public async Task Cleanup() {
-        Assert.False(string.IsNullOrWhiteSpace(_config.TestHomeserver), $"{nameof(_config.TestHomeserver)} must be set in appsettings!");
-        Assert.False(string.IsNullOrWhiteSpace(_config.TestUsername), $"{nameof(_config.TestUsername)} must be set in appsettings!");
-        Assert.False(string.IsNullOrWhiteSpace(_config.TestPassword), $"{nameof(_config.TestPassword)} must be set in appsettings!");
-
-        var hs = await HomeserverAbstraction.GetHomeserver();
-        Assert.NotNull(hs);
-
-        var syncHelper = new SyncHelper(hs, _logger) {
-            Timeout = 3000
-        };
-        _testOutputHelper.WriteLine("Starting sync loop");
-        var cancellationTokenSource = new CancellationTokenSource();
-        var sw = Stopwatch.StartNew();
-        syncHelper.SyncReceivedHandlers.Add(async response => {
-            if (sw.ElapsedMilliseconds >= 3000) {
-                _testOutputHelper.WriteLine("Cancelling sync loop");
-
-                var tasks = (await hs.GetJoinedRooms()).Select(async room => {
-                    _logger.LogInformation("Leaving room: {}", room.RoomId);
-                    await room.LeaveAsync();
-                    await room.ForgetAsync();
-                    return room;
-                }).ToList();
-                await Task.WhenAll(tasks);
-
-                cancellationTokenSource.Cancel();
-            }
-
-            sw.Restart();
-            if (response.Rooms?.Leave is { Count: > 0 }) {
-                // foreach (var room in response.Rooms.Leave) {
-                // await hs.GetRoom(room.Key).ForgetAsync();
-                // }
-                var tasks = response.Rooms.Leave.Select(async room => {
-                    await hs.GetRoom(room.Key).ForgetAsync();
-                    return room;
-                }).ToList();
-                await Task.WhenAll(tasks);
-            }
-        });
-        await syncHelper.RunSyncLoopAsync(cancellationToken: cancellationTokenSource.Token);
-
-        Assert.NotNull(hs);
-        await hs.Logout();
-    }
-}
\ No newline at end of file
+// using System.Diagnostics;
+// using LibMatrix.Helpers;
+// using LibMatrix.Services;
+// using LibMatrix.Tests.Abstractions;
+// using LibMatrix.Tests.Fixtures;
+// using Microsoft.Extensions.Logging;
+// using Xunit.Abstractions;
+// using Xunit.Microsoft.DependencyInjection.Abstracts;
+//
+// namespace LibMatrix.Tests.Tests;
+//
+// public class TestCleanup : TestBed<TestFixture> {
+//     // private readonly TestFixture _fixture;
+//     private readonly HomeserverResolverService _resolver;
+//     private readonly Config _config;
+//     private readonly HomeserverProviderService _provider;
+//     private readonly ILogger<TestCleanup> _logger;
+//
+//     public TestCleanup(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
+//         // _fixture = fixture;
+//         _resolver = _fixture.GetService<HomeserverResolverService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverResolverService)}");
+//         _config = _fixture.GetService<Config>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}");
+//         _provider = _fixture.GetService<HomeserverProviderService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverProviderService)}");
+//         _logger = _fixture.GetService<ILogger<TestCleanup>>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(ILogger<TestCleanup>)}");
+//     }
+//
+//     [Fact]
+//     public async Task Cleanup() {
+//         Assert.False(string.IsNullOrWhiteSpace(_config.TestHomeserver), $"{nameof(_config.TestHomeserver)} must be set in appsettings!");
+//         Assert.False(string.IsNullOrWhiteSpace(_config.TestUsername), $"{nameof(_config.TestUsername)} must be set in appsettings!");
+//         Assert.False(string.IsNullOrWhiteSpace(_config.TestPassword), $"{nameof(_config.TestPassword)} must be set in appsettings!");
+//
+//         var hs = await HomeserverAbstraction.GetHomeserver();
+//         Assert.NotNull(hs);
+//
+//         var syncHelper = new SyncHelper(hs, _logger) {
+//             Timeout = 3000
+//         };
+//         _testOutputHelper.WriteLine("Starting sync loop");
+//         var cancellationTokenSource = new CancellationTokenSource();
+//         var sw = Stopwatch.StartNew();
+//         syncHelper.SyncReceivedHandlers.Add(async response => {
+//             if (sw.ElapsedMilliseconds >= 3000) {
+//                 _testOutputHelper.WriteLine("Cancelling sync loop");
+//
+//                 var tasks = (await hs.GetJoinedRooms()).Select(async room => {
+//                     _logger.LogInformation("Leaving room: {}", room.RoomId);
+//                     await room.LeaveAsync();
+//                     await room.ForgetAsync();
+//                     return room;
+//                 }).ToList();
+//                 await Task.WhenAll(tasks);
+//
+//                 cancellationTokenSource.Cancel();
+//             }
+//
+//             sw.Restart();
+//             if (response.Rooms?.Leave is { Count: > 0 }) {
+//                 // foreach (var room in response.Rooms.Leave) {
+//                 // await hs.GetRoom(room.Key).ForgetAsync();
+//                 // }
+//                 var tasks = response.Rooms.Leave.Select(async room => {
+//                     await hs.GetRoom(room.Key).ForgetAsync();
+//                     return room;
+//                 }).ToList();
+//                 await Task.WhenAll(tasks);
+//             }
+//         });
+//         await syncHelper.RunSyncLoopAsync(cancellationToken: cancellationTokenSource.Token);
+//
+//         Assert.NotNull(hs);
+//         await hs.Logout();
+//     }
+// }
\ No newline at end of file