Initial server source import
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
import uuid
|
||||
from unittest import mock
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
APP_DIR = ROOT / 'app'
|
||||
sys.path.insert(0, str(APP_DIR))
|
||||
|
||||
import result_spool
|
||||
from result_spool import ResultSpool, SpoolCorruptionError
|
||||
from runtime_security import harden_private_file, private_file_ready
|
||||
|
||||
|
||||
def write_private_orphan(path, payload=b'orphaned-after-fsync'):
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
||||
if hasattr(os, 'O_BINARY'):
|
||||
flags |= os.O_BINARY
|
||||
descriptor = os.open(path, flags, 0o600)
|
||||
try:
|
||||
harden_private_file(path)
|
||||
with os.fdopen(descriptor, 'wb') as handle:
|
||||
descriptor = None
|
||||
handle.write(payload)
|
||||
handle.flush()
|
||||
os.fsync(handle.fileno())
|
||||
finally:
|
||||
if descriptor is not None:
|
||||
os.close(descriptor)
|
||||
|
||||
|
||||
class ResultSpoolTempRecoveryTests(unittest.TestCase):
|
||||
def make_spool(self, directory):
|
||||
return ResultSpool(directory, min_free_bytes=0)
|
||||
|
||||
def test_root_and_quarantine_orphans_are_durably_recovered_before_usage(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
spool = self.make_spool(os.path.join(temp_dir, 'spool'))
|
||||
event_id = str(uuid.uuid4())
|
||||
root_orphan = f'{spool._event_path(event_id)}.101.202.303.tmp'
|
||||
quarantine_final = os.path.join(
|
||||
spool.quarantine_directory,
|
||||
f'{event_id}.404.505.conflict.json',
|
||||
)
|
||||
quarantine_orphan = f'{quarantine_final}.606.707.808.tmp'
|
||||
write_private_orphan(root_orphan)
|
||||
write_private_orphan(quarantine_orphan)
|
||||
self.assertTrue(private_file_ready(root_orphan))
|
||||
self.assertTrue(private_file_ready(quarantine_orphan))
|
||||
|
||||
with mock.patch.object(
|
||||
result_spool, 'durable_unlink', wraps=result_spool.durable_unlink,
|
||||
) as durable_unlink:
|
||||
self.assertEqual(spool.pending_events(), [])
|
||||
|
||||
removed = {os.path.normcase(call.args[0]) for call in durable_unlink.call_args_list}
|
||||
self.assertEqual(
|
||||
removed,
|
||||
{os.path.normcase(root_orphan), os.path.normcase(quarantine_orphan)},
|
||||
)
|
||||
self.assertFalse(os.path.lexists(root_orphan))
|
||||
self.assertFalse(os.path.lexists(quarantine_orphan))
|
||||
|
||||
def test_reservation_orphan_is_recovered_before_reservation_load(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
spool = self.make_spool(os.path.join(temp_dir, 'spool'))
|
||||
reservation_id = spool.reserve_claims('owner', 1, 60)
|
||||
reservation_path = spool._reservation_path(reservation_id)
|
||||
orphan = f'{reservation_path}.101.202.303.tmp'
|
||||
write_private_orphan(orphan)
|
||||
|
||||
with mock.patch.object(
|
||||
result_spool, 'durable_unlink', wraps=result_spool.durable_unlink,
|
||||
) as durable_unlink:
|
||||
self.assertTrue(spool.assert_claims_allowed())
|
||||
|
||||
durable_unlink.assert_called_once_with(orphan)
|
||||
self.assertFalse(os.path.lexists(orphan))
|
||||
self.assertTrue(os.path.exists(reservation_path))
|
||||
|
||||
def test_full_event_capacity_still_has_recovery_headroom(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
spool = ResultSpool(
|
||||
os.path.join(temp_dir, 'spool'), max_events=1,
|
||||
max_event_bytes=1024 * 1024, max_total_bytes=1024 * 1024,
|
||||
min_free_bytes=0,
|
||||
)
|
||||
event_id = str(uuid.uuid4())
|
||||
spool.write_event({'version': 1, 'scan_event_id': event_id, 'result': {}})
|
||||
orphan = f'{spool._event_path(str(uuid.uuid4()))}.101.202.303.tmp'
|
||||
write_private_orphan(orphan)
|
||||
|
||||
events = spool.pending_events()
|
||||
|
||||
self.assertEqual([event.event_id for event in events], [event_id])
|
||||
self.assertFalse(os.path.lexists(orphan))
|
||||
|
||||
def test_invalid_prospective_final_name_is_left_and_blocks(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
spool = self.make_spool(os.path.join(temp_dir, 'spool'))
|
||||
unrelated = os.path.join(
|
||||
spool.reservation_directory,
|
||||
'not-an-internal-reservation.json.101.202.303.tmp',
|
||||
)
|
||||
write_private_orphan(unrelated)
|
||||
|
||||
with mock.patch.object(
|
||||
result_spool, 'durable_unlink', wraps=result_spool.durable_unlink,
|
||||
) as durable_unlink:
|
||||
with self.assertRaises(SpoolCorruptionError):
|
||||
spool.assert_claims_allowed()
|
||||
|
||||
durable_unlink.assert_not_called()
|
||||
self.assertTrue(os.path.exists(unrelated))
|
||||
|
||||
def test_matching_symlink_is_left_and_blocks_when_supported(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
spool = self.make_spool(os.path.join(temp_dir, 'spool'))
|
||||
external = os.path.join(temp_dir, 'external.txt')
|
||||
Path(external).write_text('external-data', encoding='ascii')
|
||||
link = f'{spool._event_path(str(uuid.uuid4()))}.101.202.303.tmp'
|
||||
try:
|
||||
os.symlink(external, link)
|
||||
except (OSError, NotImplementedError):
|
||||
self.skipTest('symlink creation is unavailable on this platform')
|
||||
|
||||
with mock.patch.object(
|
||||
result_spool, 'durable_unlink', wraps=result_spool.durable_unlink,
|
||||
) as durable_unlink:
|
||||
with self.assertRaises(SpoolCorruptionError):
|
||||
spool.pending_events()
|
||||
|
||||
durable_unlink.assert_not_called()
|
||||
self.assertTrue(os.path.lexists(link))
|
||||
self.assertEqual(Path(external).read_text(encoding='ascii'), 'external-data')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user