aboutsummaryrefslogtreecommitdiff
path: root/tests/Jellyfin.Server.Implementations.Tests/Library/PeopleValidatorPartitionTests.cs
blob: 30f7bed208c6115be03a9354027718be64efd0a6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
using System;
using System.Collections.Generic;
using Emby.Server.Implementations.Library.Validators;
using Xunit;

namespace Jellyfin.Server.Implementations.Tests.Library;

/// <summary>
/// Tests for how the people validator decides which credits need a person item and which person items
/// nothing credits any more. Keying either half on the item's name rather than its id put the two halves
/// in a loop that created, refreshed and deleted the same people on every run, so these pin the id.
/// </summary>
public class PeopleValidatorPartitionTests
{
    // Stands in for the real item-by-name id: derived from the credit name, case-insensitively, and
    // from nothing else. The property that matters is that it does not depend on the item's own name.
    private static Guid PersonId(string creditName)
    {
#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms
        var hash = System.Security.Cryptography.MD5.HashData(
            System.Text.Encoding.Unicode.GetBytes(creditName.ToLowerInvariant()));
#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms
        return new Guid(hash);
    }

    [Fact]
    public void PartitionCreditsByPersonId_ProviderRenamedThePerson_KeepsThemAndCreatesNothing()
    {
        // The credit still says "AURORA"; the item it made has been renamed to "Aurora" by the provider
        // that refreshed it. Nothing about the library changed, so nothing should be created or deleted.
        var credits = new[] { "AURORA" };
        var existing = new HashSet<Guid> { PersonId("AURORA") };

        var (newNames, deadIds) = PeopleValidator.PartitionCreditsByPersonId(credits, PersonId, existing);

        Assert.Empty(newNames);
        Assert.Empty(deadIds);
    }

    [Theory]
    // Every shape of rename seen in the wild on a real library.
    [InlineData("AURORA")]
    [InlineData("Amir AboulEla")]
    [InlineData("Miguel Ángel Fuentes")]
    [InlineData("a‐ha")]
    [InlineData("윤현민")]
    public void PartitionCreditsByPersonId_CreditWithAnItem_IsNeverBothCreatedAndDeleted(string creditName)
    {
        var existing = new HashSet<Guid> { PersonId(creditName) };

        var (newNames, deadIds) = PeopleValidator.PartitionCreditsByPersonId([creditName], PersonId, existing);

        Assert.Empty(newNames);
        Assert.Empty(deadIds);
    }

    [Fact]
    public void PartitionCreditsByPersonId_CreditWithNoItem_IsCreated()
    {
        var (newNames, deadIds) = PeopleValidator.PartitionCreditsByPersonId(
            ["Wanted Person"],
            PersonId,
            new HashSet<Guid>());

        Assert.Equal(["Wanted Person"], newNames);
        Assert.Empty(deadIds);
    }

    [Fact]
    public void PartitionCreditsByPersonId_ItemNoCreditNames_IsDead()
    {
        var orphan = PersonId("Nobody Credits Me");
        var existing = new HashSet<Guid> { PersonId("Credited"), orphan };

        var (newNames, deadIds) = PeopleValidator.PartitionCreditsByPersonId(["Credited"], PersonId, existing);

        Assert.Empty(newNames);
        Assert.Equal([orphan], deadIds);
    }

    [Fact]
    public void PartitionCreditsByPersonId_CreditsNormalizingOntoOneId_CreateOneItem()
    {
        // "AURORA" and "Aurora" are one person as far as the item-by-name id is concerned, so exactly
        // one of them should create the item and neither should end up dead.
        var (newNames, deadIds) = PeopleValidator.PartitionCreditsByPersonId(
            ["AURORA", "Aurora", "aurora"],
            PersonId,
            new HashSet<Guid>());

        Assert.Single(newNames);
        Assert.Empty(deadIds);
    }

    [Fact]
    public void PartitionCreditsByPersonId_SecondRunAfterCreating_AsksForNothingFurther()
    {
        // The churn showed up as a run that never settled, so drive two rounds: whatever round one
        // created must leave round two with nothing to do.
        string[] credits = ["AURORA", "Amir AboulEla", "Miguel Ángel Fuentes"];
        var existing = new HashSet<Guid>();

        var (firstNames, firstDead) = PeopleValidator.PartitionCreditsByPersonId(credits, PersonId, existing);
        Assert.Equal(3, firstNames.Count);
        Assert.Empty(firstDead);

        foreach (var created in firstNames)
        {
            existing.Add(PersonId(created));
        }

        var (secondNames, secondDead) = PeopleValidator.PartitionCreditsByPersonId(credits, PersonId, existing);

        Assert.Empty(secondNames);
        Assert.Empty(secondDead);
    }
}