ComfyUI/tests-unit/assets_test/test_prompt_id_enforcement.py
Matt Miller 1dc8dd7e0c
Some checks failed
Python Linting / Run Ruff (push) Waiting to run
Python Linting / Run Pylint (push) Waiting to run
Build package / Build Test (3.10) (push) Has been cancelled
Build package / Build Test (3.11) (push) Has been cancelled
Build package / Build Test (3.12) (push) Has been cancelled
Build package / Build Test (3.13) (push) Has been cancelled
Build package / Build Test (3.14) (push) Has been cancelled
feat(prompt): enforce canonical UUID prompt_id at job creation
POST /prompt previously accepted any client-supplied prompt_id verbatim,
str()-coercing even non-strings, and minting the literal job id "None"
for an explicit JSON null. The new GET /api/assets job_ids filter matches
stored job ids as canonical UUIDs exactly, so a non-UUID id minted a job
whose assets could never be filtered.

- validate_job_id (comfy_execution/jobs.py): requires a string in the
  canonical lowercase hyphenated UUID form; raises ValueError otherwise,
  including parseable-but-non-canonical spellings (uppercase, braced, URN,
  bare hex), which would otherwise be silently rewritten and then miss
  every exact-match lookup downstream (history keys, websocket
  correlation, /interrupt, the assets job_ids filter).
- POST /prompt: absent or null prompt_id means the server mints uuid4;
  invalid means 400 invalid_prompt_id on the standard error envelope.
- openapi.yaml: document the request-side prompt_id (format uuid,
  nullable) on PromptRequest.
- tests: unit matrix for validate_job_id; integration tests against the
  booted server covering rejection, acceptance, and null handling.
2026-06-09 21:52:52 -07:00

70 lines
2.8 KiB
Python

"""POST /prompt enforces canonical-UUID job ids at creation time.
Lives in assets_test because it uses this suite's booted-server fixture and
because the invariant exists for the assets pipeline: the GET /api/assets
``job_ids`` filter matches stored job ids exactly, so a job minted with a
non-canonical id would produce assets the filter can never find.
The prompt bodies here are intentionally invalid workflows — prompt_id
validation happens before workflow validation, so a rejected id returns
``invalid_prompt_id`` while an accepted id falls through to the ordinary
workflow-validation error (proving it cleared the id check).
"""
import requests
def _post_prompt(http: requests.Session, api_base: str, body: dict) -> requests.Response:
return http.post(api_base + "/prompt", json=body, timeout=30)
def _error_type(r: requests.Response) -> str:
return r.json()["error"]["type"]
def test_non_uuid_prompt_id_rejected(http: requests.Session, api_base: str):
r = _post_prompt(http, api_base, {"prompt": {}, "prompt_id": "not-a-uuid"})
assert r.status_code == 400, r.text
assert _error_type(r) == "invalid_prompt_id"
def test_non_string_prompt_id_rejected(http: requests.Session, api_base: str):
# Previously str()-coerced (123 became the job id "123"); must now be a 400,
# not a 500 from uuid.UUID choking on a non-string.
r = _post_prompt(http, api_base, {"prompt": {}, "prompt_id": 123})
assert r.status_code == 400, r.text
assert _error_type(r) == "invalid_prompt_id"
def test_non_canonical_uuid_rejected(http: requests.Session, api_base: str):
# Parseable as a UUID, but not the canonical lowercase form: rejected
# loudly rather than silently rewritten (downstream lookups match the
# stored id exactly).
r = _post_prompt(
http,
api_base,
{"prompt": {}, "prompt_id": "AAAAAAAA-BBBB-4CCC-8DDD-EEEEEEEEEEEE"},
)
assert r.status_code == 400, r.text
assert _error_type(r) == "invalid_prompt_id"
def test_canonical_uuid_accepted(http: requests.Session, api_base: str):
# The id clears validation; the empty workflow then fails ordinary prompt
# validation, proving the request got past the id check.
r = _post_prompt(
http,
api_base,
{"prompt": {}, "prompt_id": "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee"},
)
assert r.status_code == 400, r.text
assert _error_type(r) != "invalid_prompt_id"
def test_null_prompt_id_not_rejected(http: requests.Session, api_base: str):
# Explicit null means "server generates" and must not be rejected as an
# invalid id. (The minted id itself is not observable here because the
# workflow is invalid; unit tests cover validate_job_id directly.)
r = _post_prompt(http, api_base, {"prompt": {}, "prompt_id": None})
assert r.status_code == 400, r.text
assert _error_type(r) != "invalid_prompt_id"