Duncan Tourolle 3604e8f7a0
Some checks failed
🏗️ Build Plugin / build (pull_request) Failing after 9s
🧪 Test Plugin / test (pull_request) Failing after 9s
Add unit.tests
2025-11-14 22:13:24 +01:00

164 lines
4.6 KiB
C#

using FluentAssertions;
using Jellyfin.Plugin.SRFPlay.Api.Models;
using Jellyfin.Plugin.SRFPlay.Services;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
namespace Jellyfin.Plugin.SRFPlay.Tests.UnitTests;
/// <summary>
/// Unit tests for MetadataCache.
/// </summary>
public class MetadataCacheTests : IDisposable
{
private readonly Mock<ILogger<MetadataCache>> _loggerMock;
private readonly MetadataCache _cache;
public MetadataCacheTests()
{
_loggerMock = new Mock<ILogger<MetadataCache>>();
_cache = new MetadataCache(_loggerMock.Object);
}
[Fact]
public void SetMediaComposition_And_GetMediaComposition_ReturnsCorrectValue()
{
// Arrange
const string urn = "urn:srf:video:test-id";
var mediaComposition = new MediaComposition
{
Episode = new Episode { Id = "test-episode", Title = "Test" }
};
const int cacheDurationMinutes = 10;
// Act
_cache.SetMediaComposition(urn, mediaComposition);
var result = _cache.GetMediaComposition(urn, cacheDurationMinutes);
// Assert
result.Should().NotBeNull();
result.Should().Be(mediaComposition);
}
[Fact]
public void GetMediaComposition_NonExistentKey_ReturnsNull()
{
// Act
var result = _cache.GetMediaComposition("non-existent-urn", 10);
// Assert
result.Should().BeNull();
}
[Fact]
public void GetMediaComposition_ExpiredEntry_ReturnsNull()
{
// Arrange
const string urn = "urn:srf:video:test-id";
var mediaComposition = new MediaComposition
{
Episode = new Episode { Id = "test-episode" }
};
// Set with cache
_cache.SetMediaComposition(urn, mediaComposition);
// Wait a tiny bit to ensure expiration
System.Threading.Thread.Sleep(10);
// Act - Try to get with 0 minute cache duration (immediate expiration)
var result = _cache.GetMediaComposition(urn, 0);
// Assert - Should be null because it's expired
result.Should().BeNull();
}
[Fact]
public void RemoveMediaComposition_ExistingUrn_RemovesValue()
{
// Arrange
const string urn = "urn:srf:video:test-id";
var mediaComposition = new MediaComposition
{
Episode = new Episode { Id = "test-episode" }
};
_cache.SetMediaComposition(urn, mediaComposition);
// Act
_cache.RemoveMediaComposition(urn);
var result = _cache.GetMediaComposition(urn, 10);
// Assert
result.Should().BeNull();
}
[Fact]
public void Clear_RemovesAllValues()
{
// Arrange
var mc1 = new MediaComposition { Episode = new Episode { Id = "episode1" } };
var mc2 = new MediaComposition { Episode = new Episode { Id = "episode2" } };
_cache.SetMediaComposition("urn1", mc1);
_cache.SetMediaComposition("urn2", mc2);
// Act
_cache.Clear();
var result1 = _cache.GetMediaComposition("urn1", 10);
var result2 = _cache.GetMediaComposition("urn2", 10);
// Assert
result1.Should().BeNull();
result2.Should().BeNull();
}
[Fact]
public void GetStatistics_ReturnsCorrectCount()
{
// Arrange
var mc1 = new MediaComposition { Episode = new Episode { Id = "episode1" } };
var mc2 = new MediaComposition { Episode = new Episode { Id = "episode2" } };
_cache.SetMediaComposition("urn1", mc1);
_cache.SetMediaComposition("urn2", mc2);
// Act
var (count, sizeEstimate) = _cache.GetStatistics();
// Assert
count.Should().Be(2);
sizeEstimate.Should().BeGreaterThan(0);
}
[Fact]
public void ConcurrentAccess_DoesNotThrow()
{
// Arrange
var tasks = new List<Task>();
// Act - Perform concurrent operations
for (int i = 0; i < 100; i++)
{
var index = i;
tasks.Add(Task.Run(() =>
{
var mc = new MediaComposition { Episode = new Episode { Id = $"episode-{index}" } };
_cache.SetMediaComposition($"urn-{index}", mc);
_cache.GetMediaComposition($"urn-{index}", 10);
if (index % 2 == 0)
{
_cache.RemoveMediaComposition($"urn-{index}");
}
}));
}
// Assert - Should not throw
var action = async () => await Task.WhenAll(tasks);
action.Should().NotThrowAsync();
}
public void Dispose()
{
_cache?.Dispose();
}
}