Initial server source import
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user