first commit
This commit is contained in:
@@ -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>
|
||||
@@ -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 ===");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user