using System;
using System.Linq;
using Emby.Server.Implementations.Data;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Server.Implementations.Item;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Entities;
using Xunit;
using BaseItemKind = Jellyfin.Data.Enums.BaseItemKind;
namespace Jellyfin.Server.Implementations.Tests.Item;
///
/// Artists are by-name items: they live outside any library and carry no TopParentId, so a plain
/// item query for them is exempt from the library filter and spans every music library. Only the
/// by-name listings, which reach the artist through the tracks that credit it, can be scoped to
/// one library.
///
public sealed class BaseItemRepositoryArtistLibraryScopeTests : SqliteDbTestFixture
{
private static readonly Guid _firstLibrary = Guid.Parse("11111111-0000-0000-0000-000000000001");
private static readonly Guid _secondLibrary = Guid.Parse("22222222-0000-0000-0000-000000000001");
private readonly BaseItemRepository _repository;
private readonly ItemTypeLookup _itemTypeLookup;
public BaseItemRepositoryArtistLibraryScopeTests()
{
_itemTypeLookup = new ItemTypeLookup();
_repository = CreateBaseItemRepository(_itemTypeLookup);
Seed("First Artist", "first artist", _firstLibrary, hasImage: true);
Seed("Second Artist", "second artist", _secondLibrary, hasImage: true);
}
[Fact]
public void GetItemList_MusicArtistsScopedToOneLibrary_ReturnsEveryLibrarysArtists()
{
// The shape the library cover image used to be built from. By-name types are exempt from
// the TopParentId filter, so the scope is silently dropped.
var result = _repository.GetItemList(new InternalItemsQuery
{
DtoOptions = new DtoOptions(false),
IncludeItemTypes = [BaseItemKind.MusicArtist],
TopParentIds = [_firstLibrary]
});
Assert.Equal(["First Artist", "Second Artist"], result.Select(i => i.Name).OrderBy(n => n));
}
[Fact]
public void GetAllArtists_ScopedToOneLibrary_ReturnsOnlyThatLibrarysArtists()
{
var result = _repository.GetAllArtists(new InternalItemsQuery
{
DtoOptions = new DtoOptions(false),
TopParentIds = [_firstLibrary]
});
var (artist, _) = Assert.Single(result.Items);
Assert.Equal("First Artist", artist.Name);
}
[Fact]
public void GetAllArtists_ImageTypes_DropsArtistsWithoutThatImage()
{
// The collage has nothing to draw with an artist that has no image, so the listing has to
// honour the image filter the caller asked for.
Seed("Third Artist", "third artist", _firstLibrary, hasImage: false);
var result = _repository.GetAllArtists(new InternalItemsQuery
{
DtoOptions = new DtoOptions(false),
ImageTypes = [ImageType.Primary],
TopParentIds = [_firstLibrary]
});
var (artist, _) = Assert.Single(result.Items);
Assert.Equal("First Artist", artist.Name);
}
///
/// Seeds one by-name artist row and a track in the given library crediting it.
///
/// The artist name.
/// The cleaned artist name, which is what links the two rows.
/// The library the track belongs to.
/// Whether the artist row carries a primary image.
private void Seed(string name, string cleanName, Guid topParentId, bool hasImage)
{
using var ctx = CreateDbContext();
var artistId = Guid.NewGuid();
var artist = new BaseItemEntity
{
Id = artistId,
Type = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist],
Name = name,
CleanName = cleanName,
PresentationUniqueKey = artistId.ToString("N"),
IsFolder = true,
IsVirtualItem = false
};
if (hasImage)
{
artist.Images =
[
new BaseItemImageInfo
{
Id = Guid.NewGuid(),
ItemId = artistId,
Item = artist,
ImageType = ImageInfoImageType.Primary,
Path = $"/metadata/artists/{cleanName}/folder.jpg"
}
];
}
ctx.BaseItems.Add(artist);
var trackId = Guid.NewGuid();
var track = new BaseItemEntity
{
Id = trackId,
Type = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Audio],
Name = $"{name} - Track",
CleanName = $"{cleanName} - track",
PresentationUniqueKey = trackId.ToString("N"),
MediaType = "Audio",
TopParentId = topParentId,
IsFolder = false,
IsVirtualItem = false
};
ctx.BaseItems.Add(track);
var itemValue = new ItemValue
{
ItemValueId = Guid.NewGuid(),
Type = ItemValueType.AlbumArtist,
Value = name,
CleanValue = cleanName
};
ctx.ItemValues.Add(itemValue);
ctx.ItemValuesMap.Add(new ItemValueMap
{
ItemId = trackId,
ItemValueId = itemValue.ItemValueId,
Item = track,
ItemValue = itemValue
});
ctx.SaveChanges();
}
}