Initial import of Shirobana bot backend
Include utility modules, shared channels, prompts, read-only skills, wardrobe memory and regression tests. Externalize legacy credentials and exclude private runtime state.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user