240 lines
7.5 KiB
Python
240 lines
7.5 KiB
Python
import re
|
|
import pytest
|
|
from unittest.mock import MagicMock, patch
|
|
from pixiv_client import PixivClient
|
|
|
|
|
|
def make_client(token=None):
|
|
mock_config = MagicMock()
|
|
mock_config.get_refresh_token.return_value = token
|
|
return PixivClient(mock_config)
|
|
|
|
|
|
# ── Auth tests ────────────────────────────────────────────────────────────────
|
|
|
|
def test_generate_pkce_returns_different_values_each_call():
|
|
client = make_client()
|
|
v1, c1 = client.generate_pkce()
|
|
v2, c2 = client.generate_pkce()
|
|
assert v1 != v2 and c1 != c2
|
|
|
|
|
|
def test_challenge_is_base64url():
|
|
client = make_client()
|
|
_, challenge = client.generate_pkce()
|
|
assert re.match(r'^[A-Za-z0-9_-]+$', challenge)
|
|
|
|
|
|
def test_get_login_url_contains_challenge():
|
|
client = make_client()
|
|
_, challenge = client.generate_pkce()
|
|
url = client.get_login_url(challenge)
|
|
assert challenge in url
|
|
assert "app-api.pixiv.net" in url
|
|
|
|
|
|
def test_extract_code_from_pixiv_url():
|
|
client = make_client()
|
|
assert client.extract_code("pixiv://account/login?code=abc123&via=login") == "abc123"
|
|
|
|
|
|
def test_extract_code_raises_on_missing_code():
|
|
client = make_client()
|
|
with pytest.raises(ValueError):
|
|
client.extract_code("http://example.com/nope")
|
|
|
|
|
|
def test_login_with_code_saves_token_and_returns_it():
|
|
client = make_client()
|
|
mock_api = MagicMock()
|
|
mock_api.refresh_token = "saved_token_456"
|
|
client.api = mock_api
|
|
|
|
result = client.login_with_code("mycode", "myverifier")
|
|
|
|
mock_api.auth.assert_called_once_with(code="mycode", code_verifier="myverifier")
|
|
client.config.save_refresh_token.assert_called_once_with("saved_token_456")
|
|
assert result == "saved_token_456"
|
|
assert client._logged_in is True
|
|
|
|
|
|
def test_ensure_logged_in_uses_stored_token():
|
|
client = make_client(token="stored_token")
|
|
mock_api = MagicMock()
|
|
client.api = mock_api
|
|
|
|
client.ensure_logged_in()
|
|
|
|
mock_api.auth.assert_called_once_with(refresh_token="stored_token")
|
|
assert client._logged_in is True
|
|
|
|
|
|
def test_ensure_logged_in_raises_when_no_token():
|
|
client = make_client(token=None)
|
|
with pytest.raises(RuntimeError, match="未登录"):
|
|
client.ensure_logged_in()
|
|
|
|
|
|
def test_ensure_logged_in_skips_if_already_logged_in():
|
|
client = make_client(token="tok")
|
|
client._logged_in = True
|
|
mock_api = MagicMock()
|
|
client.api = mock_api
|
|
|
|
client.ensure_logged_in()
|
|
|
|
mock_api.auth.assert_not_called()
|
|
|
|
|
|
# ── Fetch tests ───────────────────────────────────────────────────────────────
|
|
|
|
def _mock_illust():
|
|
i = MagicMock()
|
|
i.id = 12345
|
|
i.title = "Test Art"
|
|
i.image_urls.medium = "https://i.pximg.net/medium/img.jpg"
|
|
i.image_urls.large = "https://i.pximg.net/large/img.jpg"
|
|
i.user.id = 999
|
|
i.user.name = "TestArtist"
|
|
i.user.profile_image_urls.medium = "https://i.pximg.net/avatar.jpg"
|
|
return i
|
|
|
|
|
|
def _mock_result(illusts=None, next_url=None):
|
|
r = MagicMock()
|
|
r.illusts = illusts or []
|
|
r.next_url = next_url
|
|
return r
|
|
|
|
|
|
def test_get_recommended_returns_formatted_illusts():
|
|
client = make_client(token="tok")
|
|
client._logged_in = True
|
|
client.api = MagicMock()
|
|
client.api.illust_recommended.return_value = _mock_result([_mock_illust()])
|
|
|
|
result = client.get_recommended()
|
|
|
|
assert len(result["illusts"]) == 1
|
|
assert result["illusts"][0]["id"] == 12345
|
|
assert result["illusts"][0]["title"] == "Test Art"
|
|
assert result["illusts"][0]["user"]["name"] == "TestArtist"
|
|
assert result["next_url"] is None
|
|
|
|
|
|
def test_get_recommended_next_page_calls_next_qs():
|
|
client = make_client(token="tok")
|
|
client._logged_in = True
|
|
client.api = MagicMock()
|
|
client.api.next_qs.return_value = {"offset": "30"}
|
|
client.api.illust_recommended.return_value = _mock_result()
|
|
|
|
client.get_recommended(next_url="https://app-api.pixiv.net/v1/illust/recommended?offset=30")
|
|
|
|
client.api.next_qs.assert_called_once()
|
|
client.api.illust_recommended.assert_called_once_with(offset="30")
|
|
|
|
|
|
def test_get_ranking_passes_mode():
|
|
client = make_client(token="tok")
|
|
client._logged_in = True
|
|
client.api = MagicMock()
|
|
client.api.illust_ranking.return_value = _mock_result()
|
|
|
|
client.get_ranking(mode="week")
|
|
|
|
client.api.illust_ranking.assert_called_once_with(mode="week")
|
|
|
|
|
|
def test_get_bookmarks_uses_user_id():
|
|
client = make_client(token="tok")
|
|
client._logged_in = True
|
|
client.api = MagicMock()
|
|
client.api.user_id = "42"
|
|
client.api.user_bookmarks_illust.return_value = _mock_result()
|
|
|
|
client.get_bookmarks()
|
|
|
|
client.api.user_bookmarks_illust.assert_called_once_with("42")
|
|
|
|
|
|
def test_get_bookmarked_artists_returns_formatted():
|
|
client = make_client(token="tok")
|
|
client._logged_in = True
|
|
preview = MagicMock()
|
|
preview.user.id = 777
|
|
preview.user.name = "Artist1"
|
|
preview.user.profile_image_urls.medium = "https://i.pximg.net/avatar.jpg"
|
|
client.api = MagicMock()
|
|
client.api.user_id = "42"
|
|
client.api.user_following.return_value = MagicMock(
|
|
user_previews=[preview], next_url=None
|
|
)
|
|
|
|
result = client.get_bookmarked_artists()
|
|
|
|
assert result["artists"][0]["id"] == 777
|
|
assert result["artists"][0]["name"] == "Artist1"
|
|
|
|
|
|
def test_get_artist_works_passes_artist_id():
|
|
client = make_client(token="tok")
|
|
client._logged_in = True
|
|
client.api = MagicMock()
|
|
client.api.user_illusts.return_value = _mock_result()
|
|
|
|
client.get_artist_works(artist_id=777)
|
|
|
|
client.api.user_illusts.assert_called_once_with(777)
|
|
|
|
|
|
# ── Download tests ────────────────────────────────────────────────────────────
|
|
|
|
def test_download_image_bytes_sets_referer_header():
|
|
client = make_client(token="tok")
|
|
client._logged_in = True
|
|
client.api = MagicMock()
|
|
|
|
fake_response = MagicMock()
|
|
fake_response.content = b"\xff\xd8\xff"
|
|
fake_response.raise_for_status = MagicMock()
|
|
|
|
with patch("pixiv_client.requests.get", return_value=fake_response) as mock_get:
|
|
result = client.download_image_bytes("https://i.pximg.net/img/test.jpg")
|
|
|
|
mock_get.assert_called_once_with(
|
|
"https://i.pximg.net/img/test.jpg",
|
|
headers={"Referer": "https://www.pixiv.net/"},
|
|
timeout=30,
|
|
)
|
|
assert result == b"\xff\xd8\xff"
|
|
|
|
|
|
def test_get_original_url_single_page():
|
|
client = make_client(token="tok")
|
|
client._logged_in = True
|
|
client.api = MagicMock()
|
|
client.api.illust_detail.return_value.illust.meta_single_page = {
|
|
"original_image_url": "https://i.pximg.net/orig/img.jpg"
|
|
}
|
|
client.api.illust_detail.return_value.illust.meta_pages = []
|
|
|
|
url = client.get_original_url(12345)
|
|
|
|
assert url == "https://i.pximg.net/orig/img.jpg"
|
|
client.api.illust_detail.assert_called_once_with(12345)
|
|
|
|
|
|
def test_get_original_url_multi_page_uses_first_page():
|
|
client = make_client(token="tok")
|
|
client._logged_in = True
|
|
client.api = MagicMock()
|
|
client.api.illust_detail.return_value.illust.meta_single_page = {}
|
|
page = MagicMock()
|
|
page.image_urls.original = "https://i.pximg.net/orig/p0.jpg"
|
|
client.api.illust_detail.return_value.illust.meta_pages = [page]
|
|
|
|
url = client.get_original_url(99999)
|
|
|
|
assert url == "https://i.pximg.net/orig/p0.jpg"
|