fix: serve every gallery video format, validate settings before applying, dedupe output dirs

Review findings on #153 (Codex):
- the image serve route allowed only three video types while the gallery
  lists seven, so .avi/.mkv/.m4v/.wmv cards opened to a 403; the serve
  allow-list now covers every gallery video format
- a settings payload with one invalid field still applied the valid ones
  before returning 400; the whole payload is validated first and nothing
  changes on failure. webui_display_mode is validated too

Third Windows CI run:
- the same output directory spelled twice (different case or a link) was
  listed twice in diagnostics and served roots; both dedupe on the
  resolved, case-folded path
- the root-less subfolder test expects the "/" form

Code-quality bot: index definitions are (name, target) pairs instead of
implicitly concatenated strings; redundant local asyncio imports and
two trivial lambdas in test stubs removed.
This commit is contained in:
Vito Sansevero
2026-10-08 18:39:46 -07:00
parent 8462ecbb03
commit 4c6dbc92fe
9 changed files with 146 additions and 70 deletions
+49 -6
View File
@@ -303,6 +303,23 @@ class TestNoAbsolutePathsInResponses(AdminAPITestCase):
[os.path.normcase(os.path.realpath(renders))],
)
@unittest.skipIf(sys.platform == "win32", "symlinks need privileges on Windows")
async def test_same_output_dir_spelled_twice_is_listed_once(self):
alias = self.comfy_dir / "alias"
os.symlink(self.output_dir, alias, target_is_directory=True)
# Two spellings of one directory (Windows adds case differences too)
GalleryConfig.MONITORING_DIRECTORIES = [str(alias), self._root(self.output_dir)]
self._install_monitor([self._root(self.output_dir)])
resp, body = await self._body("GET", "/prompt_manager/diagnostics")
self.assertEqual(resp.status, 200)
data = json.loads(body)
self.assertEqual(
data["diagnostics"]["comfyui_output"]["output_dirs"], ["output"]
)
self.assertEqual(len(self.api._get_all_output_dirs()), 1)
async def test_diagnostics_paths_are_relative(self):
GalleryConfig.MONITORING_DIRECTORIES = [self._root(self.output_dir)]
self._install_monitor([self._root(self.output_dir)])
@@ -882,8 +899,6 @@ class TestDuplicateJobsAreSingleFlight(AdminAPITestCase):
"""A duplicate scan or delete refuses to overlap with a running one."""
def _block_scan(self):
import asyncio
gate = asyncio.Event()
async def slow_scan():
@@ -894,8 +909,6 @@ class TestDuplicateJobsAreSingleFlight(AdminAPITestCase):
return gate
async def test_second_scan_while_one_runs_is_409(self):
import asyncio
gate = self._block_scan()
first = asyncio.ensure_future(
self.client.request("GET", "/prompt_manager/scan_duplicates")
@@ -914,8 +927,6 @@ class TestDuplicateJobsAreSingleFlight(AdminAPITestCase):
self.assertEqual((await first).status, 200)
async def test_delete_while_scan_runs_is_409_and_scan_after_is_fine(self):
import asyncio
gate = self._block_scan()
first = asyncio.ensure_future(
self.client.request("GET", "/prompt_manager/scan_duplicates")
@@ -948,6 +959,38 @@ class TestDuplicateJobsAreSingleFlight(AdminAPITestCase):
class TestSettingsMisc(AdminAPITestCase):
async def test_invalid_payload_changes_nothing(self):
before = (
PromptManagerConfig.RESULT_TIMEOUT,
PromptManagerConfig.WEBUI_DISPLAY_MODE,
PromptManagerConfig.WORKER_THREADS,
PromptManagerConfig.INFINITE_SCROLL,
list(GalleryConfig.MONITORING_DIRECTORIES),
)
resp = await self.client.request(
"POST",
"/prompt_manager/settings",
json={
"result_timeout": 42,
"webui_display_mode": "popup",
"worker_threads": 1,
"infinite_scroll": True,
"gallery_root_paths": [os.path.abspath(os.sep)], # rejected
},
)
self.assertEqual(resp.status, 400)
after = (
PromptManagerConfig.RESULT_TIMEOUT,
PromptManagerConfig.WEBUI_DISPLAY_MODE,
PromptManagerConfig.WORKER_THREADS,
PromptManagerConfig.INFINITE_SCROLL,
list(GalleryConfig.MONITORING_DIRECTORIES),
)
self.assertEqual(after, before)
self.assertFalse(os.path.exists(self.config_path))
async def test_infinite_scroll_default_is_reported_saved_and_validated(self):
data = await (
await self.client.request("GET", "/prompt_manager/settings")