mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/jxxghp/MoviePilot.git
synced 2026-09-20 08:03:34 +08:00
219 lines
7.0 KiB
Python
219 lines
7.0 KiB
Python
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock, Mock, patch
|
|
|
|
import pytest
|
|
|
|
from app.application.subscription.contract import (
|
|
SubscriptionHistorySnapshot,
|
|
SubscriptionIdentity,
|
|
SubscriptionSnapshot,
|
|
)
|
|
from app.application.subscription.query import SubscriptionQueryService
|
|
from app.chain.subscribe.facade import SubscribeChain
|
|
from app.domain.context import MediaInfo
|
|
from app.schemas.types import MediaSource, MediaType
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_subscription_history_count_preserves_owner_scope() -> None:
|
|
"""订阅历史总数必须复用列表的媒体类型和 owner 范围。"""
|
|
repository = Mock()
|
|
history_repository = AsyncMock()
|
|
history_repository.async_count_by_type.return_value = 8
|
|
history_repository.async_count_by_type_and_username.return_value = 3
|
|
service = SubscriptionQueryService(
|
|
repository,
|
|
history_repository=history_repository,
|
|
)
|
|
|
|
assert await service.count_history(MediaType.MOVIE.value) == 8
|
|
assert await service.count_history(
|
|
MediaType.MOVIE.value,
|
|
username="alice",
|
|
) == 3
|
|
history_repository.async_count_by_type.assert_awaited_once_with(
|
|
MediaType.MOVIE.value
|
|
)
|
|
history_repository.async_count_by_type_and_username.assert_awaited_once_with(
|
|
MediaType.MOVIE.value,
|
|
"alice",
|
|
)
|
|
|
|
|
|
def test_subscription_query_service_builds_complete_exists_identity() -> None:
|
|
"""存在性查询必须保留媒体、音乐实体、季和剧集组全部身份维度。"""
|
|
repository = Mock()
|
|
repository.exists.return_value = True
|
|
service = SubscriptionQueryService(repository)
|
|
media = MediaInfo(
|
|
type=MediaType.TV,
|
|
title="Demo",
|
|
media_source=MediaSource.TMDB,
|
|
media_id="123",
|
|
episode_group="group-1",
|
|
)
|
|
|
|
assert service.exists(media, SimpleNamespace(begin_season=2)) is True
|
|
repository.exists.assert_called_once_with(
|
|
SubscriptionIdentity(
|
|
media_source=MediaSource.TMDB,
|
|
media_id="123",
|
|
season=2,
|
|
episode_group="group-1",
|
|
)
|
|
)
|
|
|
|
|
|
def test_subscription_query_service_filters_source_and_music_state() -> None:
|
|
"""来源查询不透传展示字段,音乐状态查询保持 R/P 联合列表语义。"""
|
|
repository = Mock()
|
|
expected = SimpleNamespace(id=1)
|
|
repository.get_by.return_value = expected
|
|
repository.list.return_value = [
|
|
SimpleNamespace(type=MediaType.MOVIE.value),
|
|
SimpleNamespace(type=MediaType.MUSIC.value),
|
|
]
|
|
service = SubscriptionQueryService(repository)
|
|
|
|
result = service.get_by_source({
|
|
"id": 1,
|
|
"name": "Demo",
|
|
"type": MediaType.TV.value,
|
|
"season": 1,
|
|
"media_source": MediaSource.TMDB,
|
|
"media_id": "123",
|
|
"music_type": None,
|
|
})
|
|
|
|
assert result is expected
|
|
repository.get_by.assert_called_once_with(
|
|
SubscriptionIdentity(
|
|
media_source=MediaSource.TMDB,
|
|
media_id="123",
|
|
type=MediaType.TV.value,
|
|
season=1,
|
|
)
|
|
)
|
|
assert service.has_music("R,P") is True
|
|
repository.list.assert_called_once_with("R,P")
|
|
|
|
|
|
def test_get_by_source_upgrades_legacy_tmdbid_identity() -> None:
|
|
"""v2 下载记录只有 tmdbid 时,应补成 themoviedb + media_id 再查订阅。"""
|
|
repository = Mock()
|
|
expected = SimpleNamespace(id=22)
|
|
repository.get_by.return_value = expected
|
|
service = SubscriptionQueryService(repository)
|
|
|
|
result = service.get_by_source({
|
|
"id": 22,
|
|
"name": "阿滋漫画大王",
|
|
"type": MediaType.TV.value,
|
|
"season": 1,
|
|
"tmdbid": 12143,
|
|
"imdbid": "tt0339955",
|
|
"tvdbid": 79077,
|
|
})
|
|
|
|
assert result is expected
|
|
repository.get_by.assert_called_once_with(
|
|
SubscriptionIdentity(
|
|
media_source=MediaSource.TMDB,
|
|
media_id="12143",
|
|
type=MediaType.TV.value,
|
|
season=1,
|
|
)
|
|
)
|
|
|
|
|
|
def test_get_by_source_skips_incomplete_legacy_identity() -> None:
|
|
"""来源既无 media_id 也无旧 tmdbid 时,不得把半对身份传给仓储。"""
|
|
repository = Mock()
|
|
service = SubscriptionQueryService(repository)
|
|
|
|
assert service.get_by_source({
|
|
"type": MediaType.TV.value,
|
|
"season": 1,
|
|
"name": "Demo",
|
|
}) is None
|
|
repository.get_by.assert_not_called()
|
|
|
|
|
|
def test_subscribe_chain_facade_delegates_three_query_slices() -> None:
|
|
"""SubscribeChain 保持三个公开方法签名并仅负责来源解析和结果转发。"""
|
|
service = Mock()
|
|
service.exists.return_value = True
|
|
service.get_by_source.return_value = SimpleNamespace(id=7)
|
|
service.has_music.return_value = True
|
|
media = MediaInfo(
|
|
type=MediaType.MOVIE,
|
|
title="Demo",
|
|
media_source=MediaSource.TMDB,
|
|
media_id="123",
|
|
)
|
|
source = (
|
|
'Subscribe|{"type":"电影","season":null,'
|
|
'"media_source":"themoviedb","media_id":"123"}'
|
|
)
|
|
|
|
with patch.object(SubscribeChain, "_subscription_query", return_value=service):
|
|
chain = object.__new__(SubscribeChain)
|
|
assert chain.exists(media) is True
|
|
assert chain.get_subscribe_by_source(source).id == 7
|
|
assert chain.has_music_subscribe() is True
|
|
|
|
service.exists.assert_called_once_with(media, None)
|
|
service.get_by_source.assert_called_once_with({
|
|
"type": "电影",
|
|
"season": None,
|
|
"media_source": "themoviedb",
|
|
"media_id": "123",
|
|
})
|
|
service.has_music.assert_called_once_with("R,P")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_public_subscription_view_derives_album_progress_without_track_keys() -> None:
|
|
"""公开订阅响应应返回累计曲目数,但不能泄漏内部音轨稳定键。"""
|
|
record = SubscriptionSnapshot(
|
|
id=31,
|
|
name="叶惠美",
|
|
type=MediaType.MUSIC.value,
|
|
media_source=MediaSource.MusicBrainz,
|
|
media_id="release-group-31",
|
|
music_type="album",
|
|
total_tracks=3,
|
|
downloaded_tracks=["[1,1]", "[1,1]", "[1,2]", ""],
|
|
)
|
|
repository = Mock()
|
|
repository.async_list = AsyncMock(return_value=[record])
|
|
service = SubscriptionQueryService(repository, async_repository=repository)
|
|
|
|
[result] = await service.list_public()
|
|
|
|
assert result.completed_tracks == 2
|
|
assert "downloaded_tracks" not in result.model_dump()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_public_subscription_history_does_not_invent_album_progress() -> None:
|
|
"""不含活动音轨事实的订阅历史不能伪造为零首进度。"""
|
|
record = SubscriptionHistorySnapshot(
|
|
id=32,
|
|
name="叶惠美",
|
|
type=MediaType.MUSIC.value,
|
|
music_type="album",
|
|
total_tracks=3,
|
|
)
|
|
repository = Mock()
|
|
history_repository = Mock()
|
|
history_repository.async_list_by_type = AsyncMock(return_value=[record])
|
|
service = SubscriptionQueryService(
|
|
repository,
|
|
history_repository=history_repository,
|
|
)
|
|
|
|
[result] = await service.list_history(MediaType.MUSIC.value)
|
|
|
|
assert result.completed_tracks is None
|