495 lines
22 KiB
Python
495 lines
22 KiB
Python
from types import ModuleType, SimpleNamespace
|
|
|
|
from pytest import MonkeyPatch
|
|
|
|
|
|
def test_collect_dashboard_snapshot_adds_risk_heat_panel(
|
|
monkeypatch: MonkeyPatch,
|
|
health_adapter: ModuleType,
|
|
) -> None:
|
|
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
|
|
del parse_json
|
|
if arguments[0:2] == ('app', 'env'):
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'runtime': {
|
|
'cliEnv': 'dev',
|
|
'configEnv': 'dev',
|
|
'envFile': '.env.dev',
|
|
'envFileExists': True,
|
|
},
|
|
}
|
|
)
|
|
if arguments[0:2] == ('app', 'doctor'):
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': False,
|
|
'database': {'ok': False, 'message': '数据库连接失败'},
|
|
'redis': {'ok': True, 'message': 'Redis 正常'},
|
|
'crypto': {'ok': True, 'message': '加密组件正常'},
|
|
}
|
|
)
|
|
if arguments[0:2] == ('app', 'routes'):
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'count': 3,
|
|
'routes': [],
|
|
'groupedRoutes': {'system': [{}, {}], 'monitor': [{}]},
|
|
}
|
|
)
|
|
if arguments[0:2] == ('db', 'current'):
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': False,
|
|
'currentRevision': '-',
|
|
'message': 'revision unavailable',
|
|
}
|
|
)
|
|
if arguments[0:2] == ('ops', 'deps'):
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'message': '核心运行依赖已安装',
|
|
'missingRequired': [],
|
|
'packages': {
|
|
'python': {'installed': True, 'version': '3.10.0'},
|
|
'fastapi': {'installed': True, 'version': '0.111.0'},
|
|
'sqlalchemy': {'installed': True, 'version': '2.0.0'},
|
|
'redis': {'installed': True, 'version': '5.0.0'},
|
|
},
|
|
}
|
|
)
|
|
if arguments[0:2] == ('ops', 'server-info'):
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'server': {
|
|
'sys': {'computerName': 'shierfeng-node', 'computerIp': '10.0.0.8'},
|
|
'cpu': {'used': 18.2},
|
|
'mem': {'usage': 42.5},
|
|
},
|
|
}
|
|
)
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'dbSize': 128,
|
|
'info': {'redis_version': '7.0', 'connected_clients': 12},
|
|
'cacheNames': ['sys_config'],
|
|
}
|
|
)
|
|
|
|
monkeypatch.setattr(health_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
|
|
|
|
snapshot = health_adapter.DASHBOARD_ADAPTER.collect_snapshot('dev')
|
|
|
|
assert any(panel.title == '总览判断' for panel in snapshot.panels)
|
|
assert any(panel.title == '建议摘要' for panel in snapshot.panels)
|
|
assert any(panel.title == '风险摘要' for panel in snapshot.panels)
|
|
assert any(panel.title == '依赖版本' for panel in snapshot.panels)
|
|
assert any(panel.title == '服务器摘要' for panel in snapshot.panels)
|
|
app_panel = next(panel for panel in snapshot.panels if panel.title == '应用摘要')
|
|
conclusion_panel = next(panel for panel in snapshot.panels if panel.title == '总览判断')
|
|
entry_panel = next(panel for panel in snapshot.panels if panel.title == '建议摘要')
|
|
assert any('当前环境: dev | CLI 目标环境: dev' in line for line in app_panel.lines)
|
|
assert any('注册路由: 3 条 | 标签分组: 2 个' in line for line in app_panel.lines)
|
|
assert conclusion_panel.status == 'fail'
|
|
assert any('数据库存在异常' in line for line in conclusion_panel.lines)
|
|
assert any('优先进入数据库页面' in line for line in conclusion_panel.lines)
|
|
assert entry_panel.status == 'fail'
|
|
assert any('[B] 数据库' in line for line in entry_panel.lines)
|
|
assert any('[O] 运维' in line for line in entry_panel.lines)
|
|
assert any('聚焦:迁移版本 / 连接状态 / Heads' in line for line in entry_panel.lines)
|
|
assert any('聚焦:数据库连通性 / Redis 连通性 / 依赖版本' in line for line in entry_panel.lines)
|
|
heat_panel = next(panel for panel in snapshot.panels if panel.title == '风险摘要')
|
|
assert heat_panel.status == 'fail'
|
|
assert any('HOT-01' in line for line in heat_panel.lines)
|
|
assert any(metric.title == '依赖通过率' and '[' in metric.value for metric in snapshot.metrics)
|
|
|
|
|
|
def test_dashboard_adapter_collect_snapshot_exposes_metrics(
|
|
monkeypatch: MonkeyPatch,
|
|
health_adapter: ModuleType,
|
|
) -> None:
|
|
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
|
|
del parse_json
|
|
if arguments[0:2] == ('app', 'env'):
|
|
return SimpleNamespace(payload={'ok': True, 'runtime': {'cliEnv': 'dev', 'configEnv': 'dev'}})
|
|
if arguments[0:2] == ('app', 'routes'):
|
|
return SimpleNamespace(payload={'ok': True, 'count': 2, 'routes': [], 'groupedRoutes': {}})
|
|
if arguments[0:2] == ('app', 'doctor'):
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'database': {'ok': True, 'message': '数据库正常'},
|
|
'redis': {'ok': True, 'message': 'Redis 正常'},
|
|
'crypto': {'ok': True, 'message': '加密正常'},
|
|
}
|
|
)
|
|
if arguments[0:2] == ('db', 'current'):
|
|
return SimpleNamespace(payload={'ok': True, 'currentRevision': '202604300001'})
|
|
if arguments[0:2] == ('ops', 'deps'):
|
|
return SimpleNamespace(payload={'ok': True, 'missingRequired': [], 'packages': {}})
|
|
if arguments[0:2] == ('ops', 'server-info'):
|
|
return SimpleNamespace(payload={'ok': True, 'server': {'sys': {}, 'cpu': {}, 'mem': {}}})
|
|
return SimpleNamespace(payload={'ok': True, 'dbSize': 27, 'info': {}, 'cacheNames': []})
|
|
|
|
monkeypatch.setattr(health_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
|
|
|
|
snapshot = health_adapter.DASHBOARD_ADAPTER.collect_snapshot('dev')
|
|
|
|
assert snapshot.env == 'dev'
|
|
assert snapshot.metrics
|
|
assert any(metric.title == '依赖通过率' for metric in snapshot.metrics)
|
|
assert any(panel.title == '总览判断' for panel in snapshot.panels)
|
|
|
|
|
|
def test_collect_dashboard_snapshot_uses_focus_hints_for_healthy_business_entry(
|
|
monkeypatch: MonkeyPatch,
|
|
health_adapter: ModuleType,
|
|
) -> None:
|
|
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
|
|
del parse_json
|
|
if arguments[0:2] == ('app', 'env'):
|
|
return SimpleNamespace(payload={'ok': True, 'runtime': {'cliEnv': 'dev', 'configEnv': 'dev'}})
|
|
if arguments[0:2] == ('app', 'routes'):
|
|
return SimpleNamespace(
|
|
payload={'ok': True, 'count': 4, 'routes': [], 'groupedRoutes': {'system': [{}, {}]}}
|
|
)
|
|
if arguments[0:2] == ('app', 'doctor'):
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'database': {'ok': True, 'message': '数据库正常'},
|
|
'redis': {'ok': True, 'message': 'Redis 正常'},
|
|
'crypto': {'ok': True, 'message': '加密组件正常'},
|
|
}
|
|
)
|
|
if arguments[0:2] == ('db', 'current'):
|
|
return SimpleNamespace(payload={'ok': True, 'currentRevision': '202604300001'})
|
|
if arguments[0:2] == ('ops', 'deps'):
|
|
return SimpleNamespace(payload={'ok': True, 'missingRequired': [], 'packages': {}, 'message': '依赖正常'})
|
|
if arguments[0:2] == ('ops', 'server-info'):
|
|
return SimpleNamespace(payload={'ok': True, 'server': {'sys': {}, 'cpu': {}, 'mem': {}}})
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'dbSize': 27,
|
|
'info': {'redis_version': '7.2.6', 'connected_clients': 1},
|
|
'cacheNames': [],
|
|
}
|
|
)
|
|
|
|
monkeypatch.setattr(health_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
|
|
|
|
snapshot = health_adapter.DASHBOARD_ADAPTER.collect_snapshot('dev')
|
|
|
|
entry_panel = next(panel for panel in snapshot.panels if panel.title == '建议摘要')
|
|
|
|
assert entry_panel.status == 'ok'
|
|
assert any('聚焦:失败聚合 / 暂停任务 / 执行轨迹' in line for line in entry_panel.lines)
|
|
assert any('聚焦:高风险配置 / 值不一致 / 缓存漂移' in line for line in entry_panel.lines)
|
|
assert any('生成前校验' in line and '代码预览' in line for line in entry_panel.lines)
|
|
|
|
|
|
def test_collect_configs_page_snapshot_surfaces_failure_message(
|
|
monkeypatch: MonkeyPatch,
|
|
configs_adapter: ModuleType,
|
|
) -> None:
|
|
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
|
|
del arguments, parse_json
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': False,
|
|
'message': '读取参数配置诊断信息失败',
|
|
'error': 'database unavailable',
|
|
}
|
|
)
|
|
|
|
monkeypatch.setattr(configs_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
|
|
|
|
snapshot = configs_adapter.CONFIGS_BROWSER_ADAPTER.collect_snapshot('dev')
|
|
|
|
assert snapshot.title == '参数配置'
|
|
assert snapshot.records[0].status == 'fail'
|
|
assert snapshot.shared_sections[0].title == '总览判断'
|
|
assert snapshot.shared_sections[1].title == '配置巡检'
|
|
assert any('读取参数配置诊断信息失败' in line for line in snapshot.shared_sections[1].lines)
|
|
assert any('database unavailable' in line for line in snapshot.shared_sections[1].lines)
|
|
|
|
|
|
def test_configs_browser_adapter_collect_snapshot_exposes_search_context(
|
|
monkeypatch: MonkeyPatch,
|
|
configs_adapter: ModuleType,
|
|
) -> None:
|
|
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
|
|
del parse_json
|
|
if arguments[0:2] == ('config', 'doctor'):
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'databaseCount': 1,
|
|
'cacheCount': 1,
|
|
'missingInCacheCount': 0,
|
|
'orphanInCacheCount': 0,
|
|
'mismatchCount': 0,
|
|
'missingInCache': [],
|
|
'orphanInCache': [],
|
|
'mismatchKeys': [],
|
|
}
|
|
)
|
|
if arguments[0:2] == ('config', 'list'):
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'page': {
|
|
'rows': [
|
|
{
|
|
'configId': 1,
|
|
'configKey': 'site_name',
|
|
'configName': '站点名称',
|
|
'configType': 'Y',
|
|
'configValue': 'ShiErFeng',
|
|
}
|
|
]
|
|
},
|
|
}
|
|
)
|
|
return SimpleNamespace(payload={'ok': False, 'message': 'unexpected'})
|
|
|
|
monkeypatch.setattr(configs_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
|
|
|
|
snapshot = configs_adapter.CONFIGS_BROWSER_ADAPTER.collect_snapshot('dev', query='site')
|
|
|
|
assert snapshot.title == '参数配置'
|
|
assert snapshot.search is not None
|
|
assert snapshot.search.query == 'site'
|
|
assert snapshot.records
|
|
assert snapshot.records[0].title == 'site_name'
|
|
|
|
|
|
def test_collect_configs_page_snapshot_builds_browser_records(
|
|
monkeypatch: MonkeyPatch,
|
|
configs_adapter: ModuleType,
|
|
) -> None:
|
|
calls: list[tuple[str, ...]] = []
|
|
|
|
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
|
|
del parse_json
|
|
calls.append(arguments)
|
|
if arguments[0:2] == ('config', 'doctor'):
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'databaseCount': 3,
|
|
'cacheCount': 3,
|
|
'missingInCacheCount': 0,
|
|
'orphanInCacheCount': 0,
|
|
'mismatchCount': 1,
|
|
'mismatchKeys': ['site_name'],
|
|
}
|
|
)
|
|
if arguments[0:2] == ('config', 'list'):
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'page': {
|
|
'rows': [
|
|
{
|
|
'configId': 1,
|
|
'configKey': 'site_name',
|
|
'configName': '站点名称',
|
|
'configType': 'Y',
|
|
'configValue': 'ShiErFeng',
|
|
}
|
|
]
|
|
},
|
|
}
|
|
)
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'key': 'site_name',
|
|
'source': 'both',
|
|
'inSync': False,
|
|
'database': {
|
|
'configId': 1,
|
|
'configKey': 'site_name',
|
|
'configName': '站点名称',
|
|
'configValue': 'ShiErFeng',
|
|
'configType': 'Y',
|
|
'remark': '数据库值',
|
|
},
|
|
'cache': {
|
|
'configId': 1,
|
|
'configKey': 'site_name',
|
|
'configName': '站点名称',
|
|
'configValue': 'ShiErFeng-Cache',
|
|
'configType': 'Y',
|
|
'remark': '缓存值',
|
|
},
|
|
}
|
|
)
|
|
|
|
monkeypatch.setattr(configs_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
|
|
|
|
snapshot = configs_adapter.CONFIGS_BROWSER_ADAPTER.collect_snapshot('dev')
|
|
|
|
assert snapshot.title == '参数配置'
|
|
assert (
|
|
snapshot.subtitle
|
|
== '聚焦:高风险配置 / 值不一致 / 缓存漂移 | 当前筛选:全部,已匹配 1 项配置,值不一致 1 项,缓存漂移 0 项'
|
|
)
|
|
assert snapshot.active_filter_key == 'all'
|
|
assert [option.key for option in snapshot.filters] == ['all', 'risky', 'mismatch', 'cache-drift']
|
|
assert len(snapshot.records) == 1
|
|
assert snapshot.records[0].title == 'site_name'
|
|
assert snapshot.records[0].status == 'fail'
|
|
detail_sections = snapshot.records[0].resolve_detail_sections()
|
|
assert [section.title for section in detail_sections] == ['同步状态', '数据库配置', '缓存配置']
|
|
assert any('数据库与缓存一致: 否' in line for line in detail_sections[0].lines)
|
|
assert any('键值: ShiErFeng' in line for line in detail_sections[1].lines)
|
|
assert any('键值: ShiErFeng-Cache' in line for line in detail_sections[2].lines)
|
|
assert snapshot.shared_sections[0].title == '总览判断'
|
|
assert any('值不一致: 1 项' in line for line in snapshot.shared_sections[0].lines)
|
|
assert snapshot.shared_sections[1].title == '配置巡检'
|
|
assert snapshot.shared_sections[2].title == '高风险配置'
|
|
assert snapshot.shared_sections[3].title == '异常样本'
|
|
assert snapshot.shared_sections[4].title == '配置变更入口'
|
|
assert any('config set site_name ShiErFeng --output=text' in line for line in snapshot.shared_sections[4].lines)
|
|
assert any(call[0:2] == ('config', 'get') for call in calls)
|
|
|
|
|
|
def test_collect_configs_page_snapshot_sorts_and_filters_risky_records(
|
|
monkeypatch: MonkeyPatch,
|
|
configs_adapter: ModuleType,
|
|
) -> None:
|
|
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
|
|
del parse_json
|
|
if arguments[0:2] == ('config', 'doctor'):
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'databaseCount': 3,
|
|
'cacheCount': 3,
|
|
'missingInCacheCount': 1,
|
|
'orphanInCacheCount': 1,
|
|
'mismatchCount': 1,
|
|
'missingInCache': ['upload_mode'],
|
|
'orphanInCache': ['legacy_toggle'],
|
|
'mismatchKeys': ['site_name'],
|
|
}
|
|
)
|
|
if arguments[0:2] == ('config', 'list'):
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'page': {
|
|
'rows': [
|
|
{
|
|
'configId': 2,
|
|
'configKey': 'upload_mode',
|
|
'configName': '上传模式',
|
|
'configType': 'N',
|
|
'configValue': 'local',
|
|
},
|
|
{
|
|
'configId': 1,
|
|
'configKey': 'site_name',
|
|
'configName': '站点名称',
|
|
'configType': 'Y',
|
|
'configValue': 'ShiErFeng',
|
|
},
|
|
{
|
|
'configId': 3,
|
|
'configKey': 'normal_timeout',
|
|
'configName': '会话超时',
|
|
'configType': 'N',
|
|
'configValue': '30m',
|
|
},
|
|
]
|
|
},
|
|
}
|
|
)
|
|
return SimpleNamespace(payload={'ok': True, 'key': '-', 'source': 'both', 'inSync': True})
|
|
|
|
monkeypatch.setattr(configs_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
|
|
|
|
all_snapshot = configs_adapter.CONFIGS_BROWSER_ADAPTER.collect_snapshot('dev')
|
|
risky_snapshot = configs_adapter.CONFIGS_BROWSER_ADAPTER.collect_snapshot('dev', filter_key='risky')
|
|
mismatch_snapshot = configs_adapter.CONFIGS_BROWSER_ADAPTER.collect_snapshot('dev', filter_key='mismatch')
|
|
cache_drift_snapshot = configs_adapter.CONFIGS_BROWSER_ADAPTER.collect_snapshot('dev', filter_key='cache-drift')
|
|
|
|
assert [record.title for record in all_snapshot.records] == [
|
|
'site_name',
|
|
'upload_mode',
|
|
'legacy_toggle',
|
|
'normal_timeout',
|
|
]
|
|
assert [record.status for record in all_snapshot.records] == ['fail', 'warn', 'warn', 'ok']
|
|
assert any('[值不一致] site_name' in line for line in all_snapshot.shared_sections[2].lines)
|
|
assert any('[缓存缺失] upload_mode' in line for line in all_snapshot.shared_sections[2].lines)
|
|
assert any('[缓存孤立] legacy_toggle' in line for line in all_snapshot.shared_sections[2].lines)
|
|
|
|
assert risky_snapshot.active_filter_key == 'risky'
|
|
assert [record.title for record in risky_snapshot.records] == ['site_name', 'upload_mode', 'legacy_toggle']
|
|
assert [record.title for record in mismatch_snapshot.records] == ['site_name']
|
|
assert [record.title for record in cache_drift_snapshot.records] == ['upload_mode', 'legacy_toggle']
|
|
|
|
|
|
def test_collect_configs_page_snapshot_applies_query_filter(
|
|
monkeypatch: MonkeyPatch,
|
|
configs_adapter: ModuleType,
|
|
) -> None:
|
|
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
|
|
del parse_json
|
|
if arguments[0:2] == ('config', 'doctor'):
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'databaseCount': 2,
|
|
'cacheCount': 2,
|
|
'missingInCacheCount': 0,
|
|
'orphanInCacheCount': 0,
|
|
'mismatchCount': 0,
|
|
'missingInCache': [],
|
|
'orphanInCache': [],
|
|
'mismatchKeys': [],
|
|
}
|
|
)
|
|
if arguments[0:2] == ('config', 'list'):
|
|
return SimpleNamespace(
|
|
payload={
|
|
'ok': True,
|
|
'page': {
|
|
'rows': [
|
|
{
|
|
'configId': 1,
|
|
'configKey': 'site_name',
|
|
'configName': '站点名称',
|
|
'configType': 'Y',
|
|
'configValue': 'ShiErFeng',
|
|
},
|
|
{
|
|
'configId': 2,
|
|
'configKey': 'upload_mode',
|
|
'configName': '上传模式',
|
|
'configType': 'N',
|
|
'configValue': 'local',
|
|
},
|
|
]
|
|
},
|
|
}
|
|
)
|
|
return SimpleNamespace(payload={'ok': True, 'key': '-', 'source': 'both', 'inSync': True})
|
|
|
|
monkeypatch.setattr(configs_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
|
|
|
|
snapshot = configs_adapter.CONFIGS_BROWSER_ADAPTER.collect_snapshot('dev', query='site')
|
|
|
|
assert [record.title for record in snapshot.records] == ['site_name']
|
|
assert snapshot.search is not None
|
|
assert snapshot.search.query == 'site'
|