Files
truf-server/tests/test_host_agent_reconcile.py
T
2026-09-30 20:30:56 +03:00

108 lines
3.7 KiB
Python

import json
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 = ROOT / 'app'
sys.path.insert(0, str(APP))
import host_agent_reconcile
class _Database:
def __init__(self, operation_id):
self.operation_id = operation_id
self.envelopes = []
def pending_runtime_agent_operations(self, limit):
return [{'operation_id': self.operation_id}]
def reconcile_runtime_operation_result(self, envelope):
self.envelopes.append(envelope)
return {'status': json.loads(envelope)['result']}
@unittest.skipIf(os.name == 'nt', 'POSIX ownership and mode checks required')
class HostAgentReconcileTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.directory = Path(self.temp.name) / 'results'
self.directory.mkdir()
os.chmod(self.directory, 0o750)
details = os.stat(self.directory)
self.patches = (
mock.patch.object(host_agent_reconcile, 'HOST_RESULT_DIRECTORY', self.directory),
mock.patch.object(host_agent_reconcile, 'HOST_ROOT_UID', details.st_uid),
mock.patch.object(host_agent_reconcile, 'HOST_RUNTIME_GID', details.st_gid),
)
for patch in self.patches:
patch.start()
def tearDown(self):
for patch in reversed(self.patches):
patch.stop()
self.temp.cleanup()
def write_result(self, operation_id, value):
payload = json.dumps(
value, sort_keys=True, separators=(',', ':'), ensure_ascii=True,
).encode('ascii')
path = self.directory / f'{operation_id}.json'
path.write_bytes(payload)
os.chmod(path, 0o640)
return path
def test_reconciles_only_db_selected_canonical_result_and_retains_evidence(self):
operation_id = str(uuid.uuid4())
envelope = {
'schema': 1,
'operation_id': operation_id,
'action': 'restart',
'result': 'succeeded',
'safe_category': None,
'safe_detail': None,
'resulting_identity': {
'active_config_sha256': 'a' * 64,
'active_secrets_sha256': 'b' * 64,
},
}
path = self.write_result(operation_id, envelope)
self.write_result(str(uuid.uuid4()), dict(envelope, operation_id=str(uuid.uuid4())))
database = _Database(operation_id)
with mock.patch.object(
host_agent_reconcile.os, 'listdir', side_effect=AssertionError('must not enumerate'),
):
self.assertEqual(
host_agent_reconcile.reconcile_pending_host_results(database, limit=7),
1,
)
self.assertEqual(
database.envelopes,
[json.dumps(
envelope, sort_keys=True, separators=(',', ':'), ensure_ascii=True,
).encode('ascii')],
)
self.assertTrue(path.exists())
def test_missing_result_is_ignored_and_noncanonical_or_unsafe_evidence_fails(self):
operation_id = str(uuid.uuid4())
database = _Database(operation_id)
self.assertEqual(host_agent_reconcile.reconcile_pending_host_results(database), 0)
path = self.directory / f'{operation_id}.json'
path.write_text('{"schema": 1}', encoding='ascii')
os.chmod(path, 0o640)
with self.assertRaises(host_agent_reconcile.HostResultError):
host_agent_reconcile.reconcile_pending_host_results(database)
os.chmod(self.directory, 0o700)
self.assertEqual(host_agent_reconcile.reconcile_pending_host_results(database), 0)
if __name__ == '__main__':
unittest.main()