mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2025-12-18 02:23:06 +08:00
+2 tests for checking Asset downloading logic
This commit is contained in:
parent
37b81e6658
commit
cdd8d16075
@ -1,7 +1,12 @@
|
|||||||
|
import asyncio
|
||||||
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import pytest
|
import pytest
|
||||||
|
from conftest import trigger_sync_seed_assets
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@ -24,6 +29,73 @@ async def test_download_attachment_and_inline(http: aiohttp.ClientSession, api_b
|
|||||||
assert "inline" in cd2
|
assert "inline" in cd2
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.parametrize("root", ["input", "output"])
|
||||||
|
async def test_download_chooses_existing_state_and_updates_access_time(
|
||||||
|
root: str,
|
||||||
|
http: aiohttp.ClientSession,
|
||||||
|
api_base: str,
|
||||||
|
comfy_tmp_base_dir: Path,
|
||||||
|
asset_factory,
|
||||||
|
make_asset_bytes,
|
||||||
|
run_scan_and_wait,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Hashed asset with two state paths: if the first one disappears,
|
||||||
|
GET /content still serves from the remaining path and bumps last_access_time.
|
||||||
|
"""
|
||||||
|
scope = f"dl-first-{uuid.uuid4().hex[:6]}"
|
||||||
|
name = "first_existing_state.bin"
|
||||||
|
data = make_asset_bytes(name, 3072)
|
||||||
|
|
||||||
|
# Upload -> path1
|
||||||
|
a = await asset_factory(name, [root, "unit-tests", scope], {}, data)
|
||||||
|
aid = a["id"]
|
||||||
|
|
||||||
|
base = comfy_tmp_base_dir / root / "unit-tests" / scope
|
||||||
|
path1 = base / name
|
||||||
|
assert path1.exists()
|
||||||
|
|
||||||
|
# Seed path2 by copying, then scan to dedupe into a second state
|
||||||
|
path2 = base / "alt" / name
|
||||||
|
path2.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path2.write_bytes(data)
|
||||||
|
await trigger_sync_seed_assets(http, api_base)
|
||||||
|
await run_scan_and_wait(root)
|
||||||
|
|
||||||
|
# Remove path1 so server must fall back to path2
|
||||||
|
path1.unlink()
|
||||||
|
|
||||||
|
# last_access_time before
|
||||||
|
async with http.get(f"{api_base}/api/assets/{aid}") as rg0:
|
||||||
|
d0 = await rg0.json()
|
||||||
|
assert rg0.status == 200, d0
|
||||||
|
ts0 = d0.get("last_access_time")
|
||||||
|
|
||||||
|
await asyncio.sleep(0.05)
|
||||||
|
async with http.get(f"{api_base}/api/assets/{aid}/content") as r:
|
||||||
|
blob = await r.read()
|
||||||
|
assert r.status == 200
|
||||||
|
assert blob == data # must serve from the surviving state (same bytes)
|
||||||
|
|
||||||
|
async with http.get(f"{api_base}/api/assets/{aid}") as rg1:
|
||||||
|
d1 = await rg1.json()
|
||||||
|
assert rg1.status == 200, d1
|
||||||
|
ts1 = d1.get("last_access_time")
|
||||||
|
|
||||||
|
def _parse_iso8601(s: Optional[str]) -> Optional[float]:
|
||||||
|
if not s:
|
||||||
|
return None
|
||||||
|
s = s[:-1] if s.endswith("Z") else s
|
||||||
|
return datetime.fromisoformat(s).timestamp()
|
||||||
|
|
||||||
|
t0 = _parse_iso8601(ts0)
|
||||||
|
t1 = _parse_iso8601(ts1)
|
||||||
|
assert t1 is not None
|
||||||
|
if t0 is not None:
|
||||||
|
assert t1 > t0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize("seeded_asset", [{"tags": ["models", "checkpoints"]}], indirect=True)
|
@pytest.mark.parametrize("seeded_asset", [{"tags": ["models", "checkpoints"]}], indirect=True)
|
||||||
async def test_download_missing_file_returns_404(
|
async def test_download_missing_file_returns_404(
|
||||||
@ -49,3 +121,48 @@ async def test_download_missing_file_returns_404(
|
|||||||
# We created asset without the "unit-tests" tag(see `autoclean_unit_test_assets`), we need to clear it manually.
|
# We created asset without the "unit-tests" tag(see `autoclean_unit_test_assets`), we need to clear it manually.
|
||||||
async with http.delete(f"{api_base}/api/assets/{aid}") as dr:
|
async with http.delete(f"{api_base}/api/assets/{aid}") as dr:
|
||||||
await dr.read()
|
await dr.read()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.parametrize("root", ["input", "output"])
|
||||||
|
async def test_download_404_if_all_states_missing(
|
||||||
|
root: str,
|
||||||
|
http: aiohttp.ClientSession,
|
||||||
|
api_base: str,
|
||||||
|
comfy_tmp_base_dir: Path,
|
||||||
|
asset_factory,
|
||||||
|
make_asset_bytes,
|
||||||
|
run_scan_and_wait,
|
||||||
|
):
|
||||||
|
"""Multi-state asset: after the last remaining on-disk file is removed, download must return 404."""
|
||||||
|
scope = f"dl-404-{uuid.uuid4().hex[:6]}"
|
||||||
|
name = "missing_all_states.bin"
|
||||||
|
data = make_asset_bytes(name, 2048)
|
||||||
|
|
||||||
|
# Upload -> path1
|
||||||
|
a = await asset_factory(name, [root, "unit-tests", scope], {}, data)
|
||||||
|
aid = a["id"]
|
||||||
|
|
||||||
|
base = comfy_tmp_base_dir / root / "unit-tests" / scope
|
||||||
|
p1 = base / name
|
||||||
|
assert p1.exists()
|
||||||
|
|
||||||
|
# Seed a second state and dedupe
|
||||||
|
p2 = base / "copy" / name
|
||||||
|
p2.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
p2.write_bytes(data)
|
||||||
|
await trigger_sync_seed_assets(http, api_base)
|
||||||
|
await run_scan_and_wait(root)
|
||||||
|
|
||||||
|
# Remove first file -> download should still work via the second state
|
||||||
|
p1.unlink()
|
||||||
|
async with http.get(f"{api_base}/api/assets/{aid}/content") as ok1:
|
||||||
|
b1 = await ok1.read()
|
||||||
|
assert ok1.status == 200 and b1 == data
|
||||||
|
|
||||||
|
# Remove the last file -> download must 404
|
||||||
|
p2.unlink()
|
||||||
|
async with http.get(f"{api_base}/api/assets/{aid}/content") as r2:
|
||||||
|
body = await r2.json()
|
||||||
|
assert r2.status == 404
|
||||||
|
assert body["error"]["code"] == "FILE_NOT_FOUND"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user