Files

191 lines
5.7 KiB
Python

import io
import http.client
import os
import socket
import subprocess
import sys
import tempfile
import threading
import time
import unittest
import urllib.parse
from pathlib import Path
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PyQt5.QtWidgets import QApplication
from pgu4_sync import (
DownloadJob,
DownloadSignals,
FAST_HTTP_SERVER_SCRIPT,
FileTask,
TransferController,
decode_fast_remote_path,
encode_fast_remote_path,
normalize_local_filename,
)
class RemoteBytes(io.BytesIO):
def set_pipelined(self, _enabled):
return None
class FakeSftp:
def __init__(self, expected_path, payload):
self.expected_path = expected_path
self.payload = payload
def open(self, path, mode):
if mode != "rb":
raise AssertionError(mode)
if path != self.expected_path:
raise AssertionError(path)
return RemoteBytes(self.payload)
def close(self):
return None
class FakeTransport:
def set_keepalive(self, _seconds):
return None
class FakeClient:
def __init__(self, sftp):
self.sftp = sftp
def get_transport(self):
return FakeTransport()
def open_sftp(self):
return self.sftp
def close(self):
return None
class FakeFactory:
def __init__(self, sftp):
self.sftp = sftp
def open_client(self):
return FakeClient(self.sftp)
class UnicodePathTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.app = QApplication.instance() or QApplication([])
def test_fast_remote_path_round_trip(self):
remote_path = "/home/clear/中文源目录/图像 01.jpg"
encoded = encode_fast_remote_path(remote_path)
self.assertTrue(encoded.isascii())
self.assertNotIn("中文", encoded)
self.assertEqual(remote_path, decode_fast_remote_path(encoded))
def test_local_filename_preserves_chinese_and_normalizes_unicode(self):
decomposed = "Cafe\u0301_中文数据"
normalized = normalize_local_filename(decomposed)
self.assertEqual("Café_中文数据", normalized)
def test_sftp_download_supports_chinese_source_and_target(self):
payload = "中文路径传输正常".encode("utf-8")
remote_path = "/home/clear/中文源目录/实验数据.bin"
sftp = FakeSftp(remote_path, payload)
signals = DownloadSignals()
controller = TransferController()
job = DownloadJob(
FakeFactory(sftp),
[],
Path("."),
1,
signals,
controller,
)
with tempfile.TemporaryDirectory(prefix="PGU-中文目标-") as temp_dir:
local_path = Path(temp_dir) / "目标文件夹" / "实验数据.bin"
local_path.parent.mkdir(parents=True)
local_path.write_bytes(b"\x00" * len(payload))
file_task = FileTask(remote_path, local_path, len(payload))
job._file_locks[local_path] = threading.Lock()
job._download_chunk_sftp_once(file_task, 0, len(payload))
self.assertEqual(payload, local_path.read_bytes())
def test_fast_http_service_reads_chinese_path_with_range(self):
payload = "极速中文路径".encode("utf-8")
token = "unicode-test-token"
with tempfile.TemporaryDirectory(prefix="PGU-极速中文-") as temp_dir:
root = Path(temp_dir)
source = root / "中文源文件夹" / "图像 数据.bin"
source.parent.mkdir(parents=True)
source.write_bytes(payload)
script = root / "fast_service.py"
script.write_text(FAST_HTTP_SERVER_SCRIPT, encoding="utf-8")
with socket.socket() as probe:
probe.bind(("127.0.0.1", 0))
port = probe.getsockname()[1]
process = subprocess.Popen(
[
sys.executable,
str(script),
"--host",
"127.0.0.1",
"--port",
str(port),
"--token",
token,
],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
)
self.addCleanup(process.kill)
deadline = time.time() + 4
while time.time() < deadline:
try:
conn = http.client.HTTPConnection("127.0.0.1", port, timeout=0.3)
conn.request(
"GET",
"/health?token=" + urllib.parse.quote(token, safe=""),
)
response = conn.getresponse()
if response.status == 200 and response.read() == b"ok-v3":
conn.close()
break
conn.close()
except OSError:
time.sleep(0.05)
else:
self.fail("fast HTTP test service did not start")
encoded_path = encode_fast_remote_path(str(source))
conn = http.client.HTTPConnection("127.0.0.1", port, timeout=2)
request_path = (
"/file?token="
+ urllib.parse.quote(token, safe="")
+ "&path_b64="
+ urllib.parse.quote(encoded_path, safe="")
)
conn.request("GET", request_path, headers={"Range": "bytes=3-8"})
response = conn.getresponse()
body = response.read()
conn.close()
self.assertEqual(206, response.status)
self.assertEqual(payload[3:9], body)
process.terminate()
process.wait(timeout=2)