about summary refs log tree commit diff
path: root/Tests/LibMatrix.Tests
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2024-09-17 04:28:20 +0200
committerRory& <root@rory.gay>2024-09-17 04:28:20 +0200
commit92b139e41afd41f3bd3987c14208966d020a948b (patch)
treee21c6ad5bb0c833a8151c9b60bc4f1de22fa5c8d /Tests/LibMatrix.Tests
parentMerge remote-tracking branch 'origin/dev/project-cleanup' (diff)
parentWorking json canonicalisation (diff)
downloadLibMatrix-92b139e41afd41f3bd3987c14208966d020a948b.tar.xz
Merge remote-tracking branch 'origin/dev/e2ee{canonical-json}'
Diffstat (limited to '')
-rw-r--r--Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs111
-rw-r--r--Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs9
-rw-r--r--Tests/LibMatrix.Tests/Config.cs37
-rw-r--r--Tests/LibMatrix.Tests/Fixtures/TestFixture.cs28
-rw-r--r--Tests/LibMatrix.Tests/LibMatrix.Tests.csproj4
-rw-r--r--Tests/LibMatrix.Tests/Tests/AuthTests.cs35
-rw-r--r--Tests/LibMatrix.Tests/Tests/CanonicalJsonTests.cs53
-rw-r--r--Tests/LibMatrix.Tests/Tests/HomeserverResolverTests.cs43
-rw-r--r--Tests/LibMatrix.Tests/Tests/RemoteHomeserverTests.cs (renamed from Tests/LibMatrix.Tests/Tests/ResolverTest.cs)31
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests.cs248
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/OtherRoomTests.cs101
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomAvatarTests.cs27
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomNameTests.cs42
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomTopicTests.cs27
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests/RoomEventTests.cs (renamed from Tests/LibMatrix.Tests/Tests/RoomEventTests.cs)58
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs178
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs320
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests/SpaceTests.cs101
-rw-r--r--Tests/LibMatrix.Tests/Tests/TestCleanup.cs143
19 files changed, 1132 insertions, 464 deletions
diff --git a/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs b/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs
index c9727d6..401223c 100644
--- a/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs
+++ b/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs
@@ -1,71 +1,96 @@
 using ArcaneLibs.Extensions;
 using LibMatrix.Homeservers;
 using LibMatrix.Responses;
+using LibMatrix.Services;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit.Abstractions;
+using Xunit.Sdk;
 
 namespace LibMatrix.Tests.Abstractions;
 
