add # 新增Nonebot发布工作流

This commit is contained in:
2026-10-21 18:35:16 +08:00
parent c7d97e294a
commit 531409b8b6
26 changed files with 1585 additions and 19 deletions
+150
View File
@@ -0,0 +1,150 @@
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/zhenxun/runtime.py"
).read_bytes())
for suffix in ("farm_core/data/plants.json",
"farm_render/data/assets.zip"):
self.assertTrue(any(name.endswith(suffix) for name in self.files))
self.assertEqual(verify_files(self.files)["target"], "nonebot")
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))
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.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))