Some basic unit tests

This commit is contained in:
Rory&
2026-06-05 15:48:02 +02:00
parent 2597118ba4
commit 31ca17d536
14 changed files with 437 additions and 0 deletions
+3
View File
@@ -25,6 +25,9 @@
<Project Path="Models/Spacebar.Models.Gateway/Spacebar.Models.Gateway.csproj" />
<Project Path="Models/Spacebar.Models.Generic/Spacebar.Models.Generic.csproj" />
</Folder>
<Folder Name="/Tests/">
<Project Path="Tests/Spacebar.Tests/Spacebar.Tests.csproj" />
</Folder>
<Folder Name="/Utilities/">
<Project Path="Utilities/ConfigTest/ConfigTest.csproj" />
<Project Path="Utilities/DiscordEmojiConverter/DiscordEmojiConverter.csproj" />
@@ -0,0 +1,24 @@
using Spacebar.Sdk.Core;
namespace Spacebar.Tests.Abstractions;
public class UserAbstraction(Config _config, SpacebarClientProviderService _clientProvider) {
public async Task<AuthenticatedSpacebarClient> GetFreshUser(bool withAutojoinGuilds = false) {
var ua = await _clientProvider.GetUnauthenticatedClientAsync(_config.TestInstance);
var tokenResponse = await ua.RegisterAsync(new() {
Email = $"{Guid.NewGuid().ToString()}@{Guid.NewGuid().ToString()}.tld",
Username = Guid.NewGuid().ToString(),
Password = Guid.NewGuid().ToString(),
DateOfBirth = new(),
Consent = true
});
var client = await _clientProvider.GetAuthenticatedClientAsync(_config.TestInstance, tokenResponse.Token);
if (!withAutojoinGuilds) {
}
return client;
}
}
@@ -0,0 +1,11 @@
using Microsoft.Extensions.Configuration;
namespace Spacebar.Tests;
public class Config {
public Config(IConfiguration? config) {
config.GetSection("Configuration").Bind(this);
}
public string TestInstance { get; set; }
}
@@ -0,0 +1,15 @@
namespace Spacebar.Tests.Extensions;
public static class AssertExtensions {
extension(Assert) {
public static void StringNotNullOrEmpty(string? str) {
Assert.NotNull(str);
Assert.NotEqual("", str);
}
public static void StringNotNullOrWhitespace(string? str) {
StringNotNullOrEmpty(str);
Assert.Matches(".+", str);
}
}
}
@@ -0,0 +1,38 @@
using System.Net.Http.Json;
using System.Text.Json.Nodes;
namespace Spacebar.Tests.Extensions;
public static class AssertHttpExtensions {
private static readonly HttpClient Hc = new();
public static async Task<string> GetFormattedErrorDetails(HttpResponseMessage res) {
return res.Content.Headers.ContentType?.MediaType == "application/json"
? (await res.Content.ReadFromJsonAsync<JsonObject>())!.ToJsonString(new() {
WriteIndented = true
})
: await res.Content.ReadAsStringAsync();
}
extension(Assert) {
public static async Task<HttpResponseMessage> SuccessfullyHttpGetAsync(string url) {
var res = await Hc.GetAsync(url);
Assert.True(res.IsSuccessStatusCode, $"Could not get {url}: {res.StatusCode}\n{await GetFormattedErrorDetails(res)}");
return res;
}
public static async Task<HttpResponseMessage> SuccessfullyHttpPostAsJsonAsync<TValue>(string url, TValue obj) {
var res = await Hc.PostAsJsonAsync(url, obj);
if (!res.IsSuccessStatusCode)
Assert.True(res.IsSuccessStatusCode, $"Could not POST JSON to {url}: {(int)res.StatusCode} {res.StatusCode}\n{await GetFormattedErrorDetails(res)}");
return res;
}
public static async Task<HttpResponseMessage> HttpSuccess(HttpResponseMessage res) {
if (!res.IsSuccessStatusCode)
Assert.True(res.IsSuccessStatusCode,
$"Could not {res.RequestMessage!.Method.Method.ToUpper()} to {res.RequestMessage!.RequestUri!.ToString()}: {(int)res.StatusCode} {res.StatusCode}\n{await GetFormattedErrorDetails(res)}");
return res;
}
}
}
@@ -0,0 +1,29 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Spacebar.Sdk.Core;
using Spacebar.Tests.Abstractions;
using Xunit.Microsoft.DependencyInjection;
using Xunit.Microsoft.DependencyInjection.Abstracts;
namespace Spacebar.Tests.Fixtures;
public class TestFixture : TestBedFixture {
protected override void AddServices(IServiceCollection services, IConfiguration configuration) {
services.AddSingleton(configuration);
services.AddLogging();
services.AddSingleton<Config>();
services.AddSingleton<UserAbstraction>();
services.AddSingleton<SpacebarClientWellKnownResolverService>();
services.AddSingleton<SpacebarClientProviderService>();
}
protected override ValueTask DisposeAsyncCore()
=> new();
protected override IEnumerable<TestAppSettings> GetTestAppSettings() {
yield return new TestAppSettings { Filename = "appsettings.json", IsOptional = true };
}
}
@@ -0,0 +1 @@
global using Xunit;
@@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.8" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="Xunit.Microsoft.DependencyInjection" Version="10.0.4" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="10.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="xunit.v3" Version="3.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Models\Spacebar.Models.AdminApi\Spacebar.Models.AdminApi.csproj" />
<ProjectReference Include="..\..\Models\Spacebar.Models.Api\Spacebar.Models.Api.csproj" />
<ProjectReference Include="..\..\Models\Spacebar.Models.Config\Spacebar.Models.Config.csproj" />
<ProjectReference Include="..\..\Models\Spacebar.Models.Db\Spacebar.Models.Db.csproj" />
<ProjectReference Include="..\..\Models\Spacebar.Models.Gateway\Spacebar.Models.Gateway.csproj" />
<ProjectReference Include="..\..\Models\Spacebar.Models.Generic\Spacebar.Models.Generic.csproj" />
<ProjectReference Include="..\..\Utilities\Spacebar.Sdk\Spacebar.Sdk.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings*.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
@@ -0,0 +1,95 @@
using System.Net.Http.Json;
using System.Text.Json.Nodes;
using ArcaneLibs.Extensions;
using Spacebar.Models.Api;
using Spacebar.Sdk.Core;
using Spacebar.Tests.Extensions;
using Spacebar.Tests.Fixtures;
using Xunit.Microsoft.DependencyInjection.Abstracts;
namespace Spacebar.Tests.Tests;
public class AuthenticationTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : TestBed<TestFixture>(testOutputHelper, fixture) {
private readonly Config _config = fixture.GetService<Config>(testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}");
private readonly SpacebarClientWellKnownResolverService _wellKnownResolver = fixture.GetService<SpacebarClientWellKnownResolverService>(testOutputHelper) ??
throw new InvalidOperationException(
$"Failed to get {nameof(SpacebarClientWellKnownResolverService)}");
private readonly SpacebarClientProviderService _clientProvider = fixture.GetService<SpacebarClientProviderService>(testOutputHelper) ??
throw new InvalidOperationException($"Failed to get {nameof(SpacebarClientProviderService)}");
[Fact]
public async Task RegisterUser() {
var res = await Assert.SuccessfullyHttpPostAsJsonAsync($"{_config.TestInstance}/api/v9/auth/register", new RegisterRequest() {
Email = $"{Guid.NewGuid().ToString()}@{Guid.NewGuid().ToString()}.tld",
Username = Guid.NewGuid().ToString(),
Password = Guid.NewGuid().ToString(),
DateOfBirth = new(),
Consent = true
});
}
[Fact]
public async Task ConcurrentRegister50Users() {
var tasks = Enumerable.Range(0, 50).Select(async _ => {
var rr = new RegisterRequest() {
Email = $"{Guid.NewGuid().ToString()}@{Guid.NewGuid().ToString()}.tld",
Username = Guid.NewGuid().ToString(),
Password = "password",
DateOfBirth = new(),
Consent = true
};
return (rr, await Assert.SuccessfullyHttpPostAsJsonAsync($"{_config.TestInstance}/api/v9/auth/register", rr));
}).ToList();
await Task.WhenAll(tasks);
testOutputHelper.WriteLine("Waiting for server to settle...");
await Task.Delay(2500, TestContext.Current.CancellationToken);
testOutputHelper.WriteLine("Cleaning up users...");
var cleanupTasks = tasks.Select(x => x.Result).Select(async res => {
var resp = await res.Item2.Content.ReadFromJsonAsync<RegisterResponse>();
var c = await _clientProvider.GetAuthenticatedClientAsync(_config.TestInstance, resp.Token);
var dresp = (await c.ApiHttpClient.PostAsJsonAsync("/api/v9/users/@me/delete", new JsonObject() {
{ "password", "password" }
}, cancellationToken: TestContext.Current.CancellationToken));
// TODO: figure out why this fails with "invalid password"
if (!dresp.IsSuccessStatusCode)
testOutputHelper.WriteLine("Failed to delete user: " + await AssertHttpExtensions.GetFormattedErrorDetails(dresp));
}).ToList();
await Task.WhenAll(cleanupTasks);
}
[Fact]
public async Task LoginUser() {
var rr = new RegisterRequest() {
Email = $"{Guid.NewGuid().ToString()}@{Guid.NewGuid().ToString()}.tld",
Username = Guid.NewGuid().ToString(),
Password = Guid.NewGuid().ToString(),
DateOfBirth = new(),
Consent = true
};
var rrRes = await Assert.SuccessfullyHttpPostAsJsonAsync($"{_config.TestInstance}/api/v9/auth/register", rr);
var loginRes = await Assert.SuccessfullyHttpPostAsJsonAsync($"{_config.TestInstance}/api/v9/auth/login", new LoginRequest() {
Login = rr.Email,
Password = rr.Password
});
}
[Fact]
public async Task WhoAmI() {
var rr = new RegisterRequest() {
Email = $"{Guid.NewGuid().ToString()}@{Guid.NewGuid().ToString()}.tld",
Username = Guid.NewGuid().ToString(),
Password = Guid.NewGuid().ToString(),
DateOfBirth = new(),
Consent = true
};
var rrRes = await Assert.SuccessfullyHttpPostAsJsonAsync($"{_config.TestInstance}/api/v9/auth/register", rr);
var res = await rrRes.Content.ReadFromJsonAsync<RegisterResponse>();
var client = await _clientProvider.GetAuthenticatedClientAsync(_config.TestInstance, res.Token);
var waRes = await Assert.HttpSuccess(await client.ApiHttpClient.GetAsync("/api/v9/auth/whoami"));
// TODO: finish test once model exists
}
}
@@ -0,0 +1,44 @@
using Spacebar.Sdk.Core;
using Spacebar.Tests.Extensions;
using Spacebar.Tests.Fixtures;
using Xunit.Microsoft.DependencyInjection.Abstracts;
namespace Spacebar.Tests.Tests;
public class BasicWellKnownTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : TestBed<TestFixture>(testOutputHelper, fixture) {
private readonly Config _config = fixture.GetService<Config>(testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}");
private readonly SpacebarClientWellKnownResolverService _wellKnownResolver = fixture.GetService<SpacebarClientWellKnownResolverService>(testOutputHelper) ??
throw new InvalidOperationException($"Failed to get {nameof(Config)}");
[Fact]
public async Task ValidateTestConfig() {
Assert.NotNull(_config.TestInstance);
Assert.NotEmpty(_config.TestInstance);
}
[Fact]
public async Task CanReachInstance() => await Assert.SuccessfullyHttpGetAsync($"{_config.TestInstance}/api/v9/ping");
[Fact]
public async Task CanGetOldWellknown() {
await Assert.SuccessfullyHttpGetAsync($"{_config.TestInstance}/.well-known/spacebar");
await Assert.SuccessfullyHttpGetAsync($"{_config.TestInstance}/api/v9/policies/instance/domains");
}
[Fact]
public async Task CanGetNewWellknown() => await Assert.SuccessfullyHttpGetAsync($"{_config.TestInstance}/.well-known/spacebar/client");
[Fact]
public async Task SdkCanGetWellKnown() {
testOutputHelper.WriteLine("instance: " + _config.TestInstance);
var res = await _wellKnownResolver.ResolveClientWellKnown(_config.TestInstance);
Assert.StringNotNullOrWhitespace(res.Api.BaseUrl);
Assert.StringNotNullOrWhitespace(res.Cdn.BaseUrl);
Assert.StringNotNullOrWhitespace(res.Gateway.BaseUrl);
Assert.NotEmpty(res.Api.ApiVersions.Active);
Assert.StringNotNullOrWhitespace(res.Api.ApiVersions.Default);
Assert.NotEmpty(res.Gateway.Compression);
Assert.NotEmpty(res.Gateway.Encoding);
}
}
@@ -0,0 +1,65 @@
using System.Net.Http.Json;
using Spacebar.Models.Generic;
using Spacebar.Sdk.Core;
using Spacebar.Tests.Abstractions;
using Spacebar.Tests.Extensions;
using Spacebar.Tests.Fixtures;
using Xunit.Microsoft.DependencyInjection.Abstracts;
namespace Spacebar.Tests.Tests;
public class ChannelTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : TestBed<TestFixture>(testOutputHelper, fixture) {
private readonly Config _config = fixture.GetService<Config>(testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}");
private readonly SpacebarClientWellKnownResolverService _wellKnownResolver = fixture.GetService<SpacebarClientWellKnownResolverService>(testOutputHelper) ??
throw new InvalidOperationException(
$"Failed to get {nameof(SpacebarClientWellKnownResolverService)}");
private readonly SpacebarClientProviderService _clientProvider = fixture.GetService<SpacebarClientProviderService>(testOutputHelper) ??
throw new InvalidOperationException($"Failed to get {nameof(SpacebarClientProviderService)}");
private readonly UserAbstraction _userAbstraction = fixture.GetService<UserAbstraction>(testOutputHelper) ??
throw new InvalidOperationException($"Failed to get {nameof(SpacebarClientProviderService)}");
[Fact]
public async Task CreateChannel() {
var client = await _userAbstraction.GetFreshUser();
var guild = await client.CreateGuild(new() {
Name = "Test guild"
});
Assert.Equal("Test guild", guild.Name);
var channel = await client.GetGuild(guild.Id).CreateChannelAsync(new() {
Name = "test",
Type = 0
});
Assert.Equal("test", channel.Name);
}
[Fact]
public async Task GetChannel() {
var client = await _userAbstraction.GetFreshUser();
var guild = await client.CreateGuild(new() {
Name = "Test guild"
});
Assert.Equal("Test guild", guild.Name);
var channel = await client.GetGuild(guild.Id).CreateChannelAsync(new() {
Name = "test",
Type = 0
});
Assert.Equal("test", channel.Name);
var res = await client.ApiHttpClient.GetAsync("channels/" + channel.Id, TestContext.Current.CancellationToken);
await Assert.HttpSuccess(res);
var channelResp = await res.Content.ReadFromJsonAsync<Channel>(cancellationToken: TestContext.Current.CancellationToken);
Assert.Equal(channel.Name, channelResp!.Name);
Assert.Equal(channel.Id, channelResp!.Id);
}
}
@@ -0,0 +1,47 @@
using Spacebar.Sdk.Core;
using Spacebar.Tests.Abstractions;
using Spacebar.Tests.Extensions;
using Spacebar.Tests.Fixtures;
using Xunit.Microsoft.DependencyInjection.Abstracts;
namespace Spacebar.Tests.Tests;
public class GuildTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : TestBed<TestFixture>(testOutputHelper, fixture) {
private readonly Config _config = fixture.GetService<Config>(testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}");
private readonly SpacebarClientWellKnownResolverService _wellKnownResolver = fixture.GetService<SpacebarClientWellKnownResolverService>(testOutputHelper) ??
throw new InvalidOperationException(
$"Failed to get {nameof(SpacebarClientWellKnownResolverService)}");
private readonly SpacebarClientProviderService _clientProvider = fixture.GetService<SpacebarClientProviderService>(testOutputHelper) ??
throw new InvalidOperationException($"Failed to get {nameof(SpacebarClientProviderService)}");
private readonly UserAbstraction _userAbstraction = fixture.GetService<UserAbstraction>(testOutputHelper) ??
throw new InvalidOperationException($"Failed to get {nameof(SpacebarClientProviderService)}");
[Fact]
public async Task CreateGuild() {
var client = await _userAbstraction.GetFreshUser();
var guild = await client.CreateGuild(new() {
Name = "Test guild"
});
Assert.Equal("Test guild", guild.Name);
}
[Fact]
public async Task GetChannels() {
var client = await _userAbstraction.GetFreshUser();
var guild = await client.CreateGuild(new() {
Name = "Test guild"
});
Assert.Equal("Test guild", guild.Name);
var channels = await client.GetGuild(guild.Id).GetChannelsAsync();
Assert.NotEmpty(channels);
foreach (var channel in channels) {
Assert.StringNotNullOrWhitespace(channel.Name);
}
}
}
@@ -0,0 +1,16 @@
using Spacebar.Tests.Abstractions;
using Spacebar.Tests.Extensions;
using Spacebar.Tests.Fixtures;
using Xunit.Microsoft.DependencyInjection.Abstracts;
namespace Spacebar.Tests.Tests.Meta;
public class UserAbstractionTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : TestBed<TestFixture>(testOutputHelper, fixture) {
private readonly UserAbstraction _config = fixture.GetService<UserAbstraction>(testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(UserAbstraction)}");
[Fact]
public async Task CanGetUser() {
var res = await _config.GetFreshUser();
Assert.StringNotNullOrWhitespace(res.ApiHttpClient.BaseAddress!.ToString());
}
}
@@ -0,0 +1,5 @@
{
"Configuration": {
"TestInstance": "http://localhost:3001"
}
}