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,153 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Jellyfin.Plugin.Floatplane.Api.Models;
using Xunit;
namespace Jellyfin.Plugin.Floatplane.Tests;
public class ModelTests
{
[Fact]
public void ContentCreator_list_posts_have_channel_object_but_no_attachments()
{
var posts = Fixture.Json<List<PostModel>>("content-creator.json");
Assert.Equal(5, posts.Count);
var first = posts[0];
Assert.Equal("SSvhppI28t", first.Id);
Assert.Equal("How Board Games Come ALIVE...- Board", first.Title);
Assert.NotNull(first.Channel);
Assert.False(first.Channel!.IsReferenceOnly);
Assert.Equal("ShortCircuit", first.Channel.Title);
Assert.Equal("64135da7ce81077a8480c679", first.Channel.Id);
Assert.Null(first.VideoAttachments);
Assert.True(first.HasVideo);
Assert.Equal(1104, first.DurationSeconds);
Assert.NotNull(first.ReleaseDate);
Assert.Equal(2026, first.ReleaseDate!.Value.Year);
Assert.True(first.IsAccessible);
}
[Fact]
public void ContentPost_expands_video_attachments()
{
var post = Fixture.Json<PostModel>("content-post.json");
Assert.Equal("SSvhppI28t", post.Id);
Assert.NotNull(post.VideoAttachments);
var video = Assert.Single(post.VideoAttachments!);
Assert.False(video.IsReferenceOnly);
Assert.Equal("dcm2jUIg1J", video.Id);
Assert.Equal(1104, video.Duration);
Assert.Equal(5, video.Levels.Count);
Assert.Contains(video.Levels, l => l.Name == "1080" && l.Height == 1080 && l.Width == 1920);
Assert.Single(video.TextTracks);
Assert.Equal(new[] { "dcm2jUIg1J" }, post.AttachmentOrder);
Assert.Equal("ShortCircuit", post.Channel!.Title);
Assert.Equal("LinusTechTips", post.Creator!.Title);
}
[Fact]
public void ContentVideo_parses_levels_and_sprite()
{
var video = Fixture.Json<VideoModel>("content-video.json");
Assert.Equal("dcm2jUIg1J", video.Id);
Assert.Equal("SSvhppI28t", video.PrimaryBlogPost);
Assert.Equal(new[] { "360", "480", "720", "1080", "2160" }, video.Levels.Select(l => l.Name));
Assert.NotNull(video.TimelineSprite);
Assert.StartsWith("https://pbs.floatplane.com/timeline_sprite/", video.TimelineSprite!.Path);
}
[Fact]
public void IdOrObjectConverter_accepts_string_ids()
{
const string json = """
{"id":"p1","title":"T","channel":"chan123","creator":"cre456","videoAttachments":["v1","v2"],"metadata":{"hasVideo":true,"videoCount":2}}
""";
var post = JsonSerializer.Deserialize<PostModel>(json, FloatplaneJson.Options)!;
Assert.Equal("chan123", post.Channel!.Id);
Assert.True(post.Channel.IsReferenceOnly);
Assert.Equal("cre456", post.Creator!.Id);
Assert.True(post.Creator.IsReferenceOnly);
Assert.Equal(2, post.VideoAttachments!.Count);
Assert.All(post.VideoAttachments, v => Assert.True(v.IsReferenceOnly));
Assert.Equal(new[] { "v1", "v2" }, post.VideoAttachments.Select(v => v.Id));
}
[Fact]
public void IdOrObjectConverter_still_reads_objects_and_null()
{
const string json = """
{"id":"p1","channel":{"id":"c1","title":"Main","urlname":"main"},"creator":null}
""";
var post = JsonSerializer.Deserialize<PostModel>(json, FloatplaneJson.Options)!;
Assert.Equal("Main", post.Channel!.Title);
Assert.False(post.Channel.IsReferenceOnly);
Assert.Null(post.Creator);
}
[Fact]
public void Error_envelope_parses()
{
var error = Fixture.Json<ApiErrorModel>("error-not-logged-in.json");
Assert.True(error.Has("notLoggedInError"));
Assert.False(error.Has("notSubscribedError"));
Assert.Equal("You must be logged-in to access this resource.", error.Message);
}
[Fact]
public void DeliveryInfo_resolves_usable_variants_and_reports_denied_4k()
{
var info = Fixture.Json<DeliveryInfoModel>("delivery-info.json");
var resolved = info.Resolve().ToList();
Assert.Equal(4, resolved.Count);
Assert.All(resolved, v => Assert.True(v.IsUsable));
Assert.All(resolved, v => Assert.StartsWith("https://cdn-vod-drm2.floatplane.com/Videos/dcm2jUIg1J/", v.Url));
Assert.Equal(new[] { 360, 480, 720, 1080 }, resolved.Select(v => v.Height));
Assert.Equal("avc1.640028", resolved.Single(v => v.Height == 1080).Variant.Meta!.Video!.Codec);
var fourK = info.Groups.Single().Variants.Single(v => v.Name == "2160-avc1");
Assert.False(fourK.IsUsable);
Assert.False(fourK.Enabled);
Assert.Equal("isMissingPermission", fourK.Meta!.Common!.Access!.DeniedReason);
Assert.Equal(2160, fourK.Height);
}
[Fact]
public void ImageModel_BestPath_picks_largest_child_within_limit()
{
var post = Fixture.Json<List<PostModel>>("content-creator.json")[0];
var path = post.Thumbnail!.BestPath(1200);
Assert.EndsWith("_1200x675.jpeg", path);
Assert.EndsWith("_400x225.jpeg", post.Thumbnail.BestPath(500));
Assert.Equal(post.Thumbnail.Path, post.Thumbnail.BestPath(100));
}
[Fact]
public void Subscriptions_and_self_and_creator_parse()
{
var subs = Fixture.Json<List<SubscriptionModel>>("user-subscriptions.json");
var self = Fixture.Json<UserSelfModel>("user-self.json");
var creator = Fixture.Json<CreatorModel>("creator-info.json");
var sub = Assert.Single(subs);
Assert.Equal("59f94c0bdd241b70349eb72b", sub.Creator);
Assert.Equal("LTT Supporter", sub.Plan!.Title);
Assert.False(string.IsNullOrEmpty(self.Username));
Assert.Equal("LinusTechTips", creator.Title);
Assert.Equal("linustechtips", creator.Urlname);
Assert.Equal(11, creator.Channels.Count);
Assert.Contains(creator.Channels, c => c.Urlname == "techlinked");
Assert.NotNull(creator.LiveStream);
}
}