using System.Net; using System.Net.Http.Headers; using DeepDrftModels.Enums; using DeepDrftPublic.Client.Clients; namespace DeepDrftTests; /// /// Pins the Phase 21 Direction B bounded-Range request shape on . /// The network-memory bound rests on each forward fetch being a finite bytes=start-end slice (so the /// browser buffers only one segment), and on the caller learning the file total from the 206 /// Content-Range header (the EOF boundary the segment cursor advances toward). Both are request/response /// plumbing the harness can observe directly; the actual browser memory behaviour is Daniel's manual re-run. /// [TestFixture] public class TrackMediaBoundedRangeTests { // Captures the outgoing Range header and returns a 206 with a Content-Range so TotalLength resolves. private sealed class RangeCapturingHandler : HttpMessageHandler { private readonly long _total; public RangeHeaderValue? CapturedRange { get; private set; } public RangeCapturingHandler(long total) => _total = total; protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { CapturedRange = request.Headers.Range; var from = request.Headers.Range?.Ranges.First().From ?? 0; var to = request.Headers.Range?.Ranges.First().To ?? (_total - 1); var body = new byte[to - from + 1]; var response = new HttpResponseMessage(HttpStatusCode.PartialContent) { Content = new ByteArrayContent(body), }; response.Content.Headers.ContentRange = new ContentRangeHeaderValue(from, to, _total); return Task.FromResult(response); } } private sealed class SingleClientFactory : IHttpClientFactory { private readonly HttpMessageHandler _handler; public SingleClientFactory(HttpMessageHandler handler) => _handler = handler; public HttpClient CreateClient(string name) => new(_handler, disposeHandler: false) { BaseAddress = new Uri("https://content.test/") }; } [Test] public async Task GetTrackMedia_WithByteEnd_SendsBoundedRange() { var handler = new RangeCapturingHandler(total: 100_000_000); var client = new TrackMediaClient(new SingleClientFactory(handler)); await client.GetTrackMedia("track-1", byteOffset: 0, byteEnd: 4 * 1024 * 1024 - 1, format: AudioFormat.Lossless); var range = handler.CapturedRange!.Ranges.First(); Assert.Multiple(() => { Assert.That(range.From, Is.EqualTo(0), "bounded request starts at the cursor"); Assert.That(range.To, Is.EqualTo(4 * 1024 * 1024 - 1), "bounded request must carry an inclusive end so the server returns a finite slice (one segment)"); }); } [Test] public async Task GetTrackMedia_WithoutByteEnd_SendsOpenEndedRange() { var handler = new RangeCapturingHandler(total: 100_000_000); var client = new TrackMediaClient(new SingleClientFactory(handler)); await client.GetTrackMedia("track-1", byteOffset: 1024, byteEnd: null, format: AudioFormat.Lossless); var range = handler.CapturedRange!.Ranges.First(); Assert.Multiple(() => { Assert.That(range.From, Is.EqualTo(1024), "open-ended request starts at the cursor"); Assert.That(range.To, Is.Null, "no byteEnd → open-ended bytes=start- (pre-Direction-B shape, kept working)"); }); } [Test] public async Task GetTrackMedia_206Response_SurfacesTotalLengthFromContentRange() { var handler = new RangeCapturingHandler(total: 970_000_000); var client = new TrackMediaClient(new SingleClientFactory(handler)); var result = await client.GetTrackMedia("track-1", byteOffset: 0, byteEnd: 4 * 1024 * 1024 - 1); Assert.That(result.Success, Is.True); Assert.That(result.Value!.TotalLength, Is.EqualTo(970_000_000), "the file total (the segment cursor's EOF boundary) must come from the 206 Content-Range"); } }