-public static class HomeserverAbstraction {
-    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 username = "@f1a2d2d6-1924-421b-91d0-893b347b2a49:matrixunittests.rory.gay";
-        var password = "d6d782d6-8bc9-4fac-9cd8-78e101b4298b";
+public class HomeserverAbstraction(HomeserverProviderService _hsProvider, Config _config, ILogger<HomeserverAbstraction> _logger) {
+    // private static readonly HomeserverResolverService _hsResolver = new HomeserverResolverService(NullLogger<HomeserverResolverService>.Instance);
+    // private static readonly HomeserverProviderService _hsProvider = new HomeserverProviderService(NullLogger<HomeserverProviderService>.Instance, _hsResolver);
+    
+    private static AuthenticatedHomeserverGeneric? ConfiguredHomeserver { get; set; }
+    private static readonly SemaphoreSlim _lock = new(1, 1);
+    
+    public async Task<AuthenticatedHomeserverGeneric> GetConfiguredHomeserver(ITestOutputHelper? testOutputHelper = null) {
+        Assert.False(string.IsNullOrWhiteSpace(_config.TestHomeserver));
+        Assert.False(string.IsNullOrWhiteSpace(_config.TestUsername));
+        Assert.False(string.IsNullOrWhiteSpace(_config.TestPassword));
+        
+        _logger.LogDebug("Using homeserver '{0}' with login '{1}' '{2}", _config.TestHomeserver, _config.TestUsername, _config.TestPassword);
+        testOutputHelper?.WriteLine($"Using homeserver '{_config.TestHomeserver}' with login '{_config.TestUsername}' '{_config.TestPassword}'");
+
+        await _lock.WaitAsync();
+        if (ConfiguredHomeserver is not null) {
+            _lock.Release();
+            return ConfiguredHomeserver;
+        }
+
+        var rhs = await _hsProvider.GetRemoteHomeserver(_config.TestHomeserver);
+        
         LoginResponse reg;
         try {
-            reg = await rhs.LoginAsync(username, password);
+            reg = await rhs.LoginAsync(_config.TestUsername, _config.TestPassword);
         }
         catch (MatrixException e) {
             if (e.ErrorCode == "M_FORBIDDEN") {
                 await rhs.RegisterAsync(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "Unit tests!");
-                reg = await rhs.RegisterAsync(username, password, "Unit tests!");
+                reg = await rhs.RegisterAsync(_config.TestUsername, _config.TestPassword, "Unit tests!");
             }
             else throw new Exception("Failed to register", e);
         }
 
-        var hs = await reg.GetAuthenticatedHomeserver("https://matrixunittests.rory.gay");
-
-        //var rooms = await hs.GetJoinedRooms();
-
-        // var disbandRoomTasks = rooms.Select(async room => {
-        //     // await room.DisbandRoomAsync();
-        //     await room.LeaveAsync();
-        //     await room.ForgetAsync();
-        //     return room;
-        // }).ToList();
-        // await Task.WhenAll(disbandRoomTasks);
-
-        // foreach (var room in rooms) {
-        //     // await room.DisbandRoomAsync();
-        //     await room.LeaveAsync();
-        //     await room.ForgetAsync();
-        // }
+        var hs = await _hsProvider.GetAuthenticatedWithToken(reg.Homeserver, reg.AccessToken);
+        ConfiguredHomeserver = hs;
+        _lock.Release();
 
         return hs;
     }
 
-    public static async Task<AuthenticatedHomeserverGeneric> GetRandomHomeserver() {
-        var rhs = await RemoteHomeserver.Create("https://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 rooms = await hs.GetJoinedRooms();
-        //
-        // var disbandRoomTasks = rooms.Select(async room => {
-        //     // await room.DisbandRoomAsync();
-        //     await room.LeaveAsync();
-        //     await room.ForgetAsync();
-        //     return room;
-        // }).ToList();
-        // await Task.WhenAll(disbandRoomTasks);
-
+    public async Task<AuthenticatedHomeserverGeneric> GetNewHomeserver() {
+        Assert.False(string.IsNullOrWhiteSpace(_config.TestHomeserver));
+        var username = Guid.NewGuid().ToString();
+        var password = Guid.NewGuid().ToString();
+        
+        _logger.LogDebug("Creating new homeserver '{0}' with login '{1}' '{2}'", _config.TestHomeserver, username, password);
+        
+        var rhs = await _hsProvider.GetRemoteHomeserver(_config.TestHomeserver);
+        var reg = await rhs.RegisterAsync(username, password, "Unit tests!");
+        var hs = await _hsProvider.GetAuthenticatedWithToken(reg.Homeserver, reg.AccessToken);
+        
         return hs;
     }
 
-    public static async IAsyncEnumerable<AuthenticatedHomeserverGeneric> GetRandomHomeservers(int count = 1) {
+    public async IAsyncEnumerable<AuthenticatedHomeserverGeneric> GetNewHomeservers(int count = 1) {
         var createRandomUserTasks = Enumerable
             .Range(0, count)
-            .Select(_ => GetRandomHomeserver()).ToAsyncEnumerable();
+            .Select(_ => GetNewHomeserver()).ToAsyncEnumerable();
         await foreach (var hs in createRandomUserTasks) yield return hs;
     }
+
+    public async Task<(string username, string password, string token)> GetKnownCredentials() {
+        Assert.False(string.IsNullOrWhiteSpace(_config.TestHomeserver));
+        var rhs = await _hsProvider.GetRemoteHomeserver(_config.TestHomeserver);
+        
+        var username = _config.TestUsername;
+        var password = _config.TestPassword;
+        
+        LoginResponse reg;
+        try {
+            reg = await rhs.LoginAsync(username, password);
+        }
+        catch (MatrixException e) {
+            if (e.ErrorCode == "M_FORBIDDEN") {
+                await rhs.RegisterAsync(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "Unit tests!");
+                reg = await rhs.RegisterAsync(username, password, "Unit tests!");
+            }
+            else throw new Exception("Failed to log in", e);
+        }
+        
+        return (username, password, reg.AccessToken);
+    }
 }
\ No newline at end of file
diff --git a/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs b/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs
index 2a380fc..88b6758 100644
--- a/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs
+++ b/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics;
 using ArcaneLibs.Extensions;
 using LibMatrix.EventTypes.Spec.State;
 using LibMatrix.EventTypes.Spec.State.RoomInfo;
@@ -16,28 +17,28 @@ public static class RoomAbstraction {
         };
         crq.InitialState ??= new List<StateEvent>();
         crq.InitialState.Add(new StateEvent() {
-            Type = "m.room.topic",
+            Type = RoomTopicEventContent.EventId,
             StateKey = "",
             TypedContent = new RoomTopicEventContent() {
                 Topic = "LibMatrix Test Room " + DateTime.Now.ToString("O")
             }
         });
         crq.InitialState.Add(new StateEvent() {
-            Type = "m.room.name",
+            Type = RoomNameEventContent.EventId,
             StateKey = "",
             TypedContent = new RoomNameEventContent() {
                 Name = "LibMatrix Test Room " + DateTime.Now.ToString("O")
             }
         });
         crq.InitialState.Add(new StateEvent() {
-            Type = "m.room.avatar",
+            Type = RoomAvatarEventContent.EventId,
             StateKey = "",
             TypedContent = new RoomAvatarEventContent() {
                 Url = "mxc://conduit.rory.gay/r9KiT0f9eQbv8pv4RxwBZFuzhfKjGWHx"
             }
         });
         crq.InitialState.Add(new StateEvent() {
-            Type = "m.room.aliases",
+            Type = RoomAliasEventContent.EventId,
             StateKey = "",
             TypedContent = new RoomAliasEventContent() {
                 Aliases = Enumerable
diff --git a/Tests/LibMatrix.Tests/Config.cs b/Tests/LibMatrix.Tests/Config.cs
index ddbf705..045ea40 100644
--- a/Tests/LibMatrix.Tests/Config.cs
+++ b/Tests/LibMatrix.Tests/Config.cs
@@ -1,18 +1,41 @@
+using Microsoft.Extensions.Configuration;
+
 namespace LibMatrix.Tests;
 
 public class Config {
+    public Config(IConfiguration? config) {
+        config.GetSection("Configuration").Bind(this);
+    }
+
     public string? TestHomeserver { get; set; } = Environment.GetEnvironmentVariable("LIBMATRIX_TEST_HOMESERVER") ?? null;
-    public string? TestUsername { get; set; } = Environment.GetEnvironmentVariable("LIBMATRIX_TEST_USERNAME") ?? null;
-    public string? TestPassword { get; set; } = Environment.GetEnvironmentVariable("LIBMATRIX_TEST_PASSWORD") ?? null;
-    public string? TestRoomId { get; set; } = Environment.GetEnvironmentVariable("LIBMATRIX_TEST_ROOM_ID") ?? null;
-    public string? TestRoomAlias { get; set; } = Environment.GetEnvironmentVariable("LIBMATRIX_TEST_ROOM_ALIAS") ?? null;
+    public string? TestUsername { get; set; } = Environment.GetEnvironmentVariable("LIBMATRIX_TEST_USERNAME") ?? Guid.NewGuid().ToString();
+
+    public string? TestPassword { get; set; } = Environment.GetEnvironmentVariable("LIBMATRIX_TEST_PASSWORD") ?? Guid.NewGuid().ToString();
+    // public string? TestRoomId { get; set; } = Environment.GetEnvironmentVariable("LIBMATRIX_TEST_ROOM_ID") ?? null;
+    // public string? TestRoomAlias { get; set; } = Environment.GetEnvironmentVariable("LIBMATRIX_TEST_ROOM_ALIAS") ?? null;
 
-    public Dictionary<string, string> ExpectedHomeserverMappings { get; set; } = new() {
+    public Dictionary<string, string> ExpectedHomeserverClientMappings { get; set; } = new() {
         { "matrix.org", "https://matrix-client.matrix.org" },
-        { "rory.gay", "https://matrix.rory.gay" }
+        { "rory.gay", "https://matrix.rory.gay" },
+        { "feline.support", "https://matrix.feline.support" },
+        { "transfem.dev", "https://matrix.transfem.dev" },
+        { "the-apothecary.club", "https://the-apothecary.club" },
+        { "nixos.org", "https://matrix.nixos.org" },
+        { "fedora.im", "https://fedora.ems.host" }
+    };
+
+    public Dictionary<string, string> ExpectedHomeserverFederationMappings { get; set; } = new() {
+        { "rory.gay", "https://matrix.rory.gay:443" },
+        { "matrix.org", "https://matrix-federation.matrix.org:443" },
+        { "feline.support", "https://matrix.feline.support:8448" },
+        { "transfem.dev", "https://matrix.transfem.dev:443" },
+        { "the-apothecary.club", "https://the-apothecary.club:443" },
+        { "nixos.org", "https://matrix.nixos.org:443" },
+        { "fedora.im", "https://fedora.ems.host:443" }
     };
 
     public Dictionary<string, string> ExpectedAliasMappings { get; set; } = new() {
-        { "#libmatrix:rory.gay", "!tuiLEoMqNOQezxILzt:rory.gay" }
+        { "#libmatrix:rory.gay", "!tuiLEoMqNOQezxILzt:rory.gay" },
+        { "#matrix:matrix.org", "!OGEhHVWSdvArJzumhm:matrix.org" }
     };
 }
\ No newline at end of file
diff --git a/Tests/LibMatrix.Tests/Fixtures/TestFixture.cs b/Tests/LibMatrix.Tests/Fixtures/TestFixture.cs
index 35c8704..01a0d2f 100644
--- a/Tests/LibMatrix.Tests/Fixtures/TestFixture.cs
+++ b/Tests/LibMatrix.Tests/Fixtures/TestFixture.cs
@@ -1,5 +1,6 @@
 using ArcaneLibs.Extensions;
 using LibMatrix.Services;
+using LibMatrix.Tests.Abstractions;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Xunit.Microsoft.DependencyInjection;
@@ -8,24 +9,19 @@ using Xunit.Microsoft.DependencyInjection.Abstracts;
 namespace LibMatrix.Tests.Fixtures;
 
 public class TestFixture : TestBedFixture {
-    protected override void AddServices(IServiceCollection services, IConfiguration? configuration) {
-        services.AddSingleton<TieredStorageService>(x =>
-            new TieredStorageService(
-                null,
-                null
-            )
-        );
+    protected override void AddServices(IServiceCollection services, IConfiguration configuration) {
+        // services.AddSingleton<TieredStorageService>(x =>
+        //     new TieredStorageService(
+        //         null,
+        //         null
+        //     )
+        // );
+        services.AddSingleton(configuration);
 
         services.AddRoryLibMatrixServices();
-
-        services.AddSingleton<Config>(config => {
-            var conf = new Config();
-            configuration?.GetSection("Configuration").Bind(conf);
-
-            File.WriteAllText("configuration.json", conf.ToJson());
-
-            return conf;
-        });
+        services.AddLogging();
+        services.AddSingleton<HomeserverAbstraction>();
+        services.AddSingleton<Config>();
     }
 
     protected override ValueTask DisposeAsyncCore()
diff --git a/Tests/LibMatrix.Tests/LibMatrix.Tests.csproj b/Tests/LibMatrix.Tests/LibMatrix.Tests.csproj
index 095985a..52bec9f 100644
--- a/Tests/LibMatrix.Tests/LibMatrix.Tests.csproj
+++ b/Tests/LibMatrix.Tests/LibMatrix.Tests.csproj
@@ -13,9 +13,9 @@
         <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0"/>
 
         <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
-        <PackageReference Include="xunit" Version="2.8.1" />
+        <PackageReference Include="xunit" Version="2.8.0" />
         <PackageReference Include="Xunit.Microsoft.DependencyInjection" Version="8.1.0" />
-        <PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
+        <PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
             <PrivateAssets>all</PrivateAssets>
         </PackageReference>
diff --git a/Tests/LibMatrix.Tests/Tests/AuthTests.cs b/Tests/LibMatrix.Tests/Tests/AuthTests.cs
index f331dd0..633c842 100644
--- a/Tests/LibMatrix.Tests/Tests/AuthTests.cs
+++ b/Tests/LibMatrix.Tests/Tests/AuthTests.cs
@@ -1,4 +1,5 @@
 using LibMatrix.Services;
+using LibMatrix.Tests.Abstractions;
 using LibMatrix.Tests.DataTests;
 using LibMatrix.Tests.Fixtures;
 using Xunit.Abstractions;
@@ -7,43 +8,29 @@ using Xunit.Microsoft.DependencyInjection.Abstracts;
 namespace LibMatrix.Tests.Tests;
 
 public class AuthTests : TestBed<TestFixture> {
-    private readonly TestFixture _fixture;
-    private readonly HomeserverResolverService _resolver;
     private readonly Config _config;
     private readonly HomeserverProviderService _provider;
+    private readonly HomeserverAbstraction _hsAbstraction;
 
     public AuthTests(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)}");
+        _hsAbstraction = _fixture.GetService<HomeserverAbstraction>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverAbstraction)}");
     }
-
+    
     [Fact]
     public async Task LoginWithPassword() {
-        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 server = await _resolver.ResolveHomeserverFromWellKnown(_config.TestHomeserver!);
-        var login = await _provider.Login(_config.TestHomeserver!, _config.TestUsername!, _config.TestPassword!);
+        var credentials = await _hsAbstraction.GetKnownCredentials();
+        
+        var login = await _provider.Login(_config.TestHomeserver!, credentials.username, credentials.password);
         Assert.NotNull(login);
-        var hs = await _provider.GetAuthenticatedWithToken(_config.TestHomeserver!, login.AccessToken);
-        Assert.NotNull(hs);
-        await hs.Logout();
+        Assert.NotNull(login.AccessToken);
     }
 
     [Fact]
     public async Task LoginWithToken() {
-        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 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 credentials = await _hsAbstraction.GetKnownCredentials();
+        var hs = await _provider.GetAuthenticatedWithToken(_config.TestHomeserver!, credentials.token);
         Assert.NotNull(hs);
         Assert.NotNull(hs.WhoAmI);
         hs.WhoAmI.VerifyRequiredFields();
@@ -54,7 +41,7 @@ public class AuthTests : TestBed<TestFixture> {
 
     [Fact]
     public async Task RegisterAsync() {
-        var rhs = await _provider.GetRemoteHomeserver("matrixunittests.rory.gay");
+        var rhs = await _provider.GetRemoteHomeserver(_config.TestHomeserver);
         var reg = await rhs.RegisterAsync(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "Unit tests!");
         Assert.NotNull(reg);
         Assert.NotNull(reg.AccessToken);
diff --git a/Tests/LibMatrix.Tests/Tests/CanonicalJsonTests.cs b/Tests/LibMatrix.Tests/Tests/CanonicalJsonTests.cs
new file mode 100644
index 0000000..c7fde54
--- /dev/null
+++ b/Tests/LibMatrix.Tests/Tests/CanonicalJsonTests.cs
@@ -0,0 +1,53 @@
+using System.Collections.Frozen;
+using System.Diagnostics;
+using System.Text.Json;
+using LibMatrix.Extensions;
+using LibMatrix.Services;
+using LibMatrix.Tests.Abstractions;
+using LibMatrix.Tests.DataTests;
+using LibMatrix.Tests.Fixtures;
+using Xunit.Abstractions;
+using Xunit.Microsoft.DependencyInjection.Abstracts;
+using Xunit.Sdk;
+
+namespace LibMatrix.Tests.Tests;
+
+public class CanonicalJsonTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : TestBed<TestFixture>(testOutputHelper, fixture) {
+    // Test cases from https://spec.matrix.org/v1.11/appendices/#examples
+    private static readonly FrozenDictionary<string, string> testCases = new Dictionary<string, string>() {
+        ["{}"] = "{}",
+        ["{\n    \"one\": 1,\n    \"two\": \"Two\"\n}\n"] = "{\"one\":1,\"two\":\"Two\"}",
+        ["{\n    \"b\": \"2\",\n    \"a\": \"1\"\n}\n"] = "{\"a\":\"1\",\"b\":\"2\"}",
+        ["{\"b\":\"2\",\"a\":\"1\"}"] = "{\"a\":\"1\",\"b\":\"2\"}",
+        ["{\n    \"auth\": {\n        \"success\": true,\n        \"mxid\": \"@john.doe:example.com\",\n        \"profile\": {\n            \"display_name\": \"John Doe\",\n            \"three_pids\": [\n                {\n                    \"medium\": \"email\",\n                    \"address\": \"[email protected]\"\n                },\n                {\n                    \"medium\": \"msisdn\",\n                    \"address\": \"123456789\"\n                }\n            ]\n        }\n    }\n}\n"] =
+            "{\"auth\":{\"mxid\":\"@john.doe:example.com\",\"profile\":{\"display_name\":\"John Doe\",\"three_pids\":[{\"address\":\"[email protected]\",\"medium\":\"email\"},{\"address\":\"123456789\",\"medium\":\"msisdn\"}]},\"success\":true}}",
+        ["{\n    \"a\": \"日本語\"\n}\n"] = "{\"a\":\"日本語\"}",
+        ["{\n    \"本\": 2,\n    \"日\": 1\n}\n"] = "{\"日\":1,\"本\":2}",
+        ["{\n    \"a\": \"\\u65E5\"\n}\n"] = "{\"a\":\"日\"}",
+        ["{\n    \"a\": null\n}\n"] = "{\"a\":null}",
+        ["{\n    \"a\": -0,\n    \"b\": 1e10\n}\n"] = "{\"a\":0,\"b\":10000000000}"
+    }.ToFrozenDictionary();
+
+    [Fact]
+    public void SpecTests() {
+        var i = 0;
+        foreach (var (input, expected) in testCases) {
+            var deserialised = JsonSerializer.Deserialize<Dictionary<string, object>>(input);
+            var actual = CanonicalJsonSerializer.Serialize(deserialised);
+            Assert.Equal(expected, actual);
+            // testOutputHelper.WriteLine($"Test case {i++} successful!");
+        }
+    }
+
+    [Fact]
+    public void RepeatTests() {
+        var sw = Stopwatch.StartNew();
+        for (int i = 0; i < 1_000_000; i++) {
+            SpecTests();
+            if (i % 10000 == 0) {
+                testOutputHelper.WriteLine($"{i} loops successful! Delta: {sw.Elapsed}");
+                sw.Restart();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Tests/LibMatrix.Tests/Tests/HomeserverResolverTests.cs b/Tests/LibMatrix.Tests/Tests/HomeserverResolverTests.cs
new file mode 100644
index 0000000..ef2426d
--- /dev/null
+++ b/Tests/LibMatrix.Tests/Tests/HomeserverResolverTests.cs
@@ -0,0 +1,43 @@
+using LibMatrix.Services;
+using LibMatrix.Tests.Fixtures;
+using Xunit.Abstractions;
+using Xunit.Microsoft.DependencyInjection.Abstracts;
+
+namespace LibMatrix.Tests.Tests;
+
+public class HomeserverResolverTests : TestBed<TestFixture> {
+    private readonly Config _config;
+    private readonly HomeserverResolverService _resolver;
+
+    public HomeserverResolverTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
+        _config = _fixture.GetService<Config>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}");
+        _resolver = _fixture.GetService<HomeserverResolverService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverResolverService)}");
+    }
+
+    [Fact]
+    public async Task ResolveServerClient() {
+        var tasks = _config.ExpectedHomeserverClientMappings.Select(async mapping => {
+            var server = await _resolver.ResolveHomeserverFromWellKnown(mapping.Key);
+            Assert.Equal(mapping.Value, server.Client);
+            return server;
+        }).ToList();
+        await Task.WhenAll(tasks);
+    }
+
+    [Fact]
+    public async Task ResolveServerServer() {
+        var tasks = _config.ExpectedHomeserverFederationMappings.Select(async mapping => {
+            var server = await _resolver.ResolveHomeserverFromWellKnown(mapping.Key);
+            Assert.Equal(mapping.Value, server.Server);
+            return server;
+        }).ToList();
+        await Task.WhenAll(tasks);
+    }
+    
+    [Fact]
+    public async Task ResolveMedia() {
+        var media = await _resolver.ResolveMediaUri("matrix.org", "mxc://matrix.org/eqwrRZRoPpNbcMeUwyXAuVRo");
+        
+        Assert.Equal("https://matrix-client.matrix.org/_matrix/media/v3/download/matrix.org/eqwrRZRoPpNbcMeUwyXAuVRo", media);
+    }
+}
\ No newline at end of file
diff --git a/Tests/LibMatrix.Tests/Tests/ResolverTest.cs b/Tests/LibMatrix.Tests/Tests/RemoteHomeserverTests.cs
index 700aa96..03f3c24 100644
--- a/Tests/LibMatrix.Tests/Tests/ResolverTest.cs
+++ b/Tests/LibMatrix.Tests/Tests/RemoteHomeserverTests.cs
@@ -5,13 +5,13 @@ using Xunit.Microsoft.DependencyInjection.Abstracts;
 
 namespace LibMatrix.Tests.Tests;
 
-public class ResolverTest : TestBed<TestFixture> {
+public class RemoteHomeserverTests : TestBed<TestFixture> {
     private readonly TestFixture _fixture;
     private readonly HomeserverResolverService _resolver;
     private readonly Config _config;
     private readonly HomeserverProviderService _provider;
 
-    public ResolverTest(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
+    public RemoteHomeserverTests(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)}");
@@ -19,29 +19,30 @@ public class ResolverTest : TestBed<TestFixture> {
     }
 
     [Fact]
-    public async Task ResolveServer() {
-        foreach (var (domain, expected) in _config.ExpectedHomeserverMappings) {
-            var server = await _resolver.ResolveHomeserverFromWellKnown(domain);
-            Assert.Equal(expected, server.Client);
-        }
-    }
-
-    [Fact]
     public async Task ResolveMedia() {
-        var media = await _resolver.ResolveMediaUri("matrix.org", "mxc://matrix.org/eqwrRZRoPpNbcMeUwyXAuVRo");
+        var hs = await _provider.GetRemoteHomeserver("matrix.org");
+        var media = hs.ResolveMediaUri("mxc://matrix.org/eqwrRZRoPpNbcMeUwyXAuVRo");
+        
         Assert.Equal("https://matrix-client.matrix.org/_matrix/media/v3/download/matrix.org/eqwrRZRoPpNbcMeUwyXAuVRo", media);
     }
 
     [Fact]
     public async Task ResolveRoomAliasAsync() {
-        var hs = await _provider.GetRemoteHomeserver("matrix.org");
-        var alias = await hs.ResolveRoomAliasAsync("#matrix:matrix.org");
-        Assert.Equal("!OGEhHVWSdvArJzumhm:matrix.org", alias.RoomId);
+        // var hs = await _provider.GetRemoteHomeserver("matrix.org");
+        // var alias = await hs.ResolveRoomAliasAsync("#matrix:matrix.org");
+        // Assert.Equal("!OGEhHVWSdvArJzumhm:matrix.org", alias.RoomId);
+        var tasks = _config.ExpectedAliasMappings.Select(async mapping => {
+            var hs = await _provider.GetRemoteHomeserver("matrix.org");
+            var alias = await hs.ResolveRoomAliasAsync(mapping.Key);
+            Assert.Equal(mapping.Value, alias.RoomId);
+            return alias;
+        }).ToList();
+        await Task.WhenAll(tasks);
     }
 
     [Fact]
     public async Task GetClientVersionsAsync() {
-        var hs = await _provider.GetRemoteHomeserver("matrix.org");
+        var hs = await _provider.GetRemoteHomeserver(_config.TestHomeserver);
         var versions = await hs.GetClientVersionsAsync();
         Assert.NotNull(versions);
     }
diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests.cs
deleted file mode 100644
index 4c8dcb4..0000000
--- a/Tests/LibMatrix.Tests/Tests/RoomTests.cs
+++ /dev/null
@@ -1,248 +0,0 @@
-using System.Text;
-using LibMatrix.EventTypes.Spec.State;
-using LibMatrix.Homeservers;
-using LibMatrix.Responses;
-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 RoomTests : TestBed<TestFixture> {
-    private readonly TestFixture _fixture;
-    private readonly HomeserverResolverService _resolver;
-    private readonly Config _config;
-    private readonly HomeserverProviderService _provider;
-
-    public RoomTests(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 GetJoinedRoomsAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
-        //make 100 rooms
-        var createRoomTasks = Enumerable.Range(0, 10).Select(_ => RoomAbstraction.GetTestRoom(hs)).ToList();
-        await Task.WhenAll(createRoomTasks);
-
-        var rooms = await hs.GetJoinedRooms();
-        Assert.NotNull(rooms);
-        Assert.NotEmpty(rooms);
-        Assert.All(rooms, Assert.NotNull);
-        Assert.True(rooms.Count >= 10, "Not enough rooms were found");
-
-        await hs.Logout();
-    }
-
-    [Fact]
-    public async Task GetMembersAsync() {
-        Assert.True(StateEvent.KnownStateEventTypes is { Count: > 0 }, "StateEvent.KnownStateEventTypes is empty!");
-        Assert.True(StateEvent.KnownStateEventTypesByName is { Count: > 0 }, "StateEvent.KnownStateEventTypesByName is empty!");
-
-        var hs = await HomeserverAbstraction.GetHomeserver();
-        var room = await RoomAbstraction.GetTestRoom(hs);
-        Assert.NotNull(room);
-        var members = room.GetMembersEnumerableAsync();
-        Assert.NotNull(members);
-        var hitMembers = false;
-        await foreach (var member in members) {
-            Assert.NotNull(member);
-            Assert.NotNull(member.StateKey);
-            Assert.NotEmpty(member.StateKey);
-            Assert.NotNull(member.Sender);
-            Assert.NotEmpty(member.Sender);
-            Assert.NotNull(member.RawContent);
-            Assert.NotEmpty(member.RawContent);
-            Assert.NotNull(member.TypedContent);
-            Assert.IsType<RoomMemberEventContent>(member.TypedContent);
-            var content = (RoomMemberEventContent)member.TypedContent;
-            Assert.NotNull(content);
-            Assert.NotNull(content.Membership);
-            Assert.NotEmpty(content.Membership);
-            hitMembers = true;
-        }
-
-        Assert.True(hitMembers, "No members were found in the room");
-    }
-
-    [Fact]
-    public async Task JoinAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
-        var room = await RoomAbstraction.GetTestRoom(hs);
-        Assert.NotNull(room);
-        var id = await room.JoinAsync();
-        Assert.NotNull(id);
-        Assert.NotNull(id.RoomId);
-        Assert.NotEmpty(id.RoomId);
-    }
-
-    [Fact]
-    public async Task ForgetAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
-        var room = await RoomAbstraction.GetTestRoom(hs);
-        Assert.NotNull(room);
-        await room.ForgetAsync();
-    }
-
-    [Fact]
-    public async Task LeaveAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
-        var room = await RoomAbstraction.GetTestRoom(hs);
-        Assert.NotNull(room);
-        await room.LeaveAsync();
-    }
-
-    [Fact]
-    public async Task KickAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
-        var hs2 = await HomeserverAbstraction.GetRandomHomeserver();
-        var room = await RoomAbstraction.GetTestRoom(hs);
-        Assert.NotNull(room);
-        await room.InviteUserAsync(hs2.UserId, "Unit test!");
-        await hs2.GetRoom(room.RoomId).JoinAsync();
-        await room.KickAsync(hs2.UserId, "test");
-        var banState = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", hs2.UserId);
-        Assert.NotNull(banState);
-        Assert.Equal("leave", banState.Membership);
-    }
-
-    [Fact]
-    public async Task BanAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
-        var hs2 = await HomeserverAbstraction.GetRandomHomeserver();
-        var room = await RoomAbstraction.GetTestRoom(hs);
-        Assert.NotNull(room);
-        await room.BanAsync(hs2.UserId, "test");
-        var banState = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", hs2.UserId);
-        Assert.NotNull(banState);
-        Assert.Equal("ban", banState.Membership);
-    }
-
-    [Fact]
-    public async Task UnbanAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
-        var hs2 = await HomeserverAbstraction.GetRandomHomeserver();
-        var room = await RoomAbstraction.GetTestRoom(hs);
-        Assert.NotNull(room);
-        await room.BanAsync(hs2.UserId, "test");
-        var banState = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", hs2.UserId);
-        Assert.NotNull(banState);
-        Assert.Equal("ban", banState.Membership);
-        await room.UnbanAsync(hs2.UserId);
-        var unbanState = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", hs2.UserId);
-        Assert.NotNull(unbanState);
-        Assert.Equal("leave", unbanState.Membership);
-    }
-
-    [SkippableFact(typeof(MatrixException))]
-    public async Task SendStateEventAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
-        var room = await RoomAbstraction.GetTestRoom(hs);
-        Assert.NotNull(room);
-
-        await room.SendStateEventAsync("gay.rory.libmatrix.unit_tests", new UserProfileResponse() {
-            DisplayName = "wee_woo",
-            AvatarUrl = "no"
-        });
-        await room.SendStateEventAsync("gay.rory.libmatrix.unit_tests", "state_key_maybe", new UserProfileResponse() {
-            DisplayName = "wee_woo",
-            AvatarUrl = "yes"
-        });
-    }
-
-    [SkippableFact(typeof(MatrixException))]
-    public async Task SendAndGetStateEventAsync() {
-        await SendStateEventAsync();
-        var hs = await HomeserverAbstraction.GetHomeserver();
-        var room = await RoomAbstraction.GetTestRoom(hs);
-        Assert.NotNull(room);
-
-        await room.SendStateEventAsync("gay.rory.libmatrix.unit_tests", new UserProfileResponse() {
-            DisplayName = "wee_woo",
-            AvatarUrl = "no"
-        });
-        await room.SendStateEventAsync("gay.rory.libmatrix.unit_tests", "state_key_maybe", new UserProfileResponse() {
-            DisplayName = "wee_woo",
-            AvatarUrl = "yes"
-        });
-
-        var state1 = await room.GetStateAsync<UserProfileResponse>("gay.rory.libmatrix.unit_tests");
-        Assert.NotNull(state1);
-        Assert.NotNull(state1.DisplayName);
-        Assert.NotEmpty(state1.DisplayName);
-        Assert.NotNull(state1.AvatarUrl);
-        Assert.NotEmpty(state1.AvatarUrl);
-        Assert.Equal("wee_woo", state1.DisplayName);
-        Assert.Equal("no", state1.AvatarUrl);
-
-        var state2 = await room.GetStateAsync<UserProfileResponse>("gay.rory.libmatrix.unit_tests", "state_key_maybe");
-        Assert.NotNull(state2);
-        Assert.NotNull(state2.DisplayName);
-        Assert.NotEmpty(state2.DisplayName);
-        Assert.NotNull(state2.AvatarUrl);
-        Assert.NotEmpty(state2.AvatarUrl);
-        Assert.Equal("wee_woo", state2.DisplayName);
-        Assert.Equal("yes", state2.AvatarUrl);
-    }
-
-    [Fact]
-    public async Task DisbandAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
-        var room = await RoomAbstraction.GetTestRoom(hs);
-        Assert.NotNull(room);
-
-        await room.PermanentlyBrickRoomAsync();
-    }
-
-    [Fact]
-    public async Task SendFileAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
-        var room = await RoomAbstraction.GetTestRoom(hs);
-        Assert.NotNull(room);
-
-        var res = await room.SendFileAsync("test.txt", new MemoryStream(Encoding.UTF8.GetBytes("This test was written by Emma [it/its], member of the Rory& system." +
-                                                                                               "\nIf you are reading this on matrix, it means the unit test for uploading a file works!")));
-        Assert.NotNull(res);
-        Assert.NotNull(res.EventId);
-    }
-
-    [Fact]
-    public async Task GetSpaceChildrenAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
-        var space = await RoomAbstraction.GetTestSpace(hs, 2, false, 1);
-        Assert.NotNull(space);
-        var children = space.GetChildrenAsync();
-        Assert.NotNull(children);
-        var found = 0;
-        await foreach (var room in children) found++;
-        Assert.Equal(2, found);
-    }
-
-    [Fact]
-    public async Task InviteAndJoinAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
-        var room = await RoomAbstraction.GetTestRoom(hs);
-        var otherUsers = HomeserverAbstraction.GetRandomHomeservers(15);
-        Assert.NotNull(room);
-
-        // var expectedCount = 1;
-
-        var tasks = new List<Task>();
-        await foreach (var otherUser in otherUsers)
-            tasks.Add(Task.Run(async () => {
-                await room.InviteUserAsync(otherUser.UserId);
-                await otherUser.GetRoom(room.RoomId).JoinAsync();
-            }));
-        await Task.WhenAll(tasks);
-
-        var states = await room.GetMembersListAsync(false);
-        Assert.Equal(16, states.Count);
-    }
-}
\ No newline at end of file
diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/OtherRoomTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/OtherRoomTests.cs
new file mode 100644
index 0000000..1ae195d
--- /dev/null
+++ b/Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/OtherRoomTests.cs
@@ -0,0 +1,101 @@
+using LibMatrix.Tests.Abstractions;
+using LibMatrix.Tests.Fixtures;
+using Xunit.Abstractions;
+using Xunit.Microsoft.DependencyInjection.Abstracts;
+
+namespace LibMatrix.Tests.Tests.BasicRoomEventTests;
+
+public class OtherRoomTests : TestBed<TestFixture> {
+    private readonly HomeserverAbstraction _hsAbstraction;
+
+    public OtherRoomTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
+        _hsAbstraction = _fixture.GetService<HomeserverAbstraction>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverAbstraction)}");
+    }
+
+    [SkippableFact(typeof(MatrixException))]
+    public async Task GetCanonicalAliasAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        var alias = await room.GetCanonicalAliasAsync();
+        Assert.NotNull(alias);
+        Assert.NotNull(alias.Alias);
+        Assert.NotEmpty(alias.Alias);
+    }
+
+    [Fact]
+    public async Task GetJoinRuleAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        var rule = await room.GetJoinRuleAsync();
+        Assert.NotNull(rule);
+        Assert.NotNull(rule.JoinRuleValue);
+        Assert.NotEmpty(rule.JoinRuleValue);
+    }
+
+    [Fact]
+    public async Task GetHistoryVisibilityAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        var visibility = await room.GetHistoryVisibilityAsync();
+        Assert.NotNull(visibility);
+        Assert.NotNull(visibility.HistoryVisibility);
+        Assert.NotEmpty(visibility.HistoryVisibility);
+    }
+
+    [Fact]
+    public async Task GetGuestAccessAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        try {
+            var access = await room.GetGuestAccessAsync();
+            Assert.NotNull(access);
+            Assert.NotNull(access.GuestAccess);
+            Assert.NotEmpty(access.GuestAccess);
+        }
+        catch (Exception e) {
+            if (e is not MatrixException exception) throw;
+            Assert.Equal("M_NOT_FOUND", exception.ErrorCode);
+        }
+    }
+
+    [Fact]
+    public async Task GetCreateEventAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        var create = await room.GetCreateEventAsync();
+        Assert.NotNull(create);
+        Assert.NotNull(create.Creator);
+        Assert.NotEmpty(create.RoomVersion!);
+    }
+
+    [Fact]
+    public async Task GetRoomType() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        await room.GetRoomType();
+    }
+
+    [Fact]
+    public async Task GetPowerLevelsAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        var power = await room.GetPowerLevelsAsync();
+        Assert.NotNull(power);
+        Assert.NotNull(power.Ban);
+        Assert.NotNull(power.Kick);
+        Assert.NotNull(power.Invite);
+        Assert.NotNull(power.Redact);
+        Assert.NotNull(power.StateDefault);
+        Assert.NotNull(power.EventsDefault);
+        Assert.NotNull(power.UsersDefault);
+        Assert.NotNull(power.Users);
+        // Assert.NotNull(power.Events);
+    }
+}
\ No newline at end of file
diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomAvatarTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomAvatarTests.cs
new file mode 100644
index 0000000..78f007c
--- /dev/null
+++ b/Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomAvatarTests.cs
@@ -0,0 +1,27 @@
+using LibMatrix.Tests.Abstractions;
+using LibMatrix.Tests.Fixtures;
+using Xunit.Abstractions;
+using Xunit.Microsoft.DependencyInjection.Abstracts;
+
+namespace LibMatrix.Tests.Tests.BasicRoomEventTests;
+
+public class RoomAvatarTests : TestBed<TestFixture> {
+    private readonly HomeserverAbstraction _hsAbstraction;
+
+    public RoomAvatarTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
+        _hsAbstraction = _fixture.GetService<HomeserverAbstraction>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverAbstraction)}");
+    }
+
+    [SkippableFact(typeof(MatrixException))]
+    public async Task GetAvatarUrlAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        var url = await room.GetAvatarUrlAsync();
+        Assert.NotNull(url);
+        Assert.NotNull(url.Url);
+        Assert.NotEmpty(url.Url);
+
+        await room.LeaveAsync();
+    }
+}
\ No newline at end of file
diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomNameTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomNameTests.cs
new file mode 100644
index 0000000..1ea3e18
--- /dev/null
+++ b/Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomNameTests.cs
@@ -0,0 +1,42 @@
+using LibMatrix.EventTypes.Spec.State;
+using LibMatrix.Tests.Abstractions;
+using LibMatrix.Tests.Fixtures;
+using Xunit.Abstractions;
+using Xunit.Microsoft.DependencyInjection.Abstracts;
+
+namespace LibMatrix.Tests.Tests.BasicRoomEventTests;
+
+public class RoomNameTests : TestBed<TestFixture> {
+    private readonly HomeserverAbstraction _hsAbstraction;
+
+    public RoomNameTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
+        _hsAbstraction = _fixture.GetService<HomeserverAbstraction>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverAbstraction)}");
+    }
+
+    [Fact]
+    public async Task GetNameAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        var name = await room.GetNameAsync();
+        Assert.NotNull(name);
+        Assert.NotEmpty(name);
+
+        await room.LeaveAsync();
+    }
+
+    [Fact]
+    public async Task SetNameAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        var name = Guid.NewGuid().ToString();
+        await room.SendStateEventAsync(RoomNameEventContent.EventId, new RoomNameEventContent { Name = name });
+        var newName = await room.GetNameAsync();
+        Assert.Equal(name, newName);
+        
+        await room.LeaveAsync();
+    }
+}
\ No newline at end of file
diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomTopicTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomTopicTests.cs
new file mode 100644
index 0000000..6610035
--- /dev/null
+++ b/Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomTopicTests.cs
@@ -0,0 +1,27 @@
+using LibMatrix.Tests.Abstractions;
+using LibMatrix.Tests.Fixtures;
+using Xunit.Abstractions;
+using Xunit.Microsoft.DependencyInjection.Abstracts;
+
+namespace LibMatrix.Tests.Tests.BasicRoomEventTests;
+
+public class RoomTopicTests : TestBed<TestFixture> {
+    private readonly HomeserverAbstraction _hsAbstraction;
+
+    public RoomTopicTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
+        _hsAbstraction = _fixture.GetService<HomeserverAbstraction>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverAbstraction)}");
+    }
+
+    [SkippableFact(typeof(MatrixException))]
+    public async Task GetTopicAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        var topic = await room.GetTopicAsync();
+        Assert.NotNull(topic);
+        Assert.NotNull(topic.Topic);
+        Assert.NotEmpty(topic.Topic);
+        
+        await room.LeaveAsync();
+    }
+}
\ No newline at end of file
diff --git a/Tests/LibMatrix.Tests/Tests/RoomEventTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomEventTests.cs
index 932909f..9081a5a 100644
--- a/Tests/LibMatrix.Tests/Tests/RoomEventTests.cs
+++ b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomEventTests.cs
@@ -8,23 +8,16 @@ using Xunit.Microsoft.DependencyInjection.Abstracts;
 namespace LibMatrix.Tests.Tests;
 
 public class RoomEventTests : TestBed<TestFixture> {
-    private readonly TestFixture _fixture;
-    private readonly HomeserverResolverService _resolver;
-    private readonly Config _config;
-    private readonly HomeserverProviderService _provider;
+    private readonly HomeserverAbstraction _hsAbstraction;
 
     public RoomEventTests(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)}");
