mirror of
https://github.com/Shu-Ying/main_plugin_farm.git
synced 2026-10-28 13:10:02 +08:00
168 lines
7.3 KiB
Python
168 lines
7.3 KiB
Python
import base64
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import patch
|
|
from zipfile import ZipFile
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT / "scripts"))
|
|
from build_nonebot import build
|
|
from publish_nonebot import publish
|
|
from verify_nonebot import MANIFEST, ROOT_NAME, read_package, verify_files
|
|
|
|
|
|
class NoneBotPackageTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temporary = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temporary.cleanup)
|
|
self.root = Path(self.temporary.name)
|
|
self.bundle = build(ROOT, self.root / "built")
|
|
self.files = read_package(self.bundle)
|
|
|
|
def test_reproducible_standard_package_and_shared_runtime(self):
|
|
other = build(ROOT, self.root / "second")
|
|
self.assertEqual(self.bundle.read_bytes(), other.read_bytes())
|
|
self.assertEqual(self.files["nonebot_plugin_farm/runtime.py"], (
|
|
ROOT / "integrations/shared/runtime.py"
|
|
).read_bytes())
|
|
for suffix in ("farm_core/data/plants.json",
|
|
"farm_render/data/asset-manifest.json",
|
|
"farm_render/data/font-manifest.json",
|
|
"farm_render/data/OFL-Xiaolai.txt"):
|
|
self.assertTrue(any(name.endswith(suffix) for name in self.files))
|
|
self.assertEqual(verify_files(self.files)["target"], "nonebot")
|
|
self.assertFalse(any(name.endswith('.ttf') for name in self.files))
|
|
for name in ("font-manifest.json", "OFL-Xiaolai.txt"):
|
|
self.assertEqual(
|
|
self.files[
|
|
"nonebot_plugin_farm/_vendor/farm_render/data/" + name
|
|
],
|
|
(ROOT / "src/farm_render/data" / name).read_bytes(),
|
|
)
|
|
self.assertIn("pyproject.toml", self.files)
|
|
self.assertIn(".github/workflows/publish.yaml", self.files)
|
|
self.assertFalse(any("legacy/" in name for name in self.files))
|
|
self.assertFalse(any(name.endswith('/assets.zip')
|
|
for name in self.files))
|
|
with self.assertRaises(FileExistsError):
|
|
build(ROOT, self.root / "built")
|
|
|
|
def test_host_entry_has_no_zhenxun_import(self):
|
|
import ast
|
|
for name, content in self.files.items():
|
|
if not name.endswith(".py"):
|
|
continue
|
|
tree = ast.parse(content, feature_version=(3, 10))
|
|
for node in ast.walk(tree):
|
|
imports = ([node.module] if isinstance(node, ast.ImportFrom)
|
|
else [alias.name for alias in node.names]
|
|
if isinstance(node, ast.Import) else [])
|
|
self.assertFalse(any(
|
|
value and value.startswith(("zhenxun", "zhenxun_utils"))
|
|
for value in imports
|
|
), name)
|
|
|
|
def test_tampering_and_paths_rejected(self):
|
|
files = dict(self.files)
|
|
files["pyproject.toml"] += b"# changed\n"
|
|
with self.assertRaisesRegex(ValueError, "checksum"):
|
|
verify_files(files)
|
|
for path in ("../escape", "extra", "C:/escape", "..\\escape"):
|
|
bundle = self.root / "bad.zip"
|
|
bundle.write_bytes(self.bundle.read_bytes())
|
|
with ZipFile(bundle, "a") as archive:
|
|
archive.writestr(path, b"invalid")
|
|
with self.assertRaises(ValueError):
|
|
read_package(bundle)
|
|
|
|
def clean_package(self):
|
|
files = dict(self.files)
|
|
manifest = json.loads(files[MANIFEST])
|
|
manifest["source"]["dirty"] = False
|
|
files[MANIFEST] = json.dumps(manifest).encode()
|
|
bundle = self.root / "clean.zip"
|
|
with ZipFile(bundle, "w") as archive:
|
|
for name, content in files.items():
|
|
archive.writestr(f"{ROOT_NAME}/{name}", content)
|
|
return bundle, manifest
|
|
|
|
def test_default_publish_is_offline_and_dirty_source_is_rejected(self):
|
|
def forbidden(*args):
|
|
self.fail("dry-run must not make requests")
|
|
|
|
report = publish(self.bundle, request=forbidden)
|
|
self.assertFalse(report["published"])
|
|
self.assertEqual(report["repository"], "Shu-Ying/nonebot_plugin_farm")
|
|
self.assertEqual(report["branch"], "main")
|
|
bundle, manifest = self.clean_package()
|
|
manifest["source"]["dirty"] = True
|
|
self.files[MANIFEST] = json.dumps(manifest).encode()
|
|
with ZipFile(bundle, "w") as archive:
|
|
for name, content in self.files.items():
|
|
archive.writestr(f"{ROOT_NAME}/{name}", content)
|
|
with self.assertRaisesRegex(ValueError, "clean source"):
|
|
publish(bundle, True, forbidden)
|
|
|
|
def test_fixed_sync_preserves_history_and_includes_pypi_workflow(self):
|
|
bundle, manifest = self.clean_package()
|
|
calls = []
|
|
|
|
def request(method, path, data):
|
|
calls.append((method, path, data))
|
|
if path == "":
|
|
return {"default_branch": "main"}
|
|
if path.startswith("git/ref/"):
|
|
return {"object": {"sha": "existing"}}
|
|
if path.startswith("contents/"):
|
|
return None
|
|
return {"sha": "generated"}
|
|
|
|
with patch.dict("os.environ", {"FARM_RELEASE_TOKEN": "test-token"}):
|
|
self.assertTrue(publish(bundle, True, request)["published"])
|
|
tree = next(data for method, path, data in calls
|
|
if path == "git/trees")
|
|
self.assertEqual({entry["path"] for entry in tree["tree"]},
|
|
set(self.files))
|
|
commit = next(data for method, path, data in calls
|
|
if path == "git/commits")
|
|
self.assertEqual(commit["parents"], ["existing"])
|
|
self.assertEqual(commit["message"], (
|
|
manifest["source"]["message"] + "\n\n"
|
|
+ "Farm-Source-Commit: " + manifest["source"]["commit"] + "\n"
|
|
+ "Farm-Version: " + manifest["version"]
|
|
))
|
|
self.assertIn(("PATCH", "git/refs/heads/main", {
|
|
"sha": "generated", "force": False,
|
|
}), calls)
|
|
|
|
def test_wrong_owner_and_concurrent_change_are_rejected(self):
|
|
bundle, manifest = self.clean_package()
|
|
for wrong_owner in (True, False):
|
|
calls = []
|
|
|
|
def request(method, path, data):
|
|
calls.append((method, path, data))
|
|
if path == "":
|
|
return {"default_branch": "main"}
|
|
if path.startswith("contents/"):
|
|
previous = {**manifest, "target": (
|
|
"zhenxun" if wrong_owner else "nonebot"
|
|
)}
|
|
return {"encoding": "base64", "content": base64.b64encode(
|
|
json.dumps(previous).encode(),
|
|
).decode()}
|
|
if method == "GET":
|
|
reads = sum(p.startswith("git/ref/") for _, p, _ in calls)
|
|
return {"object": {"sha": (
|
|
"existing" if reads == 1 else "changed"
|
|
)}}
|
|
return {"sha": "generated"}
|
|
|
|
with patch.dict("os.environ", {"FARM_RELEASE_TOKEN": "test"}):
|
|
with self.assertRaises((ValueError, RuntimeError)):
|
|
publish(bundle, True, request)
|
|
self.assertFalse(any(method == "PATCH" for method, _, _ in calls))
|