Retire legacy implementations and flatten managed layout
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
using VoiceCat.Server.Data;
|
||||
using Voicecat.V1;
|
||||
using static VoiceCat.Tests.ServerTests;
|
||||
using static VoiceCat.Tests.ChannelManagementTests;
|
||||
using static VoiceCat.Tests.MediaRelayTests;
|
||||
|
||||
namespace VoiceCat.Tests;
|
||||
|
||||
public class AdministrationTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task AccountAdministrationIsPermissionGatedAndPersistsPasswordChanges()
|
||||
{
|
||||
await using var fixture = new ServerFixture();
|
||||
await using var guest = await fixture.ConnectAsync();
|
||||
User user = await guest.LoginAsync("Guest");
|
||||
await using var admin = await AdminAsync(fixture);
|
||||
Assert.False(await ResultAsync(guest, new() { ListAccounts = new() }));
|
||||
Assert.False(await ResultAsync(guest, new() { CreateAccount = new() { Username = "new", Password = "secret" } }));
|
||||
Assert.False(await ResultAsync(guest, new() { SetPermission = new() { UserId = user.Id, Permissions = new() { IsAdmin = true } } }));
|
||||
Assert.True(await ResultAsync(admin, new() { SetPermission = new() { UserId = user.Id, Permissions = new() { CanAdminAccounts = true, CanCreateTempChannel = true } } }));
|
||||
Assert.True(await ResultAsync(guest, new() { CreateAccount = new() { Username = "new", Password = "first" } }));
|
||||
Assert.False(await ResultAsync(guest, new() { CreateAccount = new() { Username = "new", Password = "first" } }));
|
||||
Assert.False(await ResultAsync(guest, new() { SetPermission = new() { UserId = user.Id, Permissions = new() { IsAdmin = true } } }));
|
||||
Assert.False(await ResultAsync(guest, new() { CreateChannel = new() { Channel = new() { Name = "Permanent" } } }));
|
||||
Assert.True(await ResultAsync(guest, new() { CreateChannel = new() { Channel = new() { Name = "Temporary", Type = ChannelType.ChannelTemporary,
|
||||
Audio = new() { SampleRate = 48000, BitrateBps = 24000, FrameMs = 20 } } } }));
|
||||
Assert.True(await ResultAsync(guest, new() { ResetPassword = new() { Username = "new", NewPassword = "second" } }));
|
||||
Assert.False(await ResultAsync(guest, new() { ResetPassword = new() { Username = "missing", NewPassword = "second" } }));
|
||||
using (var store = new AccountStore(Path.Combine(fixture.Directory, "voicecat.db")))
|
||||
{
|
||||
Assert.Null(await store.AuthenticateAsync("new", "first"));
|
||||
Assert.NotNull(await store.AuthenticateAsync("new", "second"));
|
||||
}
|
||||
guest.Send(new() { RequestId = 50, ListAccounts = new() });
|
||||
Envelope list = await guest.ReadUntilAsync(e => e.ListAccountsResult is not null);
|
||||
Assert.Equal(50UL, list.RequestId);
|
||||
Assert.Equal(2, list.ListAccountsResult.Accounts.Count);
|
||||
var entry = list.ListAccountsResult.Accounts.Single(a => a.Username == "new");
|
||||
Assert.False(entry.IsAdmin);
|
||||
Assert.True(entry.CreatedAtUnixMs > 1_000_000_000_000);
|
||||
Assert.True(entry.LastLoginUnixMs > 1_000_000_000_000);
|
||||
Assert.True(await ResultAsync(guest, new() { DeleteAccount = new() { Username = "new" } }));
|
||||
Assert.False(await ResultAsync(guest, new() { DeleteAccount = new() { Username = "new" } }));
|
||||
using var reopened = new AccountStore(Path.Combine(fixture.Directory, "voicecat.db"));
|
||||
Assert.Null(await reopened.AuthenticateAsync("new", "second"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AccountBanPersistsByUsernameAndBlocksNewAuthentication()
|
||||
{
|
||||
await using var fixture = new ServerFixture();
|
||||
await using var admin = await AdminAsync(fixture);
|
||||
using var store = new AccountStore(Path.Combine(fixture.Directory, "voicecat.db"));
|
||||
await store.CreateAccountAsync("Member", "password");
|
||||
await using var member = await fixture.ConnectAsync();
|
||||
member.Send(new() { ClientHello = new() { ProtoVersion = 2 } });
|
||||
await member.ReadUntilAsync(e => e.ServerHello is not null);
|
||||
member.Send(new() { AuthRequest = new() { Password = new() { Username = "Member", Password = "password" } } });
|
||||
AuthResult auth = (await member.ReadUntilAsync(e => e.AuthResult is not null)).AuthResult;
|
||||
Assert.True(auth.Ok);
|
||||
Assert.True(await ResultAsync(admin, new() { Ban = new() { UserId = auth.Self.Id, Reason = "account banned" } }));
|
||||
Assert.NotNull((await member.ReadUntilAsync(e => e.Disconnect is not null)).Disconnect);
|
||||
Assert.True(store.IsBanned("username", "Member"));
|
||||
Assert.False(store.IsBanned("ip", "127.0.0.1"));
|
||||
await using var retry = await fixture.ConnectAsync();
|
||||
retry.Send(new() { ClientHello = new() { ProtoVersion = 2 } });
|
||||
await retry.ReadUntilAsync(e => e.ServerHello is not null);
|
||||
retry.Send(new() { AuthRequest = new() { Password = new() { Username = "Member", Password = "password" } } });
|
||||
Assert.False((await retry.ReadUntilAsync(e => e.AuthResult is not null)).AuthResult.Ok);
|
||||
Assert.False(await ResultAsync(admin, new() { Kick = new() { UserId = uint.MaxValue } }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ServerMuteDeafenAndMoveImmediatelyChangeEncryptedMediaRouting()
|
||||
{
|
||||
await using var fixture = new ServerFixture();
|
||||
await using var alice = await VoicePeer.ConnectAsync(fixture, "Alice");
|
||||
await using var bob = await VoicePeer.ConnectAsync(fixture, "Bob");
|
||||
await using var admin = await AdminAsync(fixture);
|
||||
uint a = alice.Client.Authentication!.Self.Id, b = bob.Client.Authentication!.Self.Id;
|
||||
var stream = await alice.AnnounceAsync(StreamKind.StreamMic);
|
||||
Assert.False(await ResultAsync(bob.Client, new() { ServerMute = new() { UserId = a, Muted = true } }));
|
||||
Assert.True(await ResultAsync(admin, new() { ServerMute = new() { UserId = a, Muted = true } }));
|
||||
await alice.SendAsync(alice.Seal(stream.Ssrc, [1])); await bob.AssertNoVoiceAsync();
|
||||
Assert.True(await ResultAsync(admin, new() { ServerMute = new() { UserId = a } }));
|
||||
Assert.True(await ResultAsync(admin, new() { ServerMute = new() { UserId = b, Deafened = true } }));
|
||||
await alice.SendAsync(alice.Seal(stream.Ssrc, [2])); await bob.AssertNoVoiceAsync();
|
||||
Assert.True(await ResultAsync(admin, new() { ServerMute = new() { UserId = b } }));
|
||||
await alice.SendAsync(alice.Seal(stream.Ssrc, [3])); Assert.Equal(new byte[] { 3 }, (await bob.ReceiveVoiceAsync()).Payload);
|
||||
Assert.True(await ResultAsync(admin, new() { MoveUser = new() { UserId = a, ChannelId = 2 } }));
|
||||
await alice.SendAsync(alice.Seal(stream.Ssrc, [4])); await bob.AssertNoVoiceAsync();
|
||||
Assert.True(await ResultAsync(admin, new() { MoveUser = new() { UserId = a, ChannelId = 1 } }));
|
||||
await alice.SendAsync(alice.Seal(stream.Ssrc, [5])); await bob.AssertNoVoiceAsync();
|
||||
var replacement = await alice.AnnounceAsync(StreamKind.StreamMic);
|
||||
await alice.SendAsync(alice.Seal(replacement.Ssrc, [6])); Assert.Equal(new byte[] { 6 }, (await bob.ReceiveVoiceAsync()).Payload);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task KickAndGuestBanDisconnectWithOneDepartureAndRetireMedia(bool ban)
|
||||
{
|
||||
await using var fixture = new ServerFixture();
|
||||
await using var alice = await VoicePeer.ConnectAsync(fixture, "Alice");
|
||||
await using var observer = await VoicePeer.ConnectAsync(fixture, "Observer");
|
||||
await using var admin = await AdminAsync(fixture);
|
||||
uint id = alice.Client.Authentication!.Self.Id;
|
||||
var stream = await alice.AnnounceAsync(StreamKind.StreamMic);
|
||||
Envelope request = ban ? new() { Ban = new() { UserId = id, Reason = "removed", ExpiresUnixMs = (ulong)DateTimeOffset.UtcNow.AddMinutes(1).ToUnixTimeMilliseconds() } }
|
||||
: new() { Kick = new() { UserId = id, Reason = "removed" } };
|
||||
Assert.True(await ResultAsync(admin, request));
|
||||
Assert.Equal("removed", (await alice.Client.ReadUntilAsync(e => e.Disconnect is not null)).Disconnect.Reason);
|
||||
var left = (await observer.Client.ReadUntilAsync(e => e.UserEvent?.LeftId == id)).UserEvent;
|
||||
Assert.Equal("removed", left.Reason);
|
||||
observer.Client.Send(new() { Ping = new() { Nonce = 99 } });
|
||||
while (true)
|
||||
{
|
||||
Envelope message = await observer.Client.ReadUntilAsync(_ => true);
|
||||
Assert.False(message.UserEvent?.LeftId == id);
|
||||
if (message.Pong?.Nonce == 99) break;
|
||||
}
|
||||
await alice.SendAsync(alice.Seal(stream.Ssrc, [1])); await observer.AssertNoVoiceAsync();
|
||||
await using var reconnect = await fixture.ConnectAsync();
|
||||
if (ban)
|
||||
{
|
||||
reconnect.Send(new() { ClientHello = new() { ProtoVersion = 2 } });
|
||||
Assert.NotNull((await reconnect.ReadUntilAsync(e => e.Disconnect is not null)).Disconnect);
|
||||
using var store = new AccountStore(Path.Combine(fixture.Directory, "voicecat.db"));
|
||||
Assert.True(store.IsBanned("ip", "127.0.0.1"));
|
||||
store.Ban("username", "expired", "", 1);
|
||||
Assert.False(store.IsBanned("username", "expired"));
|
||||
}
|
||||
else await reconnect.LoginAsync("Alice");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user