mirror of
https://git.datalinker.icu/comfyanonymous/ComfyUI
synced 2025-12-11 15:04:25 +08:00
179 lines
6.3 KiB
Python
179 lines
6.3 KiB
Python
import uuid
|
|
import aiohttp
|
|
import pytest
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_from_hash_success(
|
|
http: aiohttp.ClientSession, api_base: str, seeded_asset: dict
|
|
):
|
|
h = seeded_asset["asset_hash"]
|
|
payload = {
|
|
"hash": h,
|
|
"name": "from_hash_ok.safetensors",
|
|
"tags": ["models", "checkpoints", "unit-tests", "from-hash"],
|
|
"user_metadata": {"k": "v"},
|
|
}
|
|
async with http.post(f"{api_base}/api/assets/from-hash", json=payload) as r1:
|
|
b1 = await r1.json()
|
|
assert r1.status == 201, b1
|
|
assert b1["asset_hash"] == h
|
|
assert b1["created_new"] is False
|
|
aid = b1["id"]
|
|
|
|
# Calling again with the same name should return the same AssetInfo id
|
|
async with http.post(f"{api_base}/api/assets/from-hash", json=payload) as r2:
|
|
b2 = await r2.json()
|
|
assert r2.status == 201, b2
|
|
assert b2["id"] == aid
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_and_delete_asset(http: aiohttp.ClientSession, api_base: str, seeded_asset: dict):
|
|
aid = seeded_asset["id"]
|
|
|
|
# GET detail
|
|
async with http.get(f"{api_base}/api/assets/{aid}") as rg:
|
|
detail = await rg.json()
|
|
assert rg.status == 200, detail
|
|
assert detail["id"] == aid
|
|
assert "user_metadata" in detail
|
|
assert "filename" in detail["user_metadata"]
|
|
|
|
# DELETE
|
|
async with http.delete(f"{api_base}/api/assets/{aid}") as rd:
|
|
assert rd.status == 204
|
|
|
|
# GET again -> 404
|
|
async with http.get(f"{api_base}/api/assets/{aid}") as rg2:
|
|
body = await rg2.json()
|
|
assert rg2.status == 404
|
|
assert body["error"]["code"] == "ASSET_NOT_FOUND"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_upon_reference_count(
|
|
http: aiohttp.ClientSession, api_base: str, seeded_asset: dict
|
|
):
|
|
# Create a second reference to the same asset via from-hash
|
|
src_hash = seeded_asset["asset_hash"]
|
|
payload = {
|
|
"hash": src_hash,
|
|
"name": "unit_ref_copy.safetensors",
|
|
"tags": ["models", "checkpoints", "unit-tests", "del-flow"],
|
|
"user_metadata": {"note": "copy"},
|
|
}
|
|
async with http.post(f"{api_base}/api/assets/from-hash", json=payload) as r2:
|
|
copy = await r2.json()
|
|
assert r2.status == 201, copy
|
|
assert copy["asset_hash"] == src_hash
|
|
assert copy["created_new"] is False
|
|
|
|
# Delete original reference -> asset identity must remain
|
|
aid1 = seeded_asset["id"]
|
|
async with http.delete(f"{api_base}/api/assets/{aid1}") as rd1:
|
|
assert rd1.status == 204
|
|
|
|
async with http.head(f"{api_base}/api/assets/hash/{src_hash}") as rh1:
|
|
assert rh1.status == 200 # identity still present
|
|
|
|
# Delete the last reference with default semantics -> identity and cached files removed
|
|
aid2 = copy["id"]
|
|
async with http.delete(f"{api_base}/api/assets/{aid2}") as rd2:
|
|
assert rd2.status == 204
|
|
|
|
async with http.head(f"{api_base}/api/assets/hash/{src_hash}") as rh2:
|
|
assert rh2.status == 404 # orphan content removed
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_asset_fields(http: aiohttp.ClientSession, api_base: str, seeded_asset: dict):
|
|
aid = seeded_asset["id"]
|
|
|
|
payload = {
|
|
"name": "unit_1_renamed.safetensors",
|
|
"tags": ["models", "checkpoints", "unit-tests", "beta"],
|
|
"user_metadata": {"purpose": "updated", "epoch": 2},
|
|
}
|
|
async with http.put(f"{api_base}/api/assets/{aid}", json=payload) as ru:
|
|
body = await ru.json()
|
|
assert ru.status == 200, body
|
|
assert body["name"] == payload["name"]
|
|
assert "beta" in body["tags"]
|
|
assert body["user_metadata"]["purpose"] == "updated"
|
|
# filename should still be present and normalized by server
|
|
assert "filename" in body["user_metadata"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_head_asset_by_hash(http: aiohttp.ClientSession, api_base: str, seeded_asset: dict):
|
|
h = seeded_asset["asset_hash"]
|
|
|
|
# Existing
|
|
async with http.head(f"{api_base}/api/assets/hash/{h}") as rh1:
|
|
assert rh1.status == 200
|
|
|
|
# Non-existent
|
|
async with http.head(f"{api_base}/api/assets/hash/blake3:{'0'*64}") as rh2:
|
|
assert rh2.status == 404
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_head_asset_bad_hash_returns_400_and_no_body(http: aiohttp.ClientSession, api_base: str):
|
|
# Invalid format; handler returns a JSON error, but HEAD responses must not carry a payload.
|
|
# aiohttp exposes an empty body for HEAD, so validate status and that there is no payload.
|
|
async with http.head(f"{api_base}/api/assets/hash/not_a_hash") as rh:
|
|
assert rh.status == 400
|
|
body = await rh.read()
|
|
assert body == b""
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_nonexistent_returns_404(http: aiohttp.ClientSession, api_base: str):
|
|
bogus = str(uuid.uuid4())
|
|
async with http.delete(f"{api_base}/api/assets/{bogus}") as r:
|
|
body = await r.json()
|
|
assert r.status == 404
|
|
assert body["error"]["code"] == "ASSET_NOT_FOUND"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_from_hash_invalids(http: aiohttp.ClientSession, api_base: str):
|
|
# Bad hash algorithm
|
|
bad = {
|
|
"hash": "sha256:" + "0" * 64,
|
|
"name": "x.bin",
|
|
"tags": ["models", "checkpoints", "unit-tests"],
|
|
}
|
|
async with http.post(f"{api_base}/api/assets/from-hash", json=bad) as r1:
|
|
b1 = await r1.json()
|
|
assert r1.status == 400
|
|
assert b1["error"]["code"] == "INVALID_BODY"
|
|
|
|
# Invalid JSON body
|
|
async with http.post(f"{api_base}/api/assets/from-hash", data=b"{not json}") as r2:
|
|
b2 = await r2.json()
|
|
assert r2.status == 400
|
|
assert b2["error"]["code"] == "INVALID_JSON"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_update_download_bad_ids(http: aiohttp.ClientSession, api_base: str):
|
|
# All endpoints should be not found, as we UUID regex directly in the route definition.
|
|
bad_id = "not-a-uuid"
|
|
|
|
async with http.get(f"{api_base}/api/assets/{bad_id}") as r1:
|
|
assert r1.status == 404
|
|
|
|
async with http.get(f"{api_base}/api/assets/{bad_id}/content") as r3:
|
|
assert r3.status == 404
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_requires_at_least_one_field(http: aiohttp.ClientSession, api_base: str, seeded_asset: dict):
|
|
aid = seeded_asset["id"]
|
|
async with http.put(f"{api_base}/api/assets/{aid}", json={}) as r:
|
|
body = await r.json()
|
|
assert r.status == 400
|
|
assert body["error"]["code"] == "INVALID_BODY"
|