first commit
🏗️ Build Plugin / call (push) Failing after 0s
📝 Create/Update Release Draft & Release Bump PR / call (push) Failing after 0s
🧪 Test Plugin / call (push) Failing after 0s
🔬 Run CodeQL / call (push) Failing after 0s

This commit is contained in:
2025-11-12 22:05:36 +01:00
parent d544b71939
commit ac6a3842dd
46 changed files with 5891 additions and 499 deletions
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="../Jellyfin.Plugin.SRFPlay/Jellyfin.Plugin.SRFPlay.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
+284
View File
@@ -0,0 +1,284 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.SRFPlay.Api;
using Jellyfin.Plugin.SRFPlay.Api.Models;
using Jellyfin.Plugin.SRFPlay.Configuration;
using Jellyfin.Plugin.SRFPlay.Services;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.SRFPlay.Tests
{
class Program
{
static async Task Main(string[] args)
{
// Run the new Play v3 API tests
await TestPlayV3Api.RunTests();
Console.WriteLine("\n\n");
Console.WriteLine("=================================================================");
Console.WriteLine("OLD API TESTS (DEPRECATED - Expected to fail)");
Console.WriteLine("=================================================================\n");
Console.WriteLine("=== SRFPlay Plugin Test Suite (Old API) ===\n");
// Setup logging
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Warning); // Only show warnings and errors
});
var apiClient = new SRFApiClient(loggerFactory);
var streamResolver = new StreamUrlResolver(loggerFactory.CreateLogger<StreamUrlResolver>());
var cancellationToken = CancellationToken.None;
// Test 1: Check API connectivity
Console.WriteLine("[Test 1] API Connectivity Test");
Console.WriteLine("-------------------------------");
try
{
var latestVideos = await apiClient.GetLatestVideosAsync("srf", cancellationToken);
if (latestVideos?.ChapterList != null && latestVideos.ChapterList.Any())
{
Console.WriteLine($"✓ Successfully fetched latest videos: {latestVideos.ChapterList.Count} items");
Console.WriteLine($" First video: {latestVideos.ChapterList[0]?.Title}");
}
else
{
Console.WriteLine("✗ Failed: No videos returned");
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ Error: {ex.Message}");
}
Console.WriteLine();
// Test 2: Check trending videos
Console.WriteLine("[Test 2] Trending Videos Test");
Console.WriteLine("-----------------------------");
try
{
var trendingVideos = await apiClient.GetTrendingVideosAsync("srf", cancellationToken);
if (trendingVideos?.ChapterList != null && trendingVideos.ChapterList.Any())
{
Console.WriteLine($"✓ Successfully fetched trending videos: {trendingVideos.ChapterList.Count} items");
Console.WriteLine($" First video: {trendingVideos.ChapterList[0]?.Title}");
}
else
{
Console.WriteLine("✗ Failed: No videos returned");
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ Error: {ex.Message}");
}
Console.WriteLine();
// Test 3: Get specific video and check stream URL
Console.WriteLine("[Test 3] Stream URL Resolution Test");
Console.WriteLine("-----------------------------------");
try
{
var latestVideos = await apiClient.GetLatestVideosAsync("srf", cancellationToken);
if (latestVideos?.ChapterList != null && latestVideos.ChapterList.Any())
{
var firstChapter = latestVideos.ChapterList[0];
Console.WriteLine($"Testing with: {firstChapter.Title}");
Console.WriteLine($"URN: {firstChapter.Urn}");
// Fetch full media composition for this URN
var mediaComposition = await apiClient.GetMediaCompositionByUrnAsync(firstChapter.Urn, cancellationToken);
if (mediaComposition?.ChapterList != null && mediaComposition.ChapterList.Any())
{
var chapter = mediaComposition.ChapterList[0];
// Check if content is playable
bool hasPlayableContent = streamResolver.HasPlayableContent(chapter);
Console.WriteLine($"Has playable content: {hasPlayableContent}");
// Check if content is expired
bool isExpired = streamResolver.IsContentExpired(chapter);
Console.WriteLine($"Is expired: {isExpired}");
// Try to get stream URL
if (hasPlayableContent && !isExpired)
{
var streamUrl = streamResolver.GetStreamUrl(chapter, Jellyfin.Plugin.SRFPlay.Configuration.QualityPreference.Auto);
if (!string.IsNullOrEmpty(streamUrl))
{
Console.WriteLine($"✓ Stream URL resolved: {streamUrl.Substring(0, Math.Min(80, streamUrl.Length))}...");
// Show available resources
if (chapter.ResourceList != null)
{
Console.WriteLine($" Available resources: {chapter.ResourceList.Count}");
foreach (var resource in chapter.ResourceList.Take(5))
{
var hasDrm = resource.DrmList != null && resource.DrmList.ToString() != "[]";
Console.WriteLine($" - {resource.Quality} ({resource.Protocol}) {(hasDrm ? "[DRM]" : "[No DRM]")}");
}
}
}
else
{
Console.WriteLine("✗ Failed to resolve stream URL");
}
}
else
{
if (!hasPlayableContent)
Console.WriteLine("✗ Content is not playable (likely DRM protected)");
if (isExpired)
Console.WriteLine($"✗ Content is expired (ValidTo: {chapter.ValidTo})");
}
}
else
{
Console.WriteLine("✗ Failed to fetch media composition");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ Error: {ex.Message}");
Console.WriteLine($"Stack trace: {ex.StackTrace}");
}
Console.WriteLine();
// Test 4: Check multiple videos for playability
Console.WriteLine("[Test 4] Multiple Videos Playability Check");
Console.WriteLine("------------------------------------------");
try
{
var latestVideos = await apiClient.GetLatestVideosAsync("srf", cancellationToken);
if (latestVideos?.ChapterList != null)
{
int totalVideos = Math.Min(10, latestVideos.ChapterList.Count);
int playableCount = 0;
int expiredCount = 0;
int drmCount = 0;
Console.WriteLine($"Checking {totalVideos} videos...\n");
for (int i = 0; i < totalVideos; i++)
{
var chapter = latestVideos.ChapterList[i];
var mediaComp = await apiClient.GetMediaCompositionByUrnAsync(chapter.Urn, cancellationToken);
if (mediaComp?.ChapterList != null && mediaComp.ChapterList.Any())
{
var fullChapter = mediaComp.ChapterList[0];
bool hasPlayable = streamResolver.HasPlayableContent(fullChapter);
bool isExpired = streamResolver.IsContentExpired(fullChapter);
string status;
if (isExpired)
{
status = "EXPIRED";
expiredCount++;
}
else if (!hasPlayable)
{
status = "DRM";
drmCount++;
}
else
{
status = "PLAYABLE";
playableCount++;
}
Console.WriteLine($"{i + 1}. [{status}] {fullChapter.Title}");
}
}
Console.WriteLine();
Console.WriteLine($"Summary:");
Console.WriteLine($" Playable: {playableCount}/{totalVideos} ({(playableCount * 100.0 / totalVideos):F1}%)");
Console.WriteLine($" DRM Protected: {drmCount}/{totalVideos} ({(drmCount * 100.0 / totalVideos):F1}%)");
Console.WriteLine($" Expired: {expiredCount}/{totalVideos} ({(expiredCount * 100.0 / totalVideos):F1}%)");
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ Error: {ex.Message}");
}
Console.WriteLine();
// Test 5: Test library content discovery (simulates what would be in the library)
Console.WriteLine("[Test 5] Library Content Discovery Simulation");
Console.WriteLine("----------------------------------------------");
try
{
var latestVideos = await apiClient.GetLatestVideosAsync("srf", cancellationToken);
var trendingVideos = await apiClient.GetTrendingVideosAsync("srf", cancellationToken);
var allUrns = new HashSet<string>();
if (latestVideos?.ChapterList != null)
{
foreach (var chapter in latestVideos.ChapterList)
{
if (!string.IsNullOrEmpty(chapter.Urn))
allUrns.Add(chapter.Urn);
}
}
if (trendingVideos?.ChapterList != null)
{
foreach (var chapter in trendingVideos.ChapterList)
{
if (!string.IsNullOrEmpty(chapter.Urn))
allUrns.Add(chapter.Urn);
}
}
Console.WriteLine($"✓ Latest videos available: {latestVideos?.ChapterList?.Count ?? 0}");
Console.WriteLine($"✓ Trending videos available: {trendingVideos?.ChapterList?.Count ?? 0}");
Console.WriteLine($"✓ Total unique items for library (deduplicated): {allUrns.Count}");
}
catch (Exception ex)
{
Console.WriteLine($"✗ Error: {ex.Message}");
}
Console.WriteLine();
// Test 6: Test different business units
Console.WriteLine("[Test 6] Business Units Test");
Console.WriteLine("----------------------------");
var businessUnits = new[] { "srf", "rts", "rsi", "rtr", "swi" };
foreach (var bu in businessUnits)
{
try
{
var videos = await apiClient.GetLatestVideosAsync(bu, cancellationToken);
if (videos?.ChapterList != null && videos.ChapterList.Any())
{
Console.WriteLine($"✓ {bu.ToUpper()}: {videos.ChapterList.Count} videos available");
}
else
{
Console.WriteLine($"✗ {bu.ToUpper()}: No videos found");
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ {bu.ToUpper()}: Error - {ex.Message}");
}
}
Console.WriteLine();
Console.WriteLine("=== Test Suite Complete ===");
}
}
}
@@ -0,0 +1,220 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.SRFPlay.Api;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.SRFPlay.Tests
{
public class TestPlayV3Api
{
public static async Task RunTests()
{
Console.WriteLine("=== Play v3 API Test Suite ===\n");
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Warning);
});
var apiClient = new SRFApiClient(loggerFactory);
var cancellationToken = CancellationToken.None;
// Test 1: Get all shows
Console.WriteLine("[Test 1] Fetch All Shows");
Console.WriteLine("------------------------");
try
{
var shows = await apiClient.GetAllShowsAsync("srf", cancellationToken);
if (shows != null && shows.Any())
{
Console.WriteLine($"✓ Successfully fetched {shows.Count} shows");
Console.WriteLine($" First show: {shows[0]?.Title} ({shows[0]?.NumberOfEpisodes} episodes)");
Console.WriteLine($" Show ID: {shows[0]?.Id}");
Console.WriteLine($" URN: {shows[0]?.Urn}");
}
else
{
Console.WriteLine("✗ Failed: No shows returned");
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ Error: {ex.Message}");
}
Console.WriteLine();
// Test 2: Get videos for a show
Console.WriteLine("[Test 2] Fetch Videos for a Show");
Console.WriteLine("--------------------------------");
try
{
var shows = await apiClient.GetAllShowsAsync("srf", cancellationToken);
if (shows != null && shows.Any())
{
// Find a show with episodes
var showWithEpisodes = shows.FirstOrDefault(s => s.NumberOfEpisodes > 0);
if (showWithEpisodes != null && showWithEpisodes.Id != null)
{
Console.WriteLine($"Testing with show: {showWithEpisodes.Title}");
var videos = await apiClient.GetVideosForShowAsync("srf", showWithEpisodes.Id, cancellationToken);
if (videos != null && videos.Any())
{
Console.WriteLine($"✓ Successfully fetched {videos.Count} videos");
var firstVideo = videos[0];
Console.WriteLine($" First video: {firstVideo?.Title}");
Console.WriteLine($" URN: {firstVideo?.Urn}");
Console.WriteLine($" Duration: {firstVideo?.Duration / 1000}s");
Console.WriteLine($" Date: {firstVideo?.Date}");
Console.WriteLine($" Playable abroad: {firstVideo?.PlayableAbroad}");
}
else
{
Console.WriteLine("✗ Failed: No videos returned");
}
}
else
{
Console.WriteLine("✗ Failed: No shows with episodes found");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ Error: {ex.Message}");
}
Console.WriteLine();
// Test 3: Get video stream URL using Integration Layer
Console.WriteLine("[Test 3] Get Stream URL for Video");
Console.WriteLine("---------------------------------");
try
{
var shows = await apiClient.GetAllShowsAsync("srf", cancellationToken);
if (shows != null && shows.Any())
{
var showWithEpisodes = shows.FirstOrDefault(s => s.NumberOfEpisodes > 0);
if (showWithEpisodes != null && showWithEpisodes.Id != null)
{
var videos = await apiClient.GetVideosForShowAsync("srf", showWithEpisodes.Id, cancellationToken);
if (videos != null && videos.Any())
{
var video = videos[0];
if (video.Urn != null)
{
Console.WriteLine($"Fetching stream for: {video.Title}");
// Use Integration Layer 2.0 to get stream URL
var mediaComposition = await apiClient.GetMediaCompositionByUrnAsync(video.Urn, cancellationToken);
if (mediaComposition?.ChapterList != null && mediaComposition.ChapterList.Any())
{
var chapter = mediaComposition.ChapterList[0];
if (chapter.ResourceList != null && chapter.ResourceList.Any())
{
var hlsResource = chapter.ResourceList.FirstOrDefault(r =>
r.Protocol == "HLS" && (r.DrmList == null || r.DrmList.ToString() == "[]"));
if (hlsResource != null)
{
Console.WriteLine($"✓ Stream URL found:");
Console.WriteLine($" URL: {hlsResource.Url?.Substring(0, Math.Min(80, hlsResource.Url?.Length ?? 0))}...");
Console.WriteLine($" Quality: {hlsResource.Quality}");
Console.WriteLine($" Protocol: {hlsResource.Protocol}");
}
else
{
Console.WriteLine("✗ No HLS stream without DRM found");
}
}
else
{
Console.WriteLine("✗ No resources found");
}
}
else
{
Console.WriteLine("✗ Failed to fetch media composition");
}
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ Error: {ex.Message}");
}
Console.WriteLine();
// Test 4: Check multiple shows
Console.WriteLine("[Test 4] Check Multiple Shows");
Console.WriteLine("----------------------------");
try
{
var shows = await apiClient.GetAllShowsAsync("srf", cancellationToken);
if (shows != null)
{
var showsWithEpisodes = shows.Where(s => s.NumberOfEpisodes > 0).Take(10).ToList();
Console.WriteLine($"Checking {showsWithEpisodes.Count} shows with episodes...\n");
int successCount = 0;
foreach (var show in showsWithEpisodes)
{
if (show.Id != null)
{
var videos = await apiClient.GetVideosForShowAsync("srf", show.Id, cancellationToken);
if (videos != null && videos.Any())
{
Console.WriteLine($"✓ {show.Title}: {videos.Count} videos available");
successCount++;
}
else
{
Console.WriteLine($"✗ {show.Title}: No videos found");
}
}
}
Console.WriteLine();
Console.WriteLine($"Summary: {successCount}/{showsWithEpisodes.Count} shows successfully fetched videos");
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ Error: {ex.Message}");
}
Console.WriteLine();
// Test 5: Test all business units
Console.WriteLine("[Test 5] Test All Business Units");
Console.WriteLine("--------------------------------");
var businessUnits = new[] { "srf", "rts", "rsi", "rtr", "swi" };
foreach (var bu in businessUnits)
{
try
{
var shows = await apiClient.GetAllShowsAsync(bu, cancellationToken);
if (shows != null && shows.Any())
{
Console.WriteLine($"✓ {bu.ToUpper()}: {shows.Count} shows available");
}
else
{
Console.WriteLine($"✗ {bu.ToUpper()}: No shows found");
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ {bu.ToUpper()}: Error - {ex.Message}");
}
}
Console.WriteLine();
Console.WriteLine("=== Test Suite Complete ===");
}
}
}