from __future__ import annotations import hashlib import json import threading import urllib.request from http.server import ThreadingHTTPServer from pathlib import Path from cognitive_agent.firmware_ota_server import FirmwareOtaHandler, FirmwareOtaRepository class TestFirmwareOtaServer: def test_latest_download_head_and_resume_are_read_only(self, tmp_path: Path) -> None: release_id = "fw-rel-123456789abc" filename = "PassengerSeat_test.zip" package = (b"vehicle-firmware-package" * 128) + b"end" release_root = tmp_path / "releases" / release_id release_root.mkdir(parents=True) package_path = release_root / filename package_path.write_bytes(package) manifest = { "schema": "n7-ota-release/v1", "release_id": release_id, "firmware_filename": filename, "size_bytes": len(package), "sha256": hashlib.sha256(package).hexdigest(), "capabilities": ["时间控制", "座椅加热"], "published_at": "2026-09-16T13:17:17+08:00", "status": "published", "download_url": "http://stale-address.invalid/download", } (release_root / "release.json").write_text( json.dumps(manifest, ensure_ascii=False), encoding="utf-8" ) (tmp_path / "latest.json").write_text( json.dumps(manifest, ensure_ascii=False), encoding="utf-8" ) older_release_id = "fw-rel-000000000001" older_root = tmp_path / "releases" / older_release_id older_root.mkdir(parents=True) older_filename = "PassengerSeat_older.zip" older_package = b"older-firmware-package" (older_root / older_filename).write_bytes(older_package) older_manifest = { **manifest, "release_id": older_release_id, "firmware_filename": older_filename, "size_bytes": len(older_package), "sha256": hashlib.sha256(older_package).hexdigest(), "published_at": "2026-09-15T13:17:17+08:00", } (older_root / "release.json").write_text( json.dumps(older_manifest, ensure_ascii=False), encoding="utf-8" ) class Handler(FirmwareOtaHandler): repository = FirmwareOtaRepository( tmp_path, "https://n7-ota.example.ts.net/api/firmware-workbench/releases" ) server = ThreadingHTTPServer(("127.0.0.1", 0), Handler) thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() base_url = f"http://127.0.0.1:{server.server_port}" try: with urllib.request.urlopen( f"{base_url}/api/firmware-workbench/releases/latest", timeout=3 ) as response: latest = json.loads(response.read().decode("utf-8")) assert latest["download_url"] == ( f"https://n7-ota.example.ts.net/api/firmware-workbench/releases/" f"{release_id}/download" ) with urllib.request.urlopen( f"{base_url}/api/firmware-workbench/releases", timeout=3 ) as response: releases = json.loads(response.read().decode("utf-8")) assert releases["schema"] == "n7-ota-release-list/v1" assert releases["count"] == 2 assert releases["latest_release_id"] == release_id assert [item["release_id"] for item in releases["releases"]] == [ release_id, older_release_id, ] assert releases["releases"][1]["download_url"].endswith( f"/{older_release_id}/download" ) request = urllib.request.Request( f"{base_url}/api/firmware-workbench/releases/{release_id}/download", headers={"Range": "bytes=8-31"}, ) with urllib.request.urlopen(request, timeout=3) as response: assert response.status == 206 assert response.headers["Content-Range"] == f"bytes 8-31/{len(package)}" assert response.headers["X-Firmware-SHA256"] == manifest["sha256"] assert response.read() == package[8:32] head = urllib.request.Request( f"{base_url}/api/firmware-workbench/releases/{release_id}/download", method="HEAD", ) with urllib.request.urlopen(head, timeout=3) as response: assert response.status == 200 assert int(response.headers["Content-Length"]) == len(package) assert response.read() == b"" finally: server.shutdown() server.server_close() thread.join(timeout=3)