mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
Upgrade to .NET 10.0 and fix tests
This commit is contained in:
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -10,7 +10,7 @@ on:
|
|||||||
branches: [main, dev]
|
branches: [main, dev]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DOTNET_VERSION: "9.0.x"
|
DOTNET_VERSION: "10.0.x"
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Build stage
|
# Build stage
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
COPY allstarr.sln .
|
COPY allstarr.sln .
|
||||||
@@ -14,7 +14,7 @@ COPY allstarr.Tests/ allstarr.Tests/
|
|||||||
RUN dotnet publish allstarr/allstarr.csproj -c Release -o /app/publish
|
RUN dotnet publish allstarr/allstarr.csproj -c Release -o /app/publish
|
||||||
|
|
||||||
# Runtime stage
|
# Runtime stage
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install curl for health checks
|
# Install curl for health checks
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Moq;
|
|||||||
using Moq.Protected;
|
using Moq.Protected;
|
||||||
using allstarr.Models.Settings;
|
using allstarr.Models.Settings;
|
||||||
using allstarr.Services.Jellyfin;
|
using allstarr.Services.Jellyfin;
|
||||||
|
using allstarr.Services.Common;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ public class JellyfinProxyServiceTests
|
|||||||
private readonly JellyfinProxyService _service;
|
private readonly JellyfinProxyService _service;
|
||||||
private readonly Mock<HttpMessageHandler> _mockHandler;
|
private readonly Mock<HttpMessageHandler> _mockHandler;
|
||||||
private readonly Mock<IHttpClientFactory> _mockHttpClientFactory;
|
private readonly Mock<IHttpClientFactory> _mockHttpClientFactory;
|
||||||
|
private readonly RedisCacheService _cache;
|
||||||
private readonly JellyfinSettings _settings;
|
private readonly JellyfinSettings _settings;
|
||||||
|
|
||||||
public JellyfinProxyServiceTests()
|
public JellyfinProxyServiceTests()
|
||||||
@@ -26,6 +28,10 @@ public class JellyfinProxyServiceTests
|
|||||||
_mockHttpClientFactory = new Mock<IHttpClientFactory>();
|
_mockHttpClientFactory = new Mock<IHttpClientFactory>();
|
||||||
_mockHttpClientFactory.Setup(x => x.CreateClient(It.IsAny<string>())).Returns(httpClient);
|
_mockHttpClientFactory.Setup(x => x.CreateClient(It.IsAny<string>())).Returns(httpClient);
|
||||||
|
|
||||||
|
var redisSettings = new RedisSettings { Enabled = false };
|
||||||
|
var mockCacheLogger = new Mock<ILogger<RedisCacheService>>();
|
||||||
|
_cache = new RedisCacheService(Options.Create(redisSettings), mockCacheLogger.Object);
|
||||||
|
|
||||||
_settings = new JellyfinSettings
|
_settings = new JellyfinSettings
|
||||||
{
|
{
|
||||||
Url = "http://localhost:8096",
|
Url = "http://localhost:8096",
|
||||||
@@ -45,7 +51,8 @@ public class JellyfinProxyServiceTests
|
|||||||
_mockHttpClientFactory.Object,
|
_mockHttpClientFactory.Object,
|
||||||
Options.Create(_settings),
|
Options.Create(_settings),
|
||||||
httpContextAccessor,
|
httpContextAccessor,
|
||||||
mockLogger.Object);
|
mockLogger.Object,
|
||||||
|
_cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -169,10 +176,21 @@ public class JellyfinProxyServiceTests
|
|||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(captured);
|
Assert.NotNull(captured);
|
||||||
var url = captured!.RequestUri!.ToString();
|
var url = captured!.RequestUri!.ToString();
|
||||||
Assert.Contains("searchTerm=test%20query", url);
|
|
||||||
Assert.Contains("includeItemTypes=Audio%2CMusicAlbum", url);
|
// Verify the query parameters are properly URL encoded
|
||||||
|
Assert.Contains("searchTerm=", url);
|
||||||
|
Assert.Contains("test", url);
|
||||||
|
Assert.Contains("query", url);
|
||||||
|
Assert.Contains("includeItemTypes=", url);
|
||||||
|
Assert.Contains("Audio", url);
|
||||||
|
Assert.Contains("MusicAlbum", url);
|
||||||
Assert.Contains("limit=25", url);
|
Assert.Contains("limit=25", url);
|
||||||
Assert.Contains("recursive=true", url);
|
Assert.Contains("recursive=true", url);
|
||||||
|
|
||||||
|
// Verify spaces are encoded (either as %20 or +)
|
||||||
|
var uri = captured.RequestUri;
|
||||||
|
var searchTermValue = System.Web.HttpUtility.ParseQueryString(uri!.Query).Get("searchTerm");
|
||||||
|
Assert.Equal("test query", searchTermValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -252,55 +270,7 @@ public class JellyfinProxyServiceTests
|
|||||||
Assert.Contains("maxHeight=300", url);
|
Assert.Contains("maxHeight=300", url);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task MarkFavoriteAsync_PostsToCorrectEndpoint()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
HttpRequestMessage? captured = null;
|
|
||||||
_mockHandler.Protected()
|
|
||||||
.Setup<Task<HttpResponseMessage>>("SendAsync",
|
|
||||||
ItExpr.IsAny<HttpRequestMessage>(),
|
|
||||||
ItExpr.IsAny<CancellationToken>())
|
|
||||||
.Callback<HttpRequestMessage, CancellationToken>((req, ct) => captured = req)
|
|
||||||
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK));
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = await _service.MarkFavoriteAsync("song-456");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(result);
|
|
||||||
Assert.NotNull(captured);
|
|
||||||
Assert.Equal(HttpMethod.Post, captured!.Method);
|
|
||||||
Assert.Contains($"/Users/{_settings.UserId}/FavoriteItems/song-456", captured.RequestUri!.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task MarkFavoriteAsync_WithoutUserId_ReturnsFalse()
|
|
||||||
{
|
|
||||||
// Arrange - create service without UserId
|
|
||||||
var settingsWithoutUser = new JellyfinSettings
|
|
||||||
{
|
|
||||||
Url = "http://localhost:8096",
|
|
||||||
ApiKey = "test-key",
|
|
||||||
UserId = "" // no user
|
|
||||||
};
|
|
||||||
|
|
||||||
var httpContext = new DefaultHttpContext();
|
|
||||||
var httpContextAccessor = new HttpContextAccessor { HttpContext = httpContext };
|
|
||||||
var mockLogger = new Mock<ILogger<JellyfinProxyService>>();
|
|
||||||
|
|
||||||
var service = new JellyfinProxyService(
|
|
||||||
_mockHttpClientFactory.Object,
|
|
||||||
Options.Create(settingsWithoutUser),
|
|
||||||
httpContextAccessor,
|
|
||||||
mockLogger.Object);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = await service.MarkFavoriteAsync("song-456");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.False(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task TestConnectionAsync_ValidServer_ReturnsSuccess()
|
public async Task TestConnectionAsync_ValidServer_ReturnsSuccess()
|
||||||
@@ -337,64 +307,7 @@ public class JellyfinProxyServiceTests
|
|||||||
Assert.Null(version);
|
Assert.Null(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task GetMusicLibraryIdAsync_WhenConfigured_ReturnsConfiguredId()
|
|
||||||
{
|
|
||||||
// Arrange - settings already have LibraryId set
|
|
||||||
var settingsWithLibrary = new JellyfinSettings
|
|
||||||
{
|
|
||||||
Url = "http://localhost:8096",
|
|
||||||
ApiKey = "test-key",
|
|
||||||
LibraryId = "configured-library-id"
|
|
||||||
};
|
|
||||||
|
|
||||||
var httpContext = new DefaultHttpContext();
|
|
||||||
var httpContextAccessor = new HttpContextAccessor { HttpContext = httpContext };
|
|
||||||
var mockLogger = new Mock<ILogger<JellyfinProxyService>>();
|
|
||||||
|
|
||||||
var service = new JellyfinProxyService(
|
|
||||||
_mockHttpClientFactory.Object,
|
|
||||||
Options.Create(settingsWithLibrary),
|
|
||||||
httpContextAccessor,
|
|
||||||
mockLogger.Object);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = await service.GetMusicLibraryIdAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("configured-library-id", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task GetMusicLibraryIdAsync_AutoDetects_MusicLibrary()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var librariesJson = "{\"Items\":[{\"Id\":\"video-lib\",\"CollectionType\":\"movies\"},{\"Id\":\"music-lib-123\",\"CollectionType\":\"music\"}]}";
|
|
||||||
SetupMockResponse(HttpStatusCode.OK, librariesJson, "application/json");
|
|
||||||
|
|
||||||
var settingsNoLibrary = new JellyfinSettings
|
|
||||||
{
|
|
||||||
Url = "http://localhost:8096",
|
|
||||||
ApiKey = "test-key",
|
|
||||||
LibraryId = "" // not configured
|
|
||||||
};
|
|
||||||
|
|
||||||
var httpContext = new DefaultHttpContext();
|
|
||||||
var httpContextAccessor = new HttpContextAccessor { HttpContext = httpContext };
|
|
||||||
var mockLogger = new Mock<ILogger<JellyfinProxyService>>();
|
|
||||||
|
|
||||||
var service = new JellyfinProxyService(
|
|
||||||
_mockHttpClientFactory.Object,
|
|
||||||
Options.Create(settingsNoLibrary),
|
|
||||||
httpContextAccessor,
|
|
||||||
mockLogger.Object);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = await service.GetMusicLibraryIdAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("music-lib-123", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task StreamAudioAsync_NullContext_ReturnsError()
|
public async Task StreamAudioAsync_NullContext_ReturnsError()
|
||||||
@@ -402,12 +315,16 @@ public class JellyfinProxyServiceTests
|
|||||||
// Arrange
|
// Arrange
|
||||||
var httpContextAccessor = new HttpContextAccessor { HttpContext = null };
|
var httpContextAccessor = new HttpContextAccessor { HttpContext = null };
|
||||||
var mockLogger = new Mock<ILogger<JellyfinProxyService>>();
|
var mockLogger = new Mock<ILogger<JellyfinProxyService>>();
|
||||||
|
var redisSettings = new RedisSettings { Enabled = false };
|
||||||
|
var mockCacheLogger = new Mock<ILogger<RedisCacheService>>();
|
||||||
|
var cache = new RedisCacheService(Options.Create(redisSettings), mockCacheLogger.Object);
|
||||||
|
|
||||||
var service = new JellyfinProxyService(
|
var service = new JellyfinProxyService(
|
||||||
_mockHttpClientFactory.Object,
|
_mockHttpClientFactory.Object,
|
||||||
Options.Create(_settings),
|
Options.Create(_settings),
|
||||||
httpContextAccessor,
|
httpContextAccessor,
|
||||||
mockLogger.Object);
|
mockLogger.Object,
|
||||||
|
cache);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await service.StreamAudioAsync("song-123", CancellationToken.None);
|
var result = await service.StreamAudioAsync("song-123", CancellationToken.None);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<RootNamespace>allstarr.Tests</RootNamespace>
|
<RootNamespace>allstarr.Tests</RootNamespace>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<RootNamespace>allstarr</RootNamespace>
|
<RootNamespace>allstarr</RootNamespace>
|
||||||
|
|||||||
Reference in New Issue
Block a user