aboutsummaryrefslogtreecommitdiff
path: root/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/ResolveAlternateVersionTests.cs
blob: 31109b29680b202ed196e1ebc1c9600f8abb70b4 (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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
using System;
using System.Collections.Generic;
using System.Threading;
using AutoFixture;
using AutoFixture.AutoMoq;
using Emby.Naming.Common;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaSegments;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.IO;
using Moq;
using Xunit;

namespace Jellyfin.Server.Implementations.Tests.Library.LibraryManager;

public sealed class ResolveAlternateVersionTests : IDisposable
{
    private const string PrimaryPath = "/movies/Up/Up.mkv";
    private const string AlternatePath = "/movies/Up/Up - 1080p.mkv";

    private readonly Emby.Server.Implementations.Library.LibraryManager _libraryManager;
    private readonly Mock<IItemPersistenceService> _persistenceServiceMock;
    private readonly Folder _staleParent;
    private readonly ILibraryManager? _previousLibraryManager;
    private readonly IMediaSourceManager? _previousMediaSourceManager;
    private readonly IItemRepository? _previousItemRepository;

    public ResolveAlternateVersionTests()
    {
        var fixture = new Fixture().Customize(new AutoMoqCustomization());
        fixture.Register(() => new NamingOptions());
        fixture.Freeze<Mock<IServerConfigurationManager>>()
            .Setup(c => c.ApplicationPaths.ProgramDataPath).Returns("/data");
        _persistenceServiceMock = fixture.Freeze<Mock<IItemPersistenceService>>();
        var itemRepositoryMock = fixture.Freeze<Mock<IItemRepository>>();
        fixture.Freeze<Mock<IFileSystem>>()
            .Setup(f => f.GetFileInfo(It.IsAny<string>()))
            .Returns<string>(path => new FileSystemMetadata { FullName = path });

        _libraryManager = fixture.Build<Emby.Server.Implementations.Library.LibraryManager>()
            .Do(s => s.AddParts(
                fixture.Create<IEnumerable<IResolverIgnoreRule>>(),
                [],
                fixture.Create<IEnumerable<IIntroProvider>>(),
                fixture.Create<IEnumerable<IBaseItemComparer>>(),
                fixture.Create<IEnumerable<ILibraryPostScanTask>>()))
            .Create();

        // BaseItem resolves these through process-wide statics; restored in Dispose.
        _previousLibraryManager = BaseItem.LibraryManager;
        _previousMediaSourceManager = BaseItem.MediaSourceManager;
        _previousItemRepository = BaseItem.ItemRepository;
        BaseItem.LibraryManager = _libraryManager;

        var mediaSourceManagerMock = new Mock<IMediaSourceManager>();
        mediaSourceManagerMock.Setup(m => m.GetMediaStreams(It.IsAny<Guid>())).Returns([]);
        mediaSourceManagerMock.Setup(m => m.GetMediaAttachments(It.IsAny<Guid>())).Returns([]);
        BaseItem.MediaSourceManager = mediaSourceManagerMock.Object;

        // A reloaded listing comes back empty, so a stale entry surviving is visible.
        itemRepositoryMock.Setup(i => i.GetItemList(It.IsAny<InternalItemsQuery>())).Returns([]);
        BaseItem.ItemRepository = itemRepositoryMock.Object;

        BaseItem.FileSystem ??= fixture.Create<IFileSystem>();
        BaseItem.MediaSegmentManager ??= fixture.Create<IMediaSegmentManager>();
        BaseItem.ConfigurationManager ??= fixture.Create<IServerConfigurationManager>();
        Video.RecordingsManager ??= fixture.Create<IRecordingsManager>();

        var primary = new Movie
        {
            Name = "Up",
            Path = PrimaryPath,
            LocalAlternateVersions = [AlternatePath],
            Id = _libraryManager.GetNewItemId(PrimaryPath, typeof(Movie))
        };

        _staleParent = new Folder
        {
            Name = "Up",
            Path = "/movies/Up",
            Id = _libraryManager.GetNewItemId("/movies/Up", typeof(Folder))
        };

        var staleAlternate = new Video
        {
            Name = "Up - 1080p",
            Path = AlternatePath,
            OwnerId = primary.Id,
            ParentId = _staleParent.Id,
            Id = _libraryManager.GetNewItemId(AlternatePath, typeof(Video))
        };
        staleAlternate.SetPrimaryVersionId(primary.Id);

        itemRepositoryMock
            .Setup(i => i.RetrieveItem(It.IsAny<Guid>()))
            .Returns<Guid>(id => id.Equals(primary.Id) ? primary
                : id.Equals(staleAlternate.Id) ? staleAlternate
                : id.Equals(_staleParent.Id) ? _staleParent
                : null!);

        StaleAlternateId = staleAlternate.Id;
    }

    private Guid StaleAlternateId { get; }

    public void Dispose()
    {
        BaseItem.LibraryManager = _previousLibraryManager!;
        BaseItem.MediaSourceManager = _previousMediaSourceManager!;
        BaseItem.ItemRepository = _previousItemRepository!;
    }

    [Fact]
    public void ResolveAlternateVersion_StaleWrongTypeItem_DropsRowWithoutResavingPrimary()
    {
        // The alternate is stored under the id of the generic Video type while its primary is a Movie.
        _libraryManager.ResolveAlternateVersion(AlternatePath, typeof(Movie), null, null);

        _persistenceServiceMock.Verify(
            p => p.DeleteItem(It.Is<IReadOnlyList<Guid>>(ids => ids.Count == 1 && ids[0].Equals(StaleAlternateId))),
            Times.Once);

        // Saving the primary is what re-enters this method before the stale row is gone.
        _persistenceServiceMock.Verify(
            p => p.SaveItems(It.IsAny<IReadOnlyList<BaseItem>>(), It.IsAny<CancellationToken>()),
            Times.Never);
    }

    [Fact]
    public void ResolveAlternateVersion_StaleWrongTypeItem_DropsCachedParentListing()
    {
        _staleParent.Children = [new Video { Name = "Up - 1080p", Path = AlternatePath }];

        _libraryManager.ResolveAlternateVersion(AlternatePath, typeof(Movie), null, null);

        Assert.Empty(_staleParent.Children);
    }
}