# FWH MCP Server 详细测试用例 > 配套文档:`TEST_PLAN.md`(总体方案)。本文件是可执行的用例清单。 > 运行路径:`/var/www/fwh-toolbox/server`(cwd,加载 `config.env`) > 传输:stdio JSON-RPC;被测二进制:`./fwh-mcp-server` > 用例编号规则:`层级-序号`;三态结果:PASS / FAIL / SKIP(原因) 约定: - 「入参」为 `tools/call` 的 `arguments` 对象。 - 「期望」描述断言点;`isError=true` 指 CallToolResult 的错误结果(`mcp.NewToolResultError`)。 - 依赖不可用(连接失败)→ 记 SKIP;进程 panic / 结构非法 / 超时 → FAIL。 - `<真实SN>` / `<真实GICCID>` 为占位,需替换为实际测试样本。 --- ## L0 协议与工具清单 ### L0-1 初始化握手 - 方法:`initialize` - 入参:`protocolVersion` 用 client 默认,`clientInfo={name:"mcp-test",version:"1.0"}` - 期望:返回成功;`serverInfo.name=="fwh-tools"`;`serverInfo.version=="2.0.0"`;`protocolVersion` 非空。 ### L0-2 工具清单可枚举 - 方法:`tools/list`(循环翻页至 `nextCursor` 为空,聚合全部) - 期望:工具总数 > 0;无重复 name;导出到 `tools_snapshot.json`。 ### L0-3 命名规范 - 数据来源:L0-2 聚合结果 - 期望:每个 name 前缀 ∈ {`service_`,`data_`,`storage_`,`util_`,`plane_`};无其它前缀。 ### L0-4 Schema 完整性 - 数据来源:L0-2 - 期望:每个工具含非空 `description`、`inputSchema.type=="object"`;`inputSchema.required` 中的字段都存在于 `properties`。 ### L0-5 未知工具调用 - 方法:`tools/call`,`name="__no_such_tool__"`,`arguments={}` - 期望:返回错误(协议层 error 或 isError),进程不崩溃。 ### L0-6 工具清单快照回归(可选) - 期望:与上次 `tools_snapshot.json` diff;新增/删除/改名的工具需人工确认(防止误删注册)。 --- ## L1 纯函数与参数校验(不依赖后端) ### L1-1 SN 解析正确性 - 工具:`util_parse_sn` - 入参:`{"sn":"10060006A03X25310091"}` - 期望:`productCode=="1006"`;`productName` 含 "aGate";`hwVersion=="0006"`;`year=="2025"`;`week=="31"`;`serialNumber=="0091"`;`summary` 非空。 ### L1-2 SN 各产品码 - 工具:`util_parse_sn`,分别传 `1005...`/`1002...`/`1003...`/`9999...` - 期望:`productName` 分别为 aPower / aHub·PowerLink / MPPT / 未知产品。 ### L1-3 SN 缺参 - 入参:`{}` - 期望:`isError=true`,消息含「参数 sn 必填」。 ### L1-4 SN 长度不足 - 入参:`{"sn":"1006"}` - 期望:`isError=true`,消息含「SN 长度不足」。 ### L1-5 站点查询缺 sn - 工具:`service_site_query_info`,入参 `{}` - 期望:`isError=true`,「参数 sn 必填」。 ### L1-6 功率流日期格式错误 - 工具:`service_site_power_flow` - 入参:`{"sn":"ANY","date":"2025/01/01"}` - 期望:`isError=true`,「date 格式错误 ... yyyy-MM-dd」(在调用后端前返回)。 ### L1-7 SIM 月数据缺 month - 工具:`service_sim_monthly_data`,入参 `{"giccid":"x"}` - 期望:`isError=true`,「参数 month 必填」。 ### L1-8 mongo 非白名单 collection - 工具:`data_mongo_find` - 入参:`{"collection":"UserInfo"}` - 期望:`isError=true`,「不允许查询此集合」(`UserInfo` 已从白名单移除)。 ### L1-9 mongo filter 非法 JSON - 工具:`data_mongo_find` - 入参:`{"collection":"SiteInfoManageData","filter":"{bad json"}` - 期望:`isError=true`,「filter ... JSON 解析失败」。 ### L1-10 redis 通配符拦截 - 工具:`data_redis_exists` - 入参:`{"key":"foo*"}` - 期望:`isError=true`,「不支持通配符」。 ### L1-11 redis 非法 type - 工具:`data_redis_get` - 入参:`{"key":"any","type":"zset"}` - 期望:`isError=true`,「可选值: string, hash, list, set」。 ### L1-12 minio 非白名单 bucket - 工具:`storage_minio_list_objects` - 入参:`{"bucket":"secret-bucket"}` - 期望:`isError=true`,「不允许访问此 bucket」。 ### L1-13 emspara id 非数字 - 工具:`data_emspara_vid_info` - 入参:`{"id":"abc"}` - 期望:`isError=true`,「id ... 必须是数字」。 ### L1-14 pgsql 日期格式错误 - 工具:`data_pgsql_query_snapshot_data` - 入参:`{"sn":"ANY","date":"20250101"}` - 期望:`isError=true`,「date ... yyyy-MM-dd」。 ### L1-15 storage_file 路径穿越拦截 - 工具:`storage_file_read` - 入参:`{"path":"/uploads/../../etc/passwd"}`(或 `storage_file_read_to_local` 同理) - 期望:安全拒绝(不存在/非法路径),不读到 `/uploads/` 外文件。 ### L1-16 反馈类型校验 - 工具:`service_feedback_create` - 入参:`{"title":"t","type":"invalid"}` - 期望:`isError=true`,「type ... 必须是 bug 或 feature」(在调用 Plane 前返回,属参数校验,不产生写入)。 --- ## L2 只读工具连通性冒烟(依赖外部服务) > 每个用例通用期望:无 panic;返回合法 CallToolResult(成功 JSON / 业务空结果 / 结构化错误)。 > 连接类失败 → SKIP。业务空数据(如「查询结果为空」)算 PASS。 ### Redis 组 | 编号 | 工具 | 入参 | 期望 | |------|------|------|------| | L2-R1 | `data_redis_exists` | `{"key":"nonexistent_test_key"}` | 返回「key 不存在」或 exists 信息,结构合法 | | L2-R2 | `data_redis_get` | `{"key":"nonexistent_test_key"}` | 返回空/不存在提示,结构合法 | ### Mongo 组(白名单 collection) | 编号 | 工具 | 入参 | 期望 | |------|------|------|------| | L2-M1 | `data_mongo_count` | `{"collection":"SiteInfoManageData"}` | 返回 `{collection,count}`,count≥0 | | L2-M2 | `data_mongo_find` | `{"collection":"SiteInfoManageData","limit":1}` | 返回数组或「查询结果为空」;**若含 PII 须已脱敏(见 L4-5)** | | L2-M3 | `data_mongo_find` | `{"collection":"StatisticsAlarm","limit":1,"sort":{"createdAt":-1}}` | 结构合法 | ### PgSQL 组 | 编号 | 工具 | 入参 | 期望 | |------|------|------|------| | L2-P1 | `data_pgsql_query_snapshot_data` | `{"sn":"<真实SN>","date":"<近期日期>"}` | 返回数据或「查询结果为空」 | | L2-P2 | `data_pgsql_query_snapshot_file_record` | `{"file_name":"<已知文件名>"}` | 返回记录或「未找到文件记录」 | ### MinIO / 文件组 | 编号 | 工具 | 入参 | 期望 | |------|------|------|------| | L2-S1 | `storage_minio_list_objects` | `{"bucket":"fwh-toolbox","limit":5}` | 返回对象列表或「无匹配对象」 | | L2-S2 | `storage_minio_read_object` | `{"bucket":"fwh-toolbox","key":"<已知文本对象>"}` | 返回文本内容;非文本/超1MB 返回相应校验错误 | | L2-S3 | `storage_file_list` | `{}` | 返回文件列表或空 | ### Plane 组(真实凭据) | 编号 | 工具 | 入参 | 期望 | |------|------|------|------| | L2-PL1 | `plane_list_workspaces` | `{}` | 返回 workspace 列表;登录失败→SKIP | | L2-PL2 | `plane_list_projects` | `{}` | 返回项目列表 | | L2-PL3 | `plane_list_issues` | `{}`(或带 project) | 返回 issue 列表 | | L2-PL4 | `plane_list_labels` | `{}` | 返回标签列表 | | L2-PL5 | `plane_list_states` | `{}` | 返回状态列表 | | L2-PL6 | `service_feedback_history` | `{}` | 返回 Feedback 标签历史或空 | ### 业务层(依赖远程 MySQL) | 编号 | 工具 | 入参 | 期望 | |------|------|------|------| | L2-B1 | `service_site_search` | `{"page":1,"size":5}` | 返回分页站点列表;**PII 须脱敏(L4-2)** | | L2-B2 | `service_sim_search` | `{"page":1,"size":5}` | 返回分页 SIM 列表;**脱敏(L4-4)** | | L2-B3 | `service_monitor_alarm_stats` | `{}` | 返回最新告警统计或空 | | L2-B4 | `service_monitor_shutdown_stats` | `{}` | 返回最新停机统计或空 | | L2-B5 | `service_dashboard_shutdown_date` | `{}` | 返回日期字符串或「暂无数据」 | | L2-B6 | `service_dashboard_shutdown_detail` | `{}` | 结构合法 | | L2-B7 | `service_dashboard_alarm_detail` | `{}` | 结构合法 | | L2-B8 | `service_tou_template_list` | `{"page":1,"size":5}` | 返回模版列表 | | L2-B9 | `service_background_task_list` | `{}` | 返回任务列表或「当前无后台任务」 | | L2-B10 | `service_site_monitor_config_list` | `{}` | 返回配置列表 | | L2-B11 | `service_site_monitor_name_list` | `{}` | 返回名称列表 | | L2-B12 | `service_site_info_update_status` | `{}` | 返回更新状态 | | L2-B13 | `service_site_info_tags_list` | `{"page":1,"size":5}` | 返回标签列表 | | L2-B14 | `service_auto_update_get_options` | `{}` | 返回升级包/版本选项 | | L2-B15 | `service_auto_update_get_apower_version` | `{}` | 返回 aPower 版本列表 | | L2-B16 | `service_auto_update_file_list` | `{"page":1,"size":5}` | 返回文件列表 | | L2-B17 | `service_batch_download_list` | `{"page":1,"size":5}` | 返回任务列表 | | L2-B18 | `service_site_analysis_list` | `{"page":1,"size":5}` | 返回分析任务列表 | ### FWH 远程 API 组(凭据/网络需实测,失败→SKIP) | 编号 | 工具 | 入参 | 期望 | |------|------|------|------| | L2-F1 | `service_site_query_info` | `{"sn":"<真实SN>"}` | 返回站点详情;**PII 脱敏(L4-1)** | | L2-F2 | `service_site_power_flow` | `{"sn":"<真实SN>","date":"<近期日期>"}` | 返回功率流数据 | | L2-F3 | `service_sim_query` | `{"giccid":"<真实GICCID>"}` | 返回 SIM 详情;**脱敏(L4-3)** | | L2-F4 | `service_sim_monthly_data` | `{"giccid":"<真实GICCID>","month":""}` | 返回月用量 | | L2-F5 | `data_fwh_query_site_base_info_list` | 见工具描述最小必填 | 结构合法或结构化错误 | | L2-F6 | 其余 `data_fwh_*`(约 20 个) | 各取最小必填参数 | 逐一冒烟:结构合法 / SKIP | | L2-F7 | `data_sim_*`(SmartHub/Acceleronix) | 最小必填 | 结构合法 / SKIP | | L2-F8 | `data_emspara_vid_info` / `pid_info` | `{"id":"1"}` | 返回宏名/描述或「未找到」 | > L2-F6/F7 需按 `data/fwh_api.go`、`data/sim_api.go` 的每个工具展开为逐条用例(driver 中据 `tools/list` 自动生成最小入参)。 --- ## L3 写操作(默认只测参数校验,不真实写入) > 除非确认隔离环境,L3 仅验证「必填缺失被拦截」,不构造合法写入。 | 编号 | 工具 | 入参 | 期望 | |------|------|------|------| | L3-1 | `service_feedback_create` | `{"title":"t"}`(缺 type) | `isError`,「参数 type 必填」 | | L3-2 | `service_feedback_create` | `{"title":"t","type":"x"}` | `isError`,「必须是 bug 或 feature」 | | L3-3 | `service_site_info_tag_update` | `{}` | `isError`,「参数 tag 必填」 | | L3-4 | `service_auto_update_create_package` | `{"package_type":"system"}`(缺 info) | `isError`,「参数 info 必填」 | | L3-5 | `service_auto_update_update_config` | `{"agate_version":"v"}`(缺 config) | `isError`,「参数 config 必填」 | | L3-6 | `service_site_monitor_config_create` | `{"name":"n"}`(缺 site_list) | `isError`,「参数 site_list 必填」 | | L3-7 | `service_site_monitor_config_delete` | `{}` | `isError`,「参数 name 必填」 | | L3-8 | `service_batch_download_create` | 缺必填 | `isError` | | L3-9 | `service_site_analysis_create` | 缺必填 | `isError` | | L3-10 | `storage_file_write` | `{"path":"/uploads/x"}`(缺 content) | `isError`,「参数 content 必填」 | | L3-11 | `storage_file_upload` | `{}`(缺 localPath) | `isError`,「参数 localPath 必填」 | **(可选)隔离环境真实写入用例**(需你确认后启用): - L3-W1 `service_site_monitor_config_create` → `_get` 校验 → `_delete` 清理(闭环) - L3-W2 `storage_file_write` → `storage_file_list`/`storage_minio_list_objects` 校验 → 删除 - 每条真实写入用例必须带**清理步骤**,避免污染数据。 --- ## L4 敏感数据脱敏验证(安全门禁) ### L4-0 脱敏单元测试 - 命令:`go test ./cmd/mcp/tools/helper/...` - 期望:全部 PASS(掩码函数、流式脱敏、强类型脱敏、A/B 一致性、fail-closed、null 保留)。 ### 端到端脱敏(负向断言:明文 PII 不得出现;正向断言:保留字段原值) 明文检测正则(对返回文本整体扫描,命中即 FAIL): - 手机号:`\b1\d{10}\b` - IMEI/IMSI:`\b\d{14,15}\b` - MAC:`([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}` - IP:`\b(?!0\.0\.0\.0)\d{1,3}(\.\d{1,3}){3}\b` - 邮箱本地部分明文:`[\w.+-]{2,}@`(脱敏后应为 `x***@`) | 编号 | 工具 | 入参 | 负向断言(不得出现) | 正向断言(应保留/应呈现) | |------|------|------|------|------| | L4-1 | `service_site_query_info` | `{"sn":"<真实SN>"}` | 明文手机号/邮箱/地址/经纬度/IMEI/MAC/IP | `sn`/`iccId`/`city` 原值;`contactNumber`/`address`/`imei`==`******`;`userName`~`X***`;`email`~`x***@域名` | | L4-2 | `service_site_search` | `{"page":1,"size":5}` | 同上(对 Data[] 每项) | 每项均脱敏;分页字段 total/page 正常 | | L4-3 | `service_sim_query` | `{"giccid":"<真实GICCID>"}` | 明文 imei/imsi/msisdn | `imei`/`imsi`/`msisdn`==`******`;`giccid` 原值保留 | | L4-4 | `service_sim_search` | `{"page":1,"size":5}` | 同上(对 Data[]) | 每项脱敏,giccid 保留 | | L4-5 | `data_mongo_find` | `{"collection":"SiteInfoManageData","limit":1}` | 明文 PII(走 B 路径流式脱敏) | 敏感 key 的 value 已脱敏;sn/iccid 等保留 | | L4-6 | `data_mongo_find` | `{"collection":"SimCardManage","limit":1}` | 明文 imei/imsi/msisdn | giccid 保留 | | L4-7 | 触发大结果落盘的查询(如 `service_site_search` 大 size 或 `data_fwh_*` 大结果) | — | 读取返回的 `localPath` 文件,文件内**同样无明文 PII** | `spilled==true`;`preview` 亦已脱敏 | ### L4-8 fail-closed(设计验证) - 主要由 L4-0 单测保证(`TestMaskJSONBytes_InvalidJSONFailsClosed`)。 - 端到端标注为「设计验证」:脱敏解析失败时返回 error,不返回原始数据。 --- ## 报告字段建议 `report.md` 每行:`用例编号 | 工具 | 结果(PASS/FAIL/SKIP) | 耗时ms | 原因/备注` `pii_report.md` 每条 L4 用例附:命中的负向断言(应为空)、正向断言结果、脱敏后返回样本片段。 ## 依赖前置检查清单(执行前跑一遍) - [ ] cwd == `/var/www/fwh-toolbox/server`,`config.env` 可读 - [ ] `./fwh-mcp-server` 存在且可执行(或已重建) - [ ] Redis:6379 / Mongo:27017 / MinIO:9198 / PgSQL:5432 / Plane:8600 可连通 - [ ] `/var/log/fwh-mcp-server/` 可写(否则 logger fallback) - [ ] 已准备 `<真实SN>` / `<真实GICCID>` / 已知 MinIO 文本对象 / 已知快照文件名 - [ ] `go test ./cmd/mcp/tools/helper/...` 通过(L4-0)