fix(recovery): support batched SQLite blob writes
This commit is contained in:
@@ -115,9 +115,10 @@ class StorageRecoveryTests(unittest.TestCase):
|
||||
|
||||
def put_bytes(backend_self, key, data, *, content_type=None):
|
||||
with self.Session() as evidence_session:
|
||||
operation = evidence_session.query(RecoveryOperation).one()
|
||||
operation = evidence_session.query(RecoveryOperation).one_or_none()
|
||||
observed_running_operation.append(
|
||||
operation.status == RecoveryStatus.RUNNING.value
|
||||
operation is not None
|
||||
and operation.status == RecoveryStatus.RUNNING.value
|
||||
and len(operation.request_sha256) == 64
|
||||
)
|
||||
put_bytes(key, data, content_type=content_type)
|
||||
@@ -140,7 +141,14 @@ class StorageRecoveryTests(unittest.TestCase):
|
||||
|
||||
operation = self._only_operation()
|
||||
self.assertEqual(RecoveryStatus.SUCCEEDED.value, operation.status)
|
||||
self.assertEqual([True], observed_running_operation)
|
||||
# SQLite uses an explicit caller-transaction mode because it permits
|
||||
# only one writer. The intent becomes visible with the business commit;
|
||||
# PostgreSQL retains the independent pre-effect commit guarantee.
|
||||
self.assertEqual([False], observed_running_operation)
|
||||
self.assertEqual(
|
||||
"sqlite_caller_transaction",
|
||||
operation.metadata_["durability_mode"],
|
||||
)
|
||||
self.assertTrue(self.backend.exists(blob.storage_key))
|
||||
self.assertNotIn("private-name", blob.storage_key)
|
||||
self.assertEqual(".blob", Path(blob.storage_key).suffix)
|
||||
@@ -164,10 +172,54 @@ class StorageRecoveryTests(unittest.TestCase):
|
||||
|
||||
operation = self._only_operation()
|
||||
self.assertEqual(RecoveryStatus.RECOVERED.value, operation.status)
|
||||
self.assertEqual(
|
||||
"sqlite_post_rollback_reconstruction",
|
||||
operation.metadata_["durability_mode"],
|
||||
)
|
||||
self.assertFalse(self.backend.exists(storage_key))
|
||||
with self.Session() as evidence_session:
|
||||
self.assertIsNone(evidence_session.get(FileBlob, blob.id))
|
||||
|
||||
def test_multiple_blob_writes_share_one_sqlite_business_transaction(self) -> None:
|
||||
with patch(
|
||||
"govoplan_files.backend.storage.files.get_storage_backend",
|
||||
return_value=self.backend,
|
||||
):
|
||||
first = _get_or_create_blob(
|
||||
self.session,
|
||||
tenant_id=TENANT_ID,
|
||||
data=b"first archive member",
|
||||
filename="first.txt",
|
||||
content_type="text/plain",
|
||||
actor_id=USER_ID,
|
||||
)
|
||||
second = _get_or_create_blob(
|
||||
self.session,
|
||||
tenant_id=TENANT_ID,
|
||||
data=b"second archive member",
|
||||
filename="second.txt",
|
||||
content_type="text/plain",
|
||||
actor_id=USER_ID,
|
||||
)
|
||||
self.session.commit()
|
||||
|
||||
self.assertTrue(self.backend.exists(first.storage_key))
|
||||
self.assertTrue(self.backend.exists(second.storage_key))
|
||||
with self.Session() as evidence_session:
|
||||
operations = evidence_session.query(RecoveryOperation).all()
|
||||
self.assertEqual(2, len(operations))
|
||||
self.assertEqual(
|
||||
{RecoveryStatus.SUCCEEDED.value},
|
||||
{operation.status for operation in operations},
|
||||
)
|
||||
self.assertEqual(
|
||||
{"sqlite_caller_transaction"},
|
||||
{
|
||||
operation.metadata_["durability_mode"]
|
||||
for operation in operations
|
||||
},
|
||||
)
|
||||
|
||||
def test_post_write_tamper_is_quarantined_and_recovery_required(self) -> None:
|
||||
with patch(
|
||||
"govoplan_files.backend.storage.files.get_storage_backend",
|
||||
|
||||
Reference in New Issue
Block a user