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

239 lines
11 KiB
Python

import json
import os
from pathlib import Path
import sys
import tempfile
import unittest
from unittest import mock
ROOT = Path(__file__).resolve().parents[1]
APP_DIR = ROOT / 'app'
sys.path.insert(0, str(APP_DIR))
import supervisor
from lifecycle_authority import (
DISCOVERY_PRODUCER_ROLE,
DISCOVERY_PRODUCER_SOURCES,
)
from runtime_security import ensure_private_directory, harden_private_file
def producer_fixture(temp_dir, source='gitlab', source_config=None):
ensure_private_directory(temp_dir, reject_reparse=True)
config_path = os.path.join(temp_dir, 'config.yaml')
Path(config_path).write_text('{}\n', encoding='ascii')
harden_private_file(config_path)
log_dir = os.path.join(temp_dir, 'logs')
state_dir = os.path.join(temp_dir, 'state')
ensure_private_directory(log_dir, reject_reparse=True)
ensure_private_directory(state_dir, reject_reparse=True)
canonical_dsn = 'postgresql://managed@127.0.0.1/truf'
gate = supervisor.DependencyGate(ready=True, database_url=canonical_dsn)
child_environment = {
'TRUF_SUPERVISOR_TOKEN': 'trusted-token',
'TRUF_SUPERVISOR_CHILD_KIND': DISCOVERY_PRODUCER_ROLE,
'TRUF_MANAGED_POSTGRES_DSN': canonical_dsn,
}
producer = supervisor.ManagedDiscoveryProducer(
source, config_path, str(APP_DIR), temp_dir,
{
'log_dir': log_dir,
'state_dir': state_dir,
'defaults': {'enabled': True, 'restart': True},
'sources': {source: dict(source_config or {})},
},
{'enabled': True},
dependency_gate=gate,
child_environment=child_environment,
)
return producer, canonical_dsn
class DiscoveryProducerSupervisorTests(unittest.TestCase):
def test_allowlist_and_authenticated_one_shot_command_are_exact(self):
self.assertEqual(
DISCOVERY_PRODUCER_SOURCES,
('gitlab', 'dockerhub', 'huggingface'),
)
with tempfile.TemporaryDirectory() as temp_dir:
for source in DISCOVERY_PRODUCER_SOURCES:
with self.subTest(source=source):
producer, _ = producer_fixture(temp_dir, source=source)
command = producer.build_command()
self.assertEqual(command[:4], [sys.executable, '-I', '-S', '-B'])
self.assertEqual(Path(command[4]).name, 'child_bootstrap.py')
self.assertEqual(command[5:7], [DISCOVERY_PRODUCER_ROLE, '--'])
self.assertEqual(command[-5:], [
'--config', producer.config_path, '--source', source, '--once',
])
self.assertTrue(producer.once)
self.assertTrue(producer.repeat)
self.assertEqual(producer.producer_id, f'discovery-producer:{source}')
self.assertTrue(producer.log_path.endswith(
f'discovery-producer-{source}.log',
))
self.assertTrue(producer.state_path.endswith(
f'runner_state_{source}.json',
))
with self.assertRaisesRegex(ValueError, 'allowlist'):
producer_fixture(temp_dir, source='github')
def test_environment_strips_overrides_and_restores_immutable_authority(self):
with tempfile.TemporaryDirectory() as temp_dir:
producer, canonical_dsn = producer_fixture(temp_dir, source_config={
'enabled': True,
'env': {
'PGPASSWORD': 'forged',
'SCANNER_DB_URL': 'postgresql://forged',
'TRUF_SUPERVISOR_TOKEN': 'forged-token',
'TRUF_SUPERVISOR_CHILD_KIND': 'scanner',
'TRUF_MANAGED_POSTGRES_DSN': 'postgresql://forged',
'SAFE_PROVIDER_COMPAT': 'retained',
},
})
environment = producer.build_env()
self.assertNotIn('PGPASSWORD', environment)
self.assertEqual(environment['SCANNER_DB_URL'], canonical_dsn)
self.assertEqual(environment['DATABASE_URL'], canonical_dsn)
self.assertEqual(environment['TRUF_MANAGED_POSTGRES_DSN'], canonical_dsn)
self.assertEqual(environment['TRUF_SUPERVISOR_TOKEN'], 'trusted-token')
self.assertEqual(environment['TRUF_SUPERVISOR_CHILD_KIND'], DISCOVERY_PRODUCER_ROLE)
self.assertEqual(environment['TRUF_DISCOVERY_SOURCE'], 'gitlab')
self.assertEqual(environment['SAFE_PROVIDER_COMPAT'], 'retained')
self.assertNotIn('SCANNER_SKIP_STARTUP_CLEANUP', environment)
def test_reconfigure_cannot_disable_one_shot_repeat_mode(self):
with tempfile.TemporaryDirectory() as temp_dir:
producer, _ = producer_fixture(temp_dir)
producer.reconfigure(
temp_dir,
{
'log_dir': os.path.join(temp_dir, 'logs'),
'state_dir': os.path.join(temp_dir, 'state'),
'defaults': {'enabled': True},
},
{'enabled': True, 'once': False, 'repeat': False, 'extra_args': ['--loop']},
)
self.assertTrue(producer.once)
self.assertTrue(producer.repeat)
self.assertNotIn('--loop', producer.build_command())
def test_structured_state_is_bounded_and_signature_remains_nine_fields(self):
with tempfile.TemporaryDirectory() as temp_dir:
producers = []
for source in DISCOVERY_PRODUCER_SOURCES:
producer, _ = producer_fixture(temp_dir, source=source)
producers.append(producer)
state = {
'sources': {
source: {
'last_status': 'failed',
'last_cycle_result': {
'status': 'must-not-leak',
'fetched_count': 7,
'queued_new_count': 5,
'queued_updated_count': 2,
'provider_error': 'must-not-leak',
},
'last_discovery_success_at': 'must-not-leak',
'last_error_category': 'must-not-leak',
'raw_error': 'must-not-leak',
},
},
'token': 'must-not-leak',
}
Path(producer.state_path).write_text(json.dumps(state), encoding='utf-8')
snapshot = producer.structured_state()
serialized = json.dumps(snapshot, sort_keys=True)
self.assertEqual(snapshot['id'], f'discovery-producer:{source}')
self.assertEqual(snapshot['source'], source)
self.assertEqual(snapshot['role'], DISCOVERY_PRODUCER_ROLE)
self.assertEqual(snapshot['process_state'], 'stopped')
self.assertEqual(snapshot['interval_seconds'], producer.interval)
self.assertTrue(snapshot['restart_enabled'])
self.assertEqual(snapshot['allowed_actions'], [
'start', 'stop', 'restart', 'pause', 'resume', 'set-interval',
])
self.assertEqual(snapshot['safe_error_category'], 'runtime_error')
self.assertIsNone(snapshot['last_successful_discovery_at'])
self.assertEqual(snapshot['last_cycle_result'], {
'status': 'unknown',
'fetched_count': 7,
'queued_new_count': 5,
'queued_updated_count': 2,
})
self.assertNotIn('must-not-leak', serialized)
signature = supervisor.table_signature(producers)
self.assertEqual(len(signature), len(DISCOVERY_PRODUCER_SOURCES))
self.assertTrue(all(len(row) == 9 for row in signature))
def test_typed_restart_targets_only_one_exact_producer(self):
with tempfile.TemporaryDirectory() as temp_dir:
producers = [
producer_fixture(temp_dir, source=source)[0]
for source in DISCOVERY_PRODUCER_SOURCES
]
restart_methods = {}
for producer in producers:
replacement = mock.Mock(return_value=True)
restart_methods[producer.producer_id] = replacement
producer.restart_now = replacement
selected = producers[1]
result = supervisor.run_managed_source_action({
'schema': supervisor.CONTROL_SCHEMA,
'instance_id': 'fixture-instance',
'token': 't' * 48,
'action': 'managed-source-action',
'source_id': selected.producer_id,
'source_action': 'restart',
}, producers, {})
self.assertEqual(result['source']['id'], selected.producer_id)
restart_methods[selected.producer_id].assert_called_once_with()
for producer_id, method in restart_methods.items():
if producer_id != selected.producer_id:
method.assert_not_called()
for source_id in ('discovery-producer:github', 'discovery-producer:unknown'):
with self.subTest(source_id=source_id), self.assertRaisesRegex(
ValueError, 'unknown',
):
supervisor.run_managed_source_action({
'schema': supervisor.CONTROL_SCHEMA,
'instance_id': 'fixture-instance',
'token': 't' * 48,
'action': 'managed-source-action',
'source_id': source_id,
'source_action': 'restart',
}, producers, {})
self.assertEqual(
sum(method.call_count for method in restart_methods.values()), 1,
)
def test_restart_does_not_relaunch_after_start_gate_closes(self):
with tempfile.TemporaryDirectory() as temp_dir:
producer, _ = producer_fixture(temp_dir)
gate = {'open': True}
producer.start_gate = lambda: gate['open']
def stop_for_shutdown(timeout=10):
gate['open'] = False
return True
with (
mock.patch.object(producer, 'stop', side_effect=stop_for_shutdown),
mock.patch.object(supervisor, 'OwnedProcess') as launch,
):
self.assertFalse(producer.restart_now())
launch.assert_not_called()
self.assertEqual(
producer.last_action_error,
'supervisor lifecycle start gate is closed',
)
if __name__ == '__main__':
unittest.main()