test(history): guard the newest-pair route and record D45/D46

TESTING.md lists the Prompt history page's spec decisions (D24-D26) and
its new deviations D45 and D46 beside D23 and D40-D44, notes under D46
that production opens on the newest lane while the concept opens on its
baseline, adds GET /prompt_manager/library/{positive_id}/history/pairs/
{pair_id}/newest to the history route list with its guards, and documents
the detail variants' run_count.

Tests: the newest-pair read skips a stale reference_epoch row, stops at
16 stale-hint candidates (17 newer stale rows hide an older valid run and
report {run: null}), and rejects a negative-only or deleted positive; the
route answers 404 for an ineligible positive, 503 storage-unavailable for
storage failures without leaking details, and 409 history-changed for a
restore during the snapshot.
This commit is contained in:
Vito Sansevero
2026-10-21 01:12:20 -07:00
parent 76eab9de4f
commit 59e2b224d3
3 changed files with 156 additions and 1 deletions
+50
View File
@@ -309,6 +309,56 @@ class HistoryAPITests(ImageAPITestCase):
response = await self.client.get(f"{base}/{pair}/newest", params={"x": "1"})
self.assert_error(response, await response.json(), 400, "invalid-request")
async def test_newest_pair_run_route_reports_storage_and_epoch_failures(self):
pair = self.api.db.save_prompt_pair("positive", "negative")
saved = self.api.db.get_prompt_pair(pair)
self.capture(0, 100)
url = (
f"/prompt_manager/library/{saved['positive_prompt_id']}"
f"/history/pairs/{pair}/newest"
)
# A prompt saved only as a negative is not a Library positive.
response = await self.client.get(
f"/prompt_manager/library/{saved['negative_prompt_id']}"
f"/history/pairs/{pair}/newest"
)
self.assert_error(response, await response.json(), 404, "not-found")
private = "SECRET_PROMPT /private/data/secret.db"
for exc in (sqlite3.OperationalError("busy " + private), OSError(private)):
with patch.object(self.api.db, "get_newest_pair_run", side_effect=exc):
with self.assertLogs("prompt_manager.api", "WARNING") as logs:
response = await self.client.get(url)
body = await response.json()
self.assert_error(
response, body, 503, "storage-unavailable", {"action": "retry-read"}
)
self.assertNotIn(private, json.dumps(body) + " ".join(logs.output))
source = str(self.comfy_dir / "source.db")
self.assertTrue(self.api.db.model.backup_database(source))
writer = PromptDatabase(self.api.db.model.db_path)
original = self.api.db.get_newest_pair_run
def during_snapshot(*args, **kwargs):
result = original(*args, **kwargs)
with patch("database.execution_history.notify_history_restore"):
writer.model.restore_from_file(source)
return result
try:
with patch.object(
self.api.db, "get_newest_pair_run", side_effect=during_snapshot
):
response = await self.client.get(url)
self.assert_error(
response,
await response.json(),
409,
"history-changed",
{"action": "refresh-history"},
)
finally:
writer.close_all()
async def test_nullable_frozen_fields_and_every_recorded_time_remain_exact(self):
start = make_start(
SESSION,
+45
View File
@@ -944,3 +944,48 @@ class HistoryReadTests(unittest.TestCase):
self.assertEqual(db.get_newest_pair_run(positive, recreated), {"run": None})
with self.assertRaises(sqlite3.ProgrammingError):
db.get_newest_pair_run(positive, pair_a)
def test_newest_pair_run_skips_stale_epoch_and_bounds_stale_hints(self):
db = self.database()
pair_a = db.save_prompt_pair("positive", "A")
pair_b = db.save_prompt_pair("positive", "B")
positive = db.get_prompt_pair(pair_a)["positive_prompt_id"]
older = self.capture(db, 0, bundle("A"), time=100)
stale_epoch = self.capture(db, 1, bundle("A"), time=200)
conn = db.model.get_connection()
# A row whose reference epoch is not the current one is never followed.
conn.execute(
"UPDATE execution_run_contexts SET reference_epoch=? WHERE run_id=?",
("00000000-0000-4000-8000-000000000000", stale_epoch.run_id),
)
conn.commit()
with db.recorded_history_snapshot():
self.assertEqual(
db.get_newest_pair_run(positive, pair_a)["run"]["run_id"],
older.run_id,
)
# Seventeen newer rows whose hint names pair A but whose texts resolve to
# pair B fill the candidate bound, so the older valid run is not reached.
for item in range(2, 19):
start = self.capture(db, item, bundle("B"), time=300 + item)
conn.execute(
"UPDATE execution_run_contexts SET pair_id=? WHERE run_id=?",
(pair_a, start.run_id),
)
conn.commit()
with db.recorded_history_snapshot():
self.assertEqual(db.get_newest_pair_run(positive, pair_a), {"run": None})
self.assertIsNotNone(db.get_newest_pair_run(positive, pair_b))
def test_newest_pair_run_rejects_an_ineligible_or_deleted_positive(self):
db = self.database()
pair = db.save_prompt_pair("positive", "negative")
saved = db.get_prompt_pair(pair)
self.capture(db, 0, bundle("negative"), time=100)
with db.recorded_history_snapshot():
# A prompt saved only as a pair's negative is not a Library positive.
self.assertIsNone(db.get_newest_pair_run(saved["negative_prompt_id"], pair))
db.delete_prompt_pair(pair)
db.delete_prompt(saved["positive_prompt_id"])
with db.recorded_history_snapshot():
self.assertIsNone(db.get_newest_pair_run(saved["positive_prompt_id"], pair))