Files
invoke-ai--invokeai/tests/app/routers/test_download_queue_router.py
wehub-resource-sync cddb07a176
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
build container image / cpu (push) Has been cancelled
build container image / cuda (push) Has been cancelled
build container image / rocm (push) Has been cancelled
frontend checks / frontend-checks (push) Has been cancelled
frontend tests / frontend-tests (push) Has been cancelled
lfs checks / lfs-check (push) Has been cancelled
python checks / python-checks (push) Has been cancelled
python tests / py3.12: macos-default (push) Has been cancelled
python tests / py3.11: windows-cpu (push) Has been cancelled
python tests / py3.12: windows-cpu (push) Has been cancelled
python tests / py3.11: linux-cpu (push) Has been cancelled
typegen checks / typegen-checks (push) Has been cancelled
uv lock checks / uv-lock-checks (push) Has been cancelled
openapi checks / openapi-checks (push) Has been cancelled
python tests / py3.11: macos-default (push) Has been cancelled
python tests / py3.12: linux-cpu (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:22:06 +08:00

113 lines
4.4 KiB
Python

"""Router-level tests for /api/v1/download_queue.
Covers:
- Auth gating (CurrentUserOrDefault on read/per-job, AdminUserOrDefault on prune & cancel-all).
- Bug regression: `dest` path validation must reject absolute paths and '..' segments
BEFORE the queue service is invoked.
"""
from typing import Any
from unittest.mock import MagicMock
import pytest
from fastapi import status
from fastapi.testclient import TestClient
from invokeai.app.services.download import DownloadJob
from invokeai.app.services.invoker import Invoker
def _make_job(id: int = 1) -> DownloadJob:
from pathlib import Path
return DownloadJob(id=id, source="http://example.com/file.bin", dest=Path("models/file.bin"))
# ----------------------------- Auth gating -----------------------------
@pytest.mark.parametrize(
("method", "path"),
[
("GET", "/api/v1/download_queue/"),
("PATCH", "/api/v1/download_queue/"),
("POST", "/api/v1/download_queue/i/"),
("GET", "/api/v1/download_queue/i/1"),
("DELETE", "/api/v1/download_queue/i/1"),
("DELETE", "/api/v1/download_queue/i"),
],
)
def test_routes_require_auth_in_multiuser_mode(enable_multiuser: Any, client: TestClient, method: str, path: str):
response = client.request(method, path, json={"source": "http://x/y", "dest": "models/x"})
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_list_downloads_as_regular_user(client: TestClient, user1_token: str, mock_invoker: Invoker):
mock_invoker.services.download_queue.list_jobs = MagicMock(return_value=[])
r = client.get("/api/v1/download_queue/", headers={"Authorization": f"Bearer {user1_token}"})
assert r.status_code == status.HTTP_200_OK
assert r.json() == []
def test_prune_downloads_forbidden_for_regular_user(client: TestClient, user1_token: str, mock_invoker: Invoker):
r = client.patch("/api/v1/download_queue/", headers={"Authorization": f"Bearer {user1_token}"})
assert r.status_code == status.HTTP_403_FORBIDDEN
mock_invoker.services.download_queue.prune_jobs.assert_not_called()
def test_prune_downloads_allowed_for_admin(client: TestClient, admin_token: str, mock_invoker: Invoker):
r = client.patch("/api/v1/download_queue/", headers={"Authorization": f"Bearer {admin_token}"})
assert r.status_code == status.HTTP_204_NO_CONTENT
mock_invoker.services.download_queue.prune_jobs.assert_called_once()
def test_cancel_all_forbidden_for_regular_user(client: TestClient, user1_token: str, mock_invoker: Invoker):
r = client.delete("/api/v1/download_queue/i", headers={"Authorization": f"Bearer {user1_token}"})
assert r.status_code == status.HTTP_403_FORBIDDEN
mock_invoker.services.download_queue.cancel_all_jobs.assert_not_called()
def test_cancel_all_allowed_for_admin(client: TestClient, admin_token: str, mock_invoker: Invoker):
r = client.delete("/api/v1/download_queue/i", headers={"Authorization": f"Bearer {admin_token}"})
assert r.status_code == status.HTTP_204_NO_CONTENT
mock_invoker.services.download_queue.cancel_all_jobs.assert_called_once()
# ----------------------------- Bug D regression: dest validation -----------------------------
@pytest.mark.parametrize(
"bad_dest",
[
"/etc/passwd",
"C:/Windows/System32",
"models/../../etc/passwd",
"..",
"",
" ",
],
)
def test_download_rejects_unsafe_dest_before_service_call(
client: TestClient, user1_token: str, mock_invoker: Invoker, bad_dest: str
):
"""Absolute paths, '..' segments, and empty strings must produce 400 and
must NOT invoke the download_queue service."""
r = client.post(
"/api/v1/download_queue/i/",
json={"source": "http://example.com/file.bin", "dest": bad_dest},
headers={"Authorization": f"Bearer {user1_token}"},
)
assert r.status_code == status.HTTP_400_BAD_REQUEST
mock_invoker.services.download_queue.download.assert_not_called()
def test_download_accepts_relative_dest(client: TestClient, user1_token: str, mock_invoker: Invoker):
mock_invoker.services.download_queue.download = MagicMock(return_value=_make_job())
r = client.post(
"/api/v1/download_queue/i/",
json={"source": "http://example.com/file.bin", "dest": "models/sd15.safetensors"},
headers={"Authorization": f"Bearer {user1_token}"},
)
assert r.status_code == status.HTTP_200_OK
mock_invoker.services.download_queue.download.assert_called_once()