Files
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

157 lines
5.9 KiB
Python

"""Router-level tests for /api/v1/model_relationships.
Covers:
- Auth gating (CurrentUserOrDefault on read/batch, AdminUserOrDefault on add/remove).
- Bug regression: self-relationship checks must return 400 (not 500 — the previous
broad `except Exception` swallowed the HTTPException and converted it).
- Service exception mapping: ValueError → 409 on add, 404 on remove.
"""
from typing import Any
from unittest.mock import MagicMock
import pytest
from fastapi import status
from fastapi.testclient import TestClient
from invokeai.app.services.invoker import Invoker
REQ_BODY = {
"model_key_1": "aa3b247f-90c9-4416-bfcd-aeaa57a5339e",
"model_key_2": "ac32b914-10ab-496e-a24a-3068724b9c35",
}
# ----------------------------- Auth gating -----------------------------
@pytest.mark.parametrize(
("method", "path", "body"),
[
("GET", "/api/v1/model_relationships/i/some-key", None),
("POST", "/api/v1/model_relationships/", REQ_BODY),
("DELETE", "/api/v1/model_relationships/", REQ_BODY),
("POST", "/api/v1/model_relationships/batch", {"model_keys": ["a", "b"]}),
],
)
def test_routes_require_auth(enable_multiuser: Any, client: TestClient, method: str, path: str, body: dict | None):
r = client.request(method, path, json=body)
assert r.status_code == status.HTTP_401_UNAUTHORIZED
def test_get_related_models_allowed_for_regular_user(client: TestClient, user1_token: str, mock_invoker: Invoker):
mock_invoker.services.model_relationships.get_related_model_keys = MagicMock(return_value=["k1"])
r = client.get(
"/api/v1/model_relationships/i/some-key",
headers={"Authorization": f"Bearer {user1_token}"},
)
assert r.status_code == status.HTTP_200_OK
assert r.json() == ["k1"]
def test_batch_allowed_for_regular_user(client: TestClient, user1_token: str, mock_invoker: Invoker):
mock_invoker.services.model_relationships.get_related_model_keys = MagicMock(side_effect=lambda k: [f"r-{k}"])
r = client.post(
"/api/v1/model_relationships/batch",
json={"model_keys": ["a", "b"]},
headers={"Authorization": f"Bearer {user1_token}"},
)
assert r.status_code == status.HTTP_200_OK
assert set(r.json()) == {"r-a", "r-b"}
def test_add_forbidden_for_regular_user(client: TestClient, user1_token: str, mock_invoker: Invoker):
r = client.post(
"/api/v1/model_relationships/",
json=REQ_BODY,
headers={"Authorization": f"Bearer {user1_token}"},
)
assert r.status_code == status.HTTP_403_FORBIDDEN
mock_invoker.services.model_relationships.add_model_relationship.assert_not_called()
def test_remove_forbidden_for_regular_user(client: TestClient, user1_token: str, mock_invoker: Invoker):
r = client.request(
"DELETE",
"/api/v1/model_relationships/",
json=REQ_BODY,
headers={"Authorization": f"Bearer {user1_token}"},
)
assert r.status_code == status.HTTP_403_FORBIDDEN
mock_invoker.services.model_relationships.remove_model_relationship.assert_not_called()
def test_add_allowed_for_admin(client: TestClient, admin_token: str, mock_invoker: Invoker):
r = client.post(
"/api/v1/model_relationships/",
json=REQ_BODY,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert r.status_code == status.HTTP_204_NO_CONTENT
mock_invoker.services.model_relationships.add_model_relationship.assert_called_once()
def test_remove_allowed_for_admin(client: TestClient, admin_token: str, mock_invoker: Invoker):
r = client.request(
"DELETE",
"/api/v1/model_relationships/",
json=REQ_BODY,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert r.status_code == status.HTTP_204_NO_CONTENT
mock_invoker.services.model_relationships.remove_model_relationship.assert_called_once()
# ----------------------------- Bug A regression: self-relationship → 400 -----------------------------
def test_add_self_relationship_returns_400_not_500(client: TestClient, admin_token: str, mock_invoker: Invoker):
"""Before the fix, the inner HTTPException(400) was caught by `except Exception`
and re-raised as 500."""
r = client.post(
"/api/v1/model_relationships/",
json={"model_key_1": "same-key", "model_key_2": "same-key"},
headers={"Authorization": f"Bearer {admin_token}"},
)
assert r.status_code == status.HTTP_400_BAD_REQUEST
mock_invoker.services.model_relationships.add_model_relationship.assert_not_called()
def test_remove_self_relationship_returns_400_not_500(client: TestClient, admin_token: str, mock_invoker: Invoker):
r = client.request(
"DELETE",
"/api/v1/model_relationships/",
json={"model_key_1": "same-key", "model_key_2": "same-key"},
headers={"Authorization": f"Bearer {admin_token}"},
)
assert r.status_code == status.HTTP_400_BAD_REQUEST
mock_invoker.services.model_relationships.remove_model_relationship.assert_not_called()
# ----------------------------- Service exception mapping -----------------------------
def test_add_value_error_returns_409(client: TestClient, admin_token: str, mock_invoker: Invoker):
mock_invoker.services.model_relationships.add_model_relationship = MagicMock(
side_effect=ValueError("relationship already exists")
)
r = client.post(
"/api/v1/model_relationships/",
json=REQ_BODY,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert r.status_code == status.HTTP_409_CONFLICT
def test_remove_value_error_returns_404(client: TestClient, admin_token: str, mock_invoker: Invoker):
mock_invoker.services.model_relationships.remove_model_relationship = MagicMock(
side_effect=ValueError("relationship not found")
)
r = client.request(
"DELETE",
"/api/v1/model_relationships/",
json=REQ_BODY,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert r.status_code == status.HTTP_404_NOT_FOUND