mirror of
https://github.com/spacebarchat/server.git
synced 2026-07-17 17:22:18 +00:00
Basic gateway tests
This commit is contained in:
+2
-1
@@ -10,6 +10,7 @@ assets/cache
|
||||
config.json
|
||||
assets/cacheMisses
|
||||
http-client.private.env.json
|
||||
.nixos-test-history
|
||||
|
||||
.vscode/settings.json
|
||||
|
||||
@@ -34,4 +35,4 @@ schemas_final/
|
||||
|
||||
temp/
|
||||
!/extra/admin-api/Utilities/Spacebar.AdminApi.TestClient/wwwroot/lib/bootstrap/dist/
|
||||
*.qcow2
|
||||
*.qcow2
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="appsettings*.json">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using ArcaneLibs.Extensions;
|
||||
using Spacebar.Models.Gateway;
|
||||
using Spacebar.Models.Generic;
|
||||
using Spacebar.Sdk.Core;
|
||||
using Spacebar.Tests.Abstractions;
|
||||
using Spacebar.Tests.Extensions;
|
||||
using Spacebar.Tests.Fixtures;
|
||||
using Xunit.Internal;
|
||||
using Xunit.Microsoft.DependencyInjection.Abstracts;
|
||||
|
||||
namespace Spacebar.Tests.Tests;
|
||||
|
||||
public class GatewayTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : TestBed<TestFixture>(testOutputHelper, fixture), IAsyncLifetime {
|
||||
private readonly Config _config = fixture.GetRequiredService<Config>(testOutputHelper);
|
||||
private readonly UserAbstraction _userAbstraction = fixture.GetRequiredService<UserAbstraction>(testOutputHelper);
|
||||
|
||||
private static AuthenticatedSpacebarClient Client { get; set; } = null!;
|
||||
|
||||
public async ValueTask InitializeAsync() {
|
||||
testOutputHelper.WriteLine("Running InitializeAsync");
|
||||
// All these tests can share a single client
|
||||
Client = await _userAbstraction.GetSharedUser();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanConnect() {
|
||||
await Client.Gateway.Connect();
|
||||
await Client.Gateway.Disconnect();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReceiveReady() {
|
||||
Client.Gateway.OnceGatewayMessage.Add(async payload => {
|
||||
if (payload is { Opcode: GatewayOpcode.S2CDispatch, DispatchEventType: "READY" }) {
|
||||
_testOutputHelper.WriteLine("Success: {0} {1} ({2} data keys)", payload.Opcode, payload.DispatchEventType, payload.EventData!.Count);
|
||||
await Client.Gateway.Disconnect();
|
||||
return true;
|
||||
}
|
||||
|
||||
_testOutputHelper.WriteLine("Received message: {0}", payload.ToJson(indent: false));
|
||||
return false;
|
||||
});
|
||||
// Client.Gateway.TraceGatewayMessages = true;
|
||||
await Client.Gateway.Connect();
|
||||
await Client.Gateway.Start();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReceiveReadySupplemental() {
|
||||
Client.Gateway.OnceGatewayMessage.Add(async payload => {
|
||||
if (payload is { Opcode: GatewayOpcode.S2CDispatch, DispatchEventType: "READY_SUPPLEMENTAL" }) {
|
||||
_testOutputHelper.WriteLine("Success: {0} {1} ({2} data keys)", payload.Opcode, payload.DispatchEventType, payload.EventData!.Count);
|
||||
await Client.Gateway.Disconnect();
|
||||
return true;
|
||||
}
|
||||
|
||||
_testOutputHelper.WriteLine("Received message: {0} {1} ({2} data keys)", payload.Opcode, payload.DispatchEventType, payload.EventData?.Count);
|
||||
return false;
|
||||
});
|
||||
// Client.Gateway.TraceGatewayMessages = true;
|
||||
await Client.Gateway.Connect();
|
||||
await Client.Gateway.Start();
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task CanReceiveHeartbeatAck() {
|
||||
Client.Gateway.OnceGatewayMessage.Add(async payload => {
|
||||
if (payload is { Opcode: GatewayOpcode.S2CHeartbeatAck }) {
|
||||
_testOutputHelper.WriteLine("Success: {0} {1} ({2} data keys)", payload.Opcode, payload.DispatchEventType, payload.EventData!.Count);
|
||||
await Client.Gateway.Disconnect();
|
||||
return true;
|
||||
}
|
||||
|
||||
_testOutputHelper.WriteLine("Received message: {0} {1} ({2} data keys)", payload.Opcode, payload.DispatchEventType, payload.EventData?.Count);
|
||||
return false;
|
||||
});
|
||||
// Client.Gateway.TraceGatewayMessages = true;
|
||||
await Client.Gateway.Connect();
|
||||
await Client.Gateway.Start();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SensibleHello() {
|
||||
Client.Gateway.OnceGatewayMessage.Add(async payload => {
|
||||
if (payload is { Opcode: GatewayOpcode.S2CHello }) {
|
||||
_testOutputHelper.WriteLine("Success: {0} {1} ({2} data keys)", payload.Opcode, payload.DispatchEventType, payload.EventData!.Count);
|
||||
var data = payload.GetData<HelloResponse>();
|
||||
await Client.Gateway.Disconnect();
|
||||
|
||||
Assert.NotEqual(0, data.HeartbeatInterval);
|
||||
Assert.True(data.HeartbeatInterval > 1000, "data.HeartbeatInterval > 1000");
|
||||
return true;
|
||||
}
|
||||
|
||||
_testOutputHelper.WriteLine("Received message: {0} {1} ({2} data keys)", payload.Opcode, payload.DispatchEventType, payload.EventData?.Count);
|
||||
return false;
|
||||
});
|
||||
// Client.Gateway.TraceGatewayMessages = true;
|
||||
await Client.Gateway.Connect();
|
||||
await Client.Gateway.Start();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using ArcaneLibs.Extensions;
|
||||
using Spacebar.Models.Api;
|
||||
using Spacebar.Models.Gateway;
|
||||
using Spacebar.Models.Generic;
|
||||
using Spacebar.Sdk.Core;
|
||||
using Spacebar.Tests.Abstractions;
|
||||
@@ -222,4 +223,29 @@ public class MessageTests(ITestOutputHelper testOutputHelper, TestFixture fixtur
|
||||
var msg = json.Deserialize<Message>();
|
||||
Assert.Equal(content, msg.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ShouldNotMessageUpdateForLinklessMessage() {
|
||||
Client.Gateway.OnceGatewayMessage.Add(async payload => {
|
||||
if (payload is { Opcode: GatewayOpcode.S2CDispatch, DispatchEventType: "READY" }) {
|
||||
_testOutputHelper.WriteLine("Got ready: {0} {1} ({2} data keys)", payload.Opcode, payload.DispatchEventType, payload.EventData!.Count);
|
||||
await Channel.SendMessageAsync(new MessageSchema() {
|
||||
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload is { Opcode: GatewayOpcode.S2CDispatch, DispatchEventType: "MESSAGE_UPDATE" }) {
|
||||
_testOutputHelper.WriteLine("Got MESSAGE_UPDATE: {0} {1} ({2} data keys)", payload.Opcode, payload.DispatchEventType, payload.EventData!.Count);
|
||||
Assert.False(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
_testOutputHelper.WriteLine("Received message: {0} {1} ({2} data keys)", payload.Opcode, payload.DispatchEventType, payload.EventData?.Count);
|
||||
return false;
|
||||
});
|
||||
// Client.Gateway.TraceGatewayMessages = true;
|
||||
await Client.Gateway.Connect();
|
||||
await Client.Gateway.Start();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http.Json;
|
||||
using System.Net.WebSockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
@@ -128,6 +129,10 @@ public class SpacebarClientChannel(AuthenticatedSpacebarClient client, long chan
|
||||
if (!resp.IsSuccessStatusCode) throw SpacebarApiException.FromJson((await resp.Content.ReadFromJsonAsync<JsonObject>())!);
|
||||
return (await resp.Content.ReadFromJsonAsync<Channel>())!;
|
||||
}
|
||||
|
||||
public async Task SendMessageAsync(MessageSchema messageSchema) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class SpacebarClientGuild(AuthenticatedSpacebarClient client, long guildId) {
|
||||
@@ -171,6 +176,7 @@ public class AuthenticatedSpacebarGatewayClient(ILogger<AuthenticatedSpacebarGat
|
||||
public ClientWebSocket RawClientWebSocket = new();
|
||||
public int Sequence;
|
||||
public bool TraceGatewayMessages = false;
|
||||
private CancellationTokenSource _cts = new CancellationTokenSource();
|
||||
|
||||
public IdentifyRequest IdentifyData { get; } = new() {
|
||||
Intents = (GatewayIntentFlags?)0xFFFFFFFF, // too lazy to do math, just gimme everything
|
||||
@@ -182,15 +188,22 @@ public class AuthenticatedSpacebarGatewayClient(ILogger<AuthenticatedSpacebarGat
|
||||
|
||||
public async Task Connect() {
|
||||
if (RawClientWebSocket.State is WebSocketState.Connecting or WebSocketState.Open) return;
|
||||
_cts = new CancellationTokenSource();
|
||||
Sequence = 0;
|
||||
RawClientWebSocket = new();
|
||||
await RawClientWebSocket.ConnectAsync(new Uri(wellKnown.Gateway.BaseUrl).AddQuery("encoding", "json"), CancellationToken.None);
|
||||
}
|
||||
|
||||
public async Task Disconnect() {
|
||||
await _cts.CancelAsync();
|
||||
await RawClientWebSocket.CloseAsync(closeStatus: WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
|
||||
}
|
||||
|
||||
public async Task Start() {
|
||||
await foreach (var msg in _runReceiveLoop()) {
|
||||
await foreach (var msg in _runReceiveLoop(_cts.Token)) {
|
||||
// logger.LogInformation("Got gateway message: {msg}", msg);
|
||||
if (msg.Opcode == GatewayOpcode.S2CHello) {
|
||||
_ = _runHeartbeatLoop(msg.GetData<HelloResponse>()!.HeartbeatInterval).ContinueWith(ct => {
|
||||
_ = _runHeartbeatLoop(msg.GetData<HelloResponse>()!.HeartbeatInterval, _cts.Token).ContinueWith(ct => {
|
||||
logger.LogWarning("Heartbeat loop exited!");
|
||||
if (ct.IsFaulted) throw ct.Exception;
|
||||
});
|
||||
@@ -226,7 +239,7 @@ public class AuthenticatedSpacebarGatewayClient(ILogger<AuthenticatedSpacebarGat
|
||||
}
|
||||
}
|
||||
|
||||
private async Task _runHeartbeatLoop(int interval) {
|
||||
private async Task _runHeartbeatLoop(int interval, CancellationToken ct) {
|
||||
while (RawClientWebSocket.State < WebSocketState.Closed) {
|
||||
await RawClientWebSocket.SendAsync(JsonSerializer.SerializeToUtf8Bytes(new GatewayPayload() {
|
||||
Opcode = GatewayOpcode.C2SQoSHeartbeat, // QoS Heartbeat
|
||||
@@ -238,23 +251,33 @@ public class AuthenticatedSpacebarGatewayClient(ILogger<AuthenticatedSpacebarGat
|
||||
Version = 27
|
||||
}
|
||||
}.ToJsonNode().AsObject()
|
||||
}), WebSocketMessageType.Text, WebSocketMessageFlags.EndOfMessage, CancellationToken.None);
|
||||
await Task.Delay(interval);
|
||||
}), WebSocketMessageType.Text, WebSocketMessageFlags.EndOfMessage, ct);
|
||||
await Task.Delay(interval, ct);
|
||||
}
|
||||
}
|
||||
|
||||
private const int ReceiveBufferSize = 2 * 1024 * 1024;
|
||||
|
||||
private async IAsyncEnumerable<GatewayPayload> _runReceiveLoop() {
|
||||
private async IAsyncEnumerable<GatewayPayload> _runReceiveLoop([EnumeratorCancellation] CancellationToken ct) {
|
||||
List<byte> messageParts = [];
|
||||
List<(string Name, TimeSpan Elapsed)> trace = [];
|
||||
var buffer = new byte[ReceiveBufferSize];
|
||||
int idx = 0;
|
||||
|
||||
while (RawClientWebSocket.State < WebSocketState.Closed) {
|
||||
while (RawClientWebSocket.State < WebSocketState.CloseSent) {
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
var msg = await RawClientWebSocket.ReceiveAsync(buffer, CancellationToken.None);
|
||||
WebSocketReceiveResult msg;
|
||||
try {
|
||||
msg = await RawClientWebSocket.ReceiveAsync(buffer, ct);
|
||||
}
|
||||
catch (TaskCanceledException) {
|
||||
yield break;
|
||||
}
|
||||
catch (InvalidOperationException) {
|
||||
yield break;
|
||||
}
|
||||
|
||||
trace.Add(($"RCV.{idx}", sw.GetElapsedAndRestart()));
|
||||
|
||||
Console.WriteLine($"Websocket message chunk read: {msg.MessageType} {msg.Count} {msg.EndOfMessage}");
|
||||
|
||||
Reference in New Issue
Block a user