feat: integrate client and server sharing workflows
This commit is contained in:
1 parent
42b93ae2d8
commit
7406e2fe37
101 files changed
+3153
-253
No files matched your search
@@ -114,6 +114,26 @@ public class ChannelServiceTests : IDisposable
|
||||
await AssertCode(ErrorCode.NotJoined, () => LogOf(ch, ch.Reader(_memberEp)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ItemIdsAndFramesAreScopedToTheirChannel()
|
||||
{
|
||||
var otherOwnerEndpoint = TestEnv.Endpoint();
|
||||
var first = _env.Create(_ownerEp);
|
||||
var second = _env.Create(otherOwnerEndpoint);
|
||||
var sharedItemId = TestEnv.NewId();
|
||||
var firstOnlyItemId = TestEnv.NewId();
|
||||
await AppendSmall(first, first.Owner(_ownerEp), sharedItemId, fill: 0x11);
|
||||
await AppendSmall(second, second.Owner(otherOwnerEndpoint), sharedItemId, fill: 0x22);
|
||||
await AppendSmall(first, first.Owner(_ownerEp), firstOnlyItemId, fill: 0x33);
|
||||
|
||||
var firstFrame = (await Svc.ReadItemFramesAsync(first.Id, first.Owner(_ownerEp), sharedItemId, 0, 10)).Frames.Single();
|
||||
var secondFrame = (await Svc.ReadItemFramesAsync(second.Id, second.Owner(otherOwnerEndpoint), sharedItemId, 0, 10)).Frames.Single();
|
||||
Assert.Equal(0x11, firstFrame[^1]);
|
||||
Assert.Equal(0x22, secondFrame[^1]);
|
||||
await AssertCode(ErrorCode.NotFound,
|
||||
() => Svc.ReadItemFramesAsync(second.Id, second.Owner(otherOwnerEndpoint), firstOnlyItemId, 0, 10));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReleasedSeatLosesAccess()
|
||||
{
|
||||
@@ -143,6 +163,22 @@ public class ChannelServiceTests : IDisposable
|
||||
Assert.Equal(2, seats.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OwnerCapabilityCanManageWithoutOccupyingAnotherSeat()
|
||||
{
|
||||
var ch = _env.Create(_ownerEp);
|
||||
Svc.Join(ch.Id, ch.Reader(_memberEp));
|
||||
var unseatedOwner = ch.Owner(TestEnv.Endpoint());
|
||||
|
||||
// Owner management uses A; an owner on a new device can manage a full channel without first taking a seat.
|
||||
Assert.Equal(ch.Id.ToBase64Url(), Svc.GetChannel(ch.Id, unseatedOwner).Id);
|
||||
Assert.Equal(3, Svc.UpdateSettings(ch.Id, unseatedOwner, new ChannelSettingsPatch { Seats = 3 }).Settings.Seats);
|
||||
Assert.Equal(2, Svc.ListSeats(ch.Id, unseatedOwner).Count);
|
||||
AssertCode(ErrorCode.NotFound, () => Svc.ReleaseSeat(ch.Id, unseatedOwner, TestEnv.Endpoint()));
|
||||
Svc.DeleteChannel(ch.Id, unseatedOwner);
|
||||
AssertCode(ErrorCode.NotFound, () => Svc.GetChannel(ch.Id, ch.Owner(_ownerEp)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SeatLabelIsStoredAndBounded()
|
||||
{
|
||||
@@ -361,6 +397,7 @@ public class ChannelServiceTests : IDisposable
|
||||
await Append(ch, o, TestEnv.Frame(item, 0, FrameFlags.Gated | FrameFlags.Enc, TestEnv.EncPayload));
|
||||
await Append(ch, o, TestEnv.Frame(item, 2, FrameFlags.Gated | FrameFlags.Enc, TestEnv.EncPayload)); // 乱序到达,缺 1
|
||||
|
||||
Svc.StartItemDownload(ch.Id, o, item, 0);
|
||||
var chunk = await Svc.ReadItemFramesAsync(ch.Id, o, item, 0, 10);
|
||||
Assert.Single(chunk.Frames);
|
||||
Assert.Equal(1u, chunk.NextIndex);
|
||||
@@ -403,7 +440,7 @@ public class ChannelServiceTests : IDisposable
|
||||
Assert.Single((await Svc.ReadItemFramesAsync(ch.Id, ch.Reader(_memberEp), item, 1, 10)).Frames);
|
||||
|
||||
// 宽限期过后拒绝,清理任务删除密文并留下墓碑
|
||||
_env.Clock.Advance(TimeSpan.FromSeconds(_env.Limits.DownloadGraceSeconds + 1));
|
||||
_env.Clock.Advance(TimeSpan.FromSeconds(_env.Limits.DownloadGraceSeconds));
|
||||
AssertCode(ErrorCode.Gone, () => Svc.StartItemDownload(ch.Id, ch.Reader(_memberEp), item, 1));
|
||||
|
||||
Assert.Equal(1, Svc.Sweep());
|
||||
@@ -412,6 +449,29 @@ public class ChannelServiceTests : IDisposable
|
||||
AssertCode(ErrorCode.NotFound, () => Svc.StartItemDownload(ch.Id, o, item, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResumeNeedsACountedDownloadFromTheSameEndpoint()
|
||||
{
|
||||
var otherEndpoint = TestEnv.Endpoint();
|
||||
var ch = _env.Create(_ownerEp, settings: new ChannelSettings { Seats = 3, FileMaxDownloads = 1 });
|
||||
Svc.Join(ch.Id, ch.Reader(_memberEp));
|
||||
Svc.Join(ch.Id, ch.Reader(otherEndpoint));
|
||||
var item = await UploadGated(ch, ch.Owner(_ownerEp), 2);
|
||||
var otherItem = await UploadGated(ch, ch.Owner(_ownerEp), 2);
|
||||
var reader = ch.Reader(_memberEp);
|
||||
|
||||
AssertCode(ErrorCode.Gone, () => Svc.StartItemDownload(ch.Id, reader, item, 1));
|
||||
await AssertCode(ErrorCode.Gone, () => Svc.ReadItemFramesAsync(ch.Id, reader, item, 1, 10));
|
||||
AssertCode(ErrorCode.Gone, () => Svc.StartItemDownload(ch.Id, ch.Reader(otherEndpoint), item, 1));
|
||||
AssertCode(ErrorCode.Gone, () => Svc.StartItemDownload(ch.Id, reader, otherItem, 1));
|
||||
|
||||
Svc.StartItemDownload(ch.Id, reader, item, 0);
|
||||
Svc.StartItemDownload(ch.Id, reader, item, 1);
|
||||
var resumed = await Svc.ReadItemFramesAsync(ch.Id, reader, item, 1, 10);
|
||||
Assert.Single(resumed.Frames);
|
||||
Assert.Equal(2u, resumed.NextIndex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ZeroMaxDownloadsMeansUnlimitedAndHeaderOverridesDefault()
|
||||
{
|
||||
@@ -506,9 +566,16 @@ public class ChannelServiceTests : IDisposable
|
||||
await AppendSmall(ch, o, old);
|
||||
|
||||
_env.Clock.Advance(TimeSpan.FromHours(12));
|
||||
await AppendSmall(ch, o); // 续期频道,新条目 TTL 从现在起算
|
||||
var fresh = TestEnv.NewId();
|
||||
await AppendSmall(ch, o, fresh); // 续期频道,新条目 TTL 从现在起算
|
||||
|
||||
_env.Clock.Advance(TimeSpan.FromHours(13)); // 第一个条目已过期,第二个还在
|
||||
var beforeCleanup = await Svc.ReadLogAsync(ch.Id, o, 0, 100);
|
||||
Assert.Single(beforeCleanup.Records);
|
||||
Assert.Equal(fresh.ToArray(), beforeCleanup.Records[0].Bytes[(9 + 2)..(9 + 2 + 16)]);
|
||||
AssertCode(ErrorCode.Gone, () => Svc.StartItemDownload(ch.Id, o, old, 0));
|
||||
await AssertCode(ErrorCode.Gone, () => Svc.ReadItemFramesAsync(ch.Id, o, old, 0, 10));
|
||||
|
||||
Assert.Equal(1, Svc.Sweep());
|
||||
|
||||
var log = await Svc.ReadLogAsync(ch.Id, o, 0, 100);
|
||||
|
||||
Reference in new issue
Block a user