+        _hsAbstraction = _fixture.GetService<HomeserverAbstraction>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverAbstraction)}");
     }
 
-    private async Task<AuthenticatedHomeserverGeneric> GetHomeserver() => await HomeserverAbstraction.GetHomeserver();
-
     [Fact]
     public async Task GetNameAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        
         var room = await RoomAbstraction.GetTestRoom(hs);
         Assert.NotNull(room);
         var name = await room.GetNameAsync();
@@ -34,7 +27,7 @@ public class RoomEventTests : TestBed<TestFixture> {
 
     [SkippableFact(typeof(MatrixException))]
     public async Task GetTopicAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
         var room = await RoomAbstraction.GetTestRoom(hs);
         Assert.NotNull(room);
         var topic = await room.GetTopicAsync();
@@ -44,19 +37,8 @@ public class RoomEventTests : TestBed<TestFixture> {
     }
 
     [SkippableFact(typeof(MatrixException))]
-    public async Task GetAliasesAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
-        var room = await RoomAbstraction.GetTestRoom(hs);
-        Assert.NotNull(room);
-        var aliases = await room.GetAliasesAsync();
-        Assert.NotNull(aliases);
-        Assert.NotEmpty(aliases);
-        Assert.All(aliases, Assert.NotNull);
-    }
-
-    [SkippableFact(typeof(MatrixException))]
     public async Task GetCanonicalAliasAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
         var room = await RoomAbstraction.GetTestRoom(hs);
         Assert.NotNull(room);
         var alias = await room.GetCanonicalAliasAsync();
