初始提交

This commit is contained in:
焦龙言
2026-07-24 09:43:10 +08:00
commit b5b4721456
793 changed files with 116305 additions and 0 deletions
@@ -0,0 +1,87 @@
import asyncio
import importlib
import sys
from collections.abc import Callable
from pathlib import Path
from types import ModuleType
import pytest
BACKEND_DIR = Path(__file__).resolve().parents[4]
sys.path.insert(0, str(BACKEND_DIR))
def _load_adapter_module(module_name: str) -> ModuleType:
sys.modules.pop(module_name, None)
sys.modules.pop('cli', None)
return importlib.import_module(module_name)
@pytest.fixture
def app_adapter() -> ModuleType:
return _load_adapter_module('cli.tui.adapters.app')
@pytest.fixture
def cache_adapter() -> ModuleType:
return _load_adapter_module('cli.tui.adapters.cache')
@pytest.fixture
def crypto_adapter() -> ModuleType:
return _load_adapter_module('cli.tui.adapters.crypto')
@pytest.fixture
def database_adapter() -> ModuleType:
return _load_adapter_module('cli.tui.adapters.database')
@pytest.fixture
def jobs_adapter() -> ModuleType:
return _load_adapter_module('cli.tui.adapters.jobs')
@pytest.fixture
def gen_adapter() -> ModuleType:
return _load_adapter_module('cli.tui.adapters.gen')
@pytest.fixture
def configs_adapter() -> ModuleType:
return _load_adapter_module('cli.tui.adapters.configs')
@pytest.fixture
def ops_adapter() -> ModuleType:
return _load_adapter_module('cli.tui.adapters.ops')
@pytest.fixture
def health_adapter() -> ModuleType:
return _load_adapter_module('cli.tui.adapters.health')
@pytest.fixture
def load_adapter_module() -> Callable[[str], ModuleType]:
return _load_adapter_module
@pytest.fixture(autouse=True)
def dispose_async_db_engine_after_test() -> None:
"""
在每个 TUI adapter 测试结束后尝试释放全局异步数据库连接池。
这些适配器测试会按需导入运行时模块;若其中某些路径触发真实数据库访问,
模块级 `async_engine` 可能在测试进程结束前仍持有连接,从而在 GC 阶段产生
SQLAlchemy 未归还连接告警。这里统一在测试后主动 `dispose()`,将清理职责收口
到测试夹具而非业务代码。
:return: None
"""
yield
database_module = sys.modules.get('config.database')
async_engine = getattr(database_module, 'async_engine', None) if database_module is not None else None
if async_engine is not None:
asyncio.run(async_engine.dispose())
@@ -0,0 +1,345 @@
from types import ModuleType, SimpleNamespace
from pytest import MonkeyPatch
def test_collect_app_page_snapshot_builds_env_config_and_route_sections(
monkeypatch: MonkeyPatch,
app_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',
'appEnv': 'dev',
'envFile': '.env.dev',
'envFileExists': True,
'backendDir': '/srv/backend',
'pythonExecutable': '/opt/conda/envs/shierfeng-fastapi/bin/python',
},
}
)
if arguments[0:2] == ('app', 'config'):
return SimpleNamespace(
payload={
'ok': True,
'config': {
'name': 'ShiErFeng',
'host': '127.0.0.1',
'port': 8000,
'rootPath': '/',
'workers': 2,
'reload': True,
'disableSwagger': False,
'disableRedoc': True,
'logLevel': 'INFO',
'dbType': 'mysql',
'dbHost': '127.0.0.1',
'dbPort': 3306,
'dbDatabase': 'shierfeng',
'redisHost': '127.0.0.1',
'redisPort': 6379,
'transportCryptoEnabled': True,
'transportCryptoMode': 'strict',
},
}
)
if arguments[0:2] == ('app', 'routes'):
return SimpleNamespace(
payload={
'ok': True,
'count': 3,
'routes': [],
'groupedRoutes': {'system': [{}, {}], 'monitor': [{}]},
}
)
if arguments[0:2] == ('app', 'doctor'):
return SimpleNamespace(
payload={
'ok': True,
'env': 'dev',
'database': {'ok': True, 'message': '数据库连接成功'},
'redis': {'ok': True, 'message': 'Redis连接成功'},
'crypto': {'ok': True, 'message': '传输加密配置有效'},
}
)
if arguments[0:2] == ('completion', 'doctor'):
return SimpleNamespace(
payload={
'ok': True,
'message': 'completion 诊断信息已生成',
'activeShell': 'bash',
'projectDir': '/srv/backend',
'envChoices': ['dev', 'prod'],
'completeEnvVar': '_SHIERFENG_COMPLETE',
'recommendedInstallCommand': 'shierfeng completion install --shell=bash --activate',
'shells': {
'bash': {
'supported': True,
'detected': True,
'targetFile': '/tmp/shierfeng.bash',
'targetFileExists': True,
'rcFile': '/Users/demo/.bashrc',
'rcFileExists': True,
'autoDiscovery': False,
'sourceCommand': 'source /tmp/shierfeng.bash',
'recommendedInstallCommand': 'shierfeng completion install --shell=bash --activate',
},
'zsh': {'supported': True, 'detected': False, 'autoDiscovery': False},
'fish': {'supported': True, 'detected': False, 'autoDiscovery': True},
'powershell': {
'supported': True,
'detected': False,
'autoDiscovery': False,
'sourceCommand': '. "/tmp/shierfeng.ps1"',
'recommendedInstallCommand': 'shierfeng completion install --shell=powershell --activate',
},
},
}
)
if arguments[0:2] == ('completion', 'show'):
return SimpleNamespace(
returncode=0,
stdout='_SHIERFENG_COMPLETE=bash_complete\n_shierfeng_completion() {\n COMPREPLY=()\n}\n',
stderr='',
)
return SimpleNamespace(
payload={
'ok': True,
'count': 3,
'routes': [
{
'path': '/system/user/list',
'methods': ['GET'],
'summary': '查询用户列表',
}
],
'groupedRoutes': {
'system': [{}, {}],
'monitor': [{}],
},
}
)
monkeypatch.setattr(app_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
snapshot = app_adapter.APP_DETAIL_ADAPTER.collect_snapshot('dev')
assert snapshot.title == '应用'
assert (
snapshot.subtitle
== '聚焦:环境映射 / 配置摘要 / 启动前检查 | 当前已加载 3 条注册路由,可继续查看环境映射、配置摘要、启动前检查、补全诊断与路由状态'
)
assert [section.title for section in snapshot.sections] == [
'总览判断',
'环境解析',
'应用配置',
'依赖配置',
'启动前检查',
'补全诊断',
'补全脚本预览',
'补全安装入口',
'路由摘要',
'启动入口',
]
assert snapshot.search is not None
assert snapshot.search.placeholder == '按分区或内容搜索'
assert any('CLI 环境: dev' in line for line in snapshot.sections[0].lines)
assert any('CLI 目标环境: dev' in line for line in snapshot.sections[1].lines)
assert any('监听地址: 127.0.0.1:8000' in line for line in snapshot.sections[2].lines)
assert any('传输加密: 开启' in line for line in snapshot.sections[3].lines)
assert any('数据库: 正常' in line for line in snapshot.sections[4].lines)
assert any('活动 Shell: bash' in line for line in snapshot.sections[5].lines)
assert any('source /tmp/shierfeng.bash' in line for line in snapshot.sections[5].lines)
assert any('Shell: bash' in line for line in snapshot.sections[6].lines)
assert any('_SHIERFENG_COMPLETE=bash_complete' in line for line in snapshot.sections[6].lines)
assert any('completion install --activate --output=text' in line for line in snapshot.sections[7].lines)
assert any('system · 2 条' in line for line in snapshot.sections[8].lines)
assert any('app run --env=dev' in line for line in snapshot.sections[9].lines)
def test_collect_app_page_snapshot_applies_query_filter(
monkeypatch: MonkeyPatch,
app_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', 'config'):
return SimpleNamespace(
payload={
'ok': True,
'config': {
'name': 'ShiErFeng',
'host': '127.0.0.1',
'port': 8000,
'rootPath': '/',
'workers': 2,
'reload': True,
'disableSwagger': False,
'disableRedoc': True,
'logLevel': 'INFO',
'dbType': 'mysql',
'dbHost': '127.0.0.1',
'dbPort': 3306,
'dbDatabase': 'shierfeng',
'redisHost': '127.0.0.1',
'redisPort': 6379,
'transportCryptoEnabled': True,
'transportCryptoMode': 'strict',
},
}
)
if arguments[0:2] == ('completion', 'doctor'):
return SimpleNamespace(
payload={
'ok': True,
'message': 'completion 诊断信息已生成',
'activeShell': 'bash',
'projectDir': '/srv/backend',
'envChoices': ['dev'],
'completeEnvVar': '_SHIERFENG_COMPLETE',
'recommendedInstallCommand': 'shierfeng completion install --shell=bash --activate',
'shells': {'bash': {'supported': True, 'detected': True, 'autoDiscovery': False}},
}
)
return SimpleNamespace(payload={'ok': True, 'count': 1, 'routes': [], 'groupedRoutes': {}})
monkeypatch.setattr(app_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
snapshot = app_adapter.APP_DETAIL_ADAPTER.collect_snapshot('dev', query='路由')
assert [section.title for section in snapshot.sections] == ['总览判断', '路由摘要']
assert snapshot.search is not None
assert snapshot.search.query == '路由'
def test_collect_ops_page_snapshot_builds_health_dependency_and_server_sections(
monkeypatch: MonkeyPatch,
ops_adapter: ModuleType,
) -> None:
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
del parse_json
if arguments[0:2] == ('ops', 'health'):
return SimpleNamespace(
payload={
'ok': True,
'env': 'dev',
'database': {'ok': True, 'message': '数据库连接成功'},
'redis': {'ok': True, 'message': 'Redis连接成功'},
}
)
if arguments[0:2] == ('ops', 'ping-db'):
return SimpleNamespace(payload={'ok': True, 'message': '数据库连接成功'})
if arguments[0:2] == ('ops', 'ping-redis'):
return SimpleNamespace(payload={'ok': True, 'message': 'Redis连接成功'})
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'},
'typer': {'installed': True, 'version': '0.12.0'},
'alembic': {'installed': True, 'version': '1.13.0'},
},
}
)
return SimpleNamespace(
payload={
'ok': True,
'server': {
'sys': {
'computerName': 'shierfeng-node',
'computerIp': '10.0.0.8',
'osName': 'Linux',
'osArch': 'x86_64',
},
'cpu': {'cpuNum': 8, 'used': 21.5},
'mem': {'total': '16 GB', 'usage': 48.2},
'py': {'version': '3.10.0', 'runTime': '2天4小时', 'used': '256 MB', 'total': '1 GB'},
'sysFiles': [
{
'dirName': '/',
'used': '120 GB',
'total': '256 GB',
'usage': '46%',
'free': '136 GB',
}
],
},
}
)
monkeypatch.setattr(ops_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
snapshot = ops_adapter.OPS_DETAIL_ADAPTER.collect_snapshot('dev')
assert snapshot.title == '运维'
assert (
snapshot.subtitle
== '聚焦:数据库连通性 / Redis 连通性 / 依赖版本 | 基础探活和依赖状态正常,可继续查看服务器资源与磁盘样本'
)
assert [section.title for section in snapshot.sections] == [
'总览判断',
'健康检查',
'数据库探活',
'Redis 探活',
'依赖版本',
'服务器摘要',
'磁盘样本',
'生产巡检入口',
]
assert any('探活状态: 正常' in line for line in snapshot.sections[0].lines)
assert any('数据库: 正常' in line for line in snapshot.sections[1].lines)
assert any('数据库连接: 正常' in line for line in snapshot.sections[2].lines)
assert any('Redis 连接: 正常' in line for line in snapshot.sections[3].lines)
assert any('fastapi · 已安装' in line for line in snapshot.sections[4].lines)
assert any('主机名: shierfeng-node' in line for line in snapshot.sections[5].lines)
assert any('使用率 46%' in line for line in snapshot.sections[6].lines)
assert any('wizard prod-check' in line for line in snapshot.sections[7].lines)
assert snapshot.search is not None
assert snapshot.search.placeholder == '按分区或内容搜索'
def test_collect_ops_page_snapshot_surfaces_health_risk_subtitle(
monkeypatch: MonkeyPatch,
ops_adapter: ModuleType,
) -> None:
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
del parse_json
if arguments[0:2] == ('ops', 'health'):
return SimpleNamespace(
payload={
'ok': False,
'env': 'dev',
'database': {'ok': False, 'message': '数据库连接失败'},
'redis': {'ok': True, 'message': 'Redis连接成功'},
}
)
if arguments[0:2] == ('ops', 'ping-db'):
return SimpleNamespace(payload={'ok': False, 'message': '数据库连接失败'})
if arguments[0:2] == ('ops', 'ping-redis'):
return SimpleNamespace(payload={'ok': True, 'message': 'Redis连接成功'})
if arguments[0:2] == ('ops', 'deps'):
return SimpleNamespace(payload={'ok': True, 'missingRequired': [], 'packages': {}, 'message': '依赖正常'})
return SimpleNamespace(payload={'ok': True, 'server': {'sys': {}, 'cpu': {}, 'mem': {}, 'py': {}}})
monkeypatch.setattr(ops_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
snapshot = ops_adapter.OPS_DETAIL_ADAPTER.collect_snapshot('dev')
assert (
snapshot.subtitle
== '聚焦:数据库连通性 / Redis 连通性 / 依赖版本 | 运维探活存在异常,优先核对数据库/Redis 连通性与依赖版本'
)
@@ -0,0 +1,346 @@
from types import ModuleType, SimpleNamespace
from pytest import MonkeyPatch
def test_collect_cache_page_snapshot_builds_cache_browser_records(
monkeypatch: MonkeyPatch,
cache_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] == ('cache', 'stats'):
return SimpleNamespace(
payload={
'ok': True,
'dbSize': 128,
'info': {
'redis_version': '7.0',
'connected_clients': 12,
'used_memory_human': '16M',
},
'commandStats': [{'name': 'get', 'value': 88}],
'cacheNames': [{'cacheName': 'sys_config', 'remark': '系统参数缓存'}],
}
)
if arguments[0:2] == ('cache', 'keys'):
return SimpleNamespace(
payload={
'ok': True,
'cacheName': 'sys_config',
'count': 2,
'keys': ['site_name', 'site_logo'],
}
)
if arguments[0:2] == ('cache', 'get'):
return SimpleNamespace(
payload={
'ok': True,
'cacheName': 'sys_config',
'cacheKey': arguments[3],
'cacheValue': 'hello-world',
}
)
return SimpleNamespace(
payload={
'ok': True,
'cacheName': 'sys_config',
'cacheKey': arguments[3],
'ttlSeconds': 3600,
'persistent': False,
'expires': True,
}
)
monkeypatch.setattr(cache_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
snapshot = cache_adapter.CACHE_BROWSER_ADAPTER.collect_snapshot('dev')
assert snapshot.title == '缓存'
assert (
snapshot.subtitle
== '聚焦:Redis 键数 / 连接数 / 缓存名前缀 | 缓存基线正常,当前已加载 1 个缓存名,Redis 键数 128,连接数 12,可继续查看键列表、键值样本和 TTL'
)
assert len(snapshot.records) == 1
assert snapshot.records[0].title == 'sys_config'
detail_sections = snapshot.records[0].resolve_detail_sections()
assert [section.title for section in detail_sections] == [
'键摘要',
'键列表',
'键详情 · site_name',
'键详情 · site_logo',
]
assert any('site_name' in line for line in detail_sections[1].lines)
assert any('TTL: 3600 秒' in line for line in detail_sections[2].lines)
assert any('hello-world' in line for line in detail_sections[2].lines)
assert any('TTL: 3600 秒' in line for line in detail_sections[3].lines)
assert snapshot.shared_sections[0].title == '总览判断'
assert any('已登记缓存名: 1 个' in line for line in snapshot.shared_sections[0].lines)
assert any('当前匹配: 1 个' in line for line in snapshot.shared_sections[0].lines)
assert snapshot.shared_sections[1].title == 'Redis 摘要'
assert snapshot.shared_sections[2].title == '命令统计'
assert snapshot.shared_sections[3].title == '缓存清理入口'
assert any('wizard cache-clear' in line for line in snapshot.shared_sections[3].lines)
assert any(call[0:2] == ('cache', 'get') for call in calls)
assert any(call[0:2] == ('cache', 'ttl') for call in calls)
def test_cache_browser_adapter_collect_snapshot_exposes_search_context(
monkeypatch: MonkeyPatch,
cache_adapter: ModuleType,
) -> None:
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
del parse_json
if arguments[0:2] == ('cache', 'stats'):
return SimpleNamespace(
payload={
'ok': True,
'dbSize': 8,
'info': {
'redis_version': '7.0',
'connected_clients': 2,
'used_memory_human': '1M',
},
'commandStats': [],
'cacheNames': [{'cacheName': 'sys_config', 'remark': '系统参数缓存'}],
}
)
return SimpleNamespace(payload={'ok': False, 'message': 'unexpected'})
monkeypatch.setattr(cache_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
snapshot = cache_adapter.CACHE_BROWSER_ADAPTER.collect_snapshot('dev', query='sys')
assert snapshot.title == '缓存'
assert snapshot.search is not None
assert snapshot.search.query == 'sys'
assert snapshot.records
assert snapshot.records[0].title == 'sys_config'
def test_collect_database_page_snapshot_builds_check_heads_and_history_sections(
monkeypatch: MonkeyPatch,
database_adapter: ModuleType,
) -> None:
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
del parse_json
if arguments[0:2] == ('db', 'current'):
return SimpleNamespace(payload={'ok': True, 'currentRevision': '202604300001'})
if arguments[0:2] == ('db', 'check'):
return SimpleNamespace(payload={'ok': True, 'message': '数据库连接成功'})
if arguments[0:2] == ('db', 'heads'):
return SimpleNamespace(
payload={
'ok': True,
'message': '已读取 Alembic heads',
'count': 1,
'items': [
{
'revision': '202604300001',
'downRevisions': ['202604290001'],
'branchLabels': ['main'],
'dependsOn': [],
'doc': 'create user table',
'path': '/srv/backend/alembic/versions/202604300001_create_user.py',
}
],
}
)
if arguments[0:2] == ('db', 'history'):
return SimpleNamespace(
payload={
'ok': True,
'message': '已读取 Alembic 历史版本',
'count': 2,
'totalCount': 2,
'limit': 8,
'items': [
{
'revision': '202604300001',
'downRevisions': ['202604290001'],
'branchLabels': ['main'],
'dependsOn': [],
'doc': 'create user table',
'path': '/srv/backend/alembic/versions/202604300001_create_user.py',
},
{
'revision': '202604290001',
'downRevisions': [],
'branchLabels': [],
'dependsOn': [],
'doc': 'init schema',
'path': '/srv/backend/alembic/versions/202604290001_init_schema.py',
},
],
}
)
return SimpleNamespace(
payload={
'ok': True,
'config': {
'dbType': 'mysql',
'dbHost': '127.0.0.1',
'dbPort': 3306,
'dbDatabase': 'shierfeng',
},
}
)
monkeypatch.setattr(database_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
snapshot = database_adapter.DATABASE_DETAIL_ADAPTER.collect_snapshot('dev')
assert snapshot.title == '数据库'
assert (
snapshot.subtitle
== '聚焦:迁移版本 / 连接状态 / Heads | 数据库基线正常,当前 revision 202604300001,可继续查看连接、heads 和历史版本'
)
assert [section.title for section in snapshot.sections] == [
'总览判断',
'迁移版本',
'连接信息',
'连通性检查',
'Heads 状态',
'历史版本',
'初始化预演入口',
'升级入口',
]
assert any('当前 revision: 202604300001' in line for line in snapshot.sections[0].lines)
assert any('迁移版本: 202604300001' in line for line in snapshot.sections[1].lines)
assert any('连接地址: 127.0.0.1:3306' in line for line in snapshot.sections[2].lines)
assert any('数据库连接: 正常' in line for line in snapshot.sections[3].lines)
assert any('Heads 数量: 1' in line for line in snapshot.sections[4].lines)
assert any('Head 01 · 202604300001' in line for line in snapshot.sections[4].lines)
assert any('总版本数: 2' in line for line in snapshot.sections[5].lines)
assert any('版本 02 · 202604290001' in line for line in snapshot.sections[5].lines)
assert any('db init --dry-run --output=text' in line for line in snapshot.sections[6].lines)
assert any('wizard db-upgrade' in line for line in snapshot.sections[7].lines)
assert snapshot.search is not None
assert snapshot.search.placeholder == '按 revision 搜索'
def test_collect_database_page_snapshot_surfaces_failed_heads(
monkeypatch: MonkeyPatch,
database_adapter: ModuleType,
) -> None:
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
del parse_json
if arguments[0:2] == ('db', 'current'):
return SimpleNamespace(payload={'ok': True, 'currentRevision': '202604300001'})
if arguments[0:2] == ('db', 'check'):
return SimpleNamespace(payload={'ok': True, 'message': '数据库连接成功'})
if arguments[0:2] == ('db', 'heads'):
return SimpleNamespace(payload={'ok': False, 'message': '读取 Alembic heads 失败', 'error': 'broken graph'})
if arguments[0:2] == ('db', 'history'):
return SimpleNamespace(payload={'ok': True, 'count': 0, 'totalCount': 0, 'limit': 8, 'items': []})
return SimpleNamespace(
payload={
'ok': True,
'config': {
'dbType': 'mysql',
'dbHost': '127.0.0.1',
'dbPort': 3306,
'dbDatabase': 'shierfeng',
},
}
)
monkeypatch.setattr(database_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
snapshot = database_adapter.DATABASE_DETAIL_ADAPTER.collect_snapshot('dev')
assert snapshot.subtitle == '聚焦:迁移版本 / 连接状态 / Heads | 数据库存在迁移分叉风险,优先确认 heads 和历史版本'
assert snapshot.sections[0].title == '总览判断'
assert snapshot.sections[0].status == 'warn'
assert snapshot.sections[4].title == 'Heads 状态'
assert snapshot.sections[4].status == 'fail'
assert any('读取 Alembic heads 失败' in line for line in snapshot.sections[4].lines)
def test_collect_database_page_snapshot_applies_query_filter(
monkeypatch: MonkeyPatch,
database_adapter: ModuleType,
) -> None:
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
del parse_json
if arguments[0:2] == ('db', 'current'):
return SimpleNamespace(payload={'ok': True, 'currentRevision': '202604300001'})
if arguments[0:2] == ('db', 'check'):
return SimpleNamespace(payload={'ok': True, 'message': '数据库连接成功'})
if arguments[0:2] == ('db', 'heads'):
return SimpleNamespace(payload={'ok': True, 'count': 1, 'items': []})
if arguments[0:2] == ('db', 'history'):
return SimpleNamespace(payload={'ok': True, 'count': 0, 'totalCount': 0, 'limit': 8, 'items': []})
return SimpleNamespace(
payload={
'ok': True,
'config': {'dbType': 'mysql', 'dbHost': '127.0.0.1', 'dbPort': 3306, 'dbDatabase': 'shierfeng'},
}
)
monkeypatch.setattr(database_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
snapshot = database_adapter.DATABASE_DETAIL_ADAPTER.collect_snapshot('dev', query='历史')
assert [section.title for section in snapshot.sections] == ['历史版本']
assert snapshot.search is not None
assert snapshot.search.query == '历史'
def test_collect_crypto_page_snapshot_builds_validate_and_public_key_sections(
monkeypatch: MonkeyPatch,
crypto_adapter: ModuleType,
) -> None:
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
del parse_json
if arguments[0:2] == ('crypto', 'validate'):
return SimpleNamespace(
payload={
'ok': True,
'message': '传输加密配置校验通过',
}
)
return SimpleNamespace(
payload={
'ok': True,
'publicKey': {
'kid': 'default',
'alg': 'RSA-OAEP',
'envelopeVersion': 'v1',
'expireAt': '2099-12-31T23:59:59',
'supportedKids': ['default', 'legacy-a'],
'publicKey': '-----BEGIN PUBLIC KEY-----\nAAA\nBBB\n-----END PUBLIC KEY-----',
},
}
)
monkeypatch.setattr(crypto_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
snapshot = crypto_adapter.CRYPTO_DETAIL_ADAPTER.collect_snapshot('dev')
assert snapshot.title == '传输加密'
assert (
snapshot.subtitle
== '聚焦:运行校验 / 公钥身份 / 兼容版本 | 当前 KID default,兼容版本 2 个,可继续查看公钥身份、兼容版本与轮换预演入口'
)
assert [section.title for section in snapshot.sections] == [
'总览判断',
'运行校验',
'公钥身份',
'兼容版本',
'公钥预览',
'密钥生成入口',
'轮换预演入口',
]
assert any('运行校验: 通过' in line for line in snapshot.sections[0].lines)
assert any('状态: 通过' in line for line in snapshot.sections[1].lines)
assert any('KID: default' in line for line in snapshot.sections[2].lines)
assert any('支持版本: legacy-a' in line for line in snapshot.sections[3].lines)
assert any('BEGIN PUBLIC KEY' in line for line in snapshot.sections[4].lines)
assert any('crypto keygen' in line for line in snapshot.sections[5].lines)
assert any('crypto rotate' in line for line in snapshot.sections[6].lines)
assert snapshot.search is not None
assert snapshot.search.placeholder == '按分区或内容搜索'
@@ -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'
@@ -0,0 +1,439 @@
from types import ModuleType, SimpleNamespace
from pytest import MonkeyPatch
def test_collect_jobs_page_snapshot_links_first_job_detail_and_logs(
monkeypatch: MonkeyPatch,
jobs_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] == ('job', 'list'):
return SimpleNamespace(
payload={
'ok': True,
'page': {
'rows': [
{
'jobId': 101,
'jobName': '同步任务',
'status': '0',
'cronExpression': '0/30 * * * * ?',
}
]
},
}
)
if arguments[0:2] == ('job', 'detail'):
return SimpleNamespace(
payload={
'ok': True,
'job': {
'jobId': 101,
'jobName': '同步任务',
'jobGroup': 'DEFAULT',
'status': '0',
'cronExpression': '0/30 * * * * ?',
'invokeTarget': 'demo.sync',
},
}
)
if arguments[0:2] == ('job', 'logs') and '--status=1' in arguments:
return SimpleNamespace(
payload={
'ok': True,
'page': {
'rows': [
{
'jobLogId': 2,
'jobName': '同步任务',
'status': '1',
'jobMessage': '执行失败',
'exceptionInfo': 'traceback...',
'createTime': '2026-04-30 10:01:00',
}
]
},
}
)
return SimpleNamespace(
payload={
'ok': True,
'page': {
'rows': [
{
'jobLogId': 1,
'jobName': '同步任务',
'status': '0',
'jobMessage': '执行成功',
'createTime': '2026-04-30 10:00:00',
}
]
},
}
)
monkeypatch.setattr(jobs_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
snapshot = jobs_adapter.JOBS_BROWSER_ADAPTER.collect_snapshot('dev')
assert snapshot.title == '任务'
assert (
snapshot.subtitle
== '聚焦:失败聚合 / 暂停任务 / 执行轨迹 | 当前筛选:全部,已匹配 1 条任务,失败任务 1 个,暂停任务 0 个'
)
assert len(snapshot.records) == 1
assert snapshot.records[0].title == '同步任务'
assert snapshot.shared_sections[0].title == '总览判断'
assert any('存在失败任务' in line for line in snapshot.shared_sections[0].lines)
assert any('失败任务: 1 个' in line for line in snapshot.shared_sections[0].lines)
assert snapshot.shared_sections[1].title == '失败聚合'
assert any('失败日志: 1 条' in line for line in snapshot.shared_sections[1].lines)
assert any('同步任务 · 1 次' in line for line in snapshot.shared_sections[1].lines)
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[2].lines)
assert any('最近一次执行:' in line for line in detail_sections[2].lines)
assert any('执行轨道:' in line for line in detail_sections[2].lines)
assert any('轨道窗口:' in line for line in detail_sections[2].lines)
assert any('2026-04-30 10:00:00' in line for line in detail_sections[3].lines)
assert any('o 状态: 成功' in line for line in detail_sections[3].lines)
assert any('轨道: ├─采集 ├─执行 └─落盘' in line for line in detail_sections[3].lines)
assert any('轨迹: ●─●─◎' in line for line in detail_sections[3].lines)
assert any('异常: traceback...' in line for line in detail_sections[4].lines)
assert any(call[0:2] == ('job', 'detail') for call in calls)
assert any(call[0:2] == ('job', 'logs') and '--job-name=同步任务' in call for call in calls)
assert any(call[0:2] == ('job', 'logs') and '--status=1' in call for call in calls)
def test_collect_jobs_page_snapshot_applies_failed_filter(
monkeypatch: MonkeyPatch,
jobs_adapter: ModuleType,
) -> None:
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
del parse_json
if arguments[0:2] == ('job', 'list'):
return SimpleNamespace(
payload={
'ok': True,
'page': {
'rows': [
{
'jobId': 101,
'jobName': '失败任务',
'status': '0',
'cronExpression': '0/30 * * * * ?',
},
{
'jobId': 102,
'jobName': '暂停任务',
'status': '1',
'cronExpression': '0/45 * * * * ?',
},
{
'jobId': 103,
'jobName': '正常任务',
'status': '0',
'cronExpression': '0/50 * * * * ?',
},
]
},
}
)
if arguments[0:2] == ('job', 'logs') and '--status=1' in arguments:
return SimpleNamespace(
payload={
'ok': True,
'page': {
'rows': [
{
'jobLogId': 2,
'jobName': '失败任务',
'status': '1',
'jobMessage': '执行失败',
'createTime': '2026-04-30 10:01:00',
}
]
},
}
)
return SimpleNamespace(payload={'ok': True, 'page': {'rows': []}})
monkeypatch.setattr(jobs_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
snapshot = jobs_adapter.JOBS_BROWSER_ADAPTER.collect_snapshot('dev', filter_key='failed')
assert snapshot.active_filter_key == 'failed'
assert [option.key for option in snapshot.filters] == ['all', 'failed', 'paused', 'ok']
assert len(snapshot.records) == 1
assert snapshot.records[0].title == '失败任务'
assert '当前筛选:失败' in snapshot.subtitle
assert snapshot.shared_sections[0].title == '总览判断'
assert any('当前筛选: 失败' in line for line in snapshot.shared_sections[0].lines)
assert snapshot.shared_sections[1].title == '失败聚合'
assert any('涉及任务: 1 个' in line for line in snapshot.shared_sections[1].lines)
assert snapshot.search is not None
assert snapshot.search.placeholder == '按任务名搜索'
def test_collect_jobs_page_snapshot_applies_query_filter(
monkeypatch: MonkeyPatch,
jobs_adapter: ModuleType,
) -> None:
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
del parse_json
if arguments[0:2] == ('job', 'list'):
return SimpleNamespace(
payload={
'ok': True,
'page': {
'rows': [
{
'jobId': 1,
'jobName': 'sync-user',
'jobGroup': 'default',
'status': '0',
'cronExpression': '*',
},
{
'jobId': 2,
'jobName': 'clean-cache',
'jobGroup': 'ops',
'status': '0',
'cronExpression': '*',
},
]
},
}
)
return SimpleNamespace(payload={'ok': True, 'page': {'rows': []}})
monkeypatch.setattr(jobs_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
snapshot = jobs_adapter.JOBS_BROWSER_ADAPTER.collect_snapshot('dev', query='sync')
assert [record.title for record in snapshot.records] == ['sync-user']
assert snapshot.search is not None
assert snapshot.search.query == 'sync'
def test_collect_jobs_page_snapshot_surfaces_failure_message(
monkeypatch: MonkeyPatch,
jobs_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(jobs_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
snapshot = jobs_adapter.JOBS_BROWSER_ADAPTER.collect_snapshot('dev')
assert snapshot.subtitle.startswith('任务数据不可用:')
assert snapshot.records[0].status == 'fail'
assert any('读取定时任务列表失败' in line for line in snapshot.records[0].detail_sections[0].lines)
def test_collect_gen_page_snapshot_links_first_table_detail(
monkeypatch: MonkeyPatch,
gen_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] == ('gen', 'list'):
return SimpleNamespace(
payload={
'ok': True,
'page': {
'rows': [
{
'tableId': 201,
'tableName': 'sys_user',
'className': 'SysUser',
'moduleName': 'system',
}
]
},
}
)
if arguments[0:2] == ('gen', 'detail'):
return SimpleNamespace(
payload={
'ok': True,
'tableId': 201,
'tableName': 'sys_user',
'columnCount': 12,
'detail': {
'info': {
'className': 'SysUser',
'moduleName': 'system',
'businessName': 'user',
'functionName': '用户管理',
},
'rows': [
{
'columnName': 'user_id',
'columnType': 'bigint',
'isPk': '1',
'isRequired': '1',
'queryType': 'EQ',
}
],
},
}
)
if arguments[0:2] == ('gen', 'preview'):
return SimpleNamespace(
payload={
'ok': True,
'tableId': 201,
'templateCount': 2,
'preview': {
'api.py.vm': 'def list_users():\n return []',
'index.vue.vm': '<template>\n <div />\n</template>',
},
}
)
if arguments[0:2] == ('gen', 'export'):
return SimpleNamespace(
payload={
'ok': True,
'env': 'dev',
'mode': 'zip',
'dryRun': True,
'message': '代码导出预演完成',
'tableNames': ['sys_user'],
'results': [
{
'tableName': 'sys_user',
'ok': True,
'message': '已生成 6 份模板预演结果',
}
],
}
)
if arguments[0:2] == ('gen', 'db-list') and '--table-name=sys_user' in arguments:
return SimpleNamespace(
payload={
'ok': True,
'page': {
'rows': [
{
'tableName': 'sys_user',
'tableComment': '用户表',
}
]
},
}
)
return SimpleNamespace(
payload={
'ok': True,
'page': {
'rows': [
{
'tableName': 'sys_role',
'tableComment': '角色表',
}
]
},
}
)
monkeypatch.setattr(gen_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
snapshot = gen_adapter.GEN_BROWSER_ADAPTER.collect_snapshot('dev')
assert snapshot.title == '代码生成'
assert (
snapshot.subtitle
== '聚焦:生成前校验 / 同步预检查 / 代码预览 | 当前已匹配 1 张业务表,可导入物理表 1 张,可继续查看表定义、预检查和代码预览'
)
assert len(snapshot.records) == 1
assert snapshot.records[0].title == 'sys_user'
detail_sections = snapshot.records[0].resolve_detail_sections()
assert [section.title for section in detail_sections] == [
'业务表摘要',
'生成配置',
'字段摘要',
'生成前校验',
'同步预检查',
'字段列表',
'代码预览',
'导出预览',
'导出入口',
]
assert any('模板预览: 2 份' in line for line in detail_sections[3].lines)
assert any('数据库物理表: sys_user' in line for line in detail_sections[4].lines)
assert any('模板数量: 2' in line for line in detail_sections[6].lines)
assert any('api.py.vm' in line for line in detail_sections[6].lines)
assert any('代码导出预演完成' in line for line in detail_sections[7].lines)
assert any('wizard gen-export' in line for line in detail_sections[8].lines)
assert snapshot.shared_sections[0].title == '总览判断'
assert any('可导入物理表: 1 张' in line for line in snapshot.shared_sections[0].lines)
assert any('代码预览 / 导出预览 / 导入入口 / 建表入口' in line for line in snapshot.shared_sections[0].lines)
assert snapshot.shared_sections[1].title == '可导入数据表'
assert snapshot.shared_sections[2].title == '导入入口'
assert any(
'gen import-table sys_role --dry-run --output=text' in line for line in snapshot.shared_sections[2].lines
)
assert snapshot.shared_sections[3].title == '建表入口'
assert any('gen create-table --dry-run --sql' in line for line in snapshot.shared_sections[3].lines)
assert any(call[0:2] == ('gen', 'detail') for call in calls)
assert any(call[0:2] == ('gen', 'preview') for call in calls)
assert any(call[0:2] == ('gen', 'export') and '--dry-run' in call for call in calls)
assert any(call[0:2] == ('gen', 'db-list') and '--table-name=sys_user' in call for call in calls)
def test_gen_browser_adapter_collect_snapshot_exposes_search_context(
monkeypatch: MonkeyPatch,
gen_adapter: ModuleType,
) -> None:
def fake_run_nested_cli_command(*arguments: str, parse_json: bool = False) -> SimpleNamespace:
del parse_json
if arguments[0:2] == ('gen', 'list'):
return SimpleNamespace(
payload={
'ok': True,
'page': {
'rows': [
{
'tableId': 201,
'tableName': 'sys_user',
'className': 'SysUser',
'moduleName': 'system',
}
]
},
}
)
return SimpleNamespace(payload={'ok': True, 'page': {'rows': []}})
monkeypatch.setattr(gen_adapter.NESTED_CLI_SUPPORT, 'run', fake_run_nested_cli_command)
snapshot = gen_adapter.GEN_BROWSER_ADAPTER.collect_snapshot('dev', query='sys')
assert snapshot.title == '代码生成'
assert snapshot.search is not None
assert snapshot.search.query == 'sys'
assert snapshot.records
assert snapshot.records[0].title == 'sys_user'