Tests: xUnit project with HAR fixtures (50 tests); fix relative delivery/HLS URLs resolving to file:// on Linux
All checks were successful
release / build-release (push) Successful in 1m19s

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 09:42:50 +01:00
parent 9e33a343ab
commit 2a883c02de
21 changed files with 2358 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
using System;
using System.Linq;
using Jellyfin.Plugin.Floatplane.Playback;
using Xunit;
namespace Jellyfin.Plugin.Floatplane.Tests;
public class HlsRewriterTests
{
private static readonly Uri PlaylistUrl = new("https://cdn-vod-drm2.floatplane.com/Videos/dcm2jUIg1J/1080.mp4/chunk.m3u8?token=X");
[Fact]
public void Media_playlist_key_is_proxied_and_segments_made_absolute()
{
var original = Fixture.Text("chunk.m3u8");
string? seenKey = null;
var rewritten = HlsRewriter.Rewrite(
original,
PlaylistUrl,
key =>
{
seenKey = key;
return "http://jellyfin/Floatplane/Stream/dcm2jUIg1J/Key?u=abc&t=sig";
});
var lines = rewritten.Split('\n', StringSplitOptions.RemoveEmptyEntries);
Assert.Equal("https://www.floatplane.com/api/video/watchKey?token=eyJwYXRoIjoiL1ZpZGVvcy9kY20yalVJZzFKLzEwODAubXA0IiwidGltZXN0YW1wIjoxNzkyODg2NDAwMDAwfQ", seenKey);
Assert.Contains("#EXT-X-KEY:METHOD=AES-128,URI=\"http://jellyfin/Floatplane/Stream/dcm2jUIg1J/Key?u=abc&t=sig\"", lines);
Assert.DoesNotContain(lines, l => l.Contains("watchKey", StringComparison.Ordinal));
var segments = lines.Where(l => !l.StartsWith('#')).ToList();
Assert.Equal(6, segments.Count);
for (var i = 0; i < segments.Count; i++)
{
Assert.StartsWith("https://cdn-vod-drm2.floatplane.com/Videos/dcm2jUIg1J/1080.mp4/", segments[i]);
Assert.EndsWith($".ts?token=SEGTOKEN{i + 1}", segments[i]);
}
Assert.Equal(6, lines.Count(l => l.StartsWith("#EXTINF:", StringComparison.Ordinal)));
Assert.Equal("#EXTM3U", lines[0]);
Assert.Equal("#EXT-X-ENDLIST", lines[^1]);
Assert.Contains("#EXT-X-TARGETDURATION:12", lines);
Assert.EndsWith("\n", rewritten);
}
[Fact]
public void Segment_mapper_is_applied_when_given()
{
var rewritten = HlsRewriter.Rewrite(
Fixture.Text("chunk.m3u8"),
PlaylistUrl,
key => "KEY",
mapSegmentUri: seg => "http://proxy/seg?u=" + Uri.EscapeDataString(seg));
var segments = rewritten.Split('\n', StringSplitOptions.RemoveEmptyEntries).Where(l => !l.StartsWith('#')).ToList();
Assert.Equal(6, segments.Count);
Assert.All(segments, s => Assert.StartsWith("http://proxy/seg?u=https%3A%2F%2Fcdn-vod-drm2.floatplane.com", s));
}
[Fact]
public void Master_playlist_variants_are_mapped_through_playlist_mapper()
{
const string master = "#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1800000,RESOLUTION=1920x1080\n1080.mp4/chunk.m3u8?token=A\n#EXT-X-STREAM-INF:BANDWIDTH=900000,RESOLUTION=1280x720\nhttps://other.cdn/720/chunk.m3u8?token=B\n";
var masterUrl = new Uri("https://cdn-vod-drm2.floatplane.com/Videos/dcm2jUIg1J/master.m3u8?token=M");
var rewritten = HlsRewriter.Rewrite(
master,
masterUrl,
key => "KEY",
playlistUri => "http://proxy/pl?u=" + playlistUri);
var lines = rewritten.Split('\n', StringSplitOptions.RemoveEmptyEntries);
Assert.Equal("http://proxy/pl?u=https://cdn-vod-drm2.floatplane.com/Videos/dcm2jUIg1J/1080.mp4/chunk.m3u8?token=A", lines[2]);
Assert.Equal("http://proxy/pl?u=https://other.cdn/720/chunk.m3u8?token=B", lines[4]);
Assert.Equal("#EXT-X-STREAM-INF:BANDWIDTH=1800000,RESOLUTION=1920x1080", lines[1]);
}
[Fact]
public void Master_playlist_without_mapper_keeps_absolute_urls()
{
const string master = "#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1\nsub/chunk.m3u8\n";
var rewritten = HlsRewriter.Rewrite(master, new Uri("https://cdn.example/dir/master.m3u8"), key => "KEY");
Assert.Contains("https://cdn.example/dir/sub/chunk.m3u8", rewritten);
}
[Fact]
public void Absolute_resolves_relative_and_keeps_absolute()
{
Assert.Equal("https://cdn.example/a/b.ts", HlsRewriter.Absolute(new Uri("https://cdn.example/a/p.m3u8?x=1"), "b.ts"));
Assert.Equal("https://cdn.example/b.ts", HlsRewriter.Absolute(new Uri("https://cdn.example/a/p.m3u8"), "/b.ts"));
Assert.Equal("https://other/b.ts", HlsRewriter.Absolute(new Uri("https://cdn.example/a/p.m3u8"), "https://other/b.ts"));
}
}