A product module first-imported while run_around_tests' autouse
monkeypatch of gns3server.utils.path.get_default_project_directory is
active (e.g. 'from gns3server.api.server import app' inside a test body)
binds the patched lambda into its own namespace forever: module-level
from-imports capture the object by value. Once the patch is reverted,
that module keeps calling the stale lambda, which closed over the very
test's tmppath — a directory deleted at that test's teardown. Every
later test hitting psutil.disk_usage(get_default_project_directory())
then fails with FileNotFoundError, but only when the importing test runs
before the API test files (full-suite collection order hides it).
The replacement now resolves Config.instance().settings at call time
instead of closing over the tmppath, so a frozen reference stays
correct; the test that triggered this imports gns3_app at module top
so no product import ever happens inside a patched window.
Also pins pytest-random-order (inert without --random-order) and
documents the order-independence rules for new tests in the
gns3-api-test-writing skill, including the known-red legacy files
whose tests share rows sequentially.
Two exception paths could permanently kill the compute notification
chain (no more compute.updated events, no reconnection until a server
restart):
- connect() only caught ComputeError, but _run_http_query translates
HTTP status errors (401/403/404/...) into sibling ControllerError
subclasses (and a raw fastapi HTTPException for unexpected statuses).
Those escaped the fire-and-forget connect() task started at controller
startup and died silently. Now they notify clients, schedule an
exponential-backoff retry, and still re-raise for explicit callers.
The dead web.HTTP* except branches (never reached since
_run_http_query converts HTTP errors itself) are removed.
- _connect_notification() only caught aiohttp.ClientError. A malformed
frame (e.g. missing 'action') or any error raised while dispatching a
compute event (e.g. a pydantic ValidationError in
node.parse_node_response) escaped the task, skipped the reconnect
scheduling placed after the try block, and killed the stream forever.
Now any exception is logged with its traceback (the gather() future
holding it was never retrieved, so nothing was ever printed) and the
reconnect scheduling + final compute.updated emit live in the finally
block so every exit path recovers.
Also moves the usage-stats reset before the disconnect log line so the
emitted compute.updated snapshot is consistent.
- Stream file GET/POST through controller without buffering in memory
- Add recursive and subdirectory filtering to node file listing
- Replace file extension with magic-based file type detection
- Add DELETE endpoint for node and project files
- Include directories in listing response
- Add params and stream support to http_query
- Fix lambda closures, streamer exception scope, and delete error codes