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);
}
}
|