Files
CloudSearch/cloudsearch_transfer/tests/test_p1_drive_api_capabilities_unittest.py
T

199 lines
9.8 KiB
Python

import unittest
from unittest.mock import patch
from cloudsearch_transfer.adapter.aliyun import AliyunAdapter
from cloudsearch_transfer.adapter.baidu import BaiduAdapter
from cloudsearch_transfer.adapter.xunlei import XunleiAdapter
from cloudsearch_transfer.config import PlatformConfig, TransferConfig
from cloudsearch_transfer.adapter.base import FileInfo
class DummyCredential:
cookie = "k=xxxxxxxx"
refresh_token = "rt"
def validate(self):
return True
def get_bdstoken(self):
return "bdstoken"
def get_drive_id(self):
return "drive-id"
def get_headers(self):
return {}
def get_auth_headers(self):
return {}
def make_config(**kwargs):
data = dict(enabled=True, cookie="k=" + "x" * 80, refresh_token="rt", account_name="test", save_dir="/")
data.update(kwargs)
return PlatformConfig(**data)
def make_transfer_config():
return TransferConfig(request_timeout=1, max_retries=0, ad_filter_enabled=False, task_poll_interval=0, task_poll_max_attempts=2)
class P1DriveApiCapabilityTests(unittest.TestCase):
def make_aliyun(self):
with patch("cloudsearch_transfer.adapter.aliyun.AliyunCredentialManager", return_value=DummyCredential()):
return AliyunAdapter(make_config(), make_transfer_config())
def make_baidu(self):
with patch("cloudsearch_transfer.adapter.baidu.BaiduCredentialManager", return_value=DummyCredential()):
return BaiduAdapter(make_config(), make_transfer_config())
def make_xunlei(self):
with patch("cloudsearch_transfer.adapter.xunlei.XunleiCredentialManager", return_value=DummyCredential()):
return XunleiAdapter(make_config(), make_transfer_config())
def test_p1_adapters_declare_callable_core_drive_api_methods(self):
expectations = {
"aliyun": (self.make_aliyun(), ["ensure_dir", "save_files", "rename", "move_files", "delete_files"]),
"baidu": (self.make_baidu(), ["ensure_dir", "save_files", "rename", "move_files", "delete_files"]),
"xunlei": (self.make_xunlei(), ["ensure_dir", "save_files", "poll_task", "rename", "move_files"]),
}
for platform, (adapter, required) in expectations.items():
with self.subTest(platform=platform):
for capability in required:
self.assertTrue(adapter.capabilities.get(capability), f"{platform}.{capability} capability not declared")
for method in ["ensure_dir", "mkdir", "rename", "move_files"]:
self.assertNotEqual(getattr(type(adapter), method), getattr(adapter.__class__.__mro__[1], method, None), f"{platform}.{method} not overridden")
if adapter.capabilities.get("delete_files"):
self.assertNotEqual(getattr(type(adapter), "delete_files"), getattr(adapter.__class__.__mro__[1], "delete_files", None), f"{platform}.delete_files not overridden")
def test_aliyun_save_files_resolves_path_to_file_id_before_copy(self):
adapter = self.make_aliyun()
adapter.ensure_dir = lambda path: "target-folder-id"
captured = {}
transfer = adapter._get_transfer()
def fake_batch_copy(share_id, share_token, file_ids, to_parent):
captured["to_parent"] = to_parent
return ["new-file"]
transfer._batch_copy = fake_batch_copy
detail = {"share_token": "share-token", "files": [{"file_id": "src-file"}]}
self.assertEqual(adapter._save_files("share-id", detail, "/Media"), ["new-file"])
self.assertEqual(captured["to_parent"], "target-folder-id")
def test_aliyun_ensure_dir_creates_nested_paths_progressively(self):
adapter = self.make_aliyun()
existing = {"/A": "fid-A"}
created = []
adapter.get_fids = lambda paths: [{"file_path": p, "fid": existing[p]} for p in paths if p in existing]
def fake_mkdir(path):
created.append(path)
existing[path] = "fid-" + path.rsplit("/", 1)[-1]
return {"code": 0, "data": {"fid": existing[path]}}
adapter.mkdir = fake_mkdir
self.assertEqual(adapter.ensure_dir("/A/B"), "fid-B")
self.assertEqual(created, ["/A/B"])
def test_baidu_save_files_ensures_path_before_transfer_and_matching(self):
adapter = self.make_baidu()
adapter.ensure_dir = lambda path: "/CloudSearch/Media"
captured = {}
adapter.credential.get_bdstoken = lambda: "bdstoken"
def fake_transfer_files(shareid, uk, fs_ids, save_dir, token):
captured["transfer_dir"] = save_dir
adapter._transfer._transfer_files = fake_transfer_files
def fake_list_and_match(save_dir, filenames, token):
captured["list_dir"] = save_dir
return ["1001"]
adapter._transfer._list_and_match = fake_list_and_match
detail = {"shareid": "sid", "uk": "uk", "fs_ids": ["old"], "filenames": ["demo.mkv"]}
self.assertEqual(adapter._save_files("pwd", detail, "/CloudSearch/Media"), ["1001"])
self.assertEqual(captured["transfer_dir"], "/CloudSearch/Media")
self.assertEqual(captured["list_dir"], "/CloudSearch/Media")
def test_baidu_ensure_dir_creates_nested_paths_progressively(self):
adapter = self.make_baidu()
dirs = {"/A"}
created = []
adapter.get_files = lambda parent="/": [FileInfo(fid=parent.rstrip("/") + "/A", name="A", is_dir=True)] if parent == "/" else []
def fake_mkdir(path):
created.append(path)
dirs.add(path)
return {"code": 0, "data": {"path": path}}
adapter.mkdir = fake_mkdir
self.assertEqual(adapter.ensure_dir("/A/B"), "/A/B")
self.assertEqual(created, ["/A/B"])
def test_xunlei_save_files_resolves_path_and_polls_restore_task(self):
adapter = self.make_xunlei()
adapter.ensure_dir = lambda path: "target-parent-id"
transfer = adapter._transfer
transfer._restore_files = lambda share_id, token, fids, parent_id="": "task-1" if parent_id == "target-parent-id" else (_ for _ in ()).throw(AssertionError(parent_id))
transfer._poll_restore_task = lambda task_id: {"old-1": "new-1"}
detail = {"share_id": "share", "pass_code_token": "pct", "files": [{"id": "old-1", "name": "demo"}]}
self.assertEqual(adapter._save_files("share", detail, "/Media"), ["new-1"])
def test_xunlei_query_task_delegates_to_restore_poll(self):
adapter = self.make_xunlei()
adapter._transfer._poll_restore_task = lambda task_id: {"old": "new"}
self.assertEqual(adapter.query_task("task-1"), {"code": 0, "status": 200, "data": {"old": "new"}})
def test_xunlei_does_not_advertise_permanent_delete_as_drive_delete_capability(self):
adapter = self.make_xunlei()
self.assertFalse(adapter.capabilities.get("delete_files"), "迅雷 batchDelete is permanent and must not be advertised as generic delete_files")
def test_xunlei_get_share_detail_passes_extraction_code_to_transfer_engine(self):
adapter = self.make_xunlei()
captured = {}
def fake_get_share_info(share_id, passcode=""):
captured["share_id"] = share_id
captured["passcode"] = passcode
return {"title": "demo", "files": [{"id": "old"}], "pass_code_token": "pct"}
adapter._transfer._get_share_info = fake_get_share_info
detail = adapter._get_share_detail("share-id", "abcd")
self.assertEqual(detail["title"], "demo")
self.assertEqual(captured, {"share_id": "share-id", "passcode": "abcd"})
def test_xunlei_create_share_forwards_requested_share_password(self):
adapter = self.make_xunlei()
captured = {}
def fake_create_share(file_ids, password=""):
captured["file_ids"] = file_ids
captured["password"] = password
return ("https://pan.xunlei.com/s/new", password)
adapter._transfer._create_share = fake_create_share
self.assertEqual(adapter._create_share(["fid-1"], "demo", "xy12"), ("https://pan.xunlei.com/s/new", "xy12"))
self.assertEqual(captured, {"file_ids": ["fid-1"], "password": "xy12"})
def test_xunlei_poll_task_alias_matches_declared_capability(self):
adapter = self.make_xunlei()
adapter._transfer._poll_restore_task = lambda task_id: {"old": "new"}
self.assertEqual(adapter.poll_task("task-1"), {"code": 0, "status": 200, "data": {"old": "new"}})
def test_xunlei_get_fids_is_read_only_and_does_not_create_missing_paths(self):
adapter = self.make_xunlei()
calls = []
def fake_get_files(parent="0"):
calls.append(("get_files", parent))
if parent in ("0", ""):
return [FileInfo(fid="fid-A", name="A", is_dir=True)]
if parent == "fid-A":
return [FileInfo(fid="fid-B", name="B", is_dir=False)]
return []
def fail_mkdir(*args, **kwargs):
raise AssertionError("get_fids must not create directories")
adapter.get_files = fake_get_files
adapter.mkdir = fail_mkdir
self.assertEqual(adapter.get_fids(["/A/B", "/A/Missing"]), [{"file_path": "/A/B", "fid": "fid-B"}])
self.assertEqual(calls, [("get_files", "0"), ("get_files", "fid-A"), ("get_files", "0"), ("get_files", "fid-A")])
def test_baidu_get_fids_checks_existing_parent_listing(self):
adapter = self.make_baidu()
calls = []
def fake_get_files(parent="/"):
calls.append(parent)
if parent == "/A":
return [FileInfo(fid="1001", name="movie.mkv", is_dir=False)]
return []
adapter.get_files = fake_get_files
self.assertEqual(adapter.get_fids(["/A/movie.mkv", "/A/missing.mkv"]), [{"file_path": "/A/movie.mkv", "fid": "1001", "path": "/A/movie.mkv"}])
self.assertEqual(calls, ["/A", "/A"])
if __name__ == "__main__":
unittest.main()