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
@@ -1,5 +1,7 @@
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using WebDrop.Api;
|
||||
using WebDrop.Core;
|
||||
using static WebDrop.Api.Tests.ServerFixture;
|
||||
|
||||
@@ -17,6 +19,7 @@ public class RestTests(ServerFixture s) : IClassFixture<ServerFixture>
|
||||
|
||||
var json = await ReadJsonAsync(resp);
|
||||
Assert.Equal(1, json.GetProperty("protocol").GetInt32());
|
||||
Assert.Equal(ApiContract.Version, json.GetProperty("contract_version").GetString());
|
||||
Assert.True(json.GetProperty("transports").GetProperty("websocket").GetBoolean());
|
||||
Assert.Equal(1048592, json.GetProperty("limits").GetProperty("max_payload_bytes").GetInt32());
|
||||
// 默认不启用验证码,不下发 Cap 的地址
|
||||
@@ -89,6 +92,31 @@ public class RestTests(ServerFixture s) : IClassFixture<ServerFixture>
|
||||
await AssertErrorAsync(await s.AppendAsync(ch, ch.Member, Small(NewId())), 403, "not_joined");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OwnerCapabilityCanManageWithoutOccupyingAnotherSeat()
|
||||
{
|
||||
var ch = await s.CreateAsync();
|
||||
using (var joined = await s.JoinAsync(ch, ch.Member)) Assert.Equal(HttpStatusCode.OK, joined.StatusCode);
|
||||
var unseatedOwner = new Who("owner", ch.OwnerToken, RandomNumberGenerator.GetBytes(32));
|
||||
async Task<HttpResponseMessage> OwnerRequest(HttpMethod method, string path, HttpContent? body = null)
|
||||
{
|
||||
using var request = s.Req(method, path, unseatedOwner, body);
|
||||
request.Headers.Remove("X-WD-Endpoint");
|
||||
return await s.Http.SendAsync(request);
|
||||
}
|
||||
|
||||
using (var patch = await OwnerRequest(HttpMethod.Patch, $"/api/v1/channels/{ch.Id}/settings", JsonBody(new { seats = 3 })))
|
||||
Assert.Equal(HttpStatusCode.OK, patch.StatusCode);
|
||||
using (var seats = await OwnerRequest(HttpMethod.Get, $"/api/v1/channels/{ch.Id}/seats"))
|
||||
Assert.Equal(HttpStatusCode.OK, seats.StatusCode);
|
||||
using (var absentSeat = await OwnerRequest(HttpMethod.Delete,
|
||||
$"/api/v1/channels/{ch.Id}/seats/{B64(RandomNumberGenerator.GetBytes(32))}"))
|
||||
await AssertErrorAsync(absentSeat, 404, "not_found");
|
||||
using (var deleted = await OwnerRequest(HttpMethod.Delete, $"/api/v1/channels/{ch.Id}"))
|
||||
Assert.Equal(HttpStatusCode.NoContent, deleted.StatusCode);
|
||||
await AssertErrorAsync(await s.Http.SendAsync(s.Req(HttpMethod.Get, $"/api/v1/channels/{ch.Id}", ch.Owner)), 404, "not_found");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AppendIsIdempotentOverHttp()
|
||||
{
|
||||
@@ -287,6 +315,7 @@ public class RestTests(ServerFixture s) : IClassFixture<ServerFixture>
|
||||
public async Task DeleteChannelRemovesIt()
|
||||
{
|
||||
var ch = await s.CreateAsync();
|
||||
using (var joined = await s.JoinAsync(ch, ch.Member)) Assert.Equal(HttpStatusCode.OK, joined.StatusCode);
|
||||
await s.AppendAsync(ch, ch.Owner, Small(NewId()));
|
||||
|
||||
await AssertErrorAsync(await s.Http.SendAsync(s.Req(HttpMethod.Delete, $"/api/v1/channels/{ch.Id}", ch.Member)), 403, "forbidden");
|
||||
|
||||
@@ -119,6 +119,37 @@ public sealed class ShareTests(ServerFixture server) : IClassFixture<ServerFixtu
|
||||
await AssertErrorAsync(shareCollision, 409, "conflict");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResumeWithoutAnInitialDownloadCannotBypassShareDownloadLimit()
|
||||
{
|
||||
var share = new ShareClient(NewId());
|
||||
using (var created = await CreateShare(share)) Assert.Equal(HttpStatusCode.Created, created.StatusCode);
|
||||
|
||||
string manifestId = NewId();
|
||||
using (var manifest = await SendFrame(share, share.Owner, Frame(manifestId, 0, FinalEnc, EncPayload)))
|
||||
Assert.Equal(HttpStatusCode.Created, manifest.StatusCode);
|
||||
string itemId = NewId();
|
||||
using (var first = await SendFrame(share, share.Owner, Frame(itemId, 0, GatedEnc, EncPayload), "1"))
|
||||
Assert.Equal(HttpStatusCode.Created, first.StatusCode);
|
||||
using (var last = await SendFrame(share, share.Owner, Frame(itemId, 1, GatedFinalEnc, EncPayload)))
|
||||
Assert.Equal(HttpStatusCode.Created, last.StatusCode);
|
||||
using (var finish = await server.Http.SendAsync(server.Req(HttpMethod.Post,
|
||||
$"/api/v1/shares/{share.Id}/finish", share.Owner, JsonBody(new { manifest_item_id = manifestId }))))
|
||||
Assert.Equal(HttpStatusCode.OK, finish.StatusCode);
|
||||
|
||||
string path = $"/api/v1/shares/{share.Id}/items/{itemId}/frames";
|
||||
await AssertErrorAsync(await server.Http.SendAsync(server.Req(HttpMethod.Get, path + "?from=1", share.OtherVisitor)), 410, "gone");
|
||||
|
||||
var (initialResponse, initialBytes) = await server.GetBytesAsync(path, share.Visitor);
|
||||
using (initialResponse) Assert.Equal(Frame(itemId, 0, GatedEnc, EncPayload, 0x42).Concat(Frame(itemId, 1, GatedFinalEnc, EncPayload, 0x42)), initialBytes);
|
||||
var (resumeResponse, resumedBytes) = await server.GetBytesAsync(path + "?from=1", share.Visitor);
|
||||
using (resumeResponse) Assert.Equal(Frame(itemId, 1, GatedFinalEnc, EncPayload, 0x42), resumedBytes);
|
||||
|
||||
using var infoResponse = await server.Http.SendAsync(server.Req(HttpMethod.Get, $"/api/v1/shares/{share.Id}", share.CreatorReader));
|
||||
var item = (await ReadJsonAsync(infoResponse)).GetProperty("items").EnumerateArray().Single();
|
||||
Assert.Equal(0, item.GetProperty("downloads_left").GetInt32());
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> CreateShare(ShareClient share)
|
||||
{
|
||||
using var req = server.Req(HttpMethod.Post, "/api/v1/shares", share.Owner, JsonBody(new
|
||||
|
||||
Reference in new issue
Block a user