using System; using System.Collections.Generic; using Jellyfin.Plugin.JRay.Models; using Jellyfin.Plugin.JRay.Services; using Xunit; namespace Jellyfin.Plugin.JRay.Tests; /// /// JR-016 — prioritise/ignore rules resolve by specificity: Item beats Series /// beats Genre. A scope+value holds only one action, so the only conflicts /// possible are across scopes, and that is exactly what these cover. /// /// TRACES: UT-007, UT-008, UT-009, UT-010, UT-011 | JR-016 /// public class PolicyResolverTests { private static readonly Guid ItemId = Guid.Parse("11111111-1111-1111-1111-111111111111"); private static readonly Guid SeriesId = Guid.Parse("22222222-2222-2222-2222-222222222222"); private static MediaPolicyRule Rule(PolicyScope scope, string value, PolicyAction action) => new() { Scope = scope, Value = value, Action = action }; // UT-007 [Fact] public void Resolve_WithNoMatchingRule_ReturnsNull() { var rules = new List { Rule(PolicyScope.Genre, "Anime", PolicyAction.Ignore) }; Assert.Null(PolicyResolver.Resolve(rules, ItemId, SeriesId, new[] { "Drama" })); } // UT-008 [Fact] public void Resolve_ItemRuleBeatsSeriesRule() { var rules = new List { Rule(PolicyScope.Series, SeriesId.ToString("D"), PolicyAction.Ignore), Rule(PolicyScope.Item, ItemId.ToString("D"), PolicyAction.Prioritise), }; Assert.Equal(PolicyAction.Prioritise, PolicyResolver.Resolve(rules, ItemId, SeriesId, Array.Empty())); } // UT-009 [Fact] public void Resolve_PrioritisedSeriesInsideIgnoredGenre_SeriesWins() { // The case that motivated specificity resolution: an admin ignores a // whole genre but wants one series out of it anyway. If genre won, the // more specific instruction would be silently discarded. var rules = new List { Rule(PolicyScope.Genre, "Anime", PolicyAction.Ignore), Rule(PolicyScope.Series, SeriesId.ToString("D"), PolicyAction.Prioritise), }; Assert.Equal(PolicyAction.Prioritise, PolicyResolver.Resolve(rules, ItemId, SeriesId, new[] { "Anime" })); } // UT-010 [Fact] public void Resolve_GenreMatchIsCaseInsensitive() { var rules = new List { Rule(PolicyScope.Genre, "anime", PolicyAction.Ignore) }; Assert.Equal(PolicyAction.Ignore, PolicyResolver.Resolve(rules, ItemId, SeriesId, new[] { "AnImE" })); } // UT-011 [Fact] public void Resolve_SeriesRuleDoesNotMatchNonEpisode() { // A movie carries Guid.Empty as its series id. A series rule whose value // happened to be an empty GUID must not swallow every movie in the // library. var rules = new List { Rule(PolicyScope.Series, Guid.Empty.ToString("D"), PolicyAction.Ignore), }; Assert.Null(PolicyResolver.Resolve(rules, ItemId, Guid.Empty, Array.Empty())); } }