"""Verify the native release in an isolated directory on the workspace filesystem.""" import http.cookiejar import json import os from pathlib import Path import secrets import shutil import socket import sqlite3 import subprocess import tarfile import tempfile import time import urllib.request project = Path(__file__).resolve().parent.parent stage = Path(tempfile.mkdtemp(prefix=".bluebird-smoke-", dir=project)) server = None try: with tarfile.open(project / "build/bluebird-linux-amd64.tar.gz") as archive: archive.extractall(stage, filter="data") with socket.socket() as sock: sock.bind(("127.0.0.1", 0)) port = sock.getsockname()[1] base = f"http://127.0.0.1:{port}" config = stage / "config.toml" source = (config.read_text().replace("127.0.0.1:8080", f"127.0.0.1:{port}") .replace("http://localhost:8080", base) .replace("min_free_bytes = 1073741824", "min_free_bytes = 0") .replace("encryption = false", "encryption = true")) config.write_text(source) binary = str(stage / "bluebird") password = secrets.token_urlsafe(24) env = os.environ.copy() env["BLUEBIRD_ADMIN_PASSWORD"] = password subprocess.run([binary, "init", "--config", str(config)], env=env, check=True, capture_output=True, timeout=15) server = subprocess.Popen([binary, "serve", "--config", str(config)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) opener = urllib.request.build_opener( urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar())) def call(method, path, body=None, raw=False): headers = {} if body is not None: if not raw: body = json.dumps(body).encode() headers = {"Origin": base, "Content-Type": "application/octet-stream" if raw else "application/json"} request = urllib.request.Request(base + "/api/v1" + path, data=body, headers=headers, method=method) with opener.open(request, timeout=10) as response: return json.loads(response.read()) for _ in range(50): try: assert call("GET", "/health")["ready"] break except (OSError, AssertionError): time.sleep(.1) else: raise AssertionError("Release server did not start") with opener.open(base + "/", timeout=10) as response: assert b'
' in response.read() call("GET", "/policy") payload = b"bluebird-release-smoke\n" * 4096 parcel = call("POST", "/uploads", { "files": [{"name": "first.dat", "size": len(payload)}, {"name": "same.dat", "size": len(payload)}], "days": 7, "downloads": 2, "renew": True, "password": ""}) for file in parcel["files"]: call("PUT", f"/uploads/{parcel['id']}/files/{file['id']}", payload, True) share = call("POST", f"/uploads/{parcel['id']}/publish", {}) call("POST", "/pickup/resolve", {"code": share["code"]}) request = urllib.request.Request( base + f"/api/v1/pickup/files/{parcel['files'][0]['id']}/download") with opener.open(request, timeout=10) as response: assert response.read() == payload call("POST", "/admin/login", {"username": "admin", "password": password}) stats = call("GET", "/admin/stats") assert stats["objects"] == 1 and stats["files"] == 2 server.terminate() server.wait(timeout=10) server = None subprocess.run([binary, "check", "--config", str(config)], check=True, capture_output=True, timeout=15) backup = stage / "backup.tar.gz" subprocess.run([binary, "backup", "--config", str(config), "--output", str(backup)], check=True, capture_output=True, timeout=15) restored = stage / "restore.toml" restored.write_text(source.replace('data_dir = "var"', 'data_dir = "restored-var"')) subprocess.run([binary, "restore", "--config", str(restored), "--input", str(backup)], check=True, capture_output=True, timeout=15) subprocess.run([binary, "check", "--config", str(restored)], check=True, capture_output=True, timeout=15) with sqlite3.connect(stage / "restored-var/db/bluebird.db") as db: assert db.execute("SELECT state,code_hash FROM parcels").fetchone() == ("restored", None) assert db.execute("SELECT count(*) FROM grants").fetchone()[0] == 0 assert db.execute("SELECT count(*) FROM objects").fetchone()[0] == 1 print("发布包验证通过:初始化、内嵌前端、匿名去重、加密下载、管理、检查、备份恢复。") finally: if server is not None: server.terminate() try: server.wait(timeout=10) except subprocess.TimeoutExpired: server.kill() server.wait() shutil.rmtree(stage)