Files
MoviePilot/tests/test_wechat_notification_image.py
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

143 lines
4.6 KiB
Python

"""
企业微信豆瓣图片代理的签名、取图和消息载荷测试。
"""
from __future__ import annotations
from typing import Any
from unittest.mock import AsyncMock
from urllib.parse import parse_qs, urlparse
import pytest
from fastapi import HTTPException, Response
from app.api.endpoints import system as system_endpoint
from app.application.messaging import image as image_urls
from app.modules.wechat.wechat import WeChat
def _settings_with_domain(key: str) -> Any:
"""
提供企业微信图片 URL 构造所需的最小运行配置。
"""
return {
"APP_DOMAIN": "https://moviepilot.example",
"API_V1_STR": "/api/v1",
}[key]
def test_build_wechat_image_url_signs_only_douban_images(monkeypatch: pytest.MonkeyPatch) -> None:
"""
只为豆瓣图片生成公开代理地址,其他图片保持原始 URL。
"""
monkeypatch.setattr(image_urls, "get_runtime_setting", _settings_with_domain)
source_url = "https://img9.doubanio.com/view/photo/l/public/p2932615136.webp"
proxy_url = image_urls.build_wechat_image_url(source_url)
parsed = urlparse(proxy_url or "")
signed_source = parse_qs(parsed.query)["url"][0]
assert parsed.scheme == "https"
assert parsed.netloc == "moviepilot.example"
assert parsed.path == "/api/v1/system/notification-image"
assert image_urls.verify_wechat_image_url(signed_source) == source_url
assert image_urls.build_wechat_image_url("https://image.tmdb.org/poster.jpg") == (
"https://image.tmdb.org/poster.jpg"
)
def test_build_wechat_image_url_keeps_original_without_public_domain(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""
未配置对外域名时不生成微信无法访问的本地地址。
"""
monkeypatch.setattr(
image_urls,
"get_runtime_setting",
lambda key: "" if key == "APP_DOMAIN" else "/api/v1",
)
source_url = "https://img9.doubanio.com/view/photo/l/public/p2932615136.webp"
assert image_urls.build_wechat_image_url(source_url) == source_url
@pytest.mark.asyncio
async def test_notification_image_rejects_wrong_signature_and_domain() -> None:
"""
公开入口拒绝错误用途签名及非豆瓣来源,避免退化为通用代理。
"""
from app.application.security.url import SecurityUtils
signed_tmdb_url = SecurityUtils.sign_url(
"https://image.tmdb.org/poster.jpg",
purpose=image_urls.WECHAT_IMAGE_SIGNATURE_PURPOSE,
)
with pytest.raises(HTTPException) as error:
await system_endpoint.notification_image(url=signed_tmdb_url)
assert error.value.status_code == 404
@pytest.mark.asyncio
async def test_notification_image_fetches_verified_source(monkeypatch: pytest.MonkeyPatch) -> None:
"""
验证通过后复用统一图片代理,并保留全局缓存和 ETag 参数。
"""
from app.application.security.url import SecurityUtils
source_url = "https://img9.doubanio.com/view/photo/l/public/p2932615136.webp"
signed_url = SecurityUtils.sign_url(
source_url,
purpose=image_urls.WECHAT_IMAGE_SIGNATURE_PURPOSE,
)
response = Response(content=b"image", media_type="image/webp")
fetch_image = AsyncMock(return_value=response)
monkeypatch.setattr(system_endpoint, "fetch_image", fetch_image)
monkeypatch.setattr(
system_endpoint,
"get_runtime_settings",
lambda: {"GLOBAL_IMAGE_CACHE": True},
)
result = await system_endpoint.notification_image(
url=signed_url,
if_none_match="etag",
)
assert result is response
fetch_image.assert_awaited_once_with(
url=source_url,
use_cache=True,
if_none_match="etag",
)
def test_wechat_news_payload_uses_signed_douban_proxy(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""
企业微信图文载荷应把豆瓣图片替换为可公开取图的地址。
"""
monkeypatch.setattr(image_urls, "get_runtime_setting", _settings_with_domain)
captured: dict[str, Any] = {}
client = WeChat.__new__(WeChat)
client._appid = "1"
monkeypatch.setattr(
client,
"_WeChat__post_request",
lambda url, payload: captured.update(payload) or True,
)
source_url = "https://img9.doubanio.com/view/photo/l/public/p2932615136.webp"
assert client._WeChat__send_image_message(
title="奥德赛",
text="已添加订阅",
image_url=source_url,
) is True
image_url = captured["news"]["articles"][0]["picurl"]
signed_source = parse_qs(urlparse(image_url).query)["url"][0]
assert image_urls.verify_wechat_image_url(signed_source) == source_url