"""Bilibili OpenAPI command tests; no real network or message delivery.""" import asyncio from pathlib import Path import sys import tempfile import unittest from types import SimpleNamespace from unittest.mock import AsyncMock, patch BASE = Path(__file__).resolve().parents[1] sys.path[:0] = [str(BASE / "bot-utility"), str(BASE)] import bilibili class FakeResponse: def __init__(self, status=200, content_type="video/mp4", chunks=(b"video",), text=""): self.status_code = status self.headers = {"Content-Type": content_type} self._chunks = chunks self.text = text self.closed = False def iter_content(self, size): yield from self._chunks def close(self): self.closed = True class FakeChannel: name = "tg" @staticmethod def is_admin(uid): return False class BilibiliTests(unittest.IsolatedAsyncioTestCase): def test_sanitize_bv(self): self.assertEqual(bilibili.sanitize_bv("bv1uT4y1P7CX"), "BV1uT4y1P7CX") self.assertEqual(bilibili.sanitize_bv(" BV1uT4y1P7CX "), "BV1uT4y1P7CX") self.assertIsNone(bilibili.sanitize_bv("1uT4y1P7CX")) self.assertIsNone(bilibili.sanitize_bv("https://www.bilibili.com/video/BV1uT4y1P7CX")) self.assertIsNone(bilibili.sanitize_bv("BV1uT4y1P7CX extra")) async def test_handle_sends_one_file_and_cleans_temp(self): with tempfile.TemporaryDirectory() as directory: video = Path(directory) / "video.mp4" video.write_bytes(b"video") sender = AsyncMock(return_value=True) with patch("channel.send_message", new=sender), \ patch.object(bilibili, "download_video", return_value=video): await bilibili.handle_bilibili( FakeChannel(), "private", 10001, 1, ".bilibili BV1uT4y1P7CX" ) self.assertEqual(sender.await_count, 2) segments = sender.await_args.args[3] self.assertEqual(len(segments), 1) self.assertEqual(segments[0]["type"], "file") self.assertTrue(segments[0]["data"]["file"].startswith("file://")) self.assertIn("bv=BV1uT4y1P7CX", segments[0]["data"]["manual"]) self.assertFalse(video.exists()) async def test_invalid_input_does_not_call_openapi(self): sender = AsyncMock(return_value=True) with patch("channel.send_message", new=sender), \ patch.object(bilibili, "download_video") as download: await bilibili.handle_bilibili(FakeChannel(), "private", 10001, 1, ".bilibili nope") download.assert_not_called() self.assertIn("格式", sender.await_args.args[3]) async def test_openapi_error_is_reported_and_temp_is_cleaned(self): sender = AsyncMock(return_value=True) with patch("channel.send_message", new=sender), \ patch.object(bilibili, "download_video", side_effect=RuntimeError("OpenAPI HTTP 502")): await bilibili.handle_bilibili( FakeChannel(), "private", 10001, 1, ".bilibili BV1uT4y1P7CX" ) self.assertIn("OpenAPI HTTP 502", sender.await_args.args[3]) def test_download_writes_stream_and_closes_response(self): response = FakeResponse(chunks=(b"abc", b"def")) with tempfile.TemporaryDirectory() as directory, \ patch.object(bilibili, "CACHE_DIR", Path(directory)), \ patch("requests.get", return_value=response) as get: path = bilibili.download_video("BV1uT4y1P7CX") try: self.assertEqual(path.read_bytes(), b"abcdef") self.assertEqual(path.suffix, ".mp4") get.assert_called_once_with( bilibili.OPENAPI_URL, params={"bv": "BV1uT4y1P7CX"}, stream=True, timeout=bilibili.HTTP_TIMEOUT, ) self.assertTrue(response.closed) finally: bilibili.cleanup_file(path) def test_download_http_error_includes_detail_and_closes_response(self): response = FakeResponse( status=502, content_type="application/json", chunks=(), text='{"error":"download failed"}', ) with tempfile.TemporaryDirectory() as directory, \ patch.object(bilibili, "CACHE_DIR", Path(directory)), \ patch("requests.get", return_value=response): with self.assertRaisesRegex(RuntimeError, "download failed"): bilibili.download_video("BV1uT4y1P7CX") self.assertTrue(response.closed) self.assertEqual(list(Path(directory).iterdir()), []) if __name__ == "__main__": unittest.main()