@@ -67,40 +49,46 @@ public class RoomEventTests : TestBed<TestFixture> {
 
     [SkippableFact(typeof(MatrixException))]
     public async Task GetAvatarUrlAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
         var room = await RoomAbstraction.GetTestRoom(hs);
         Assert.NotNull(room);
         var url = await room.GetAvatarUrlAsync();
         Assert.NotNull(url);
         Assert.NotNull(url.Url);
         Assert.NotEmpty(url.Url);
+
+        await room.LeaveAsync();
     }
 
     [Fact]
     public async Task GetJoinRuleAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
         var room = await RoomAbstraction.GetTestRoom(hs);
         Assert.NotNull(room);
         var rule = await room.GetJoinRuleAsync();
         Assert.NotNull(rule);
         Assert.NotNull(rule.JoinRuleValue);
         Assert.NotEmpty(rule.JoinRuleValue);
+        
+        await room.LeaveAsync();
     }
 
     [Fact]
     public async Task GetHistoryVisibilityAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
         var room = await RoomAbstraction.GetTestRoom(hs);
         Assert.NotNull(room);
         var visibility = await room.GetHistoryVisibilityAsync();
         Assert.NotNull(visibility);
         Assert.NotNull(visibility.HistoryVisibility);
         Assert.NotEmpty(visibility.HistoryVisibility);
