* test(legacy): snapshot the routes and the settings response Pins every registered route and the key paths of GET /prompt_manager/settings while System S13 (#330) moves the legacy pages' tools to native System. Recorded on the unchanged main; cutover (#334) removes it. * test(legacy): prove each removed legacy control has native coverage TESTING.md gains the "Legacy System UI removed by S13" table (no rows yet). legacy-coverage.test.mjs fails when a removal cites a capability that is not covered, copies another native test, lists an id or method twice, or leaves the id or method on its page. legacy-system-links.spec.mjs holds the System link table and guards the kept admin flows. * refactor(admin): open System for Diagnostics and logs The admin page's Diagnostics and View Logs buttons become links that open System › Diagnostics & logs in a new tab. The legacy Diagnostics and System Logs dialogs and their methods go; TESTING.md lists them against their covered capabilities (rows B1–B7). * test(legacy): find a removed modal id passed to showModal and settle the links spec The removal check also finds an id passed as a string to a ...Modal() call and no longer reads data-id= as id=. The no-request test waits for the page's own start-up reads to finish, the sort check compares as the server sorts, and the kept-flow test checks the added prompt shows in the list.
137 lines
4.5 KiB
Python
137 lines
4.5 KiB
Python
"""The legacy pages' backend contract does not move under System S13 (#330).
|
|
|
|
S13 points the legacy admin and gallery buttons at native System and removes
|
|
their duplicate dialogs, changing no backend file. This snapshot pins what the
|
|
legacy pages talk to: every registered route (method and path) and the key
|
|
paths of ``GET /prompt_manager/settings``, the one route whose legacy request
|
|
body S13 shrinks. Record again only with a reason in the PR body:
|
|
|
|
PM_RECORD_LEGACY_SNAPSHOT=1 python3 -m unittest tests.test_legacy_contract_snapshot
|
|
|
|
Cutover (#334) removes this test and its fixture.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from tests.test_api_admin import AdminAPITestCase
|
|
except ImportError: # discovered with tests/ as the top-level directory
|
|
from test_api_admin import AdminAPITestCase
|
|
|
|
from py.config import GalleryConfig # noqa: E402
|
|
|
|
SNAPSHOT = (
|
|
Path(__file__).resolve().parent / "fixtures" / "legacy_contract_snapshot.json"
|
|
)
|
|
RECORD_ENV = "PM_RECORD_LEGACY_SNAPSHOT"
|
|
SETTINGS = "/prompt_manager/settings"
|
|
DEPTH = 2
|
|
CHANGED = (
|
|
"the legacy contract changed; if the change is intended (a new route, a new"
|
|
" settings key), record it with PM_RECORD_LEGACY_SNAPSHOT=1 and say why in"
|
|
" the PR body"
|
|
)
|
|
|
|
|
|
def key_paths(value, prefix="", depth=1):
|
|
"""The key paths of a JSON object to ``DEPTH`` levels.
|
|
|
|
``{"a": {"b": 1}}`` gives ``a`` and ``a.b``; the items of a list of
|
|
objects give the union of their keys as ``a[].b``.
|
|
"""
|
|
paths = set()
|
|
for key, child in value.items():
|
|
path = f"{prefix}{key}"
|
|
paths.add(path)
|
|
if depth >= DEPTH:
|
|
continue
|
|
if isinstance(child, dict):
|
|
paths |= key_paths(child, f"{path}.", depth + 1)
|
|
elif isinstance(child, list):
|
|
for item in child:
|
|
if isinstance(item, dict):
|
|
paths |= key_paths(item, f"{path}[].", depth + 1)
|
|
return paths
|
|
|
|
|
|
class TestLegacyContractSnapshot(AdminAPITestCase):
|
|
"""Routes and settings keys against tests/fixtures/legacy_contract_snapshot.json.
|
|
|
|
The base case isolates config.json (``PROMPT_MANAGER_CONFIG_PATH`` in the
|
|
temp tree), stubs ComfyUI's ``folder_paths`` and resets the gallery roots;
|
|
nothing here reads the maintainer's install.
|
|
"""
|
|
|
|
async def asyncSetUp(self):
|
|
await super().asyncSetUp()
|
|
self.api._get_all_output_dirs = lambda: [str(self.output_dir)]
|
|
GalleryConfig.MONITORING_DIRECTORIES = [str(self.output_dir)]
|
|
|
|
def _routes(self):
|
|
return sorted(
|
|
{
|
|
f"{route.method} {route.resource.canonical}"
|
|
for route in self.app.router.routes()
|
|
if route.method != "HEAD"
|
|
}
|
|
)
|
|
|
|
async def _settings_keys(self):
|
|
resp = await self.client.request("GET", SETTINGS)
|
|
body = await resp.json()
|
|
self.assertEqual(resp.status, 200, body)
|
|
return sorted(key_paths(body))
|
|
|
|
def _snapshot(self):
|
|
with open(SNAPSHOT, encoding="utf-8") as f:
|
|
return json.load(f)
|
|
|
|
def _record(self, key, value):
|
|
recorded = self._snapshot() if SNAPSHOT.exists() else {}
|
|
recorded[key] = value
|
|
with open(SNAPSHOT, "w", encoding="utf-8", newline="\n") as f:
|
|
json.dump(recorded, f, indent=2)
|
|
f.write("\n")
|
|
|
|
async def test_routes_match_the_snapshot(self):
|
|
routes = self._routes()
|
|
if os.environ.get(RECORD_ENV) == "1":
|
|
self._record("routes", routes)
|
|
self.assertGreater(len(routes), 50)
|
|
self.assertEqual(routes, self._snapshot()["routes"], CHANGED)
|
|
|
|
async def test_settings_response_keeps_its_keys(self):
|
|
keys = await self._settings_keys()
|
|
if os.environ.get(RECORD_ENV) == "1":
|
|
self._record("settings_keys", keys)
|
|
self.assertIn("settings.infinite_scroll", keys)
|
|
self.assertEqual(keys, self._snapshot()["settings_keys"], CHANGED)
|
|
|
|
|
|
class TestKeyPaths(unittest.TestCase):
|
|
def test_depth_and_list_items(self):
|
|
value = {
|
|
"success": True,
|
|
"settings": {"a": 1, "deep": {"hidden": 1}},
|
|
"rows": [{"path": "x"}, {"label": "y"}],
|
|
}
|
|
self.assertEqual(
|
|
sorted(key_paths(value)),
|
|
[
|
|
"rows",
|
|
"rows[].label",
|
|
"rows[].path",
|
|
"settings",
|
|
"settings.a",
|
|
"settings.deep",
|
|
"success",
|
|
],
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|