repo creation with initial code after cloning public repo

This commit is contained in:
2026-02-19 07:36:25 -05:00
commit 460884fea3
2860 changed files with 650825 additions and 0 deletions
@@ -0,0 +1,30 @@
using System.Net;
using System.Net.Mime;
using System.Threading.Tasks;
using Xunit;
namespace Jellyfin.Server.Integration.Tests.Controllers
{
public sealed class ActivityLogControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
private static string? _accessToken;
public ActivityLogControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task ActivityLog_GetEntries_Ok()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var response = await client.GetAsync("System/ActivityLog/Entries");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
}
}
}
@@ -0,0 +1,14 @@
using Jellyfin.Api;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Server.Integration.Tests.Controllers
{
/// <summary>
/// Base controller for testing infrastructure.
/// Automatically ignored in generated openapi spec.
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public class BaseJellyfinTestController : BaseJellyfinApiController
{
}
}
@@ -0,0 +1,54 @@
using System.Net;
using System.Net.Http.Json;
using System.Net.Mime;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using MediaBrowser.Model.Branding;
using Xunit;
namespace Jellyfin.Server.Integration.Tests
{
public sealed class BrandingControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
public BrandingControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task GetConfiguration_ReturnsCorrectResponse()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync("/Branding/Configuration");
// Assert
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<BrandingOptions>();
}
[Theory]
[InlineData("/Branding/Css")]
[InlineData("/Branding/Css.css")]
public async Task GetCss_ReturnsCorrectResponse(string url)
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync(url);
// Assert
Assert.True(response.IsSuccessStatusCode);
Assert.Equal("text/css", response.Content.Headers.ContentType?.MediaType);
Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
}
}
}
@@ -0,0 +1,89 @@
using System.IO;
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.Api.Models;
using Jellyfin.Extensions.Json;
using Xunit;
namespace Jellyfin.Server.Integration.Tests.Controllers
{
public sealed class DashboardControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private static string? _accessToken;
public DashboardControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task GetDashboardConfigurationPage_NonExistingPage_NotFound()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("web/ConfigurationPage?name=ThisPageDoesntExists");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GetDashboardConfigurationPage_ExistingPage_CorrectPage()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/web/ConfigurationPage?name=TestPlugin");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Text.Html, response.Content.Headers.ContentType?.MediaType);
StreamReader reader = new StreamReader(typeof(TestPlugin).Assembly.GetManifestResourceStream("Jellyfin.Server.Integration.Tests.TestPage.html")!);
Assert.Equal(await response.Content.ReadAsStringAsync(), await reader.ReadToEndAsync());
}
[Fact]
public async Task GetDashboardConfigurationPage_BrokenPage_NotFound()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/web/ConfigurationPage?name=BrokenPage");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GetConfigurationPages_NoParams_AllConfigurationPages()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var response = await client.GetAsync("/web/ConfigurationPages");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
_ = await response.Content.ReadFromJsonAsync<ConfigurationPageInfo[]>(_jsonOptions);
// TODO: check content
}
[Fact]
public async Task GetConfigurationPages_True_MainMenuConfigurationPages()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var response = await client.GetAsync("/web/ConfigurationPages?enableInMainMenu=true");
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 data = await response.Content.ReadFromJsonAsync<ConfigurationPageInfo[]>(_jsonOptions);
Assert.NotNull(data);
Assert.Empty(data);
}
}
}
@@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Server.Integration.Tests.Controllers
{
/// <summary>
/// Controller for testing the encoded url.
/// </summary>
public class EncoderController : BaseJellyfinTestController
{
/// <summary>
/// Tests the url decoding.
/// </summary>
/// <param name="params">Parameters to echo back in the response.</param>
/// <returns>An <see cref="OkResult"/>.</returns>
/// <response code="200">Information retrieved.</response>
[HttpGet("UrlDecode")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ContentResult TestUrlDecoding([FromQuery] Dictionary<string, string>? @params = null)
{
return new ContentResult()
{
Content = (@params is not null && @params.Count > 0)
? string.Join("&", @params.Select(x => x.Key + "=" + x.Value))
: string.Empty,
ContentType = "text/plain; charset=utf-8",
StatusCode = 200
};
}
/// <summary>
/// Tests the url decoding.
/// </summary>
/// <param name="params">Parameters to echo back in the response.</param>
/// <returns>An <see cref="OkResult"/>.</returns>
/// <response code="200">Information retrieved.</response>
[HttpGet("UrlArrayDecode")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ContentResult TestUrlArrayDecoding([FromQuery] Dictionary<string, string[]>? @params = null)
{
return new ContentResult()
{
Content = (@params is not null && @params.Count > 0)
? string.Join("&", @params.Select(x => x.Key + "=" + string.Join(',', x.Value)))
: string.Empty,
ContentType = "text/plain; charset=utf-8",
StatusCode = 200
};
}
}
}
@@ -0,0 +1,63 @@
using System;
using System.Globalization;
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Extensions.Json;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
using Xunit;
namespace Jellyfin.Server.Integration.Tests.Controllers;
public sealed class ItemsControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private static string? _accessToken;
public ItemsControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task GetItems_NoApiKeyOrUserId_Success()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var response = await client.GetAsync("Items");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[Theory]
[InlineData("Users/{0}/Items")]
[InlineData("Users/{0}/Items/Resume")]
public async Task GetUserItems_NonexistentUserId_NotFound(string format)
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid()));
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData("Items?userId={0}")]
[InlineData("Users/{0}/Items")]
[InlineData("Users/{0}/Items/Resume")]
public async Task GetItems_UserId_Ok(string format)
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var userDto = await AuthHelper.GetUserDtoAsync(client);
var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, userDto.Id));
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var items = await response.Content.ReadFromJsonAsync<QueryResult<BaseItemDto>>(_jsonOptions);
Assert.NotNull(items);
}
}
@@ -0,0 +1,63 @@
using System;
using System.Globalization;
using System.Net;
using System.Threading.Tasks;
using Xunit;
namespace Jellyfin.Server.Integration.Tests.Controllers;
public sealed class LibraryControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
private static string? _accessToken;
public LibraryControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Theory]
[InlineData("Items/{0}/File")]
[InlineData("Items/{0}/ThemeSongs")]
[InlineData("Items/{0}/ThemeVideos")]
[InlineData("Items/{0}/ThemeMedia")]
[InlineData("Items/{0}/Ancestors")]
[InlineData("Items/{0}/Download")]
[InlineData("Artists/{0}/Similar")]
[InlineData("Items/{0}/Similar")]
[InlineData("Albums/{0}/Similar")]
[InlineData("Shows/{0}/Similar")]
[InlineData("Movies/{0}/Similar")]
[InlineData("Trailers/{0}/Similar")]
public async Task Get_NonexistentItemId_NotFound(string format)
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid()));
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData("Items/{0}")]
[InlineData("Items?ids={0}")]
public async Task Delete_NonexistentItemId_Unauthorised(string format)
{
var client = _factory.CreateClient();
var response = await client.DeleteAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid()));
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Theory]
[InlineData("Items/{0}")]
[InlineData("Items?ids={0}")]
public async Task Delete_NonexistentItemId_NotFound(string format)
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var response = await client.DeleteAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid()));
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
@@ -0,0 +1,127 @@
using System;
using System.Linq;
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Api.Models.LibraryStructureDto;
using Jellyfin.Extensions.Json;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using Xunit;
using Xunit.Priority;
namespace Jellyfin.Server.Integration.Tests.Controllers;
[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
public sealed class LibraryStructureControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private static string? _accessToken;
public LibraryStructureControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Fact]
[Priority(-1)]
public async Task Post_NewVirtualFolder_NotFound()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var body = new AddVirtualFolderDto()
{
LibraryOptions = new LibraryOptions()
{
Enabled = false
}
};
using var response = await client.PostAsJsonAsync("Library/VirtualFolders?name=test&refreshLibrary=true", body, _jsonOptions);
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
[Fact]
[Priority(-2)]
public async Task UpdateLibraryOptions_Invalid_NotFound()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var body = new UpdateLibraryOptionsDto()
{
Id = Guid.NewGuid(),
LibraryOptions = new LibraryOptions()
};
using var response = await client.PostAsJsonAsync("Library/VirtualFolders/LibraryOptions", body, _jsonOptions);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
[Priority(-2)]
public async Task UpdateLibraryOptions_Valid_Success()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var createBody = new AddVirtualFolderDto()
{
LibraryOptions = new LibraryOptions()
{
Enabled = false
}
};
using var createResponse = await client.PostAsJsonAsync("Library/VirtualFolders?name=test&refreshLibrary=true", createBody, _jsonOptions);
Assert.Equal(HttpStatusCode.NoContent, createResponse.StatusCode);
await Task.Delay(2000).ConfigureAwait(true);
using var response = await client.GetAsync("Library/VirtualFolders");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var library = await response.Content.ReadFromJsonAsAsyncEnumerable<VirtualFolderInfo>(_jsonOptions)
.FirstOrDefaultAsync(x => string.Equals(x?.Name, "test", StringComparison.Ordinal));
Assert.NotNull(library);
var options = library.LibraryOptions;
Assert.NotNull(options);
Assert.False(options.Enabled);
options.Enabled = true;
var body = new UpdateLibraryOptionsDto()
{
Id = Guid.Parse(library.ItemId),
LibraryOptions = options
};
using var response2 = await client.PostAsJsonAsync("Library/VirtualFolders/LibraryOptions", body, _jsonOptions);
Assert.Equal(HttpStatusCode.NoContent, response2.StatusCode);
}
[Fact]
[Priority(1)]
public async Task DeleteLibrary_Invalid_NotFound()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
using var response = await client.DeleteAsync("Library/VirtualFolders?name=doesntExist");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
[Priority(1)]
public async Task DeleteLibrary_Valid_Success()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
using var response = await client.DeleteAsync("Library/VirtualFolders?name=test&refreshLibrary=true");
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
}
@@ -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);
}
}
@@ -0,0 +1,61 @@
using System.Globalization;
using System.Net;
using System.Net.Mime;
using System.Threading.Tasks;
using Xunit;
namespace Jellyfin.Server.Integration.Tests.Controllers
{
public sealed class MediaInfoControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
private static string? _accessToken;
public MediaInfoControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task BitrateTest_Default_Ok()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var response = await client.GetAsync("Playback/BitrateTest");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Octet, response.Content.Headers.ContentType?.MediaType);
Assert.NotNull(response.Content.Headers.ContentLength);
}
[Theory]
[InlineData(102400)]
public async Task BitrateTest_WithValidParam_Ok(int size)
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var response = await client.GetAsync("Playback/BitrateTest?size=" + size.ToString(CultureInfo.InvariantCulture));
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Octet, response.Content.Headers.ContentType?.MediaType);
Assert.NotNull(response.Content.Headers.ContentLength);
Assert.InRange(response.Content.Headers.ContentLength!.Value, size, long.MaxValue);
}
[Theory]
[InlineData(0)] // Zero
[InlineData(-102400)] // Negative value
[InlineData(1000000000)] // Too large
public async Task BitrateTest_InvalidValue_BadRequest(int size)
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var response = await client.GetAsync("Playback/BitrateTest?size=" + size.ToString(CultureInfo.InvariantCulture));
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
}
}
@@ -0,0 +1,117 @@
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Api.Models.LibraryStructureDto;
using Jellyfin.Extensions.Json;
using MediaBrowser.Model.Configuration;
using Xunit;
namespace Jellyfin.Server.Integration.Tests.Controllers
{
public sealed class MediaStructureControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private static string? _accessToken;
public MediaStructureControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task RenameVirtualFolder_WhiteSpaceName_ReturnsBadRequest()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
using var postContent = new ByteArrayContent(Array.Empty<byte>());
var response = await client.PostAsync("Library/VirtualFolders/Name?name=+&newName=test", postContent);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task RenameVirtualFolder_WhiteSpaceNewName_ReturnsBadRequest()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
using var postContent = new ByteArrayContent(Array.Empty<byte>());
var response = await client.PostAsync("Library/VirtualFolders/Name?name=test&newName=+", postContent);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task RenameVirtualFolder_NameDoesntExist_ReturnsNotFound()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
using var postContent = new ByteArrayContent(Array.Empty<byte>());
var response = await client.PostAsync("Library/VirtualFolders/Name?name=doesnt+exist&newName=test", postContent);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task AddMediaPath_PathDoesntExist_ReturnsNotFound()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var data = new MediaPathDto()
{
Name = "Test",
Path = "/this/path/doesnt/exist"
};
var response = await client.PostAsJsonAsync("Library/VirtualFolders/Paths", data, _jsonOptions);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task UpdateMediaPath_WhiteSpaceName_ReturnsBadRequest()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var data = new UpdateMediaPathRequestDto()
{
Name = " ",
PathInfo = new MediaPathInfo("test")
};
var response = await client.PostAsJsonAsync("Library/VirtualFolders/Paths/Update", data, _jsonOptions);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task RemoveMediaPath_WhiteSpaceName_ReturnsBadRequest()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=+");
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task RemoveMediaPath_PathDoesntExist_ReturnsNotFound()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=none&path=%2Fthis%2Fpath%2Fdoesnt%2Fexist");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
}
@@ -0,0 +1,26 @@
using System.Net;
using System.Threading.Tasks;
using Xunit;
namespace Jellyfin.Server.Integration.Tests.Controllers;
public sealed class MusicGenreControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
private static string? _accessToken;
public MusicGenreControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task MusicGenres_FakeMusicGenre_NotFound()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var response = await client.GetAsync("MusicGenres/Fake-MusicGenre");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
@@ -0,0 +1,26 @@
using System.Net;
using System.Threading.Tasks;
using Xunit;
namespace Jellyfin.Server.Integration.Tests.Controllers;
public class PersonsControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
private static string? _accessToken;
public PersonsControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task GetPerson_DoesntExist_NotFound()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
using var response = await client.GetAsync($"Persons/DoesntExist");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
@@ -0,0 +1,61 @@
using System;
using System.Net;
using System.Threading.Tasks;
using Xunit;
namespace Jellyfin.Server.Integration.Tests.Controllers;
public class PlaystateControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
private static string? _accessToken;
public PlaystateControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task DeleteMarkUnplayedItem_NonexistentUserId_NotFound()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
using var response = await client.DeleteAsync($"Users/{Guid.NewGuid()}/PlayedItems/{Guid.NewGuid()}");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task PostMarkPlayedItem_NonexistentUserId_NotFound()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
using var response = await client.PostAsync($"Users/{Guid.NewGuid()}/PlayedItems/{Guid.NewGuid()}", null);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task DeleteMarkUnplayedItem_NonexistentItemId_NotFound()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var userDto = await AuthHelper.GetUserDtoAsync(client);
using var response = await client.DeleteAsync($"Users/{userDto.Id}/PlayedItems/{Guid.NewGuid()}");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task PostMarkPlayedItem_NonexistentItemId_NotFound()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var userDto = await AuthHelper.GetUserDtoAsync(client);
using var response = await client.PostAsync($"Users/{userDto.Id}/PlayedItems/{Guid.NewGuid()}", null);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
@@ -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);
}
}
@@ -0,0 +1,116 @@
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Net.Mime;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Api.Models.StartupDtos;
using Jellyfin.Extensions.Json;
using Xunit;
using Xunit.Priority;
namespace Jellyfin.Server.Integration.Tests.Controllers
{
[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
public sealed class StartupControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
public StartupControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Fact]
[Priority(-2)]
public async Task Configuration_EditConfig_Success()
{
var client = _factory.CreateClient();
var config = new StartupConfigurationDto()
{
ServerName = "NewServer",
UICulture = "NewCulture",
MetadataCountryCode = "be",
PreferredMetadataLanguage = "nl"
};
using var postResponse = await client.PostAsJsonAsync("/Startup/Configuration", config, _jsonOptions);
Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode);
using var getResponse = await client.GetAsync("/Startup/Configuration");
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, getResponse.Content.Headers.ContentType?.MediaType);
var newConfig = await getResponse.Content.ReadFromJsonAsync<StartupConfigurationDto>(_jsonOptions);
Assert.Equal(config.ServerName, newConfig!.ServerName);
Assert.Equal(config.UICulture, newConfig.UICulture);
Assert.Equal(config.MetadataCountryCode, newConfig.MetadataCountryCode);
Assert.Equal(config.PreferredMetadataLanguage, newConfig.PreferredMetadataLanguage);
}
[Fact]
[Priority(-2)]
public async Task User_DefaultUser_NameWithoutPassword()
{
var client = _factory.CreateClient();
using var response = await client.GetAsync("/Startup/User");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
var user = await response.Content.ReadFromJsonAsync<StartupUserDto>(_jsonOptions);
Assert.NotNull(user);
Assert.NotNull(user.Name);
Assert.NotEmpty(user.Name);
Assert.Null(user.Password);
}
[Fact]
[Priority(-1)]
public async Task User_EditUser_Success()
{
var client = _factory.CreateClient();
var user = new StartupUserDto()
{
Name = "NewName",
Password = "NewPassword"
};
var postResponse = await client.PostAsJsonAsync("/Startup/User", user, _jsonOptions);
Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode);
var getResponse = await client.GetAsync("/Startup/User");
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, getResponse.Content.Headers.ContentType?.MediaType);
var newUser = await getResponse.Content.ReadFromJsonAsync<StartupUserDto>(_jsonOptions);
Assert.NotNull(newUser);
Assert.Equal(user.Name, newUser.Name);
Assert.Null(newUser.Password);
}
[Fact]
[Priority(0)]
public async Task CompleteWizard_Success()
{
var client = _factory.CreateClient();
var response = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty<byte>()));
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
[Fact]
[Priority(1)]
public async Task GetFirstUser_CompleteWizard_Unauthorized()
{
var client = _factory.CreateClient();
using var response = await client.GetAsync("/Startup/User");
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
}
}
@@ -0,0 +1,168 @@
using System;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Api.Models.UserDtos;
using Jellyfin.Extensions.Json;
using MediaBrowser.Model.Dto;
using Xunit;
using Xunit.Priority;
namespace Jellyfin.Server.Integration.Tests.Controllers
{
[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
public sealed class UserControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private const string TestUsername = "testUser01";
private readonly JellyfinApplicationFactory _factory;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private static string? _accessToken;
private static Guid _testUserId = Guid.Empty;
public UserControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
private Task<HttpResponseMessage> CreateUserByName(HttpClient httpClient, CreateUserByName request)
=> httpClient.PostAsJsonAsync("Users/New", request, _jsonOptions);
private Task<HttpResponseMessage> UpdateUserPassword(HttpClient httpClient, Guid userId, UpdateUserPassword request)
=> httpClient.PostAsJsonAsync("Users/" + userId.ToString("N", CultureInfo.InvariantCulture) + "/Password", request, _jsonOptions);
[Fact]
[Priority(-1)]
public async Task GetPublicUsers_Valid_Success()
{
var client = _factory.CreateClient();
using var response = await client.GetAsync("Users/Public");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var users = await response.Content.ReadFromJsonAsync<UserDto[]>(_jsonOptions);
// User are hidden by default
Assert.NotNull(users);
Assert.Empty(users);
}
[Fact]
[Priority(-1)]
public async Task GetUsers_Valid_Success()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
using var response = await client.GetAsync("Users");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var users = await response.Content.ReadFromJsonAsync<UserDto[]>(_jsonOptions);
Assert.NotNull(users);
Assert.Single(users);
}
[Fact]
[Priority(-1)]
public async Task Me_Valid_Success()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
_ = await AuthHelper.GetUserDtoAsync(client);
}
[Fact]
[Priority(0)]
public async Task New_Valid_Success()
{
var client = _factory.CreateClient();
// access token can't be null here as the previous test populated it
client.DefaultRequestHeaders.AddAuthHeader(_accessToken!);
var createRequest = new CreateUserByName()
{
Name = TestUsername
};
using var response = await CreateUserByName(client, createRequest);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var user = await response.Content.ReadFromJsonAsync<UserDto>(_jsonOptions);
Assert.Equal(TestUsername, user!.Name);
_testUserId = user.Id;
Console.WriteLine(user.Id.ToString("N", CultureInfo.InvariantCulture));
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData("‼️")]
[Priority(0)]
public async Task New_Invalid_Fail(string? username)
{
var client = _factory.CreateClient();
// access token can't be null here as the previous test populated it
client.DefaultRequestHeaders.AddAuthHeader(_accessToken!);
var createRequest = new CreateUserByName()
{
Name = username!
};
using var response = await CreateUserByName(client, createRequest);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
[Priority(0)]
public async Task Delete_DoesntExist_NotFound()
{
var client = _factory.CreateClient();
// access token can't be null here as the previous test populated it
client.DefaultRequestHeaders.AddAuthHeader(_accessToken!);
using var response = await client.DeleteAsync($"User/{Guid.NewGuid()}");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
[Priority(1)]
public async Task UpdateUserPassword_Valid_Success()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken!);
var createRequest = new UpdateUserPassword()
{
NewPw = "4randomPa$$word"
};
using var response = await UpdateUserPassword(client, _testUserId, createRequest);
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
[Fact]
[Priority(2)]
public async Task UpdateUserPassword_Empty_RemoveSetPassword()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken!);
var createRequest = new UpdateUserPassword()
{
CurrentPw = "4randomPa$$word",
};
using var response = await UpdateUserPassword(client, _testUserId, createRequest);
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
}
}
@@ -0,0 +1,124 @@
using System;
using System.Globalization;
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Extensions.Json;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
using Xunit;
namespace Jellyfin.Server.Integration.Tests.Controllers;
public sealed class UserLibraryControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private static string? _accessToken;
public UserLibraryControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task GetRootFolder_NonexistentUserId_NotFound()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var response = await client.GetAsync($"Users/{Guid.NewGuid()}/Items/Root");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GetRootFolder_UserId_Valid()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
_ = await AuthHelper.GetRootFolderDtoAsync(client);
}
[Theory]
[InlineData("Users/{0}/Items/{1}")]
[InlineData("Users/{0}/Items/{1}/Intros")]
[InlineData("Users/{0}/Items/{1}/LocalTrailers")]
[InlineData("Users/{0}/Items/{1}/SpecialFeatures")]
[InlineData("Users/{0}/Items/{1}/Lyrics")]
public async Task GetItem_NonexistentUserId_NotFound(string format)
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var rootFolderDto = await AuthHelper.GetRootFolderDtoAsync(client);
var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid(), rootFolderDto.Id));
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData("Users/{0}/Items/{1}")]
[InlineData("Users/{0}/Items/{1}/Intros")]
[InlineData("Users/{0}/Items/{1}/LocalTrailers")]
[InlineData("Users/{0}/Items/{1}/SpecialFeatures")]
[InlineData("Users/{0}/Items/{1}/Lyrics")]
public async Task GetItem_NonexistentItemId_NotFound(string format)
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var userDto = await AuthHelper.GetUserDtoAsync(client);
var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, userDto.Id, Guid.NewGuid()));
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact(Skip = "Disabled for flaky execution after refactor.")]
public async Task GetItem_UserIdAndItemId_Valid()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var userDto = await AuthHelper.GetUserDtoAsync(client);
var rootFolderDto = await AuthHelper.GetRootFolderDtoAsync(client, userDto.Id);
var response = await client.GetAsync($"Users/{userDto.Id}/Items/{rootFolderDto.Id}");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var rootDto = await response.Content.ReadFromJsonAsync<BaseItemDto>(_jsonOptions);
Assert.NotNull(rootDto);
}
[Fact(Skip = "Disabled for flaky execution after refactor.")]
public async Task GetIntros_UserIdAndItemId_Valid()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var userDto = await AuthHelper.GetUserDtoAsync(client);
var rootFolderDto = await AuthHelper.GetRootFolderDtoAsync(client, userDto.Id);
var response = await client.GetAsync($"Users/{userDto.Id}/Items/{rootFolderDto.Id}/Intros");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var rootDto = await response.Content.ReadFromJsonAsync<QueryResult<BaseItemDto>>(_jsonOptions);
Assert.NotNull(rootDto);
}
[Theory(Skip = "Disabled for flaky execution after refactor.")]
[InlineData("Users/{0}/Items/{1}/LocalTrailers")]
[InlineData("Users/{0}/Items/{1}/SpecialFeatures")]
public async Task LocalTrailersAndSpecialFeatures_UserIdAndItemId_Valid(string format)
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var userDto = await AuthHelper.GetUserDtoAsync(client);
var rootFolderDto = await AuthHelper.GetRootFolderDtoAsync(client, userDto.Id);
var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, userDto.Id, rootFolderDto.Id));
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var rootDto = await response.Content.ReadFromJsonAsync<BaseItemDto[]>(_jsonOptions);
Assert.NotNull(rootDto);
}
}
@@ -0,0 +1,27 @@
using System;
using System.Net;
using System.Threading.Tasks;
using Xunit;
namespace Jellyfin.Server.Integration.Tests.Controllers;
public sealed class VideosControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
private static string? _accessToken;
public VideosControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task DeleteAlternateSources_NonexistentItemId_NotFound()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var response = await client.DeleteAsync($"Videos/{Guid.NewGuid()}");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}