+        
+        await room.LeaveAsync();
     }
 
     [Fact]
     public async Task GetGuestAccessAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
         var room = await RoomAbstraction.GetTestRoom(hs);
         Assert.NotNull(room);
         try {
@@ -113,30 +101,36 @@ public class RoomEventTests : TestBed<TestFixture> {
             if (e is not MatrixException exception) throw;
             Assert.Equal("M_NOT_FOUND", exception.ErrorCode);
         }
+        
+        await room.LeaveAsync();
     }
 
     [Fact]
     public async Task GetCreateEventAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
         var room = await RoomAbstraction.GetTestRoom(hs);
         Assert.NotNull(room);
         var create = await room.GetCreateEventAsync();
         Assert.NotNull(create);
         Assert.NotNull(create.Creator);
         Assert.NotEmpty(create.RoomVersion!);
+        
+        await room.LeaveAsync();
     }
 
     [Fact]
     public async Task GetRoomType() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
         var room = await RoomAbstraction.GetTestRoom(hs);
         Assert.NotNull(room);
         await room.GetRoomType();
+        
+        await room.LeaveAsync();
     }
 
     [Fact]
     public async Task GetPowerLevelsAsync() {
-        var hs = await HomeserverAbstraction.GetHomeserver();
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
         var room = await RoomAbstraction.GetTestRoom(hs);
         Assert.NotNull(room);
         var power = await room.GetPowerLevelsAsync();
@@ -150,5 +144,7 @@ public class RoomEventTests : TestBed<TestFixture> {
         Assert.NotNull(power.UsersDefault);
         Assert.NotNull(power.Users);
         // Assert.NotNull(power.Events);
+        
+        await room.LeaveAsync();
     }
 }
