升级 NUPACK 4.1 并支持混合材料设计

This commit is contained in:
Lihatoo 2026-08-20 16:33:16 +08:00
parent 5daa60a464
commit c6189d857d
33 changed files with 5830 additions and 466 deletions

View file

@ -3,7 +3,7 @@ import time
import unittest
from pathlib import Path
from service.account import AccountStore
from service.account import AccountStore, DEFAULT_TRASH_RETENTION_SECONDS
def user(user_id):
@ -48,7 +48,7 @@ class AccountStoreTest(unittest.TestCase):
self.assertEqual(self.store.list_jobs("alice")["total"], 1)
self.assertEqual(self.store.list_jobs("bob")["total"], 0)
def test_share_can_expire_and_be_disabled(self):
def test_share_can_expire_and_be_deleted(self):
self.store.create_job("job-2", self.alice, self.payload, status="success", result={"ok": True})
share = self.store.create_share("alice", "job-2")
resolved = self.store.resolve_share(share["share_id"])
@ -57,6 +57,7 @@ class AccountStoreTest(unittest.TestCase):
self.store.update_share("alice", share["share_id"], active=False)
self.assertIsNone(self.store.resolve_share(share["share_id"]))
self.assertIsNone(self.store.get_share_for_owner("alice", share["share_id"]))
self.assertIsNone(self.store.update_share("bob", share["share_id"], active=True))
expiring = self.store.create_share("alice", "job-2", expires_in=1)
@ -67,6 +68,13 @@ class AccountStoreTest(unittest.TestCase):
)
self.assertIsNone(self.store.resolve_share(expiring["share_id"]))
def test_create_share_reuses_existing_active_link(self):
self.store.create_job("job-share-once", self.alice, self.payload, status="success")
first = self.store.create_share("alice", "job-share-once", expires_in=604800)
second = self.store.create_share("alice", "job-share-once", expires_in=604800)
self.assertEqual(first["share_id"], second["share_id"])
self.assertEqual(len(self.store.list_shares("alice")), 1)
def test_running_job_can_be_shared_and_later_exposes_result(self):
self.store.create_job("job-running", self.alice, self.payload, status="running")
share = self.store.create_share("alice", "job-running")
@ -93,6 +101,86 @@ class AccountStoreTest(unittest.TestCase):
self.store.update_job("job-3", "canceled", error={"message": "canceled"})
self.assertTrue(self.store.delete_job("alice", "job-3"))
def test_deleted_job_moves_to_trash_and_deletes_shares(self):
self.assertEqual(self.store.trash_retention_seconds(), DEFAULT_TRASH_RETENTION_SECONDS)
self.store.create_job("job-trash", self.alice, self.payload, status="success", result={"ok": True})
share = self.store.create_share("alice", "job-trash")
self.assertTrue(self.store.delete_job("alice", "job-trash"))
self.assertIsNone(self.store.get_job("alice", "job-trash"))
self.assertEqual(self.store.list_jobs("alice")["total"], 0)
self.assertIsNone(self.store.resolve_share(share["share_id"]))
self.assertIsNone(self.store.get_share_for_owner("alice", share["share_id"]))
self.assertEqual(self.store.list_shares("alice"), [])
trash = self.store.list_all_jobs({"deleted": "1"})
self.assertEqual(trash["total"], 1)
self.assertEqual(trash["items"][0]["job_id"], "job-trash")
self.assertIsNotNone(trash["items"][0]["deleted_at"])
self.assertAlmostEqual(
trash["items"][0]["purge_after"] - trash["items"][0]["deleted_at"],
DEFAULT_TRASH_RETENTION_SECONDS,
delta=1,
)
def test_initialize_removes_legacy_shares_for_trashed_jobs(self):
self.store.create_job("job-legacy", self.alice, self.payload, status="success")
share = self.store.create_share("alice", "job-legacy")
with self.store._connect() as connection:
connection.execute(
"UPDATE jobs SET deleted_at=?,purge_after=? WHERE job_id=?",
(time.time(), time.time() + 86400, "job-legacy"),
)
self.store._initialized = False
self.store.initialize()
self.assertIsNone(self.store.get_share_for_owner("alice", share["share_id"]))
self.assertEqual(self.store.list_shares("alice"), [])
def test_restore_and_permanent_delete_complete_trash_lifecycle(self):
self.store.create_job("job-live", self.alice, self.payload, status="success")
self.assertFalse(self.store.permanently_delete_job("job-live"))
self.store.create_job("job-restore", self.alice, self.payload, status="success")
self.store.delete_job("alice", "job-restore")
self.assertTrue(self.store.restore_job("job-restore"))
self.assertIsNotNone(self.store.get_job("alice", "job-restore"))
self.assertEqual(self.store.list_all_jobs({"deleted": "1"})["total"], 0)
self.store.delete_job("alice", "job-restore")
self.assertTrue(self.store.permanently_delete_job("job-restore"))
self.assertFalse(self.store.permanently_delete_job("job-restore"))
self.assertIsNone(self.store.get_job("alice", "job-restore", include_deleted=True))
def test_retention_setting_and_expired_job_purge(self):
self.assertEqual(self.store.set_trash_retention_days(3.5), int(3.5 * 86400))
self.assertEqual(self.store.trash_retention_seconds(), int(3.5 * 86400))
with self.assertRaises(ValueError):
self.store.set_trash_retention_days(0)
self.store.create_job("job-expired", self.alice, self.payload, status="success")
self.store.delete_job("alice", "job-expired")
with self.store._connect() as connection:
connection.execute(
"UPDATE jobs SET purge_after=? WHERE job_id=?",
(time.time() - 1, "job-expired"),
)
self.assertEqual(self.store.purge_expired_jobs(), 1)
self.assertIsNone(self.store.get_job("alice", "job-expired", include_deleted=True))
def test_admin_listing_includes_all_users_and_supports_filters(self):
self.store.create_job("alice-job", self.alice, self.payload, status="running")
bob_payload = {**self.payload, "workflow": "design"}
self.store.create_job("bob-job", self.bob, bob_payload, status="success")
listing = self.store.list_all_jobs({"limit": 10})
self.assertEqual(listing["total"], 2)
self.assertEqual({item["user_id"] for item in listing["items"]}, {"alice", "bob"})
self.assertTrue(all("username" in item for item in listing["items"]))
filtered = self.store.list_all_jobs({"q": self.bob["email"], "workflow": "design"})
self.assertEqual(filtered["total"], 1)
self.assertEqual(filtered["items"][0]["job_id"], "bob-job")
if __name__ == "__main__":
unittest.main()