初始提交
This commit is contained in:
@@ -0,0 +1,494 @@
|
||||
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'
|
||||
Reference in New Issue
Block a user