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,85 @@
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Api.Models.StartupDtos;
using Jellyfin.Api.Models.UserDtos;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
using MediaBrowser.Model.Dto;
using Xunit;
namespace Jellyfin.Server.Integration.Tests
{
public static class AuthHelper
{
public const string AuthHeaderName = "Authorization";
public const string DummyAuthHeader = "MediaBrowser Client=\"Jellyfin.Server%20Integration%20Tests\", DeviceId=\"69420\", Device=\"Apple%20II\", Version=\"10.8.0\"";
public static async Task<string> CompleteStartupAsync(HttpClient client)
{
var jsonOptions = JsonDefaults.Options;
var userResponse = await client.GetByteArrayAsync("/Startup/User");
var user = JsonSerializer.Deserialize<StartupUserDto>(userResponse, jsonOptions);
using var completeResponse = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty<byte>()));
Assert.Equal(HttpStatusCode.NoContent, completeResponse.StatusCode);
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/Users/AuthenticateByName");
httpRequest.Headers.TryAddWithoutValidation(AuthHeaderName, DummyAuthHeader);
httpRequest.Content = JsonContent.Create(
new AuthenticateUserByName()
{
Username = user!.Name,
Pw = user.Password,
},
options: jsonOptions);
using var authResponse = await client.SendAsync(httpRequest);
authResponse.EnsureSuccessStatusCode();
var auth = await authResponse.Content.ReadFromJsonAsync<AuthenticationResultDto>(jsonOptions);
return auth!.AccessToken;
}
public static async Task<UserDto> GetUserDtoAsync(HttpClient client)
{
using var response = await client.GetAsync("Users/Me");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var userDto = await response.Content.ReadFromJsonAsync<UserDto>(JsonDefaults.Options);
Assert.NotNull(userDto);
return userDto;
}
public static async Task<BaseItemDto> GetRootFolderDtoAsync(HttpClient client, Guid userId = default)
{
if (userId.IsEmpty())
{
var userDto = await GetUserDtoAsync(client);
userId = userDto.Id;
}
var response = await client.GetAsync($"Users/{userId}/Items/Root");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var rootDto = await response.Content.ReadFromJsonAsync<BaseItemDto>(JsonDefaults.Options);
Assert.NotNull(rootDto);
return rootDto;
}
public static void AddAuthHeader(this HttpHeaders headers, string accessToken)
{
headers.Add(AuthHeaderName, DummyAuthHeader + $", Token={accessToken}");
}
private sealed class AuthenticationResultDto
{
public string AccessToken { get; set; } = string.Empty;
public string ServerId { get; set; } = string.Empty;
}
}
}
@@ -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);
}
}
@@ -0,0 +1,49 @@
using System.Net;
using System.Threading.Tasks;
using Xunit;
namespace Jellyfin.Server.Integration.Tests
{
/// <summary>
/// Defines the test for encoded querystrings in the url.
/// </summary>
public class EncodedQueryStringTest : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
public EncodedQueryStringTest(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Theory]
[InlineData("a=1&b=2&c=3", "a=1&b=2&c=3")] // won't be processed as there is more than 1.
[InlineData("a=1", "a=1")] // won't be processed as it has a value
[InlineData("a%3D1%26b%3D2%26c%3D3", "a=1&b=2&c=3")] // will be processed.
[InlineData("a=b&a=c", "a=b")]
[InlineData("a%3D1", "a=1")]
[InlineData("a%3Db%26a%3Dc", "a=b")]
public async Task Ensure_Decoding_Of_Urls_Is_Working(string sourceUrl, string unencodedUrl)
{
var client = _factory.CreateClient();
var response = await client.GetAsync("Encoder/UrlDecode?" + sourceUrl);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
string reply = await response.Content.ReadAsStringAsync();
Assert.Equal(unencodedUrl, reply);
}
[Theory]
[InlineData("a=b&a=c", "a=b,c")]
[InlineData("a%3Db%26a%3Dc", "a=b,c")]
public async Task Ensure_Array_Decoding_Of_Urls_Is_Working(string sourceUrl, string unencodedUrl)
{
var client = _factory.CreateClient();
var response = await client.GetAsync("Encoder/UrlArrayDecode?" + sourceUrl);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
string reply = await response.Content.ReadAsStringAsync();
Assert.Equal(unencodedUrl, reply);
}
}
}
@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="AutoFixture" />
<PackageReference Include="AutoFixture.AutoMoq" />
<PackageReference Include="AutoFixture.Xunit2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Xunit.Priority" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup>
<None Include="Test Data\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<!-- Don't run tests in parallel -->
<None Update="xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../Jellyfin.Server/Jellyfin.Server.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="TestPage.html" />
</ItemGroup>
</Project>
@@ -0,0 +1,194 @@
using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.IO;
using Emby.Server.Implementations;
using Jellyfin.Server.Extensions;
using Jellyfin.Server.Helpers;
using Jellyfin.Server.ServerSetupApp;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Serilog;
using Serilog.Core;
using Serilog.Extensions.Logging;
namespace Jellyfin.Server.Integration.Tests
{
/// <summary>
/// Factory for bootstrapping the Jellyfin application in memory for functional end to end tests.
/// </summary>
public class JellyfinApplicationFactory : WebApplicationFactory<Startup>
{
private static readonly string _testPathRoot = Path.Combine(Path.GetTempPath(), "jellyfin-test-data");
private readonly ConcurrentBag<IDisposable> _disposableComponents = new ConcurrentBag<IDisposable>();
/// <summary>
/// Initializes static members of the <see cref="JellyfinApplicationFactory"/> class.
/// </summary>
static JellyfinApplicationFactory()
{
// Perform static initialization that only needs to happen once per test-run
Log.Logger = new LoggerConfiguration()
.WriteTo.Console(formatProvider: CultureInfo.InvariantCulture)
.CreateLogger();
StartupHelpers.PerformStaticInitialization();
}
/// <inheritdoc/>
protected override IHostBuilder CreateHostBuilder()
{
return new HostBuilder();
}
/// <inheritdoc/>
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
// Skip ffmpeg check for testing
Environment.SetEnvironmentVariable("JELLYFIN_FFMPEG__NOVALIDATION", "true");
// Specify the startup command line options
var commandLineOpts = new StartupOptions();
// Use a temporary directory for the application paths
var webHostPathRoot = Path.Combine(_testPathRoot, "test-host-" + Path.GetFileNameWithoutExtension(Path.GetRandomFileName()));
Directory.CreateDirectory(Path.Combine(webHostPathRoot, "logs"));
Directory.CreateDirectory(Path.Combine(webHostPathRoot, "config"));
Directory.CreateDirectory(Path.Combine(webHostPathRoot, "cache"));
Directory.CreateDirectory(Path.Combine(webHostPathRoot, "jellyfin-web"));
var appPaths = new ServerApplicationPaths(
webHostPathRoot,
Path.Combine(webHostPathRoot, "logs"),
Path.Combine(webHostPathRoot, "config"),
Path.Combine(webHostPathRoot, "cache"),
Path.Combine(webHostPathRoot, "jellyfin-web"));
// Create the logging config file
// TODO: We shouldn't need to do this since we are only logging to console
StartupHelpers.InitLoggingConfigFile(appPaths).GetAwaiter().GetResult();
// Create a copy of the application configuration to use for startup
var startupConfig = Program.CreateAppConfiguration(commandLineOpts, appPaths);
ILoggerFactory loggerFactory = new SerilogLoggerFactory();
_disposableComponents.Add(loggerFactory);
// Create the app host and initialize it
var appHost = new TestAppHost(
appPaths,
loggerFactory,
commandLineOpts,
startupConfig);
_disposableComponents.Add(appHost);
builder.ConfigureServices(services => appHost.Init(services))
.ConfigureWebHostBuilder(appHost, startupConfig, appPaths, NullLogger.Instance)
.ConfigureAppConfiguration((context, builder) =>
{
builder
.SetBasePath(appPaths.ConfigurationDirectoryPath)
.AddInMemoryCollection(ConfigurationOptions.DefaultConfiguration)
.AddEnvironmentVariables("JELLYFIN_")
.AddInMemoryCollection(commandLineOpts.ConvertToConfig());
})
.ConfigureServices(e => e
.AddSingleton<IStartupLogger, NullStartupLogger<object>>()
.AddTransient(typeof(IStartupLogger<>), typeof(NullStartupLogger<>))
.AddSingleton(e));
}
/// <inheritdoc/>
protected override IHost CreateHost(IHostBuilder builder)
{
var host = builder.Build();
var appHost = (TestAppHost)host.Services.GetRequiredService<IApplicationHost>();
appHost.ServiceProvider = host.Services;
var applicationPaths = appHost.ServiceProvider.GetRequiredService<IApplicationPaths>();
Program.ApplyStartupMigrationAsync((ServerApplicationPaths)applicationPaths, appHost.ServiceProvider.GetRequiredService<IConfiguration>()).GetAwaiter().GetResult();
Program.ApplyCoreMigrationsAsync(appHost.ServiceProvider, Migrations.Stages.JellyfinMigrationStageTypes.CoreInitialisation).GetAwaiter().GetResult();
appHost.InitializeServices(Mock.Of<IConfiguration>()).GetAwaiter().GetResult();
Program.ApplyCoreMigrationsAsync(appHost.ServiceProvider, Migrations.Stages.JellyfinMigrationStageTypes.AppInitialisation).GetAwaiter().GetResult();
host.Start();
appHost.RunStartupTasksAsync().GetAwaiter().GetResult();
return host;
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
foreach (var disposable in _disposableComponents)
{
disposable.Dispose();
}
_disposableComponents.Clear();
base.Dispose(disposing);
}
private sealed class NullStartupLogger<TCategory> : IStartupLogger<TCategory>
{
public StartupLogTopic? Topic => throw new NotImplementedException();
public IStartupLogger BeginGroup(FormattableString logEntry)
{
return this;
}
public IStartupLogger<TCategory1> BeginGroup<TCategory1>(FormattableString logEntry)
{
return new NullStartupLogger<TCategory1>();
}
public IDisposable? BeginScope<TState>(TState state)
where TState : notnull
{
return NullLogger.Instance.BeginScope(state);
}
public bool IsEnabled(LogLevel logLevel)
{
return NullLogger.Instance.IsEnabled(logLevel);
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
NullLogger.Instance.Log(logLevel, eventId, state, exception, formatter);
}
public Microsoft.Extensions.Logging.ILogger With(Microsoft.Extensions.Logging.ILogger logger)
{
return this;
}
public IStartupLogger<TCategory1> With<TCategory1>(Microsoft.Extensions.Logging.ILogger logger)
{
return new NullStartupLogger<TCategory1>();
}
IStartupLogger<TCategory> IStartupLogger<TCategory>.BeginGroup(FormattableString logEntry)
{
return new NullStartupLogger<TCategory>();
}
IStartupLogger IStartupLogger.With(Microsoft.Extensions.Logging.ILogger logger)
{
return this;
}
IStartupLogger<TCategory> IStartupLogger<TCategory>.With(Microsoft.Extensions.Logging.ILogger logger)
{
return this;
}
}
}
}
@@ -0,0 +1,32 @@
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
namespace Jellyfin.Server.Integration.Tests.Middleware
{
public sealed class RobotsRedirectionMiddlewareTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
public RobotsRedirectionMiddlewareTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task RobotsDotTxtRedirects()
{
var client = _factory.CreateClient(
new WebApplicationFactoryClientOptions()
{
AllowAutoRedirect = false
});
var response = await client.GetAsync("robots.txt");
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("web/robots.txt", response.Headers.Location?.ToString());
}
}
}
@@ -0,0 +1,41 @@
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using Xunit;
using Xunit.Abstractions;
namespace Jellyfin.Server.Integration.Tests
{
public sealed class OpenApiSpecTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
private readonly ITestOutputHelper _outputHelper;
public OpenApiSpecTests(JellyfinApplicationFactory factory, ITestOutputHelper outputHelper)
{
_factory = factory;
_outputHelper = outputHelper;
}
[Fact]
public async Task GetSpec_ReturnsCorrectResponse()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync("/api-docs/openapi.json");
// Assert
response.EnsureSuccessStatusCode();
Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString());
// Write out for publishing
string outputPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "openapi.json"));
_outputHelper.WriteLine("Writing OpenAPI Spec JSON to '{0}'.", outputPath);
await using var fs = AsyncFile.Create(outputPath);
await response.Content.CopyToAsync(fs);
}
}
}
@@ -0,0 +1 @@
C:\Music
@@ -0,0 +1,46 @@
using System.Collections.Generic;
using System.Reflection;
using Emby.Server.Implementations;
using MediaBrowser.Controller;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Integration.Tests
{
/// <summary>
/// Implementation of the abstract <see cref="ApplicationHost" /> class.
/// </summary>
public class TestAppHost : CoreAppHost
{
/// <summary>
/// Initializes a new instance of the <see cref="TestAppHost" /> class.
/// </summary>
/// <param name="applicationPaths">The <see cref="ServerApplicationPaths" /> to be used by the <see cref="CoreAppHost" />.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
/// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
/// <param name="startup">The <see cref="IConfiguration" /> to be used by the <see cref="CoreAppHost" />.</param>
public TestAppHost(
IServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory,
IStartupOptions options,
IConfiguration startup)
: base(
applicationPaths,
loggerFactory,
options,
startup)
{
}
/// <inheritdoc />
protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
{
foreach (var a in base.GetAssembliesWithPartsInternal())
{
yield return a;
}
yield return typeof(TestPlugin).Assembly;
}
}
}
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<title>TestPlugin</title>
</head>
<body>
<h1>This is a Test Page.</h1>
</body>
</html>
@@ -0,0 +1,43 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
namespace Jellyfin.Server.Integration.Tests
{
public class TestPlugin : BasePlugin<BasePluginConfiguration>, IHasWebPages
{
public TestPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
: base(applicationPaths, xmlSerializer)
{
Instance = this;
}
public static TestPlugin? Instance { get; private set; }
public override Guid Id => new Guid("2d350a13-0bf7-4b61-859c-d5e601b5facf");
public override string Name => nameof(TestPlugin);
public override string Description => "Server test Plugin.";
public IEnumerable<PluginPageInfo> GetPages()
{
yield return new PluginPageInfo
{
Name = Name,
EmbeddedResourcePath = GetType().Namespace + ".TestPage.html"
};
yield return new PluginPageInfo
{
Name = "BrokenPage",
EmbeddedResourcePath = GetType().Namespace + ".foobar"
};
}
}
}
@@ -0,0 +1,27 @@
#pragma warning disable CS1591
using System;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
namespace Jellyfin.Server.Integration.Tests
{
public class TestPluginWithoutPages : BasePlugin<BasePluginConfiguration>
{
public TestPluginWithoutPages(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
: base(applicationPaths, xmlSerializer)
{
Instance = this;
}
public static TestPluginWithoutPages? Instance { get; private set; }
public override Guid Id => new Guid("ae95cbe6-bd3d-4d73-8596-490db334611e");
public override string Name => nameof(TestPluginWithoutPages);
public override string Description => "Server test Plugin without web pages.";
}
}
@@ -0,0 +1,33 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Jellyfin.Server.Integration.Tests
{
public sealed class WebSocketTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
public WebSocketTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task WebSocket_Unauthenticated_ThrowsInvalidOperationException()
{
var server = _factory.Server;
var client = server.CreateWebSocketClient();
await Assert.ThrowsAsync<InvalidOperationException>(
() => client.ConnectAsync(
new UriBuilder(server.BaseAddress)
{
Scheme = "ws",
Path = "websocket"
}.Uri,
CancellationToken.None));
}
}
}
@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")]
@@ -0,0 +1,22 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Jellyfin.Server.Integration.Tests")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyProductAttribute("Jellyfin.Server.Integration.Tests")]
[assembly: System.Reflection.AssemblyTitleAttribute("Jellyfin.Server.Integration.Tests")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class.
@@ -0,0 +1 @@
537ed3606760d44acd7bc6e65b7fbbcd67c648b7601a0092a41e4849777d179c
@@ -0,0 +1,27 @@
is_global = true
build_property.TargetFramework = net10.0
build_property.TargetFramework = net10.0
build_property.TargetFrameworkIdentifier = .NETCoreApp
build_property.TargetFrameworkIdentifier = .NETCoreApp
build_property.TargetFrameworkVersion = v10.0
build_property.TargetFrameworkVersion = v10.0
build_property.TargetPlatformMinVersion =
build_property.TargetPlatformMinVersion =
build_property.UsingMicrosoftNETSdkWeb =
build_property.UsingMicrosoftNETSdkWeb =
build_property.ProjectTypeGuids =
build_property.ProjectTypeGuids =
build_property.InvariantGlobalization =
build_property.InvariantGlobalization =
build_property.PlatformNeutralAssembly =
build_property.PlatformNeutralAssembly =
build_property.EnforceExtendedAnalyzerRules =
build_property.EnforceExtendedAnalyzerRules =
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property.RootNamespace = Jellyfin.Server.Integration.Tests
build_property.ProjectDir = /srv/common_drive/Projects/pgsql-jellyfin/tests/Jellyfin.Server.Integration.Tests/
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.EffectiveAnalysisLevelStyle = 10.0
build_property.EnableCodeStyleSeverity =
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="/home/wjones/.nuget/packages/" />
</ItemGroup>
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)xunit.runner.visualstudio/2.8.2/build/net6.0/xunit.runner.visualstudio.props" Condition="Exists('$(NuGetPackageRoot)xunit.runner.visualstudio/2.8.2/build/net6.0/xunit.runner.visualstudio.props')" />
<Import Project="$(NuGetPackageRoot)xunit.core/2.9.3/build/xunit.core.props" Condition="Exists('$(NuGetPackageRoot)xunit.core/2.9.3/build/xunit.core.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.testplatform.testhost/18.0.1/build/net8.0/Microsoft.TestPlatform.TestHost.props" Condition="Exists('$(NuGetPackageRoot)microsoft.testplatform.testhost/18.0.1/build/net8.0/Microsoft.TestPlatform.TestHost.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.codecoverage/18.0.1/build/netstandard2.0/Microsoft.CodeCoverage.props" Condition="Exists('$(NuGetPackageRoot)microsoft.codecoverage/18.0.1/build/netstandard2.0/Microsoft.CodeCoverage.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.net.test.sdk/18.0.1/build/net8.0/Microsoft.NET.Test.Sdk.props" Condition="Exists('$(NuGetPackageRoot)microsoft.net.test.sdk/18.0.1/build/net8.0/Microsoft.NET.Test.Sdk.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Configuration.UserSecrets.props" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Configuration.UserSecrets.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props')" />
</ImportGroup>
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Pkgxunit_analyzers Condition=" '$(Pkgxunit_analyzers)' == '' ">/home/wjones/.nuget/packages/xunit.analyzers/1.18.0</Pkgxunit_analyzers>
<PkgMicrosoft_Extensions_ApiDescription_Server Condition=" '$(PkgMicrosoft_Extensions_ApiDescription_Server)' == '' ">/home/wjones/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5</PkgMicrosoft_Extensions_ApiDescription_Server>
<PkgStyleCop_Analyzers_Unstable Condition=" '$(PkgStyleCop_Analyzers_Unstable)' == '' ">/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556</PkgStyleCop_Analyzers_Unstable>
<PkgSmartAnalyzers_MultithreadingAnalyzer Condition=" '$(PkgSmartAnalyzers_MultithreadingAnalyzer)' == '' ">/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31</PkgSmartAnalyzers_MultithreadingAnalyzer>
<PkgSerilogAnalyzer Condition=" '$(PkgSerilogAnalyzer)' == '' ">/home/wjones/.nuget/packages/seriloganalyzer/0.15.0</PkgSerilogAnalyzer>
<PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers Condition=" '$(PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers)' == '' ">/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0</PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers>
</PropertyGroup>
</Project>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)xunit.core/2.9.3/build/xunit.core.targets" Condition="Exists('$(NuGetPackageRoot)xunit.core/2.9.3/build/xunit.core.targets')" />
<Import Project="$(NuGetPackageRoot)sqlitepclraw.lib.e_sqlite3/2.1.11/buildTransitive/net9.0/SQLitePCLRaw.lib.e_sqlite3.targets" Condition="Exists('$(NuGetPackageRoot)sqlitepclraw.lib.e_sqlite3/2.1.11/buildTransitive/net9.0/SQLitePCLRaw.lib.e_sqlite3.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.binder/10.0.3/buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.binder/10.0.3/buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.testplatform.testhost/18.0.1/build/net8.0/Microsoft.TestPlatform.TestHost.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.testplatform.testhost/18.0.1/build/net8.0/Microsoft.TestPlatform.TestHost.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.codecoverage/18.0.1/build/netstandard2.0/Microsoft.CodeCoverage.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codecoverage/18.0.1/build/netstandard2.0/Microsoft.CodeCoverage.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.net.test.sdk/18.0.1/build/net8.0/Microsoft.NET.Test.Sdk.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.net.test.sdk/18.0.1/build/net8.0/Microsoft.NET.Test.Sdk.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Configuration.UserSecrets.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Configuration.UserSecrets.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.aspnetcore.mvc.testing/10.0.3/buildTransitive/net10.0/Microsoft.AspNetCore.Mvc.Testing.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.aspnetcore.mvc.testing/10.0.3/buildTransitive/net10.0/Microsoft.AspNetCore.Mvc.Testing.targets')" />
<Import Project="$(NuGetPackageRoot)coverlet.collector/8.0.0/build/net8.0/coverlet.collector.targets" Condition="Exists('$(NuGetPackageRoot)coverlet.collector/8.0.0/build/net8.0/coverlet.collector.targets')" />
</ImportGroup>
</Project>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,161 @@
{
"version": 2,
"dgSpecHash": "HPKM7WueLog=",
"success": true,
"projectFilePath": "/srv/common_drive/Projects/pgsql-jellyfin/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj",
"expectedPackageFiles": [
"/home/wjones/.nuget/packages/asynckeyedlock/8.0.2/asynckeyedlock.8.0.2.nupkg.sha512",
"/home/wjones/.nuget/packages/autofixture/4.18.1/autofixture.4.18.1.nupkg.sha512",
"/home/wjones/.nuget/packages/autofixture.automoq/4.18.1/autofixture.automoq.4.18.1.nupkg.sha512",
"/home/wjones/.nuget/packages/autofixture.xunit2/4.18.1/autofixture.xunit2.4.18.1.nupkg.sha512",
"/home/wjones/.nuget/packages/bdinfo/0.8.0/bdinfo.0.8.0.nupkg.sha512",
"/home/wjones/.nuget/packages/bitfaster.caching/2.5.4/bitfaster.caching.2.5.4.nupkg.sha512",
"/home/wjones/.nuget/packages/blurhashsharp/1.4.0-pre.1/blurhashsharp.1.4.0-pre.1.nupkg.sha512",
"/home/wjones/.nuget/packages/blurhashsharp.skiasharp/1.4.0-pre.1/blurhashsharp.skiasharp.1.4.0-pre.1.nupkg.sha512",
"/home/wjones/.nuget/packages/castle.core/5.1.1/castle.core.5.1.1.nupkg.sha512",
"/home/wjones/.nuget/packages/commandlineparser/2.9.1/commandlineparser.2.9.1.nupkg.sha512",
"/home/wjones/.nuget/packages/coverlet.collector/8.0.0/coverlet.collector.8.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/diacritics/4.1.4/diacritics.4.1.4.nupkg.sha512",
"/home/wjones/.nuget/packages/discutils.core/0.16.13/discutils.core.0.16.13.nupkg.sha512",
"/home/wjones/.nuget/packages/discutils.iso9660/0.16.13/discutils.iso9660.0.16.13.nupkg.sha512",
"/home/wjones/.nuget/packages/discutils.streams/0.16.13/discutils.streams.0.16.13.nupkg.sha512",
"/home/wjones/.nuget/packages/discutils.udf/0.16.13/discutils.udf.0.16.13.nupkg.sha512",
"/home/wjones/.nuget/packages/dotnet.glob/3.1.3/dotnet.glob.3.1.3.nupkg.sha512",
"/home/wjones/.nuget/packages/excss/4.3.1/excss.4.3.1.nupkg.sha512",
"/home/wjones/.nuget/packages/fare/2.1.1/fare.2.1.1.nupkg.sha512",
"/home/wjones/.nuget/packages/harfbuzzsharp/8.3.0.1/harfbuzzsharp.8.3.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/harfbuzzsharp.nativeassets.linux/8.3.1.1/harfbuzzsharp.nativeassets.linux.8.3.1.1.nupkg.sha512",
"/home/wjones/.nuget/packages/harfbuzzsharp.nativeassets.macos/8.3.0.1/harfbuzzsharp.nativeassets.macos.8.3.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/harfbuzzsharp.nativeassets.win32/8.3.0.1/harfbuzzsharp.nativeassets.win32.8.3.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/icu4n/60.1.0-alpha.356/icu4n.60.1.0-alpha.356.nupkg.sha512",
"/home/wjones/.nuget/packages/icu4n.transliterator/60.1.0-alpha.356/icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512",
"/home/wjones/.nuget/packages/ignore/0.2.1/ignore.0.2.1.nupkg.sha512",
"/home/wjones/.nuget/packages/j2n/2.0.0/j2n.2.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/jellyfin.xmltv/10.8.0/jellyfin.xmltv.10.8.0.nupkg.sha512",
"/home/wjones/.nuget/packages/libse/4.0.12/libse.4.0.12.nupkg.sha512",
"/home/wjones/.nuget/packages/lrcparser/2025.623.0/lrcparser.2025.623.0.nupkg.sha512",
"/home/wjones/.nuget/packages/metabrainz.common/4.1.1/metabrainz.common.4.1.1.nupkg.sha512",
"/home/wjones/.nuget/packages/metabrainz.common.json/7.2.0/metabrainz.common.json.7.2.0.nupkg.sha512",
"/home/wjones/.nuget/packages/metabrainz.musicbrainz/8.0.1/metabrainz.musicbrainz.8.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.aspnetcore.authorization/10.0.3/microsoft.aspnetcore.authorization.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.aspnetcore.metadata/10.0.3/microsoft.aspnetcore.metadata.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.aspnetcore.mvc.testing/10.0.3/microsoft.aspnetcore.mvc.testing.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.aspnetcore.testhost/10.0.3/microsoft.aspnetcore.testhost.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0/microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.codecoverage/18.0.1/microsoft.codecoverage.18.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.data.sqlite/10.0.3/microsoft.data.sqlite.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.data.sqlite.core/10.0.3/microsoft.data.sqlite.core.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore/10.0.3/microsoft.entityframeworkcore.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore.abstractions/10.0.3/microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore.analyzers/10.0.3/microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore.relational/10.0.3/microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore.sqlite/10.0.3/microsoft.entityframeworkcore.sqlite.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore.sqlite.core/10.0.3/microsoft.entityframeworkcore.sqlite.core.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5/microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.caching.abstractions/10.0.3/microsoft.extensions.caching.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.caching.memory/10.0.3/microsoft.extensions.caching.memory.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration/10.0.3/microsoft.extensions.configuration.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.abstractions/10.0.3/microsoft.extensions.configuration.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.binder/10.0.3/microsoft.extensions.configuration.binder.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.commandline/10.0.3/microsoft.extensions.configuration.commandline.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.environmentvariables/10.0.3/microsoft.extensions.configuration.environmentvariables.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.fileextensions/10.0.3/microsoft.extensions.configuration.fileextensions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.json/10.0.3/microsoft.extensions.configuration.json.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.usersecrets/10.0.3/microsoft.extensions.configuration.usersecrets.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection/10.0.3/microsoft.extensions.dependencyinjection.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/10.0.3/microsoft.extensions.dependencyinjection.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.dependencymodel/10.0.3/microsoft.extensions.dependencymodel.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.diagnostics/10.0.3/microsoft.extensions.diagnostics.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.diagnostics.abstractions/10.0.3/microsoft.extensions.diagnostics.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.diagnostics.healthchecks/10.0.3/microsoft.extensions.diagnostics.healthchecks.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.diagnostics.healthchecks.abstractions/10.0.3/microsoft.extensions.diagnostics.healthchecks.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.diagnostics.healthchecks.entityframeworkcore/10.0.3/microsoft.extensions.diagnostics.healthchecks.entityframeworkcore.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.fileproviders.abstractions/10.0.3/microsoft.extensions.fileproviders.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.fileproviders.physical/10.0.3/microsoft.extensions.fileproviders.physical.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.filesystemglobbing/10.0.3/microsoft.extensions.filesystemglobbing.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.hosting/10.0.3/microsoft.extensions.hosting.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.hosting.abstractions/10.0.3/microsoft.extensions.hosting.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.http/10.0.3/microsoft.extensions.http.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging/10.0.3/microsoft.extensions.logging.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging.abstractions/10.0.3/microsoft.extensions.logging.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging.configuration/10.0.3/microsoft.extensions.logging.configuration.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging.console/10.0.3/microsoft.extensions.logging.console.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging.debug/10.0.3/microsoft.extensions.logging.debug.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging.eventlog/10.0.3/microsoft.extensions.logging.eventlog.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging.eventsource/10.0.3/microsoft.extensions.logging.eventsource.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.objectpool/7.0.0/microsoft.extensions.objectpool.7.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.options/10.0.3/microsoft.extensions.options.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.options.configurationextensions/10.0.3/microsoft.extensions.options.configurationextensions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.primitives/10.0.3/microsoft.extensions.primitives.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.net.test.sdk/18.0.1/microsoft.net.test.sdk.18.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.netcore.platforms/1.1.0/microsoft.netcore.platforms.1.1.0.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.openapi/1.6.22/microsoft.openapi.1.6.22.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.testplatform.objectmodel/18.0.1/microsoft.testplatform.objectmodel.18.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.testplatform.testhost/18.0.1/microsoft.testplatform.testhost.18.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.win32.systemevents/9.0.2/microsoft.win32.systemevents.9.0.2.nupkg.sha512",
"/home/wjones/.nuget/packages/moq/4.18.4/moq.4.18.4.nupkg.sha512",
"/home/wjones/.nuget/packages/morestachio/5.0.1.631/morestachio.5.0.1.631.nupkg.sha512",
"/home/wjones/.nuget/packages/nebml/1.1.0.5/nebml.1.1.0.5.nupkg.sha512",
"/home/wjones/.nuget/packages/netstandard.library/1.6.1/netstandard.library.1.6.1.nupkg.sha512",
"/home/wjones/.nuget/packages/newtonsoft.json/13.0.4/newtonsoft.json.13.0.4.nupkg.sha512",
"/home/wjones/.nuget/packages/playlistsnet/1.4.1/playlistsnet.1.4.1.nupkg.sha512",
"/home/wjones/.nuget/packages/polly/8.6.5/polly.8.6.5.nupkg.sha512",
"/home/wjones/.nuget/packages/polly.core/8.6.5/polly.core.8.6.5.nupkg.sha512",
"/home/wjones/.nuget/packages/prometheus-net/8.2.1/prometheus-net.8.2.1.nupkg.sha512",
"/home/wjones/.nuget/packages/prometheus-net.aspnetcore/8.2.1/prometheus-net.aspnetcore.8.2.1.nupkg.sha512",
"/home/wjones/.nuget/packages/prometheus-net.dotnetruntime/4.4.1/prometheus-net.dotnetruntime.4.4.1.nupkg.sha512",
"/home/wjones/.nuget/packages/serilog/4.3.0/serilog.4.3.0.nupkg.sha512",
"/home/wjones/.nuget/packages/serilog.aspnetcore/10.0.0/serilog.aspnetcore.10.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/serilog.enrichers.thread/4.0.0/serilog.enrichers.thread.4.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/serilog.expressions/5.0.0/serilog.expressions.5.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/serilog.extensions.hosting/10.0.0/serilog.extensions.hosting.10.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/serilog.extensions.logging/10.0.0/serilog.extensions.logging.10.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/serilog.formatting.compact/3.0.0/serilog.formatting.compact.3.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/serilog.settings.configuration/10.0.0/serilog.settings.configuration.10.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/serilog.sinks.async/2.1.0/serilog.sinks.async.2.1.0.nupkg.sha512",
"/home/wjones/.nuget/packages/serilog.sinks.console/6.1.1/serilog.sinks.console.6.1.1.nupkg.sha512",
"/home/wjones/.nuget/packages/serilog.sinks.debug/3.0.0/serilog.sinks.debug.3.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/serilog.sinks.file/7.0.0/serilog.sinks.file.7.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/serilog.sinks.graylog/3.1.1/serilog.sinks.graylog.3.1.1.nupkg.sha512",
"/home/wjones/.nuget/packages/seriloganalyzer/0.15.0/seriloganalyzer.0.15.0.nupkg.sha512",
"/home/wjones/.nuget/packages/shimskiasharp/3.4.1/shimskiasharp.3.4.1.nupkg.sha512",
"/home/wjones/.nuget/packages/skiasharp/3.116.1/skiasharp.3.116.1.nupkg.sha512",
"/home/wjones/.nuget/packages/skiasharp.harfbuzz/3.116.1/skiasharp.harfbuzz.3.116.1.nupkg.sha512",
"/home/wjones/.nuget/packages/skiasharp.nativeassets.linux/3.116.1/skiasharp.nativeassets.linux.3.116.1.nupkg.sha512",
"/home/wjones/.nuget/packages/skiasharp.nativeassets.macos/3.116.1/skiasharp.nativeassets.macos.3.116.1.nupkg.sha512",
"/home/wjones/.nuget/packages/skiasharp.nativeassets.win32/3.116.1/skiasharp.nativeassets.win32.3.116.1.nupkg.sha512",
"/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31/smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512",
"/home/wjones/.nuget/packages/sqlitepclraw.bundle_e_sqlite3/2.1.11/sqlitepclraw.bundle_e_sqlite3.2.1.11.nupkg.sha512",
"/home/wjones/.nuget/packages/sqlitepclraw.core/2.1.11/sqlitepclraw.core.2.1.11.nupkg.sha512",
"/home/wjones/.nuget/packages/sqlitepclraw.lib.e_sqlite3/2.1.11/sqlitepclraw.lib.e_sqlite3.2.1.11.nupkg.sha512",
"/home/wjones/.nuget/packages/sqlitepclraw.provider.e_sqlite3/2.1.11/sqlitepclraw.provider.e_sqlite3.2.1.11.nupkg.sha512",
"/home/wjones/.nuget/packages/stylecop.analyzers/1.2.0-beta.556/stylecop.analyzers.1.2.0-beta.556.nupkg.sha512",
"/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556/stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512",
"/home/wjones/.nuget/packages/svg.custom/3.4.1/svg.custom.3.4.1.nupkg.sha512",
"/home/wjones/.nuget/packages/svg.model/3.4.1/svg.model.3.4.1.nupkg.sha512",
"/home/wjones/.nuget/packages/svg.skia/3.4.1/svg.skia.3.4.1.nupkg.sha512",
"/home/wjones/.nuget/packages/swashbuckle.aspnetcore/7.3.2/swashbuckle.aspnetcore.7.3.2.nupkg.sha512",
"/home/wjones/.nuget/packages/swashbuckle.aspnetcore.redoc/6.9.0/swashbuckle.aspnetcore.redoc.6.9.0.nupkg.sha512",
"/home/wjones/.nuget/packages/swashbuckle.aspnetcore.swagger/7.3.2/swashbuckle.aspnetcore.swagger.7.3.2.nupkg.sha512",
"/home/wjones/.nuget/packages/swashbuckle.aspnetcore.swaggergen/7.3.2/swashbuckle.aspnetcore.swaggergen.7.3.2.nupkg.sha512",
"/home/wjones/.nuget/packages/swashbuckle.aspnetcore.swaggerui/7.3.2/swashbuckle.aspnetcore.swaggerui.7.3.2.nupkg.sha512",
"/home/wjones/.nuget/packages/system.diagnostics.eventlog/10.0.3/system.diagnostics.eventlog.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/system.drawing.common/9.0.2/system.drawing.common.9.0.2.nupkg.sha512",
"/home/wjones/.nuget/packages/taglibsharp/2.3.0/taglibsharp.2.3.0.nupkg.sha512",
"/home/wjones/.nuget/packages/tmdblib/2.3.0/tmdblib.2.3.0.nupkg.sha512",
"/home/wjones/.nuget/packages/ude.netstandard/1.2.0/ude.netstandard.1.2.0.nupkg.sha512",
"/home/wjones/.nuget/packages/utf.unknown/2.6.0/utf.unknown.2.6.0.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit/2.9.3/xunit.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.abstractions/2.0.3/xunit.abstractions.2.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.analyzers/1.18.0/xunit.analyzers.1.18.0.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.assert/2.9.3/xunit.assert.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.core/2.9.3/xunit.core.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.extensibility.core/2.9.3/xunit.extensibility.core.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.extensibility.execution/2.9.3/xunit.extensibility.execution.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.priority/1.1.6/xunit.priority.1.1.6.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.runner.visualstudio/2.8.2/xunit.runner.visualstudio.2.8.2.nupkg.sha512",
"/home/wjones/.nuget/packages/z440.atl.core/7.11.0/z440.atl.core.7.11.0.nupkg.sha512",
"/home/wjones/.nuget/packages/zlib.net-mutliplatform/1.0.8/zlib.net-mutliplatform.1.0.8.nupkg.sha512"
],
"logs": []
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
17714532024900000
@@ -0,0 +1 @@
17715044168400000
@@ -0,0 +1,4 @@
{
"parallelizeAssembly": false,
"parallelizeTestCollections": false
}