feat(ai-studio): separate image pool from task states

This commit is contained in:
chengma
2026-07-13 09:54:08 +08:00
parent 42fe233ed9
commit b1c050f633
9 changed files with 500 additions and 59 deletions
+95
View File
@@ -62,6 +62,11 @@ class ImageStudioTests(TempDirMixin, unittest.TestCase):
"deleted_at",
}.issubset(projects_columns)
)
jobs_columns = {
row["name"]
for row in conn.execute("PRAGMA table_info(image_studio_jobs)").fetchall()
}
self.assertIn("recovery_action", jobs_columns)
finally:
conn.close()
@@ -305,6 +310,7 @@ class ImageStudioTests(TempDirMixin, unittest.TestCase):
self.assertEqual("stable-task-key", job.task_key)
self.assertEqual("pending", job.status)
self.assertEqual("cmhub", job.provider)
self.assertEqual(image_studio.JOB_RECOVERY_REGENERATE, job.recovery_action)
submitted = image_studio.set_job_submitted(
job.id,
@@ -317,6 +323,7 @@ class ImageStudioTests(TempDirMixin, unittest.TestCase):
self.assertEqual("submitted", submitted.status)
self.assertEqual("cmhub-task-1", submitted.task_id)
self.assertEqual(2, submitted.points_cost)
self.assertEqual(image_studio.JOB_RECOVERY_RESUME, submitted.recovery_action)
self.assertEqual([job.id], [item.id for item in image_studio.list_resumable_jobs(path=db_path)])
running = image_studio.update_job_status(
@@ -346,9 +353,30 @@ class ImageStudioTests(TempDirMixin, unittest.TestCase):
self.assertEqual("succeeded", succeeded.status)
self.assertEqual(output_asset.id, succeeded.output_asset_id)
self.assertEqual(96, succeeded.points_balance)
self.assertEqual(image_studio.JOB_RECOVERY_NONE, succeeded.recovery_action)
self.assertIsNotNone(succeeded.finished_at)
self.assertEqual([], image_studio.list_resumable_jobs(path=db_path))
terminal = image_studio.create_job(
project.id,
source_asset_id=source_asset.id,
task_key="terminal-task-key",
path=db_path,
)
image_studio.set_job_submitted(terminal.id, "cmhub-terminal", path=db_path)
terminal = image_studio.update_job_status(
terminal.id,
"failed",
error="上游生成失败",
recovery_action=image_studio.JOB_RECOVERY_REGENERATE,
path=db_path,
)
self.assertEqual(image_studio.JOB_RECOVERY_REGENERATE, terminal.recovery_action)
self.assertEqual(
[],
image_studio.list_resumable_jobs(path=db_path, include_failed_downloads=True),
)
with self.assertRaises(db.DbError):
image_studio.create_job(
project.id,
@@ -358,6 +386,73 @@ class ImageStudioTests(TempDirMixin, unittest.TestCase):
self.assert_removed(temp_dir)
def test_init_db_migrates_legacy_image_studio_job_recovery_action(self):
with self.make_temp_dir() as temp_dir:
db_path = os.path.join(temp_dir, "legacy.db")
conn = db.connect(db_path)
try:
conn.execute(
"""
CREATE TABLE image_studio_jobs (
id INTEGER PRIMARY KEY,
project_id INTEGER NOT NULL,
source_asset_id INTEGER,
output_asset_id INTEGER,
generation_source TEXT NOT NULL DEFAULT 'cmhub',
provider TEXT NOT NULL DEFAULT 'cmhub',
job_type TEXT NOT NULL,
task_key TEXT NOT NULL UNIQUE,
task_id TEXT,
status TEXT NOT NULL DEFAULT 'pending',
prompt TEXT,
error TEXT,
attempts INTEGER NOT NULL DEFAULT 0,
points_cost INTEGER,
points_balance INTEGER,
call_id TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
submitted_at TEXT,
finished_at TEXT
)
"""
)
conn.executemany(
"""
INSERT INTO image_studio_jobs
(id, project_id, job_type, task_key, task_id, status, created_at, updated_at)
VALUES (?, 1, 'main', ?, ?, ?, '2026-07-13T00:00:00', '2026-07-13T00:00:00')
""",
[
(1, "legacy-submitted", "task-submitted", "submitted"),
(2, "legacy-running", "task-running", "running"),
(3, "legacy-failed", "task-failed", "failed"),
(4, "legacy-success", "task-success", "succeeded"),
],
)
conn.commit()
finally:
conn.close()
db.init_db(db_path)
conn = db.connect(db_path)
try:
recovery_actions = {
row["task_key"]: row["recovery_action"]
for row in conn.execute(
"SELECT task_key, recovery_action FROM image_studio_jobs ORDER BY id"
).fetchall()
}
finally:
conn.close()
self.assertEqual(image_studio.JOB_RECOVERY_RESUME, recovery_actions["legacy-submitted"])
self.assertEqual(image_studio.JOB_RECOVERY_RESUME, recovery_actions["legacy-running"])
self.assertEqual(image_studio.JOB_RECOVERY_REGENERATE, recovery_actions["legacy-failed"])
self.assertEqual(image_studio.JOB_RECOVERY_NONE, recovery_actions["legacy-success"])
self.assert_removed(temp_dir)
def test_selections_are_consecutive_unique_and_replaceable(self):
with self.make_temp_dir() as temp_dir:
db_path = os.path.join(temp_dir, "cmshopee.db")