diff options
15 files changed, 244 insertions, 138 deletions
diff --git a/.github/ISSUE_TEMPLATE/issue report.yml b/.github/ISSUE_TEMPLATE/issue report.yml index b71b365f7..cfb5a6ec2 100644 --- a/.github/ISSUE_TEMPLATE/issue report.yml +++ b/.github/ISSUE_TEMPLATE/issue report.yml @@ -86,7 +86,7 @@ body: label: Jellyfin Server version description: What version of Jellyfin are you using? options: - - 10.9.9+ + - 10.9.10+ - Master - Unstable - Older* diff --git a/.github/workflows/ci-openapi.yml b/.github/workflows/ci-openapi.yml index 93bfc83a2..d01c506db 100644 --- a/.github/workflows/ci-openapi.yml +++ b/.github/workflows/ci-openapi.yml @@ -27,7 +27,7 @@ jobs: - name: Generate openapi.json run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests" - name: Upload openapi.json - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: openapi-head retention-days: 14 @@ -61,7 +61,7 @@ jobs: - name: Generate openapi.json run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests" - name: Upload openapi.json - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: openapi-base retention-days: 14 diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 91c2be87b..af8106c0a 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -34,7 +34,7 @@ jobs: --verbosity minimal - name: Merge code coverage results - uses: danielpalme/ReportGenerator-GitHub-Action@5808021ec4deecb0ab3da051d49b4ce65fcc20af # 5.3.8 + uses: danielpalme/ReportGenerator-GitHub-Action@e3af7259842d9c814021ea121f85526e0872b25f # v5.3.9 with: reports: "**/coverage.cobertura.xml" targetdir: "merged/" diff --git a/Directory.Packages.props b/Directory.Packages.props index a153e1d5f..a52bcb53e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -22,7 +22,7 @@ <PackageVersion Include="IDisposableAnalyzers" Version="4.0.8" /> <PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" /> <PackageVersion Include="libse" Version="4.0.7" /> - <PackageVersion Include="LrcParser" Version="2023.524.0" /> + <PackageVersion Include="LrcParser" Version="2024.0728.2" /> <PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" /> <PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.8" /> <PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.8" /> diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index ce98979e6..865a1ef95 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -130,5 +130,7 @@ "TaskCleanCollectionsAndPlaylists": "Sammlungen und Playlisten aufräumen", "TaskCleanCollectionsAndPlaylistsDescription": "Lösche nicht mehr vorhandene Einträge aus den Sammlungen und Playlisten.", "TaskAudioNormalization": "Audio Normalisierung", - "TaskAudioNormalizationDescription": "Durchsucht Dateien nach Audionormalisierungsdaten." + "TaskAudioNormalizationDescription": "Durchsucht Dateien nach Audionormalisierungsdaten.", + "TaskDownloadMissingLyricsDescription": "Lädt Liedtexte herunter", + "TaskDownloadMissingLyrics": "Fehlende Liedtexte herunterladen" } diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json index 9433da28b..b926d9d30 100644 --- a/Emby.Server.Implementations/Localization/Core/es-AR.json +++ b/Emby.Server.Implementations/Localization/Core/es-AR.json @@ -130,5 +130,7 @@ "TaskAudioNormalization": "Normalización de audio", "TaskAudioNormalizationDescription": "Escanea archivos en busca de datos de normalización de audio.", "TaskCleanCollectionsAndPlaylists": "Limpiar colecciones y listas de reproducción", - "TaskCleanCollectionsAndPlaylistsDescription": "Elimina elementos de colecciones y listas de reproducción que ya no existen." + "TaskCleanCollectionsAndPlaylistsDescription": "Elimina elementos de colecciones y listas de reproducción que ya no existen.", + "TaskDownloadMissingLyrics": "Descargar letra faltante", + "TaskDownloadMissingLyricsDescription": "Descarga letras de canciones" } diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index 13e007b4c..210ee4446 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -130,5 +130,7 @@ "TaskCleanCollectionsAndPlaylists": "Limpiar colecciones y listas de reproducción", "TaskCleanCollectionsAndPlaylistsDescription": "Elimina elementos de colecciones y listas de reproducción que ya no existen.", "TaskAudioNormalization": "Normalización de audio", - "TaskAudioNormalizationDescription": "Escanear archivos para obtener datos de normalización." + "TaskAudioNormalizationDescription": "Escanear archivos para obtener datos de normalización.", + "TaskDownloadMissingLyricsDescription": "Descargar letras para las canciones", + "TaskDownloadMissingLyrics": "Descargar letras faltantes" } diff --git a/Emby.Server.Implementations/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json index e7deefbb0..b458ed423 100644 --- a/Emby.Server.Implementations/Localization/Core/es_419.json +++ b/Emby.Server.Implementations/Localization/Core/es_419.json @@ -129,5 +129,7 @@ "TaskAudioNormalization": "Normalización de audio", "TaskCleanCollectionsAndPlaylistsDescription": "Quita elementos que ya no existen de colecciones y listas de reproducción.", "TaskAudioNormalizationDescription": "Analiza los archivos para normalizar el audio.", - "TaskCleanCollectionsAndPlaylists": "Limpieza de colecciones y listas de reproducción" + "TaskCleanCollectionsAndPlaylists": "Limpieza de colecciones y listas de reproducción", + "TaskDownloadMissingLyrics": "Descargar letra faltante", + "TaskDownloadMissingLyricsDescription": "Descarga letras de canciones" } diff --git a/Emby.Server.Implementations/Localization/Core/kw.json b/Emby.Server.Implementations/Localization/Core/kw.json index d6ff58785..ffb4345c8 100644 --- a/Emby.Server.Implementations/Localization/Core/kw.json +++ b/Emby.Server.Implementations/Localization/Core/kw.json @@ -26,13 +26,13 @@ "NotificationOptionPluginUninstalled": "Ystynnans anynstallys", "NotificationOptionPluginUpdateInstalled": "Nowedheans ystynnans ynstallys", "Application": "Gweythres", - "Favorites": "Drudh", + "Favorites": "Moyha Kerys", "Forced": "Konstrynys", "Albums": "Albomow", "Books": "Lyvrow", - "Channels": "Gothi", + "Channels": "Kanolyow", "AppDeviceValues": "App: {0}, Devis: {1}", - "Artists": "Artydhyon", + "Artists": "Artyhdyon", "HeaderAlbumArtists": "Albom artydhyon", "HeaderNextUp": "Nessa", "CameraImageUploadedFrom": "Skeusen kamera nowydh re beu ughkargys a-dhyworth {0}", @@ -44,7 +44,7 @@ "ItemRemovedWithName": "{0} a veu dileys a-dhyworth an lyverva", "LabelIpAddressValue": "Trigva PK: {)}", "Music": "Ilow", - "HeaderContinueWatching": "Pesya ow kweles", + "HeaderContinueWatching": "Pesya Ow Kweles", "NameSeasonNumber": "Seson {0}", "NotificationOptionApplicationUpdateInstalled": "Nowedheans gweythres ynstallys", "NotificationOptionCameraImageUploaded": "Skeusen kamera ughkargys", @@ -59,5 +59,77 @@ "NotificationOptionInstallationFailed": "Defowt ynstallyans", "Genres": "Eghennow", "NotificationOptionPluginInstalled": "Ystynnans ynstallys", - "NotificationOptionServerRestartRequired": "Dastalleth servell yw res" + "NotificationOptionServerRestartRequired": "Dastalleth servell yw res", + "StartupEmbyServerIsLoading": "Yma Servell Jellyfin ow kargya. Assay arta yn berr mar pleg.", + "SubtitleDownloadFailureFromForItem": "Istitlow a fyllis iskarga a-dhyworth {0] rag {1}", + "System": "Kevreyth", + "User": "Devnydhyer", + "UserDeletedWithName": "Devnydhyer {0} re beu dileys", + "UserLockedOutWithName": "Devnydhyer {0} re beu alhwedhys yn-mes", + "UserStoppedPlayingItemWithValues": "{0} re worfennas gwari {1} war {2}", + "UserOfflineFromDevice": "{0} re anjunyas a-dhyworth {1}", + "UserOnlineFromDevice": "{0} yw warlinen a-dhyworth {1}", + "NotificationOptionUserLockedOut": "Devnydhyer yw alhwedhys yn-mes", + "Photos": "Skeusennow", + "Playlists": "Rolyow-gwari", + "Plugin": "Ystynnans", + "PluginInstalledWithName": "{0} a veu ynstallys", + "UserPolicyUpdatedWithName": "Polici devnydhyer re beu nowedhys rag {0}", + "PluginUpdatedWithName": "{0} a veu nowedhys", + "ScheduledTaskFailedWithName": "{0} a fyllis", + "Songs": "Kanow", + "Sync": "Kesseni", + "TvShows": "Towlennow PW", + "Undefined": "Anstyrys", + "UserCreatedWithName": "Devnydhyer {0} re beu gwruthys", + "UserDownloadingItemWithValues": "Yma {0} owth iskarga {1}", + "UserPasswordChangedWithName": "Ger-tremena re beu chanjys rag devnydhyer {0}", + "UserStartedPlayingItemWithValues": "Yma {0} ow kwari {1} war {2}", + "ValueHasBeenAddedToLibrary": "{0} re beu keworrys dhe'th lyverva media", + "VersionNumber": "Versyon {0}", + "TasksLibraryCategory": "Lyverva", + "TaskCleanActivityLog": "Glanhe Kovlyver Gwrians", + "TaskRefreshPeople": "Disegha Tus", + "TaskRefreshLibrary": "Arhwilas Lyverva Media", + "TaskCleanTranscodeDescription": "Y hwra dilea restrennow treylya neg a veu gwrys kyns nans yw dydh.", + "NotificationOptionVideoPlaybackStopped": "Gwareans gwydhyow yw hedhys", + "NotificationOptionVideoPlayback": "Gwareans gwydhyow yw dallethys", + "PluginUninstalledWithName": "{0} a veu anynstallys", + "NotificationOptionTaskFailed": "Defowt oberen towlennys", + "ProviderValue": "Provier: {0}", + "ScheduledTaskStartedWithName": "{0} a dhallathas", + "ServerNameNeedsToBeRestarted": "Yma edhom dhe {0} a vos dastallathys", + "ValueSpecialEpisodeName": "Arbennik - {0}", + "TasksMaintenanceCategory": "Mentons", + "TasksApplicationCategory": "Gweythres", + "TasksChannelsCategory": "Kanolyow Kesrosweyth", + "TaskCleanLogs": "Glanhe Kevarwodhyador Kovlyver", + "TaskAudioNormalization": "Normalheans Klewans", + "TaskRefreshChannels": "Disegha Kanolyow", + "TaskCleanTranscode": "Glanhe Kevarwodhyador Treylya", + "TaskUpdatePlugins": "Nowedhi Ystynansow", + "Shows": "Diskwedhyansow", + "TaskCleanCache": "Glanhe Kevarwodhyador Gwithva", + "TaskCleanActivityLogDescription": "Y hwra dilea lin kovlyver gwrians kottha ages an bloodh dewisys.", + "TaskCleanCacheDescription": "Y hwra dilea restrennow gwithva nag yw res rag an kevreyth.", + "TaskRefreshPeopleDescription": "Y hwra nowedhi metadata rag gwarioryon ha kevarwodhoryon yn dha lyverva media.", + "TaskRefreshChapterImages": "Kuntel Imajys Chaptra", + "TaskRefreshChapterImagesDescription": "Y hwra ewines meus rag gwydhyowyow gans chaptraow.", + "TaskRefreshTrickplayImagesDescription": "Y hwra kynwelyow trickplay rag gwydhyowyow yn lyvervaow gallosegys.", + "TaskRefreshTrickplayImages": "Dinythi Imajys Trickplay", + "TaskCleanLogsDescription": "Y hwra dilea restrennow kovlyver a veu gwrys kyns nans yw {0} dydh.", + "TaskDownloadMissingLyrics": "Iskarga geryow kellys", + "TaskUpdatePluginsDescription": "Y hwra iskarga hag ynstallya nowedheansow rag ystynansow neb yw dewisys dhe nowedhi yn awtomatek.", + "TaskDownloadMissingSubtitles": "Iskarga istitlow kellys", + "TaskRefreshChannelsDescription": "Y hwra disegha kedhlow kanolyow kesrosweyth.", + "TaskDownloadMissingLyricsDescription": "Y hwra iskarga geryow rag kanow", + "TaskDownloadMissingSubtitlesDescription": "Y hwra hwilas an kesrosweyth rag istitlow kellys a-dhywoth dewisyans metadata.", + "TaskOptimizeDatabase": "Gwellhe selvanylyon", + "TaskOptimizeDatabaseDescription": "Y hwra kesstrotha ha berrhe efander rydh. Martesen y hwra gwellhe gwryth mar kwre'ta an oberen ma wosa ty dhe arhwilas an lyverva, po neb chanj aral neb a brof chanjyansow selvanylyon.", + "TaskAudioNormalizationDescription": "Y hwra arhwilas restrennow rag manylyon normalheans klewans.", + "TaskRefreshLibraryDescription": "Y hwra arhwilas dha lyverva media rag restrennow nowydh ha disegha metamanylyon.", + "TaskCleanCollectionsAndPlaylists": "Glanhe kuntellow ha rolyow-gwari", + "TaskKeyframeExtractor": "Estennell Framalhwedh", + "TaskCleanCollectionsAndPlaylistsDescription": "Y hwra dilea taklow a-dhyworth kuntellow ha rolyow-gwari na vos na moy.", + "TaskKeyframeExtractorDescription": "Y hwra kuntel framyowalhwedh a-dhyworth restrennow gwydhyowyow rag gul rolyow-gwari HLS moy poran. Martesen y hwra an oberen ma ow ponya rag termyn hir." } diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index b66818ddc..747652538 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -130,5 +130,7 @@ "TaskCleanCollectionsAndPlaylists": "Rydd kolleksjoner og spillelister", "TaskAudioNormalization": "Lyd Normalisering", "TaskAudioNormalizationDescription": "Skan filer for lyd normaliserende data", - "TaskCleanCollectionsAndPlaylistsDescription": "Fjerner elementer fra kolleksjoner og spillelister som ikke lengere finnes" + "TaskCleanCollectionsAndPlaylistsDescription": "Fjerner elementer fra kolleksjoner og spillelister som ikke lengere finnes", + "TaskDownloadMissingLyrics": "Last ned manglende tekster", + "TaskDownloadMissingLyricsDescription": "Last ned sangtekster" } diff --git a/MediaBrowser.Providers/Lyric/LrcLyricParser.cs b/MediaBrowser.Providers/Lyric/LrcLyricParser.cs index 67b26e457..fffdf4887 100644 --- a/MediaBrowser.Providers/Lyric/LrcLyricParser.cs +++ b/MediaBrowser.Providers/Lyric/LrcLyricParser.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using Jellyfin.Extensions; @@ -20,7 +19,6 @@ public class LrcLyricParser : ILyricParser private readonly LyricParser _lrcLyricParser; private static readonly string[] _supportedMediaTypes = [".lrc", ".elrc"]; - private static readonly string[] _acceptedTimeFormats = ["HH:mm:ss", "H:mm:ss", "mm:ss", "m:ss"]; /// <summary> /// Initializes a new instance of the <see cref="LrcLyricParser"/> class. @@ -59,37 +57,7 @@ public class LrcLyricParser : ILyricParser return null; } - List<LrcParser.Model.Lyric> sortedLyricData = lyricData.Lyrics.Where(x => x.TimeTags.Count > 0).OrderBy(x => x.TimeTags.First().Value).ToList(); - - // Parse metadata rows - var metaDataRows = lyricData.Lyrics - .Where(x => x.TimeTags.Count == 0) - .Where(x => x.Text.StartsWith('[') && x.Text.EndsWith(']')) - .Select(x => x.Text) - .ToList(); - - var fileMetaData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - foreach (string metaDataRow in metaDataRows) - { - var index = metaDataRow.IndexOf(':', StringComparison.OrdinalIgnoreCase); - if (index == -1) - { - continue; - } - - // Remove square bracket before field name, and after field value - // Example 1: [au: 1hitsong] - // Example 2: [ar: Calabrese] - var metaDataFieldName = GetMetadataFieldName(metaDataRow, index); - var metaDataFieldValue = GetMetadataValue(metaDataRow, index); - - if (string.IsNullOrEmpty(metaDataFieldName) || string.IsNullOrEmpty(metaDataFieldValue)) - { - continue; - } - - fileMetaData[metaDataFieldName] = metaDataFieldValue; - } + List<LrcParser.Model.Lyric> sortedLyricData = lyricData.Lyrics.OrderBy(x => x.StartTime).ToList(); if (sortedLyricData.Count == 0) { @@ -100,99 +68,10 @@ public class LrcLyricParser : ILyricParser for (int i = 0; i < sortedLyricData.Count; i++) { - var timeData = sortedLyricData[i].TimeTags.First().Value; - if (timeData is null) - { - continue; - } - - long ticks = TimeSpan.FromMilliseconds(timeData.Value).Ticks; + long ticks = TimeSpan.FromMilliseconds(sortedLyricData[i].StartTime).Ticks; lyricList.Add(new LyricLine(sortedLyricData[i].Text.Trim(), ticks)); } - if (fileMetaData.Count != 0) - { - // Map metaData values from LRC file to LyricMetadata properties - LyricMetadata lyricMetadata = MapMetadataValues(fileMetaData); - - return new LyricDto { Metadata = lyricMetadata, Lyrics = lyricList }; - } - return new LyricDto { Lyrics = lyricList }; } - - /// <summary> - /// Converts metadata from an LRC file to LyricMetadata properties. - /// </summary> - /// <param name="metaData">The metadata from the LRC file.</param> - /// <returns>A lyricMetadata object with mapped property data.</returns> - private static LyricMetadata MapMetadataValues(Dictionary<string, string> metaData) - { - LyricMetadata lyricMetadata = new(); - - if (metaData.TryGetValue("ar", out var artist) && !string.IsNullOrEmpty(artist)) - { - lyricMetadata.Artist = artist; - } - - if (metaData.TryGetValue("al", out var album) && !string.IsNullOrEmpty(album)) - { - lyricMetadata.Album = album; - } - - if (metaData.TryGetValue("ti", out var title) && !string.IsNullOrEmpty(title)) - { - lyricMetadata.Title = title; - } - - if (metaData.TryGetValue("au", out var author) && !string.IsNullOrEmpty(author)) - { - lyricMetadata.Author = author; - } - - if (metaData.TryGetValue("length", out var length) && !string.IsNullOrEmpty(length)) - { - if (DateTime.TryParseExact(length, _acceptedTimeFormats, null, DateTimeStyles.None, out var value)) - { - lyricMetadata.Length = value.TimeOfDay.Ticks; - } - } - - if (metaData.TryGetValue("by", out var by) && !string.IsNullOrEmpty(by)) - { - lyricMetadata.By = by; - } - - if (metaData.TryGetValue("offset", out var offset) && !string.IsNullOrEmpty(offset)) - { - if (int.TryParse(offset, out var value)) - { - lyricMetadata.Offset = TimeSpan.FromMilliseconds(value).Ticks; - } - } - - if (metaData.TryGetValue("re", out var creator) && !string.IsNullOrEmpty(creator)) - { - lyricMetadata.Creator = creator; - } - - if (metaData.TryGetValue("ve", out var version) && !string.IsNullOrEmpty(version)) - { - lyricMetadata.Version = version; - } - - return lyricMetadata; - } - - private static string GetMetadataFieldName(string metaDataRow, int index) - { - var metadataFieldName = metaDataRow.AsSpan(1, index - 1).Trim(); - return metadataFieldName.IsEmpty ? string.Empty : metadataFieldName.ToString(); - } - - private static string GetMetadataValue(string metaDataRow, int index) - { - var metadataValue = metaDataRow.AsSpan(index + 1, metaDataRow.Length - index - 2).Trim(); - return metadataValue.IsEmpty ? string.Empty : metadataValue.ToString(); - } } diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LiveTvControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LiveTvControllerTests.cs new file mode 100644 index 000000000..dd971fa87 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LiveTvControllerTests.cs @@ -0,0 +1,96 @@ +using System.Net; +using System.Net.Http.Json; +using System.Net.Mime; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Jellyfin.Extensions.Json; +using MediaBrowser.Model.LiveTv; +using Xunit; + +namespace Jellyfin.Server.Integration.Tests.Controllers; + +public sealed class LiveTvControllerTests : IClassFixture<JellyfinApplicationFactory> +{ + private readonly JellyfinApplicationFactory _factory; + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + private static string? _accessToken; + + public LiveTvControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task AddTunerHost_Unauthorized_ReturnsUnauthorized() + { + var client = _factory.CreateClient(); + + var body = new TunerHostInfo() + { + Type = "m3u", + Url = "Test Data/dummy.m3u8" + }; + + var response = await client.PostAsJsonAsync("/LiveTv/TunerHosts", body, _jsonOptions); + + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + } + + [Fact] + public async Task AddTunerHost_Valid_ReturnsCorrectResponse() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client)); + + var body = new TunerHostInfo() + { + Type = "m3u", + Url = "Test Data/dummy.m3u8" + }; + + var response = await client.PostAsJsonAsync("/LiveTv/TunerHosts", body, _jsonOptions); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType); + Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet); + var responseBody = await response.Content.ReadFromJsonAsync<TunerHostInfo>(); + Assert.NotNull(responseBody); + Assert.Equal(body.Type, responseBody.Type); + Assert.Equal(body.Url, responseBody.Url); + } + + [Fact] + public async Task AddTunerHost_InvalidType_ReturnsNotFound() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client)); + + var body = new TunerHostInfo() + { + Type = "invalid", + Url = "Test Data/dummy.m3u8" + }; + + var response = await client.PostAsJsonAsync("/LiveTv/TunerHosts", body, _jsonOptions); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task AddTunerHost_InvalidUrl_ReturnsNotFound() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client)); + + var body = new TunerHostInfo() + { + Type = "m3u", + Url = "thisgoesnowhere" + }; + + var response = await client.PostAsJsonAsync("/LiveTv/TunerHosts", body, _jsonOptions); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } +} diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/PluginsControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/PluginsControllerTests.cs new file mode 100644 index 000000000..547bfcc0f --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/PluginsControllerTests.cs @@ -0,0 +1,45 @@ +using System.Net; +using System.Net.Http.Json; +using System.Net.Mime; +using System.Text; +using System.Threading.Tasks; +using Jellyfin.Extensions.Json; +using MediaBrowser.Model.Plugins; +using Xunit; + +namespace Jellyfin.Server.Integration.Tests.Controllers; + +public sealed class PluginsControllerTests : IClassFixture<JellyfinApplicationFactory> +{ + private readonly JellyfinApplicationFactory _factory; + private static string? _accessToken; + + public PluginsControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task GetPlugins_Unauthorized_ReturnsUnauthorized() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("/Plugins"); + + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + } + + [Fact] + public async Task GetPlugins_Authorized_ReturnsCorrectResponse() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client)); + + var response = await client.GetAsync("/Plugins"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType); + Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet); + _ = await response.Content.ReadFromJsonAsync<PluginInfo[]>(JsonDefaults.Options); + } +} diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index a5296d8c9..8228c0df7 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -18,6 +18,9 @@ </ItemGroup> <ItemGroup> + <None Include="Test Data\**\*.*"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </None> <!-- Don't run tests in parallel --> <None Update="xunit.runner.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> diff --git a/tests/Jellyfin.Server.Integration.Tests/Test Data/dummy.m3u8 b/tests/Jellyfin.Server.Integration.Tests/Test Data/dummy.m3u8 new file mode 100644 index 000000000..7f60f38a6 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Test Data/dummy.m3u8 @@ -0,0 +1 @@ +C:\Music |
