using System.Buffers.Binary; using WebDrop.Core; namespace WebDrop.Core.Tests; public class Id16Tests { [Fact] public void RoundTrips() { var id = TestEnv.NewId(); Assert.True(Id16.TryParse(id.ToBase64Url(), out var parsed)); Assert.Equal(id, parsed); Assert.Equal(22, id.ToBase64Url().Length); Assert.Equal(32, id.ToHex().Length); } [Theory] [InlineData("")] [InlineData("short")] [InlineData("AAAAAAAAAAAAAAAAAAAAAAA")] // 23 字符 [InlineData("AAAAAAAAAAAAAAAAAAAA+/")] // 非 url 字母表 public void RejectsMalformed(string text) => Assert.False(Id16.TryParse(text, out _)); [Fact] public void RejectsNonCanonicalEncoding() { // 末字符的低 2 位在 16 字节编码中必须为 0;AAAAAAAAAAAAAAAAAAAAAB 解码后与 ...AA 同值 Assert.True(Id16.TryParse("AAAAAAAAAAAAAAAAAAAAAA", out var zero)); Assert.False(Id16.TryParse("AAAAAAAAAAAAAAAAAAAAAB", out _)); Assert.Equal(new byte[16], zero.ToArray()); } } public class EnvelopeTests { [Fact] public void ParsesValidHeader() { var item = TestEnv.NewId(); var bytes = TestEnv.Frame(item, 7, FrameFlags.Final | FrameFlags.Gated | FrameFlags.Enc, 1040); Assert.True(Envelope.TryParse(bytes, out var env)); Assert.Equal(item, env.ItemId); Assert.Equal(7u, env.FrameIndex); Assert.Equal(1040, env.PayloadLength); Assert.True(env.IsFinal && env.IsGated && env.IsEnc && !env.IsSnapshot); Assert.Equal(FrameFlags.Gated, env.ItemFlags); } [Fact] public void RejectsBadVersion() { var bytes = TestEnv.Frame(TestEnv.NewId(), 0, FrameFlags.Final, 4); bytes[0] = 2; Assert.False(Envelope.TryParse(bytes, out _)); } [Fact] public void RejectsReservedFlagBits() { var bytes = TestEnv.Frame(TestEnv.NewId(), 0, FrameFlags.Final, 4); bytes[1] |= 0x10; Assert.False(Envelope.TryParse(bytes, out _)); } [Fact] public void RejectsLengthMismatch() { var bytes = TestEnv.Frame(TestEnv.NewId(), 0, FrameFlags.Final, 8); Assert.False(Envelope.TryParse(bytes.AsSpan(0, bytes.Length - 1), out _)); Assert.False(Envelope.TryParse([.. bytes, 0], out _)); Assert.False(Envelope.TryParse(bytes.AsSpan(0, 25), out _)); } [Fact] public void RejectsHugePayloadLength() { var bytes = TestEnv.Frame(TestEnv.NewId(), 0, FrameFlags.Final, 4); BinaryPrimitives.WriteUInt32BigEndian(bytes.AsSpan(22), uint.MaxValue); Assert.False(Envelope.TryParse(bytes, out _)); } [Theory] [InlineData(1024 + 16, true)] [InlineData(4096 + 16, true)] [InlineData(1048576 + 16, true)] [InlineData(1024, false)] [InlineData(16, false)] [InlineData(1000 + 16, false)] [InlineData(0, false)] public void EncPayloadMustBeTierPlusTag(int length, bool valid) => Assert.Equal(valid, PayloadTiers.IsValidEncPayloadLength(length)); } public class AuthTests { [Fact] public void TokenHashMatchesOnlyTheSameToken() { var token = TestEnv.Random(32); var hash = TokenHash.Compute(token); Assert.True(TokenHash.Matches(token, hash)); Assert.False(TokenHash.Matches(TestEnv.Random(32), hash)); Assert.False(TokenHash.Matches(token, null)); Assert.False(TokenHash.Matches(token, hash[..16])); } } public class PolicyTests { private static Channel Chan(ChannelSettings s, bool writeHash = true) => new( TestEnv.NewId(), ChannelMode.Enc, s, new byte[32], writeHash ? new byte[32] : null, new byte[32], 0, 0, 0, 0, 0); private static Item ItemBy(byte[] author) => new( TestEnv.NewId(), TestEnv.NewId(), author, FrameFlags.None, 0, 0, 0, -1, null, 0, 0, 0, null, null); [Fact] public void WritePermission() { var ep = TestEnv.Endpoint(); Assert.True(ChannelPolicy.CanWrite(Chan(new()), new Principal(Role.Owner, ep))); Assert.True(ChannelPolicy.CanWrite(Chan(new()), new Principal(Role.Write, ep))); Assert.False(ChannelPolicy.CanWrite(Chan(new()), new Principal(Role.Read, ep))); Assert.False(ChannelPolicy.CanWrite(Chan(new() { MembersCanUpload = false }), new Principal(Role.Write, ep))); Assert.False(ChannelPolicy.CanWrite(Chan(new(), writeHash: false), new Principal(Role.Write, ep))); Assert.True(ChannelPolicy.CanWrite(Chan(new() { MembersCanUpload = false }), new Principal(Role.Owner, ep))); } [Theory] [InlineData(DeletePolicy.OwnerOnly, Role.Write, true, false)] [InlineData(DeletePolicy.OwnerOnly, Role.Owner, false, true)] [InlineData(DeletePolicy.Own, Role.Write, true, true)] [InlineData(DeletePolicy.Own, Role.Write, false, false)] [InlineData(DeletePolicy.Own, Role.Owner, false, true)] [InlineData(DeletePolicy.Anyone, Role.Read, false, true)] public void DeletePermission(DeletePolicy policy, Role role, bool isAuthor, bool expected) { var me = TestEnv.Endpoint(); var item = ItemBy(isAuthor ? me : TestEnv.Endpoint()); var channel = Chan(new() { DeletePolicy = policy }); Assert.Equal(expected, ChannelPolicy.CanDeleteItem(channel, item, new Principal(role, me))); } [Fact] public void SettingsPatchOnlyChangesProvidedFields() { var before = new ChannelSettings { Seats = 2, TtlSeconds = 100 }; var after = new ChannelSettingsPatch { Seats = 5 }.ApplyTo(before); Assert.Equal(5, after.Seats); Assert.Equal(100, after.TtlSeconds); } [Theory] [InlineData(0, 86400, 10)] [InlineData(999, 86400, 10)] [InlineData(2, 10, 10)] [InlineData(2, 99999999, 10)] [InlineData(2, 86400, 0)] public void SettingsValidationRejectsOutOfRange(int seats, int ttl, int maxItems) { var s = new ChannelSettings { Seats = seats, TtlSeconds = ttl, MaxItems = maxItems }; var ex = Assert.Throws(() => s.Validate(new ServerLimits())); Assert.Equal(ErrorCode.BadRequest, ex.Code); } }