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

311 lines
14 KiB
Python

from datetime import datetime, timedelta, timezone
import hashlib
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 dashboard
class DashboardDatabaseTests(unittest.TestCase):
def test_postgres_connection_uses_bounded_timeouts(self):
connection = mock.Mock()
with mock.patch.object(dashboard, 'connect_postgres', return_value=connection) as connect:
result = dashboard.connect_db(None, 'postgresql://truf:secret@127.0.0.1:5432/truf')
self.assertIs(result, connection)
self.assertEqual(connect.call_args.kwargs['connect_timeout_sec'], 3)
self.assertEqual(connect.call_args.kwargs['statement_timeout_ms'], 10000)
self.assertEqual(connect.call_args.kwargs['lock_timeout_ms'], 2000)
connection.commit.assert_called_once()
def test_failed_query_rolls_back_before_rendering_degraded_result(self):
connection = mock.Mock(is_postgres=True)
connection.execute.side_effect = RuntimeError('database unavailable')
with mock.patch.object(dashboard.st, 'error'):
result = dashboard.query_df(connection, 'SELECT 1')
self.assertTrue(result.empty)
connection.rollback.assert_called_once()
def test_default_frames_and_queries_do_not_include_raw_secret(self):
known_secret = 'known-super-secret-value'
frame = dashboard.pd.DataFrame([{
'service': 'example',
'raw_secret': known_secret,
'redacted_secret': 'know...alue',
}])
with mock.patch.object(dashboard.st, 'dataframe') as render:
dashboard.display_df(frame)
rendered = render.call_args.args[0]
self.assertNotIn('raw_secret', rendered.columns)
self.assertNotIn(known_secret, rendered.to_string())
queries = []
def capture(_conn, sql, params=None):
queries.append(sql)
return dashboard.pd.DataFrame()
with mock.patch.object(dashboard, 'query_df', side_effect=capture):
result = dashboard.finding_metadata_search(mock.Mock(), known_secret)
self.assertTrue(result.empty)
self.assertEqual(queries, [])
def test_hash_metadata_search_never_selects_raw_columns(self):
queries = []
with mock.patch.object(
dashboard,
'query_df',
side_effect=lambda _conn, sql, params=None: queries.append(sql) or dashboard.pd.DataFrame(),
):
dashboard.finding_metadata_search(mock.Mock(), 'a' * 64)
self.assertTrue(queries)
self.assertTrue(all('raw_secret' not in sql for sql in queries))
def test_no_available_dashboard_source_path_queries_or_reveals_raw_payloads(self):
source = (APP_DIR / 'dashboard.py').read_text(encoding='utf-8')
forbidden = (
'f.raw_secret', 'fh.raw_secret', 'SELECT raw_secret',
'Reveal raw credentials', 'Show raw secrets', 'config_json FROM',
'Download shown log text',
)
self.assertTrue(all(value not in source for value in forbidden))
launcher = (ROOT / 'start_runtime.ps1').read_text(encoding='utf-8')
self.assertIn('--no-dashboard', launcher)
self.assertNotIn('--background --dashboard', launcher)
def test_query_guard_refuses_sensitive_payload_columns_before_database_access(self):
connection = mock.Mock()
with mock.patch.object(dashboard.st, 'error'):
result = dashboard.query_df(connection, 'SELECT raw_secret FROM findings')
self.assertTrue(result.empty)
connection.execute.assert_not_called()
def test_reporting_presets_produce_exact_utc_window(self):
now = datetime(2026, 8, 2, 12, 30, tzinfo=timezone.utc)
start, end = dashboard.reporting_window('24 hours', now=now)
self.assertEqual(end, now)
self.assertEqual(end - start, timedelta(hours=24))
self.assertEqual(start.tzinfo, timezone.utc)
def test_invalid_custom_reporting_window_is_refused(self):
now = datetime(2026, 8, 2, 12, 30, tzinfo=timezone.utc)
with self.assertRaisesRegex(ValueError, 'later'):
dashboard.reporting_window('Custom', custom_start=now, custom_end=now)
def test_source_activity_applies_time_bounds_before_limit(self):
captured = {}
def capture(_conn, sql, params=None):
captured['sql'] = sql
captured['params'] = params
return dashboard.pd.DataFrame()
start = datetime(2026, 8, 1, tzinfo=timezone.utc)
end = datetime(2026, 8, 2, tzinfo=timezone.utc)
with mock.patch.object(dashboard, 'query_df', side_effect=capture):
dashboard.source_activity_df(mock.Mock(), start, end)
sql = captured['sql'].lower()
self.assertLess(sql.index('where'), sql.index('limit'))
self.assertIn('ended_at::timestamptz >=', sql)
self.assertIn('sum(sc.queued_new_count)', sql)
self.assertIn('sum(sc.queued_updated_count)', sql)
self.assertEqual(captured['params'], [start.isoformat(timespec='seconds'), end.isoformat(timespec='seconds')])
def test_period_summary_counts_first_alive_and_first_usable_results(self):
captured = {}
def capture(_conn, sql, params=None):
captured['sql'] = sql
captured['params'] = params
return dashboard.pd.DataFrame()
start = datetime(2026, 8, 1, tzinfo=timezone.utc)
end = datetime(2026, 8, 2, tzinfo=timezone.utc)
with mock.patch.object(dashboard, 'query_df', side_effect=capture):
dashboard.period_summary_df(mock.Mock(), start, end)
sql = captured['sql'].lower()
self.assertEqual(sql.count('partition by r.credential_id'), 2)
self.assertIn("r.status_group = 'alive'", sql)
self.assertIn('r.alive_rank = 1', sql)
self.assertIn('r.usable_rank = 1', sql)
self.assertIn('pd.queued_new', sql)
self.assertIn('pd.queued_updated', sql)
self.assertNotIn('c.created_at', sql)
self.assertEqual(captured['params'], [start.isoformat(timespec='seconds'), end.isoformat(timespec='seconds')])
def test_new_alive_breakdown_uses_first_alive_result_time(self):
captured = {}
def capture(_conn, sql, params=None):
captured['sql'] = sql
captured['params'] = params
return dashboard.pd.DataFrame()
start = datetime(2026, 8, 1, tzinfo=timezone.utc)
end = datetime(2026, 8, 2, tzinfo=timezone.utc)
with mock.patch.object(dashboard, 'query_df', side_effect=capture):
dashboard.new_alive_breakdown_df(mock.Mock(), start, end)
sql = captured['sql'].lower()
self.assertIn('partition by kr.credential_id', sql)
self.assertIn("kr.status_group = 'alive'", sql)
self.assertIn('ra.alive_rank = 1', sql)
self.assertNotIn('keycheck_current_state', sql)
self.assertNotIn('c.created_at', sql)
self.assertEqual(captured['params'], [start.isoformat(timespec='seconds'), end.isoformat(timespec='seconds')])
def test_raw_lookup_is_replaced_by_sha256_identity(self):
raw = 'sk-proj-dashboard-test-not-a-real-key'
request = dashboard.normalize_lookup(raw)
self.assertEqual(request, {
'kind': 'digest',
'digest': hashlib.sha256(raw.encode('utf-8')).hexdigest(),
})
self.assertNotIn(raw, request.values())
def test_pasted_finding_json_extracts_and_hashes_raw_value(self):
raw = 'ghp_dashboard_test_not_a_real_token'
request = dashboard.normalize_lookup('{"Raw": "' + raw + '", "DetectorName": "GitHub"}')
self.assertEqual(request['kind'], 'digest')
self.assertEqual(request['digest'], hashlib.sha256(raw.encode('utf-8')).hexdigest())
self.assertNotIn(raw, request.values())
def test_digest_lookup_uses_exact_identity_without_raw_columns(self):
digest = 'a' * 64
captured = {}
def capture(_conn, sql, params=None):
captured['sql'] = sql
captured['params'] = params
return dashboard.pd.DataFrame()
with mock.patch.object(dashboard, 'query_df', side_effect=capture):
dashboard.lookup_findings_df(mock.Mock(), {'kind': 'digest', 'digest': digest})
sql = captured['sql'].lower()
self.assertIn('f.secret_hash = ?', sql)
self.assertIn('f.finding_uid = ?', sql)
self.assertNotIn('raw_secret', sql)
self.assertTrue(all(value == digest or isinstance(value, int) for value in captured['params']))
def test_metadata_wildcards_are_escaped(self):
self.assertEqual(dashboard.escaped_like('repo_100%!'), '%repo!_100!%!!%')
class DashboardConfigTests(unittest.TestCase):
def test_direct_main_without_authority_never_parses_args_or_opens_database(self):
with mock.patch.object(
dashboard, 'require_active_supervisor_child',
side_effect=dashboard.LifecycleAuthorityError('direct dashboard refused'),
), mock.patch.object(dashboard, 'parse_args') as parse_args, \
mock.patch.object(dashboard, 'connect_db') as connect_db, \
mock.patch.dict(os.environ, {}, clear=True):
with self.assertRaisesRegex(SystemExit, 'direct dashboard refused'):
dashboard.main()
parse_args.assert_not_called()
connect_db.assert_not_called()
def test_authenticated_dashboard_still_rejects_non_loopback_launch_marker_before_args(self):
with mock.patch.object(dashboard, 'require_active_supervisor_child', return_value={}), \
mock.patch.object(dashboard, 'parse_args') as parse_args, \
mock.patch.dict(os.environ, {
'TRUF_DASHBOARD_CANONICAL_LAUNCH': '1',
'TRUF_DASHBOARD_HOST': '0.0.0.0',
}, clear=True):
with self.assertRaisesRegex(SystemExit, 'loopback'):
dashboard.main()
parse_args.assert_not_called()
def test_managed_config_parse_failure_is_fatal(self):
with tempfile.TemporaryDirectory() as temp_dir:
config = os.path.join(temp_dir, 'config.yaml')
Path(config).write_text('[invalid', encoding='ascii')
with mock.patch.object(sys, 'argv', ['dashboard.py', '--config', config]), \
mock.patch.dict(os.environ, {'TRUF_SUPERVISOR_CHILD_KIND': 'dashboard'}, clear=False):
with self.assertRaisesRegex(SystemExit, 'failed closed'):
dashboard.parse_args()
def test_legacy_mutation_ui_contains_no_scanner_controls(self):
source = (APP_DIR / 'app.py').read_text(encoding='utf-8')
self.assertNotIn('from scanner import', source)
self.assertNotIn('start_scan(', source)
self.assertIn('controls are retired', source)
def test_main_routes_only_to_single_page(self):
source = (APP_DIR / 'dashboard.py').read_text(encoding='utf-8')
main_source = source[source.index('def main():'):]
self.assertIn('page_simple_dashboard(', main_source)
self.assertNotIn('st.sidebar', main_source)
self.assertNotIn('current_queue_counts(', main_source)
def test_streamlit_usage_telemetry_is_disabled(self):
config = (APP_DIR / '.streamlit' / 'config.toml').read_text(encoding='utf-8')
self.assertIn('[browser]', config)
self.assertIn('gatherUsageStats = false', config)
def test_log_tail_never_materializes_more_than_bounded_bytes(self):
with tempfile.TemporaryDirectory() as temp_dir:
path = os.path.join(temp_dir, 'large.log')
Path(path).write_bytes(b'x' * (dashboard.MAX_LOG_TAIL_BYTES * 3))
lines = dashboard.read_tail(path, 5000)
self.assertLessEqual(
sum(len(line.encode('utf-8')) for line in lines),
dashboard.MAX_LOG_TAIL_BYTES,
)
class SupervisorStatusParsingTests(unittest.TestCase):
def test_current_status_columns_are_mapped_by_header(self):
with tempfile.TemporaryDirectory() as temp_dir:
path = os.path.join(temp_dir, 'supervisor.status.txt')
Path(path).write_text(
'PostgreSQL: READY failures=0 detail=ready\n\n'
'source | status | desired | pid | mode | up | exit | next | rs | auth | last_log\n'
'-------+--------+---------+-----+------+----+------+------|----|------|---------\n'
'github | running | running | 42 | loop | 5s | - | - | 1/2 | token-1 | healthy\n',
encoding='utf-8',
)
rows, found_path = dashboard.parse_supervisor_status(temp_dir)
self.assertEqual(found_path, path)
self.assertEqual(len(rows), 1)
row = rows.iloc[0]
self.assertEqual(row['runtime_status'], 'running')
self.assertEqual(row['desired'], 'running')
self.assertEqual(row['pid'], '42')
self.assertEqual(row['restarts'], '1/2')
self.assertEqual(row['auth'], 'token-1')
self.assertNotIn('last_log', row.index)
def test_blocked_paused_backoff_and_done_statuses_remain_distinct(self):
with tempfile.TemporaryDirectory() as temp_dir:
path = os.path.join(temp_dir, 'supervisor.status.txt')
rows = '\n'.join(
f'{name} | {status} | running | - | loop | - | - | - | 0/0 | - | detail'
for name, status in (
('one', 'blocked'), ('two', 'paused'), ('three', 'backoff'), ('four', 'done'),
)
)
Path(path).write_text(
'source | status | desired | pid | mode | up | exit | next | rs | auth | last_log\n'
+ rows + '\n',
encoding='utf-8',
)
with mock.patch.object(dashboard, 'load_per_source_states', return_value=dashboard.pd.DataFrame()), \
mock.patch.object(dashboard, 'latest_cycle_df', return_value=dashboard.pd.DataFrame()):
health, _ = dashboard.runtime_source_health(mock.Mock(), temp_dir)
mapped = dict(zip(health['source'], health['health']))
self.assertEqual(mapped['one'], 'blocked')
self.assertEqual(mapped['two'], 'paused')
self.assertEqual(mapped['three'], 'backoff')
self.assertEqual(mapped['four'], 'done')
if __name__ == '__main__':
unittest.main()