Files
WebDrop/server/tests/WebDrop.Core.Tests/TestEnv.cs
T

108 lines
4.3 KiB
C#

using System.Buffers.Binary;
using System.Security.Cryptography;
using WebDrop.Core;
using WebDrop.Storage;
namespace WebDrop.Core.Tests;
public sealed class FakeClock : IClock
{
public long NowMs { get; set; } = 1_700_000_000_000;
public void Advance(TimeSpan span) => NowMs += (long)span.TotalMilliseconds;
}
public sealed class RecordingEvents : IChannelEvents
{
public List<(Id16 Channel, long Seq)> Advances { get; } = [];
public int Settings, Seats, Deleted;
public void Advanced(Id16 channelId, long seq) { lock (Advances) Advances.Add((channelId, seq)); }
public void SettingsChanged(Id16 channelId) => Interlocked.Increment(ref Settings);
public void SeatsChanged(Id16 channelId) => Interlocked.Increment(ref Seats);
public void ChannelDeleted(Id16 channelId) => Interlocked.Increment(ref Deleted);
}
/// <summary>一个频道的全部凭据,模拟客户端从 K / A 派生出的令牌。</summary>
public sealed class TestChannel(Id16 id, ChannelMode mode, byte[] read, byte[] write, byte[] owner, byte[] ownerEndpoint)
{
public Id16 Id { get; } = id;
public ChannelMode Mode { get; } = mode;
public byte[] ReadToken { get; } = read;
public byte[] WriteToken { get; } = write;
public byte[] OwnerToken { get; } = owner;
public Credentials Owner(byte[] endpoint) => new(Role.Owner, OwnerToken, endpoint, "owner");
public Credentials Member(byte[] endpoint) => new(Role.Write, WriteToken, endpoint, endpoint.AsSpan().SequenceEqual(ownerEndpoint) ? "owner" : null);
public Credentials Reader(byte[] endpoint) => new(Role.Read, ReadToken, endpoint, endpoint.AsSpan().SequenceEqual(ownerEndpoint) ? "owner" : null);
}
public sealed class TestEnv : IDisposable
{
public string Dir { get; } = Path.Combine(Path.GetTempPath(), "webdrop-tests-" + Guid.NewGuid().ToString("N"));
public FakeClock Clock { get; } = new();
public RecordingEvents Events { get; } = new();
public ServerLimits Limits { get; } = new();
public SqliteChannelStore Store { get; }
public DiskBlobStore Blobs { get; }
public ChannelService Service { get; private set; }
public TestEnv()
{
Directory.CreateDirectory(Dir);
Store = new SqliteChannelStore(Path.Combine(Dir, "test.db"));
Blobs = new DiskBlobStore(Path.Combine(Dir, "blobs"));
Service = new ChannelService(Store, Blobs, Clock, Limits, Events);
}
public static byte[] Random(int n) => RandomNumberGenerator.GetBytes(n);
public static byte[] Endpoint() => TokenHash.Compute(Random(32));
public static Id16 NewId() => Id16.FromBytes(Random(16));
public TestChannel Create(
byte[] ownerEndpoint,
ChannelMode mode = ChannelMode.Enc,
ChannelSettings? settings = null,
bool withWriteHash = true)
{
var id = NewId();
var read = Random(32);
var write = Random(32);
var owner = Random(32);
settings ??= new ChannelSettings();
settings.Name ??= "Test space";
Service.CreateChannel(new CreateChannelRequest
{
Id = id.ToBase64Url(),
Mode = mode,
Settings = settings,
ReadHash = mode == ChannelMode.Enc ? Codec.Encode(TokenHash.Compute(read)) : null,
WriteHash = withWriteHash ? Codec.Encode(TokenHash.Compute(write)) : null,
OwnerHash = Codec.Encode(TokenHash.Compute(owner)),
}, ownerEndpoint, "owner");
return new TestChannel(id, mode, read, write, owner, ownerEndpoint);
}
/// <summary>构造一个封装。enc 模式的负载长度必须是 档位+16。</summary>
public static byte[] Frame(Id16 item, uint index, FrameFlags flags, int payloadLength, byte fill = 0x42)
{
var bytes = new byte[Envelope.HeaderSize + payloadLength];
bytes[0] = Envelope.Version;
bytes[1] = (byte)flags;
item.WriteTo(bytes.AsSpan(2));
BinaryPrimitives.WriteUInt32BigEndian(bytes.AsSpan(18), index);
BinaryPrimitives.WriteUInt32BigEndian(bytes.AsSpan(22), (uint)payloadLength);
bytes.AsSpan(Envelope.HeaderSize).Fill(fill);
return bytes;
}
public const int EncPayload = 1024 + 16;
public const FrameFlags SmallEnc = FrameFlags.Final | FrameFlags.Enc;
public void Dispose()
{
Store.Dispose();
try { Directory.Delete(Dir, recursive: true); } catch (IOException) { }
}
}