\ No newline at end of file
diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs
new file mode 100644
index 0000000..2e552e2
--- /dev/null
+++ b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs
@@ -0,0 +1,178 @@
+using System.Diagnostics;
+using System.Text;
+using ArcaneLibs.Extensions;
+using LibMatrix.EventTypes.Spec.State;
+using LibMatrix.Homeservers;
+using LibMatrix.Responses;
+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 RoomMembershipTests : TestBed<TestFixture> {
+    private readonly HomeserverAbstraction _hsAbstraction;
+
+    public RoomMembershipTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
+        _hsAbstraction = _fixture.GetService<HomeserverAbstraction>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverAbstraction)}");
+    }
+    
+    [Fact]
+    public async Task GetMembersAsync() {
+        Assert.True(StateEvent.KnownStateEventTypes is { Count: > 0 }, "StateEvent.KnownStateEventTypes is empty!");
+        Assert.True(StateEvent.KnownStateEventTypesByName is { Count: > 0 }, "StateEvent.KnownStateEventTypesByName is empty!");
+
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        var members = room.GetMembersEnumerableAsync();
+        Assert.NotNull(members);
+        var hitMembers = false;
+        await foreach (var member in members) {
+            Assert.NotNull(member);
+            Assert.NotNull(member.StateKey);
+            Assert.NotEmpty(member.StateKey);
+            Assert.NotNull(member.Sender);
+            Assert.NotEmpty(member.Sender);
+            Assert.NotNull(member.RawContent);
+            Assert.NotEmpty(member.RawContent);
+            Assert.NotNull(member.TypedContent);
+            Assert.IsType<RoomMemberEventContent>(member.TypedContent);
+            var content = (RoomMemberEventContent)member.TypedContent;
+            Assert.NotNull(content);
+            Assert.NotNull(content.Membership);
+            Assert.NotEmpty(content.Membership);
+            hitMembers = true;
+        }
+
+        Assert.True(hitMembers, "No members were found in the room");
+        
+        await room.LeaveAsync();
+    }
+
+    [Fact]
+    public async Task JoinAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver(_testOutputHelper);
+        var hs2 = await _hsAbstraction.GetNewHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        await room.SendStateEventAsync(RoomJoinRulesEventContent.EventId, new RoomJoinRulesEventContent() {
+            JoinRule = RoomJoinRulesEventContent.JoinRules.Public
+        });
+        // var id = await room.JoinAsync();
+        var id = await hs2.GetRoom(room.RoomId).JoinAsync();
+        Assert.NotNull(id);
+        Assert.NotNull(id.RoomId);
+        Assert.NotEmpty(id.RoomId);
+        
+        await room.LeaveAsync();
+    }
+
+    [Fact]
+    public async Task ForgetAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        await room.ForgetAsync();
+    }
+
+    [Fact]
+    public async Task LeaveAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        await room.LeaveAsync();
+    }
+
+    [Fact]
+    public async Task KickAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var hs2 = await _hsAbstraction.GetNewHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        await room.InviteUserAsync(hs2.UserId, "Unit test!");
+        await hs2.GetRoom(room.RoomId).JoinAsync();
+        await room.KickAsync(hs2.UserId, "test");
+        var banState = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", hs2.UserId);
+        Assert.NotNull(banState);
+        Assert.Equal("leave", banState.Membership);
+        Assert.Equal("test", banState.Reason);
+        
+        await room.LeaveAsync();
+    }
+
+    [Fact]
+    public async Task BanAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var hs2 = await _hsAbstraction.GetNewHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        await room.BanAsync(hs2.UserId, "test");
+        var banState = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", hs2.UserId);
+        Assert.NotNull(banState);
+        Assert.Equal("ban", banState.Membership);
+        
+        await room.LeaveAsync();
+    }
+
+    [Fact]
+    public async Task UnbanAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var hs2 = await _hsAbstraction.GetNewHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        await room.BanAsync(hs2.UserId, "test");
+        var banState = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", hs2.UserId);
+        Assert.NotNull(banState);
+        Assert.Equal("ban", banState.Membership);
+        await room.UnbanAsync(hs2.UserId, "testing");
+        
+        var unbanState = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", hs2.UserId);
+        Assert.NotNull(unbanState);
+        Assert.Equal("leave", unbanState.Membership);
+        Assert.Equal("testing", unbanState.Reason);
+        
+        await room.LeaveAsync();
+    }
+
+    [Fact]
+    public async Task InviteAndJoinAsync() {
+        int count = 5;
+
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        var otherUsers = _hsAbstraction.GetNewHomeservers(count);
+        Assert.NotNull(room);
+
+        // var expectedCount = 1;
+
+        // var tasks = new List<Task>();
+        // await foreach (var otherUser in otherUsers)
+        // tasks.AddRange([
+        // room.InviteUserAsync(otherUser.UserId),
+        // otherUser.GetRoom(room.RoomId).JoinAsync()
+        // ]);
+
+        Dictionary<AuthenticatedHomeserverGeneric, Task> tasks = new();
+        await foreach (var otherUser in otherUsers) {
+            _testOutputHelper.WriteLine($"Inviting {otherUser.UserId} to {room.RoomId}");
+            tasks.Add(otherUser, room.InviteUserAsync(otherUser.UserId, "Unit test!"));
+        }
+
+        await foreach (var otherUser in tasks.ToAsyncEnumerable()) {
+            _testOutputHelper.WriteLine($"Joining {otherUser.UserId} to {room.RoomId}");
+            await otherUser.GetRoom(room.RoomId).JoinAsync(reason: "Unit test!");
+        }
+
+        var states = await room.GetMembersListAsync(false);
+        Assert.Equal(count + 1, states.Count);
+        
+        await room.LeaveAsync();
+        await foreach (var authenticatedHomeserverGeneric in otherUsers)
+        {
+            await authenticatedHomeserverGeneric.GetRoom(room.RoomId).LeaveAsync();
+        }
+    }
+}
\ No newline at end of file
diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs
new file mode 100644
index 0000000..401b24f
--- /dev/null
+++ b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs
@@ -0,0 +1,320 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Text;
+using ArcaneLibs.Extensions;
+using LibMatrix.EventTypes.Spec;
+using LibMatrix.EventTypes.Spec.State;
+using LibMatrix.Homeservers;
+using LibMatrix.Responses;
+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 RoomTests : TestBed<TestFixture> {
+    private readonly HomeserverAbstraction _hsAbstraction;
+
+    public RoomTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
+        _hsAbstraction = _fixture.GetService<HomeserverAbstraction>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverAbstraction)}");
+    }
+
+    [Fact]
+    public async Task GetJoinedRoomsAsync() {
+        var hs = await _hsAbstraction.GetNewHomeserver();
+        //make 100 rooms
+        var createRoomTasks = Enumerable.Range(0, 10).Select(_ => RoomAbstraction.GetTestRoom(hs)).ToList();
+        await Task.WhenAll(createRoomTasks);
+
+        var rooms = await hs.GetJoinedRooms();
+        Assert.NotNull(rooms);
+        Assert.NotEmpty(rooms);
+        Assert.All(rooms, Assert.NotNull);
+        // Assert.True(rooms.Count >= 10, "Not enough rooms were found");
+        Assert.Equal(10, rooms.Count);
+        await hs.Logout();
+    }
+
+    [Fact]
+    public async Task GetMembersAsync() {
+        Assert.True(StateEvent.KnownStateEventTypes is { Count: > 0 }, "StateEvent.KnownStateEventTypes is empty!");
+        Assert.True(StateEvent.KnownStateEventTypesByName is { Count: > 0 }, "StateEvent.KnownStateEventTypesByName is empty!");
+
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+        var members = room.GetMembersEnumerableAsync();
+        Assert.NotNull(members);
+        var hitMembers = false;
+        await foreach (var member in members) {
+            Assert.NotNull(member);
+            Assert.NotNull(member.StateKey);
+            Assert.NotEmpty(member.StateKey);
+            Assert.NotNull(member.Sender);
+            Assert.NotEmpty(member.Sender);
+            Assert.NotNull(member.RawContent);
+            Assert.NotEmpty(member.RawContent);
+            Assert.NotNull(member.TypedContent);
+            Assert.IsType<RoomMemberEventContent>(member.TypedContent);
+            var content = (RoomMemberEventContent)member.TypedContent;
+            Assert.NotNull(content);
+            Assert.NotNull(content.Membership);
+            Assert.NotEmpty(content.Membership);
+            hitMembers = true;
+        }
+
+        Assert.True(hitMembers, "No members were found in the room");
+
+        await room.LeaveAsync();
+    }
+
+    [SkippableFact(typeof(MatrixException))]
+    public async Task SendStateEventAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+
+        await room.SendStateEventAsync("gay.rory.libmatrix.unit_tests", new UserProfileResponse() {
+            DisplayName = "wee_woo",
+            AvatarUrl = "no"
+        });
+        await room.SendStateEventAsync("gay.rory.libmatrix.unit_tests", "state_key_maybe", new UserProfileResponse() {
+            DisplayName = "wee_woo",
+            AvatarUrl = "yes"
+        });
+
+        await room.LeaveAsync();
+    }
+
+    [SkippableFact(typeof(MatrixException))]
+    public async Task SendAndGetStateEventAsync() {
+        await SendStateEventAsync();
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+
+        await room.SendStateEventAsync("gay.rory.libmatrix.unit_tests", new UserProfileResponse() {
+            DisplayName = "wee_woo",
+            AvatarUrl = "no"
+        });
+        await room.SendStateEventAsync("gay.rory.libmatrix.unit_tests", "state_key_maybe", new UserProfileResponse() {
+            DisplayName = "wee_woo",
+            AvatarUrl = "yes"
+        });
+
+        var state1 = await room.GetStateAsync<UserProfileResponse>("gay.rory.libmatrix.unit_tests");
+        Assert.NotNull(state1);
+        Assert.NotNull(state1.DisplayName);
+        Assert.NotEmpty(state1.DisplayName);
+        Assert.NotNull(state1.AvatarUrl);
+        Assert.NotEmpty(state1.AvatarUrl);
+        Assert.Equal("wee_woo", state1.DisplayName);
+        Assert.Equal("no", state1.AvatarUrl);
+
+        var state2 = await room.GetStateAsync<UserProfileResponse>("gay.rory.libmatrix.unit_tests", "state_key_maybe");
+        Assert.NotNull(state2);
+        Assert.NotNull(state2.DisplayName);
+        Assert.NotEmpty(state2.DisplayName);
+        Assert.NotNull(state2.AvatarUrl);
+        Assert.NotEmpty(state2.AvatarUrl);
+        Assert.Equal("wee_woo", state2.DisplayName);
+        Assert.Equal("yes", state2.AvatarUrl);
+
+        await room.LeaveAsync();
+    }
+
+    [Fact]
+    public async Task DisbandAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+
+        await room.PermanentlyBrickRoomAsync();
+
+        await room.LeaveAsync();
+    }
+
+    [Fact]
+    public async Task SendFileAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+
+        var res = await room.SendFileAsync("test.txt", new MemoryStream(Encoding.UTF8.GetBytes("This test was written by Emma [it/its], member of the Rory& system." +
+                                                                                               "\nIf you are reading this on matrix, it means the unit test for uploading a file works!")));
+        Assert.NotNull(res);
+        Assert.NotNull(res.EventId);
+
+        await room.LeaveAsync();
+    }
+
+    [Fact]
+    public async Task GetFullStateAsListAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+
+        var state = await room.GetFullStateAsListAsync();
+        Assert.NotNull(state);
+        Assert.NotEmpty(state);
+        Assert.All(state, Assert.NotNull);
+        Assert.All(state, s => {
+            Assert.NotNull(s.EventId);
+            Assert.NotEmpty(s.EventId);
+            Assert.NotNull(s.Sender);
+            Assert.NotEmpty(s.Sender);
+            Assert.NotNull(s.RawContent);
+            Assert.NotNull(s.TypedContent);
+        });
+
+        await room.LeaveAsync();
+    }
+
+    [SkippableFact(typeof(LibMatrixException))]
+    public async Task GetStateEventAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+
+        var state = await room.GetStateEventAsync("m.room.name");
+        Assert.NotNull(state);
+        Assert.NotNull(state.EventId);
+        Assert.NotEmpty(state.EventId);
+        Assert.NotNull(state.Sender);
+        Assert.NotEmpty(state.Sender);
+        Assert.NotNull(state.RawContent);
+        Assert.NotEmpty(state.RawContent);
+        Assert.NotNull(state.TypedContent);
+
+        await room.LeaveAsync();
+    }
+
+    [Fact]
+    public async Task GetStateEventIdAsync() {
+        var hs = await _hsAbstraction.GetNewHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+
+        var state = await room.GetStateEventIdAsync("m.room.name");
+        Assert.NotNull(state);
+        Assert.NotEmpty(state);
+
+        await room.LeaveAsync();
+    }
+
+    [SkippableFact(typeof(LibMatrixException))]
+    public async Task GetStateEventOrNullAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+
+        var state = await room.GetStateEventOrNullAsync("m.room.name");
+        Assert.NotNull(state);
+        Assert.NotNull(state.EventId);
+        Assert.NotEmpty(state.EventId);
+        Assert.NotNull(state.Sender);
+        Assert.NotEmpty(state.Sender);
+        Assert.NotNull(state.RawContent);
+        Assert.NotEmpty(state.RawContent);
+        Assert.NotNull(state.TypedContent);
+
+        await room.LeaveAsync();
+    }
+
+    [Fact]
+    public async Task GetMessagesAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+
+        var messages = await room.GetMessagesAsync();
+        Assert.NotNull(messages);
+        Assert.NotNull(messages.Chunk);
+        Assert.NotEmpty(messages.Chunk);
+        Assert.All(messages.Chunk, Assert.NotNull);
+        Assert.All(messages.Chunk, m => {
+            Assert.NotNull(m.EventId);
+            Assert.NotEmpty(m.EventId);
+            Assert.NotNull(m.Sender);
+            Assert.NotEmpty(m.Sender);
+            Assert.NotNull(m.RawContent);
+            Assert.NotNull(m.TypedContent);
+        });
+
+        await room.LeaveAsync();
+        
+        await File.WriteAllTextAsync("test.json", messages.ToJson());
+    }
+
+    [Fact]
+    public async Task GetManyMessagesAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+
+        var messages = room.GetManyMessagesAsync(chunkSize: 2);
+        await foreach (var resp in messages) {
+            Assert.NotNull(resp);
+            Assert.NotNull(resp.Chunk);
+            // Assert.NotEmpty(resp.Chunk);
+            Assert.All(resp.Chunk, Assert.NotNull);
+            Assert.All(resp.Chunk, m => {
+                Assert.NotNull(m.EventId);
+                Assert.NotEmpty(m.EventId);
+                Assert.NotNull(m.Sender);
+                Assert.NotEmpty(m.Sender);
+                Assert.NotNull(m.RawContent);
+                Assert.NotNull(m.TypedContent);
+            });
+        }
+
+        await room.LeaveAsync();
+    }
+    
+    [Fact]
+    public async Task SendMessageEventAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+
+        var res = await room.SendMessageEventAsync(new RoomMessageEventContent(body: "This test was written by Emma [it/its], member of the Rory& system." +
+                                                                                     "\nIf you are reading this on matrix, it means the unit test for sending a message works!", messageType: "m.text"));
+        Assert.NotNull(res);
+        Assert.NotNull(res.EventId);
+
+        await room.LeaveAsync();
+    }
+    
+    [Fact]
+    public async Task InviteUsersAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var room = await RoomAbstraction.GetTestRoom(hs);
+        Assert.NotNull(room);
+
+        var users = _hsAbstraction.GetNewHomeservers(32).ToBlockingEnumerable().ToList();
+        Assert.NotNull(users);
+        Assert.NotEmpty(users);
+        Assert.All(users, Assert.NotNull);
+        Assert.All(users, u => {
+            Assert.NotNull(u);
+            Assert.NotNull(u.UserId);
+            Assert.NotEmpty(u.UserId);
+        });
+        
+        await room.InviteUsersAsync(users.Select(u => u.UserId));
+        var members = await room.GetMembersListAsync(false);
+        Assert.NotNull(members);
+        Assert.NotEmpty(members);
+        Assert.All(members, Assert.NotNull);
+        Assert.All(members, m => {
+            Assert.NotNull(m);
+            Assert.NotNull(m.StateKey);
+            Assert.NotEmpty(m.StateKey);
+        });
+        Assert.All(users, u => Assert.Contains(u.UserId, members.Select(m => m.StateKey)));
+        
+        await room.LeaveAsync();
+    }
+}
\ No newline at end of file
diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/SpaceTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/SpaceTests.cs
new file mode 100644
index 0000000..148b5fe
--- /dev/null
+++ b/Tests/LibMatrix.Tests/Tests/RoomTests/SpaceTests.cs
@@ -0,0 +1,101 @@
+using System.Diagnostics;
+using System.Text;
+using ArcaneLibs.Extensions;
+using LibMatrix.EventTypes.Spec.State;
+using LibMatrix.Homeservers;
+using LibMatrix.Responses;
+using LibMatrix.RoomTypes;
+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 SpaceTests : TestBed<TestFixture> {
+    private readonly HomeserverAbstraction _hsAbstraction;
+
+    public SpaceTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
+        _hsAbstraction = _fixture.GetService<HomeserverAbstraction>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverAbstraction)}");
+    }
+
+    [Fact]
+    public async Task AddChildAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var crq = new CreateRoomRequest() {
+            Name = "Test space"
+        };
+        crq.CreationContent["type"] = SpaceRoom.TypeName;
+        var space = (await hs.CreateRoom(crq)).AsSpace;
+
+        var child = await hs.CreateRoom(new CreateRoomRequest() {
+            Name = "Test child"
+        });
+
+        await space.AddChildAsync(child);
+
+        //validate children
+        var children = space.GetChildrenAsync().ToBlockingEnumerable().ToList();
+        Assert.NotNull(children);
+        Assert.NotEmpty(children);
+        Assert.Single(children, x => x.RoomId == child.RoomId);
+    }
+
+    [Fact]
+    public async Task AddChildByIdAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var crq = new CreateRoomRequest() {
+            Name = "Test space"
+        };
+        crq.CreationContent["type"] = SpaceRoom.TypeName;
+        var space = (await hs.CreateRoom(crq)).AsSpace;
+
+        var child = await hs.CreateRoom(new CreateRoomRequest() {
+            Name = "Test child"
+        });
+
+        await space.AddChildByIdAsync(child.RoomId);
+        
+        //validate children
+        var children = space.GetChildrenAsync().ToBlockingEnumerable().ToList();
+        Assert.NotNull(children);
+        Assert.NotEmpty(children);
+        Assert.Single(children, x => x.RoomId == child.RoomId);
+    }
+    
+    [Fact]
+    public async Task GetChildrenAsync() {
+        var hs = await _hsAbstraction.GetConfiguredHomeserver();
+        var expectedChildren = Enumerable.Range(0, 10).Select(async _ => {
+            var room = await hs.CreateRoom(new CreateRoomRequest() {
+                Name = "Test child"
+            });
+            return room;
+        }).ToAsyncEnumerable().ToBlockingEnumerable().ToList();
+        
+        var crq = new CreateRoomRequest() {
+            Name = "Test space",
+            InitialState = expectedChildren.Select(c => new StateEvent() {
+                Type = "m.space.child",
+                StateKey = c.RoomId,
+                TypedContent = new SpaceChildEventContent() {
+                    Via = new List<string> {
+                        c.RoomId.Split(":")[1]
+                    }
+                }
+            }).ToList()
+        };
+        crq.CreationContent["type"] = SpaceRoom.TypeName;
+        var space = (await hs.CreateRoom(crq)).AsSpace;
+
+        var children = space.GetChildrenAsync().ToBlockingEnumerable().ToList();
+        Assert.NotNull(children);
+        Assert.NotEmpty(children);
+        Assert.Equal(expectedChildren.Count, children.Count);
+        foreach (var expectedChild in expectedChildren)
+        {
+            Assert.Single(children, x => x.RoomId == expectedChild.RoomId);
+        }
+    }
+}
\ No newline at end of file
diff --git a/Tests/LibMatrix.Tests/Tests/TestCleanup.cs b/Tests/LibMatrix.Tests/Tests/TestCleanup.cs
index 7fc7c64..1c5747c 100644
--- a/Tests/LibMatrix.Tests/Tests/TestCleanup.cs
+++ b/Tests/LibMatrix.Tests/Tests/TestCleanup.cs
@@ -1,74 +1,69 @@
-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 HomeserverAbstraction _hsAbstraction;
+//     private readonly ILogger<TestCleanup> _logger;
+//
+//     public TestCleanup(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
+//         // _fixture = fixture;
+//         _logger = _fixture.GetService<ILogger<TestCleanup>>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(ILogger<TestCleanup>)}");
+//         _hsAbstraction = _fixture.GetService<HomeserverAbstraction>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverAbstraction)}");
+//     }
+//
+//     [SkippableFact(typeof(MatrixException))]
+//     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 _hsAbstraction.GetConfiguredHomeserver();
+//         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 tasks2 = response.Rooms.Leave.Select(async room => {
+//                     await hs.GetRoom(room.Key).ForgetAsync();
+//                     return room;
+//                 }).ToList();
+//                 await Task.WhenAll(tasks2);
+//             }
+//         });
+//         await syncHelper.RunSyncLoopAsync(cancellationToken: cancellationTokenSource.Token);
+//
+//         Assert.NotNull(hs);
+//         await hs.Logout();
+//     }
+// }
\ No newline at end of file