fix(client): accept whitespace in screenshot base64

This commit is contained in:
QiuSW
2026-08-04 08:51:00 +08:00
parent c4cf19fd55
commit 7905fa0b70
5 changed files with 97 additions and 13 deletions
+71 -3
View File
@@ -153,22 +153,90 @@ class BaselineCaptureTests(unittest.TestCase):
self.assertFalse(output.exists())
self.assertEqual(list(Path(directory).iterdir()), [])
def test_invalid_screenshot_base64_fails_closed_without_partial_output(self) -> None:
def test_invalid_screenshot_base64_syntax_fails_closed_without_partial_output(self) -> None:
class InvalidScreenshotDevice(FakeUiDevice):
def jsonrpc_call(self, method: str, params: object = None, timeout: float = 10) -> str:
if method == "takeScreenshot":
return "not-valid-base64"
valid = super().jsonrpc_call(method, params, timeout)
return valid[:12] + "!" + valid[12:]
return super().jsonrpc_call(method, params, timeout)
capturer = DeviceBaselineCapturer(StaticAdbClient(), lambda serial: InvalidScreenshotDevice(), timeout_seconds=5)
with tempfile.TemporaryDirectory() as directory:
output = Path(directory) / "baseline"
with self.assertRaises(BaselineCaptureError):
with self.assertRaises(BaselineCaptureError) as raised:
capturer.capture(SERIAL, output)
self.assertIn("Base64 语法无效", str(raised.exception))
self.assertNotIn("!", str(raised.exception))
self.assertFalse(output.exists())
self.assertEqual(list(Path(directory).iterdir()), [])
def test_invalid_padding_and_unapproved_ascii_whitespace_fail_closed(self) -> None:
invalid_insertions = {
"padding": lambda value: value[:-1],
"vertical-tab": lambda value: value[:12] + "\v" + value[12:],
"form-feed": lambda value: value[:12] + "\f" + value[12:],
}
for name, make_invalid in invalid_insertions.items():
with self.subTest(name=name), tempfile.TemporaryDirectory() as directory:
class InvalidScreenshotDevice(FakeUiDevice):
def jsonrpc_call(self, method: str, params: object = None, timeout: float = 10) -> str:
value = super().jsonrpc_call(method, params, timeout)
if method == "takeScreenshot":
return make_invalid(value)
return value
output = Path(directory) / "baseline"
capturer = DeviceBaselineCapturer(
StaticAdbClient(),
lambda serial: InvalidScreenshotDevice(),
timeout_seconds=5,
)
with self.assertRaises(BaselineCaptureError) as raised:
capturer.capture(SERIAL, output)
self.assertIn("Base64 语法无效", str(raised.exception))
self.assertNotIn(SERIAL, str(raised.exception))
self.assertFalse(output.exists())
self.assertEqual(list(Path(directory).iterdir()), [])
def test_base64_decoded_nonimage_fails_closed_without_partial_output(self) -> None:
class NonImageScreenshotDevice(FakeUiDevice):
def jsonrpc_call(self, method: str, params: object = None, timeout: float = 10) -> str:
if method == "takeScreenshot":
return base64.b64encode(b"not an image").decode("ascii")
return super().jsonrpc_call(method, params, timeout)
capturer = DeviceBaselineCapturer(StaticAdbClient(), lambda serial: NonImageScreenshotDevice(), timeout_seconds=5)
with tempfile.TemporaryDirectory() as directory:
output = Path(directory) / "baseline"
with self.assertRaises(BaselineCaptureError) as raised:
capturer.capture(SERIAL, output)
self.assertIn("图像数据无效", str(raised.exception))
self.assertNotIn("not an image", str(raised.exception))
self.assertFalse(output.exists())
self.assertEqual(list(Path(directory).iterdir()), [])
def test_ascii_base64_whitespace_is_normalized_before_strict_decode(self) -> None:
class WhitespaceScreenshotDevice(FakeUiDevice):
def jsonrpc_call(self, method: str, params: object = None, timeout: float = 10) -> str:
value = super().jsonrpc_call(method, params, timeout)
if method == "takeScreenshot":
return value[:10] + " \t\r\n" + value[10:30] + "\n" + value[30:]
return value
capturer = DeviceBaselineCapturer(StaticAdbClient(), lambda serial: WhitespaceScreenshotDevice(), timeout_seconds=5)
with tempfile.TemporaryDirectory() as directory:
output = Path(directory) / "baseline"
result = capturer.capture(SERIAL, output)
self.assertTrue(result.screenshot_path.is_file())
with Image.open(result.screenshot_path) as image:
self.assertEqual(image.size, (1, 1))
def test_invalid_or_non_hierarchy_xml_fails_closed_without_partial_output(self) -> None:
class InvalidHierarchyDevice(FakeUiDevice):
def jsonrpc_call(self, method: str, params: object = None, timeout: float = 10) -> str: