LisgloSIPS V2 测试方案与测试用例集
1. 测试目标
验证 LisgloSIPS V2 在本地 KVM A/B/T 联调环境和后续阿里云迁移环境中,能够稳定完成“客户接入 - OpenSIPS 路由 - 计费话单 - 录音搬运 - Web 运营管理 - 监控与回滚”的核心闭环。
重点目标:
- 核心通话链路可用:客户 IP/SIP 注册认证、业务前缀识别、客户网关策略、落地线路组、落地网关路由、RTP 录音、CDR 生成。
- 业务数据正确:充值幂等、余额扣减、Decimal 计费、话单归属地/运营商、录音与质检关联、审计日志完整。
- 安全边界明确:登录认证、刷新令牌轮换、RBAC、敏感字段脱敏、录音文件路径安全、SIP 非法来源和超 CPS 防护。
- 运维能力可验收:健康检查、备份恢复、发布/回滚、Prometheus/Grafana、HOMER 信令排障、Worker 故障恢复。
- 前端可运营:核心页面接真实 API,权限驱动导航和按钮裁剪,加载/空状态/错误状态可用。
2. 测试范围
2.1 纳入范围
- Web:登录、Dashboard、客户、客户网关、业务前缀、充值、供应商、落地网关、落地线路组、号码库、当前通话、话单、质检、用户、角色权限、操作日志。
- API:
apps/api/src/modules 下所有已实现模块。
- Worker:CDR/Billing Worker、Recording Worker、Config Publisher。
- 数据层:Prisma/MySQL schema、Redis 热路径、Redis Stream、Outbox、幂等键。
- 通信层:Server A OpenSIPS、RTPEngine、HEP;Server T SIP 模拟脚本。
- 运维层:Nginx、TLS、systemd 服务、备份恢复、发布 preflight、回滚脚本、监控告警基础设施。
2.2 不纳入范围
- 多 OpenSIPS 节点高可用。
- Kubernetes、跨地域容灾、复杂账单/发票/对账。
- 自研 SIP Trace UI、完整自研监控告警页面、完整系统设置页面。
- 超长时间满载压测。当前以功能闭环、小规模并发、故障恢复和上线前冒烟为主。
3. 测试环境
| 环境 |
用途 |
说明 |
| 本地开发 |
单元、服务、API e2e、前端构建 |
Windows 工作区,Node.js 22+,pnpm 10.33.0,Vitest |
| Server A |
SIP/RTP/录音/HEP/Exporter |
OpenSIPS、RTPEngine、录音 tmpfs、Redis 热路径 |
| Server B |
Web/API/Worker/DB/监控 |
Nginx、API、Redis、MySQL、Worker、HOMER、Prometheus/Grafana |
| Server T |
客户与落地模拟 |
SIP 注册、呼叫、失败场景、并发呼叫脚本 |
| 阿里云迁移环境 |
上线前复验 |
生产网络、安全组、正式 TLS、数据盘、备份恢复和灰度呼叫必须重新执行 |
4. 测试类型与执行策略
| 类型 |
目标 |
执行方式 |
建议频率 |
| 静态检查 |
代码规范、类型、Prisma schema 合法性 |
pnpm lint、pnpm typecheck、pnpm prisma:validate |
每次提交前 |
| 单元测试 |
认证、计费、CDR、录音路径、质检抽样、Dashboard 聚合等纯逻辑 |
pnpm test 或定向 Vitest |
每次提交前 |
| API e2e |
模块接口、权限、审计、事务和错误码 |
Vitest + Supertest |
每个后端变更 |
| Worker 集成 |
Redis Stream、幂等、pending 重试、录音搬运校验 |
Vitest + Redis/MySQL mock 或真实联调 |
Worker 变更 |
| 前端功能 |
菜单、表格、表单、弹窗、权限按钮、错误状态 |
浏览器人工/自动冒烟 |
前端变更 |
| 三机端到端 |
SIP 呼叫、路由、录音、CDR、计费、质检 |
T 发起呼叫,A/B 核对数据 |
发布前、迁移前 |
| 性能/故障/安全 |
CPS、并发、Worker/Redis/MySQL 故障、越权和重放 |
按 S29 方法执行 |
里程碑和上线前 |
| 上线验收 |
备份、preflight、灰度、回滚 |
按 S30 Runbook 执行 |
每次生产发布 |
5. 准入与准出标准
5.1 测试准入
- 需求或缺陷边界明确,已标注影响模块。
- 数据库迁移、OpenSIPS 配置、Nginx 配置、systemd 变更均有回滚点。
.env.example 或部署环境变量已补齐新增配置项。
- 本地依赖可安装,
pnpm install 或当前锁文件状态可复现。
5.2 发布准出
pnpm lint、pnpm typecheck、pnpm test、pnpm build 通过。
- API
/api/v2/health/live 和 /api/v2/health/ready 正常。
- 新 release 必须由
pnpm release:artifact -- --release-id <release-id> 或等价 Linux 环境命令生成,并校验 RELEASE_MANIFEST.json 与 .sha256。
- B release preflight 通过,Nginx 配置测试通过,关键 systemd 服务 active。
- 至少完成一通灰度呼叫,并核对 SIP 200 OK、CDR 入库、计费、录音 READY。
- 备份目录、SHA256 校验和回滚脚本已确认。
- 无 P0/P1 缺陷;P2 缺陷有明确规避方案和负责人。
6. 自动化测试命令
在仓库根目录执行:
常用定向测试:
7. 测试数据准备
| 数据 |
准备方式 |
验证点 |
| 管理员用户 |
Prisma seed 或既有 B 环境内置用户 |
可登录,可访问全部核心菜单 |
| 只读/质检/运营角色 |
角色权限页面或 seed |
菜单和按钮按权限裁剪 |
| 客户 |
客户管理创建 |
余额、授信、状态、删除保护 |
| 客户网关 |
IP、SIP_DIGEST、MIXED 各一条 |
鉴权、匹配、密码不回显 |
| 业务前缀 |
至少两条不同优先级前缀 |
被叫前缀识别与剥离 |
| 供应商与落地网关 |
至少一条启用落地网关 |
CPS、并发、禁呼时段、号码转换 |
| 落地线路组 |
绑定落地网关 |
优先级、启停、并发汇总 |
| 号码库 |
城市、区号、手机号段、运营商规则 |
CDR 归属地/运营商识别 |
| 录音样本 |
T 发起真实呼叫生成 |
播放、Range、质检评分 |
8. 测试用例集
8.1 认证、会话与权限
本节覆盖登录验证码、用户名密码校验、refresh cookie、access token、退出登录、后端 RBAC 和前端权限裁剪。后端接口统一以 /api/v2 为前缀。
公共前置数据:
- 管理员用户
admin_auth_001:状态 ENABLED,角色包含 admin,权限至少包含 dashboard.view、customers.view、customers.manage、users.view、users.manage、roles.view、quality.view、quality.manage、cdr.view、recordings.play。
- 只读用户
viewer_auth_001:状态 ENABLED,仅包含 dashboard.view、customers.view。
- 质检用户
quality_auth_001:状态 ENABLED,包含 quality.view、quality.manage,不包含 users.view、users.manage。
- 禁用用户
disabled_auth_001:状态 DISABLED,密码正确但不可登录。
- 所有测试用户密码满足生产密码策略;测试结束后删除测试用户或重置为禁用状态。
AUTH-001 管理员登录成功
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证验证码、用户名密码、access token、refresh cookie、角色权限返回和敏感信息不泄露。 |
| 前置条件 |
admin_auth_001 启用,密码已知;API 服务正常;AUTH_COOKIE_SECURE=false 时本地 HTTP 测试可读取 Set-Cookie,生产 HTTPS 环境应为 secure cookie。 |
| 测试数据 |
用户名 admin_auth_001;正确密码;通过 GET /api/v2/auth/captcha 获取 captchaId,自动化测试可使用注入的 CaptchaService 读取答案,人工测试使用图片验证码。 |
| 步骤 |
1. 请求 GET /api/v2/auth/captcha。2. 使用正确用户名、密码、captchaId、captchaCode 请求 POST /api/v2/auth/login。3. 解析响应体和 Set-Cookie。4. 使用返回的 access token 请求一个管理员可访问接口,例如 GET /api/v2/users。 |
| 预期结果 |
验证码接口返回 captchaId、imageDataUrl、expiresAt,不返回验证码答案;登录返回 200;响应体包含 accessToken 和 user;user.roles、user.permissions 非空且包含管理员权限;Set-Cookie 包含 refresh cookie。 |
| 数据检查 |
auth_sessions 或等价会话存储新增一条未撤销会话;库中只保存 refresh token hash,不保存明文 refresh token;用户失败次数清零。 |
| 安全检查 |
响应体不包含 passwordHash、sipHa1、refresh token 明文、权限表以外的敏感字段;access token 为 Bearer token,不能通过 cookie 自动作为 access token 使用。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| AUTH-001-N1 |
缺少验证码 |
不传 captchaId 或 captchaCode 登录 |
返回 401,错误码 AUTH_INVALID_CREDENTIALS 或 AUTH_CAPTCHA_INVALID,不创建会话 |
| AUTH-001-N2 |
验证码错误 |
使用错误 captchaCode 登录 |
返回 401 AUTH_CAPTCHA_INVALID,不校验出具体用户名是否存在 |
| AUTH-001-N3 |
验证码复用 |
同一验证码成功登录后再次使用 |
第二次失败,验证码一次性消费 |
| AUTH-001-N4 |
禁用用户登录 |
使用 disabled_auth_001 正确密码登录 |
返回 401 AUTH_INVALID_CREDENTIALS,不暴露“用户被禁用”细节 |
AUTH-002 登录失败锁定与节流
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证连续错误密码会增加失败次数并锁定用户;大量失败会触发用户名/IP 维度节流。 |
| 前置条件 |
viewer_auth_001 启用;默认配置 AUTH_LOCK_MAX_FAILURES=5、AUTH_LOCK_WINDOW_SECONDS=900、AUTH_LOGIN_THROTTLE_MAX_FAILURES=10、AUTH_LOGIN_THROTTLE_WINDOW_SECONDS=300,如测试环境覆盖配置则按实际值执行。 |
| 测试数据 |
正确用户名 viewer_auth_001;错误密码 WrongPassword-Auth002;每次登录使用新的有效验证码。 |
| 步骤 |
1. 连续使用错误密码登录,次数达到锁定阈值。2. 在锁定窗口内使用正确密码登录。3. 使用不存在用户名连续失败直到节流阈值。4. 等待或模拟节流窗口过期后再次尝试。 |
| 预期结果 |
阈值前每次错误返回 401 AUTH_INVALID_CREDENTIALS;达到锁定阈值后用户 lockedUntil 被设置;锁定窗口内正确密码仍返回 401;达到节流阈值后返回 429 AUTH_LOGIN_THROTTLED;窗口过期后节流解除。 |
| 数据检查 |
failedLoginCount 按失败次数增加;登录成功后失败次数清零;锁定只影响目标用户,不应锁定其他用户。 |
| 安全检查 |
错误响应不区分“用户名不存在”“密码错误”“用户禁用”“用户锁定”;日志和审计中密码、验证码答案必须脱敏。 |
补充边界用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| AUTH-002-N1 |
大小写/空格 |
用户名前后加空格或大小写变化登录 |
按系统约定归一化;不产生绕过锁定或重复账号 |
| AUTH-002-N2 |
多 IP 节流 |
同一用户名不同 IP 连续失败 |
用户名维度节流生效;IP 维度不误伤无关用户 |
| AUTH-002-N3 |
锁定恢复 |
锁定窗口过期后用正确密码登录 |
登录成功,失败次数清零,lockedUntil 清空 |
AUTH-003 刷新令牌轮换
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 refresh token 只能使用一次,刷新时旧会话撤销并创建新会话,防止 refresh token 重放。 |
| 前置条件 |
admin_auth_001 已成功登录,保存登录返回的 refresh cookie cookieA。 |
| 测试数据 |
cookieA 为登录时 Set-Cookie;cookieB 为首次 refresh 后新的 Set-Cookie。 |
| 步骤 |
1. 携带 cookieA 请求 POST /api/v2/auth/refresh。2. 保存响应体 access token 和新的 cookieB。3. 再次携带旧 cookieA 请求 refresh。4. 携带 cookieB 请求 refresh。 |
| 预期结果 |
第一次 refresh 返回 200,签发新 access token 和新 refresh cookie;旧 cookieA 再次 refresh 返回 401;cookieB 可以继续 refresh 并再次轮换。 |
| 数据检查 |
旧 session revokedAt 被设置;新 session 未撤销;refresh token hash 发生变化;不会出现多个同一 refresh hash 的活跃会话。 |
| 安全检查 |
refresh 接口不接受 Bearer token 替代 cookie;响应体不返回 refresh token 明文;cookie 设置 HttpOnly、SameSite、合理 Max-Age,生产环境应包含 Secure。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| AUTH-003-N1 |
缺少 refresh cookie |
不带 cookie 请求 refresh |
返回 401 |
| AUTH-003-N2 |
伪造 refresh cookie |
携带随机 refresh cookie |
返回 401 |
| AUTH-003-N3 |
过期 refresh cookie |
使用已过期 session 对应 cookie |
返回 401,旧 session 不恢复 |
AUTH-004 退出登录
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 logout 会撤销当前 refresh session 并清除浏览器 cookie。 |
| 前置条件 |
admin_auth_001 已登录并持有有效 refresh cookie。 |
| 测试数据 |
登录后 refresh cookie。 |
| 步骤 |
1. 携带 refresh cookie 请求 POST /api/v2/auth/logout。2. 检查响应状态和 Set-Cookie。3. 使用同一 cookie 请求 POST /api/v2/auth/refresh。4. 使用旧 access token 访问接口,记录是否仍在 access token TTL 内可用。 |
| 预期结果 |
logout 返回 204;响应设置 refresh cookie 过期或清空;同一 cookie refresh 返回 401;旧 access token 在过期前是否可用以系统设计为准,但不得再换取新 refresh token。 |
| 数据检查 |
当前 session revokedAt 被设置;其他设备/其他 session 不应被误撤销,除非产品要求单点登出。 |
| 安全检查 |
重复 logout 不应造成 500;无 cookie logout 返回 204 或 401 需与接口约定一致,但不得产生新会话。 |
AUTH-005 未登录访问保护接口
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证非 Public API 必须携带有效 Bearer access token。 |
| 前置条件 |
API 服务正常;无需登录态。 |
| 测试数据 |
受保护接口:GET /api/v2/users、GET /api/v2/customers、GET /api/v2/recordings;公开接口:GET /api/v2/auth/captcha、POST /api/v2/auth/login、POST /api/v2/auth/refresh、GET /api/v2/health/live。 |
| 步骤 |
1. 不带 Authorization 请求受保护接口。2. 携带格式错误的 Authorization: Token xxx 请求。3. 携带伪造 JWT 请求。4. 请求公开接口。 |
| 预期结果 |
受保护接口返回 401 AUTH_REQUIRED;格式错误和伪造 token 也返回 401;公开接口不因缺少 Bearer token 被拒绝。 |
| 数据检查 |
未认证请求不写入业务数据;审计策略如记录失败访问,应不包含敏感 token 原文。 |
| 安全检查 |
不应返回堆栈、JWT 校验细节、用户存在性信息。 |
AUTH-006 权限不足访问
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 RBAC 后端强制生效,前端隐藏按钮不能作为唯一防线。 |
| 前置条件 |
viewer_auth_001 已登录,仅有 dashboard.view、customers.view;quality_auth_001 已登录,仅有质检权限。 |
| 测试数据 |
viewer access token;quality access token;管理员 access token 作为对照。 |
| 步骤 |
1. 使用 viewer token 请求 GET /api/v2/customers。2. 使用 viewer token 请求客户写接口,例如 POST /api/v2/customers。3. 使用 quality token 请求 GET /api/v2/users 或用户写接口。4. 使用管理员 token 请求同一接口作为对照。 |
| 预期结果 |
viewer 可访问 customers.view 对应只读接口;viewer 写客户返回 403 RBAC_FORBIDDEN;quality 访问用户管理返回 403;管理员访问成功。 |
| 数据检查 |
403 请求不产生业务变更;如写操作被拒绝,不应创建客户、充值、用户或审计成功记录。 |
| 安全检查 |
403 响应只说明权限不足,不泄露所需权限全集、SQL 信息或内部角色结构。 |
补充矩阵:
| 子 ID |
账号 |
接口 |
期望 |
| AUTH-006-M1 |
viewer |
GET /api/v2/customers |
200 |
| AUTH-006-M2 |
viewer |
POST /api/v2/customers |
403 RBAC_FORBIDDEN |
| AUTH-006-M3 |
viewer |
GET /api/v2/users |
403 或 200 取决于是否授予 users.view;本测试用户应为 403 |
| AUTH-006-M4 |
quality |
GET /api/v2/quality/recordings 或质检列表接口 |
200 |
| AUTH-006-M5 |
quality |
GET /api/v2/users |
403 |
| AUTH-006-M6 |
admin |
上述接口 |
按业务参数返回 200/201 或合法业务响应 |
AUTH-007 前端菜单权限裁剪
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证 Web 根据登录返回的 user.permissions 裁剪导航,并且手动访问无权限页面时有明确反馈。 |
| 前置条件 |
Web 和 API 正常;viewer_auth_001 仅有 dashboard.view、customers.view;浏览器清空旧登录态。 |
| 测试数据 |
viewer 用户、admin 用户。 |
| 步骤 |
1. 使用 viewer 登录 Web。2. 检查侧边栏/导航菜单。3. 手动修改 hash/path 或通过浏览器地址进入无权限页面,例如用户管理、供应商管理、质检中心。4. 退出后用 admin 登录,对照完整菜单。 |
| 预期结果 |
viewer 只看到 Dashboard 和客户管理等有权限页面;供应商、落地网关、质检、用户、角色、操作日志等无权限菜单不可见;手动访问无权限页面显示“无权限访问”类状态或跳转到有权限页面;admin 可看到完整授权菜单。 |
| 数据检查 |
viewer 登录后全局数据刷新只请求其有权限页面需要的 API,不应批量触发无权限接口 403。 |
| 前端检查 |
浏览器 console 无 uncaught error;页面无空白;刷新浏览器后权限裁剪仍生效;登录返回 permissions 为空时应显示最小可用状态而不是崩溃。 |
补充菜单矩阵:
| 权限 |
应显示菜单 |
dashboard.view |
概览 Dashboard |
customers.view |
客户管理 |
customer_gateways.view |
客户网关管理、业务前缀管理 |
recharges.view |
充值记录 |
vendors.view |
供应商管理 |
vendor_gateways.view |
落地网关管理 |
line_groups.view |
落地线路组 |
number_library.view |
号码库 |
active_calls.view |
当前通话 |
cdr.view |
话单中心 |
quality.view |
质检中心 |
users.view |
用户管理 |
roles.view |
角色与权限 |
audit.view |
操作日志 |
AUTH-008 按钮级权限裁剪
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证 Web 操作按钮根据 manage/play/review 类权限裁剪,同时后端仍强制校验。 |
| 前置条件 |
Web 和 API 正常;viewer、quality、admin 三类用户可登录;系统中至少存在一个客户、一条话单、一条 READY 录音。 |
| 测试数据 |
viewer 用户仅可看客户;quality 用户可看质检并评分;admin 用户全权限。 |
| 步骤 |
1. viewer 登录客户管理页面,检查新增、编辑、充值/扣款、删除、启停按钮。2. viewer 进入话单/录音相关页面,检查录音播放按钮。3. quality 登录质检中心,检查抽检规则、保存评分、播放录音按钮。4. admin 登录相同页面,对照按钮完整性。5. 使用浏览器 devtools 或 API 客户端绕过前端按钮,直接调用无权限写接口。 |
| 预期结果 |
viewer 只能查看客户列表,不显示新增、编辑、充值/扣款、删除、启停按钮;无 recordings.play 时不显示或不可用播放入口;quality 按其权限显示质检评分/保存按钮;admin 显示完整操作按钮;绕过前端直接调用无权限写接口返回 403。 |
| 数据检查 |
无权限用户不会产生写入、删除、充值、挂断、评分等副作用;管理员操作产生对应审计记录。 |
| 前端检查 |
按钮隐藏后布局不塌陷;空状态、加载态和错误态仍可读;权限刷新后按钮状态同步更新,无需清理缓存才能生效。 |
按钮权限建议矩阵:
| 页面 |
操作 |
需要权限 |
无权限表现 |
| 客户管理 |
新增/编辑/启停/删除 |
customers.manage |
按钮不可见 |
| 客户管理 |
充值/扣款 |
recharges.manage |
按钮不可见 |
| 客户网关 |
新增/编辑/启停/删除 |
customer_gateways.manage |
按钮不可见 |
| 业务前缀 |
新增/编辑/启停/删除 |
customer_gateways.manage |
按钮不可见 |
| 供应商管理 |
新增/编辑/删除 |
vendors.manage |
按钮不可见 |
| 落地网关 |
新增/编辑/启停/删除 |
vendor_gateways.manage |
按钮不可见 |
| 落地线路组 |
新增/成员维护/重排/删除 |
line_groups.manage |
按钮不可见 |
| 号码库 |
导入 |
number_library.manage |
按钮不可见 |
| 当前通话 |
强制挂断 |
active_calls.manage |
按钮不可见 |
| 话单中心 |
播放录音 |
recordings.play |
按钮不可见或禁用 |
| 质检中心 |
播放录音 |
recordings.play |
按钮不可见或禁用 |
| 质检中心 |
保存评分/问题标注 |
quality.manage |
按钮不可见 |
| 用户管理 |
新增/编辑/启停/重置密码/删除 |
users.manage |
按钮不可见 |
| 角色与权限 |
新增/编辑/删除/权限维护 |
roles.manage |
按钮不可见 |
8.2 客户、充值与余额
本节覆盖客户主数据、余额展示、授信/最低余额、客户状态、受控删除、客户充值/扣款流水、幂等和金额精度。后端接口统一以 /api/v2 为前缀。
公共前置数据:
- 客户管理账号
customer_admin_001:包含 customers.view、customers.manage、recharges.view、recharges.manage、audit.view。
- 客户只读账号
customer_viewer_001:仅包含 customers.view。
- 财务账号
finance_recharge_001:包含 recharges.view、recharges.manage、customers.view,可执行人工充值和人工扣款。
- 种子客户
cus_balance_seed_001:状态 ENABLED,余额 10.000000,授信 100.000000,最低余额 5.000000,无删除保护关系。
- 受保护客户
cus_protected_001:至少关联 1 个客户网关或 1 条历史话单,用于删除保护验证。
- 所有测试金额统一按字符串传入,避免 JavaScript number 精度影响测试判断;充值/扣款接口中正数表示入金,负数表示扣款,
0 不允许。
CUS-001 创建客户
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证有 customers.manage 权限的用户可以创建客户,默认值、金额归一化、审计和列表展示正确。 |
| 前置条件 |
customer_admin_001 已登录;客户名 Auto Customer CUS001、域名 cus001.example.test 尚不存在。 |
| 测试数据 |
name=Auto Customer CUS001,contactName=Ops,phone=13800000001,email=ops-cus001@example.test,domain=cus001.example.test,billingMode=POSTPAID,creditLimit=88.5,minBalance=2.25,notes=created by CUS-001。 |
| 步骤 |
1. 使用 admin token 请求 POST /api/v2/customers。2. 使用返回的 id 请求 GET /api/v2/customers/:id。3. 请求 GET /api/v2/customers 检查列表。4. 查询操作日志或审计存储。 |
| 预期结果 |
创建返回 201;客户状态为 ENABLED;billingMode=POSTPAID;balance=0.000000 或系统默认初始余额;creditLimit=88.500000;minBalance=2.250000;列表可见该客户。 |
| 数据检查 |
customers.created_by 为当前用户;version 初始化正确;deleted_at 为空;审计存在 module=customers、action=create、result=SUCCESS。 |
| 安全检查 |
响应不包含数据库内部异常、无关权限信息或其他客户敏感数据。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| CUS-001-N1 |
无权限创建 |
customer_viewer_001 请求 POST /api/v2/customers |
返回 403 RBAC_FORBIDDEN,不创建客户 |
| CUS-001-N2 |
缺少客户名 |
name 为空、空格或非字符串 |
返回 400 VALIDATION_ERROR |
| CUS-001-N3 |
字段超长 |
name 超 120、email 超 160、notes 超 500 |
返回 400 VALIDATION_ERROR |
| CUS-001-N4 |
非法状态 |
status=DELETED |
返回 400 STATUS_INVALID |
| CUS-001-N5 |
非法计费模式 |
billingMode=MONTHLY |
返回 400 BILLING_MODE_INVALID |
CUS-002 客户唯一性校验
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证客户名称和域名唯一性,防止重复主数据影响路由和财务归属。 |
| 前置条件 |
已存在客户 Auto Customer CUS002,域名 cus002.example.test。 |
| 测试数据 |
同名客户请求;同域名不同名称请求;大小写或前后空格变体按产品约定执行。 |
| 步骤 |
1. 创建基准客户。2. 再次使用相同 name 创建。3. 使用不同 name 但相同 domain 创建。4. 查询客户列表和数据库记录数。 |
| 预期结果 |
重复名称返回 409 或明确业务错误;重复域名返回 409 或明确业务错误;客户记录不重复。 |
| 数据检查 |
失败请求不写入 customers;不生成成功审计;如有失败审计,应标记 result=FAILURE 且包含安全脱敏后的错误摘要。 |
| 安全检查 |
错误信息可以提示重复字段,但不应泄露 SQL 约束名、堆栈或数据库连接信息。 |
补充边界用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| CUS-002-N1 |
名称前后空格 |
先创建 Acme,再创建 Acme |
按 trim 后重复处理 |
| CUS-002-N2 |
空域名重复 |
多个客户不填 domain |
如 schema 允许 null,应均可创建 |
| CUS-002-N3 |
软删除后同名 |
软删除客户后再次创建同名客户 |
按产品规则允许或拒绝,结果需固定并记录 |
CUS-003 客户启停与更新
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证客户状态变更、客户资料更新和余额只读边界。 |
| 前置条件 |
存在客户 cus_status_001,状态 ENABLED。 |
| 测试数据 |
更新 contactName、phone、email、notes;状态切换 DISABLED/ENABLED。 |
| 步骤 |
1. 请求 PATCH /api/v2/customers/:id 更新基础资料。2. 请求 POST /api/v2/customers/:id/disable。3. 请求 GET /api/v2/customers/:id。4. 请求 POST /api/v2/customers/:id/enable。5. 尝试在 PATCH 请求体中传入 balance 字段。 |
| 预期结果 |
资料更新成功,字段 trim/nullable 行为符合规则;禁用后状态为 DISABLED;启用后状态为 ENABLED;传入 balance 不应直接修改余额。 |
| 数据检查 |
updated_by、updated_at、version 按更新变化;启停分别写入 customers:disable、customers:enable 审计;余额只能通过充值或计费链路变化。 |
| 安全检查 |
无 customers.manage 的用户更新、启停均返回 403;禁用客户不应影响其他客户。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| CUS-003-N1 |
更新不存在客户 |
PATCH /customers/not-found |
返回 404 或明确业务错误 |
| CUS-003-N2 |
无效金额字段 |
creditLimit=-1、minBalance=1.1234567 |
返回 400 MONEY_INVALID |
| CUS-003-N3 |
幂等启停 |
对已禁用客户再次 disable,对已启用客户再次 enable |
不产生异常;结果状态稳定,审计按约定记录 |
CUS-004 客户受控删除
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证客户删除为受控操作,避免删除仍被网关、话单、充值或质检引用的客户。 |
| 前置条件 |
cus_protected_001 已关联客户网关或历史话单;cus_delete_free_001 无关联网关、话单、充值等保护关系。 |
| 测试数据 |
受保护客户 ID;可删除客户 ID。 |
| 步骤 |
1. 使用 admin token 请求 DELETE /api/v2/customers/cus_protected_001。2. 请求 DELETE /api/v2/customers/cus_delete_free_001。3. 查询列表和详情。4. 查询相关关联数据是否仍完整。 |
| 预期结果 |
受保护客户删除被拒绝或只允许禁用,具体按实现返回明确业务错误;无保护客户删除成功,列表默认不再展示或标记为已删除;关联数据不被级联破坏。 |
| 数据检查 |
删除成功时设置 deleted_at 或等价软删除标记,不做物理删除;写入 customers:delete 审计;受保护删除失败不修改客户状态。 |
| 安全检查 |
无权限删除返回 403;重复删除已删除客户返回稳定结果,不应 500。 |
CUS-005 客户充值成功
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证客户人工充值在一个事务内完成余额增加、充值流水、幂等记录和审计。正数金额表示入金。 |
| 前置条件 |
finance_recharge_001 已登录;客户 cus_balance_seed_001 当前余额 10.000000。 |
| 测试数据 |
amount=15.25,idempotencyKey=cus005-20260628-0001,remark=manual top up CUS-005。 |
| 步骤 |
1. 请求 POST /api/v2/customers/cus_balance_seed_001/recharges。2. 请求 GET /api/v2/customers/cus_balance_seed_001。3. 请求 GET /api/v2/recharges?accountType=CUSTOMER&accountId=cus_balance_seed_001&take=10。4. 查询审计。 |
| 预期结果 |
充值返回 201;amount=15.250000;beforeBalance=10.000000;afterBalance=25.250000;客户余额更新为 25.250000;充值列表包含该流水,状态 SUCCEEDED。 |
| 数据检查 |
customer_recharges 或统一充值/扣款流水表新增一条;idempotency_keys 新增成功记录;余额和流水前后值一致;created_by 为财务用户。 |
| 安全检查 |
只读用户请求充值返回 403;充值/扣款接口不接受前端传入 beforeBalance、afterBalance 覆盖服务端计算。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| CUS-005-N1 |
不存在客户充值/扣款 |
请求 POST /customers/not-found/recharges |
返回 404 或明确业务错误,不创建流水 |
| CUS-005-N2 |
禁用客户充值/扣款 |
对 DISABLED 客户提交正数或负数金额 |
按产品规则允许或拒绝,结果需固定;若允许,客户状态不自动启用 |
| CUS-005-N3 |
缺少备注 |
不传 remark |
充值或扣款成功,remark=null |
| CUS-005-N4 |
备注超长 |
remark 超 500 |
返回 400 VALIDATION_ERROR |
CUS-006 客户扣款成功
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证财务可通过同一充值接口提交负数金额,对客户账户执行人工扣款,并生成可追溯流水。 |
| 前置条件 |
finance_recharge_001 已登录;客户 cus_balance_seed_001 当前余额 25.250000 或其他已知正余额;系统允许余额扣为负数或有明确最低余额规则。 |
| 测试数据 |
amount=-3.25,idempotencyKey=cus006-deduct-0001,remark=manual deduction CUS-006。 |
| 步骤 |
1. 请求 POST /api/v2/customers/cus_balance_seed_001/recharges,提交负数 amount。2. 请求 GET /api/v2/customers/cus_balance_seed_001。3. 请求 GET /api/v2/recharges?accountType=CUSTOMER&accountId=cus_balance_seed_001&take=10。4. 查询审计。 |
| 预期结果 |
扣款返回 201;amount=-3.250000;beforeBalance 为扣款前余额;afterBalance=beforeBalance-3.250000;客户余额同步减少;充值/扣款列表包含该流水,状态 SUCCEEDED。 |
| 数据检查 |
流水应保留负数金额,不应转成正数或另建不可追踪字段;幂等 key 新增成功记录;余额和流水前后值一致;created_by 为财务用户。 |
| 安全检查 |
只读用户请求扣款返回 403;负数金额不得绕过幂等、审计、权限或备注长度校验。 |
补充边界用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| CUS-006-N1 |
扣款后余额为 0 |
当前余额 3.250000 时提交 amount=-3.25 |
成功,余额为 0.000000 |
| CUS-006-N2 |
扣款后余额为负 |
当前余额不足时提交负数扣款 |
若产品允许透支则成功并显示负余额;若不允许则返回明确业务错误,规则需固定 |
| CUS-006-N3 |
负数最小精度 |
提交 amount=-0.000001 |
成功,余额减少 0.000001 |
| CUS-006-N4 |
负零 |
提交 amount=-0 或 -0.000000 |
返回 400 MONEY_INVALID |
CUS-007 充值/扣款幂等重放
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证同一账号、同一请求体、同一 idempotencyKey 重放不会重复入金或重复扣款。 |
| 前置条件 |
客户 cus_balance_seed_001 可充值/扣款;幂等 key cus007-replay-0001 和 cus007-replay-deduct-0001 未被使用。 |
| 测试数据 |
入金:amount=0.000001,idempotencyKey=cus007-replay-0001;扣款:amount=-0.000001,idempotencyKey=cus007-replay-deduct-0001。 |
| 步骤 |
1. 第一次提交正数充值请求。2. 立即使用完全相同请求体再次提交。3. 第一次提交负数扣款请求。4. 立即使用完全相同请求体再次提交。5. 查询客户余额、充值/扣款流水和幂等键记录。 |
| 预期结果 |
每组两次请求均返回成功;每组第二次返回与第一次相同的流水 ID 和前后余额;客户余额对每个 key 只变化一次;正数和负数流水各只有一条。 |
| 数据检查 |
每个 idempotencyKey 只有一个成功记录;重复请求不新增成功流水,或审计标记为幂等重放,具体按审计设计固定。 |
| 安全检查 |
幂等 key 不应跨账号、跨业务类型复用成功;响应时间和错误信息不泄露其他账号充值/扣款内容。 |
补充并发用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| CUS-007-C1 |
并发充值重放 |
并发发送 5 个完全相同正数请求 |
只产生 1 条流水,最终余额只增加一次 |
| CUS-007-C2 |
并发扣款重放 |
并发发送 5 个完全相同负数请求 |
只产生 1 条流水,最终余额只减少一次 |
| CUS-007-C3 |
请求超时后重试 |
客户端超时后用同一 key 重试 |
返回已成功结果,不重复入金或扣款 |
CUS-008 充值/扣款幂等冲突
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证同一 idempotencyKey 不能被不同请求体、不同账号类型或不同账号复用,尤其不能用同一 key 混用入金和扣款。 |
| 前置条件 |
已使用 idempotencyKey=cus008-conflict-0001 对 cus_balance_seed_001 成功充值 1.000000。 |
| 测试数据 |
同 key 不同 amount;同 key 正负号相反;同 key 不同 remark;同 key 不同客户;同 key 用于供应商充值。 |
| 步骤 |
1. 使用相同 key、不同金额对同客户充值。2. 使用相同 key、amount=-1.000000 对同客户扣款。3. 使用相同 key、不同备注对同客户充值。4. 使用相同 key 对另一客户充值。5. 使用相同 key 对供应商充值。 |
| 预期结果 |
所有冲突请求返回 409 IDEMPOTENCY_KEY_CONFLICT;不修改任何账号余额;不新增充值/扣款流水。 |
| 数据检查 |
原充值记录保持不变;冲突请求可记录失败审计,但不得标记为充值或扣款成功。 |
| 安全检查 |
冲突响应不返回原请求的金额、备注、客户名等敏感业务内容。 |
CUS-009 金额精度校验
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证客户授信/最低余额和充值/扣款金额的格式、范围和归一化,避免浮点误差和非法金额进入财务数据。 |
| 前置条件 |
有可创建/更新客户权限和充值权限。 |
| 测试数据 |
客户金额合法值:0、0.000001、1、1.2、99999999999999.999999;充值/扣款合法值:0.000001、-0.000001、1.2、-1.2、99999999999999.999999、-99999999999999.999999;非法金额:客户金额字段中的负数、充值/扣款中的 0、-0、0.000000、-0.000000、1.1234567、-1.1234567、100000000000000、-100000000000000、abc、空字符串、科学计数法 1e3。 |
| 步骤 |
1. 创建/更新客户时分别提交合法和非法 creditLimit、minBalance。2. 充值/扣款时分别提交合法和非法 amount。3. 查询返回金额格式。 |
| 预期结果 |
客户金额字段允许非负,最多 14 位整数和 6 位小数,并归一化为 6 位小数;充值/扣款金额允许正数或负数,但不允许 0,绝对值最多 14 位整数和 6 位小数;非法金额返回 400 MONEY_INVALID。 |
| 数据检查 |
非法金额请求不写入客户、充值/扣款流水或幂等成功记录;合法金额在 API、数据库、列表中均以 6 位小数字符串展示,负数保留负号。 |
| 安全检查 |
不接受 NaN、Infinity、科学计数法或 JSON object/array 作为金额;服务端以 Decimal/字符串规则计算,不暴露二进制浮点误差。 |
金额矩阵:
| 场景 |
字段 |
输入 |
期望 |
| 客户授信 |
creditLimit |
0 |
成功,返回 0.000000 |
| 客户最低余额 |
minBalance |
1.2 |
成功,返回 1.200000 |
| 充值最小金额 |
amount |
0.000001 |
成功,余额增加 0.000001 |
| 扣款最小金额 |
amount |
-0.000001 |
成功,余额减少 0.000001 |
| 充值/扣款零金额 |
amount |
0、-0.000000 |
400 MONEY_INVALID |
| 客户授信负数 |
creditLimit |
-1 |
400 MONEY_INVALID |
| 客户最低余额负数 |
minBalance |
-1 |
400 MONEY_INVALID |
| 小数超长 |
任意金额字段 |
1.1234567 |
400 MONEY_INVALID |
| 整数超长 |
任意金额字段 |
100000000000000 |
400 MONEY_INVALID |
CUS-010 客户列表、详情与充值/扣款流水查询
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证客户列表、客户详情和充值/扣款记录查询的只读权限、分页和筛选。 |
| 前置条件 |
至少存在 2 个客户、1 条客户充值、1 条客户扣款、1 条供应商充值。 |
| 测试数据 |
accountType=CUSTOMER,accountId=cus_balance_seed_001,take=10,skip=0。 |
| 步骤 |
1. 使用 customer_viewer_001 请求 GET /api/v2/customers。2. 请求 GET /api/v2/customers/:id。3. 使用 finance_recharge_001 或充值只读用户请求 GET /api/v2/recharges?accountType=CUSTOMER&accountId=...。4. 检查正数充值和负数扣款流水。5. 使用非法分页和非法 accountType 查询。 |
| 预期结果 |
客户列表返回余额、授信、最低余额、可用余额、网关数量;客户详情字段完整;充值/扣款列表按筛选返回,包含 items 和 total;正数金额和负数金额均原样展示为 6 位小数字符串;非法 accountType 返回 400 ACCOUNT_TYPE_INVALID;非法分页返回 400 PAGINATION_INVALID。 |
| 数据检查 |
只读查询不产生业务变更;列表默认不展示软删除客户,除非接口明确支持。 |
| 安全检查 |
没有 customers.view 不能查客户;没有 recharges.view 不能查充值/扣款流水;列表不泄露其他模块敏感字段。 |
8.3 客户网关、业务前缀与配置发布
本节覆盖客户网关 IP/SIP/MIXED 鉴权、客户侧计费参数、主叫前缀、业务前缀、客户网关策略、配置 outbox、Config Publisher Redis 快照,以及 Web 业务前缀页面回归。后端接口统一以 /api/v2 为前缀。
公共前置数据:
- 运营账号
gateway_ops_001:包含 customer_gateways.view、customer_gateways.manage、audit.view。
- 只读账号
gateway_viewer_001:仅包含 customer_gateways.view。
- 客户
cus_gateway_seed_001:状态 ENABLED,余额充足,未删除。
- 线路组
llg_gateway_seed_001:状态 ENABLED,至少包含 1 个可用落地网关。
- 业务前缀
bp_671_seed:prefix=671,状态 ENABLED,优先级 10。
- 客户网关
cgw_ip_seed_001:authMode=IP,sourceIps=["100.93.185.30"],绑定 llg_gateway_seed_001。
- Redis 配置发布测试环境可读取
cfg:active_version 及 cfg:v:{version}:* key。
GW-001 创建 IP 认证客户网关
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 IP 鉴权客户网关创建、源 IP 校验、多 IP 去重、计费参数归一化、审计和配置发布 outbox。 |
| 前置条件 |
gateway_ops_001 已登录;客户和线路组存在;待创建网关名 Auto IP Gateway GW001 不重复。 |
| 测试数据 |
customerId=cus_gateway_seed_001,name=Auto IP Gateway GW001,authMode=IP,sourceIps=["100.93.185.30","100.93.185.31","100.93.185.30"],lineGroupId=llg_gateway_seed_001,billingCycleSec=6,cycleRate=0.01,callerMatchMode=ANY,calleeMatchMode=ANY。 |
| 步骤 |
1. 请求 POST /api/v2/customer-gateways。2. 请求 GET /api/v2/customer-gateways?customerId=cus_gateway_seed_001。3. 请求 GET /api/v2/customer-gateways/:id。4. 查询 outbox 和审计。 |
| 预期结果 |
创建返回 201;authMode=IP;sourceIps 去重后保留两个 IP;sourceIp 兼容字段为第一个 IP;sipUsername、sipDomain 为空;billingCycleSec=6;cycleRate=0.010000;状态默认 ENABLED。 |
| 数据检查 |
写入客户网关主表和网关 IP 明细;生成 aggregateType=customer_gateway_config 的待发布 outbox;审计存在 module=customer_gateways、action=create、result=SUCCESS。 |
| 安全检查 |
无 customer_gateways.manage 创建返回 403;响应不包含无关客户数据或内部异常。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| GW-001-N1 |
IP 鉴权缺少 IP |
authMode=IP 但不传 sourceIp/sourceIps |
400 SOURCE_IP_REQUIRED |
| GW-001-N2 |
IP 格式非法 |
传 999.1.1.1、abc |
400 SOURCE_IP_INVALID |
| GW-001-N3 |
IP 数量超限 |
传超过 20 个源 IP |
400 SOURCE_IP_TOO_MANY |
| GW-001-N4 |
非法鉴权模式 |
authMode=PASSWORD |
400 AUTH_MODE_INVALID |
| GW-001-N5 |
计费周期非法 |
billingCycleSec=0 或 86401 |
400 INTEGER_INVALID |
| GW-001-N6 |
费率非法 |
cycleRate=-1 或 1.1234567 |
400 MONEY_INVALID |
GW-002 创建 SIP Digest 网关
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 SIP Digest 鉴权网关创建、HA1 生成、域名归一化、密码不回显和不进入审计。 |
| 前置条件 |
gateway_ops_001 已登录;客户和线路组存在;SIP 用户名未重复。 |
| 测试数据 |
authMode=SIP_DIGEST,sipUsername=alice-gw,sipDomain=SIP.EXAMPLE.LOCAL,sipPassword=change-me-very-strong,lineGroupId=llg_gateway_seed_001,calleeMatchMode=BUSINESS_PREFIXES,businessPrefixIds=["bp_671_seed"]。 |
| 步骤 |
1. 请求 POST /api/v2/customer-gateways 创建 SIP 网关。2. 请求列表和详情。3. 查询数据库中 sip_ha1。4. 查询审计日志。 |
| 预期结果 |
创建返回 201;sipDomain 归一化为小写;hasSipCredential=true;列表和详情不返回 sipPassword、sipHa1;sourceIps=[];业务前缀绑定可见。 |
| 数据检查 |
数据库存储 HA1 摘要,值应等于 md5(username:domain:password);生成配置 outbox;审计不包含明文密码。 |
| 安全检查 |
明文密码不出现在 API 响应、审计 request/response、应用日志和错误信息中。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| GW-002-N1 |
缺少 SIP 用户名 |
authMode=SIP_DIGEST 不传 sipUsername |
400 VALIDATION_ERROR |
| GW-002-N2 |
缺少 SIP 域 |
不传 sipDomain |
400 VALIDATION_ERROR |
| GW-002-N3 |
密码过短 |
sipPassword 少于 12 字符 |
400 SIP_PASSWORD_WEAK |
| GW-002-N4 |
用户名非法字符 |
sipUsername=alice space |
400 SIP_USERNAME_INVALID |
| GW-002-N5 |
域名非法字符 |
sipDomain=sip_example_local! |
400 SIP_DOMAIN_INVALID |
GW-003 创建 MIXED 鉴权网关
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证 MIXED 模式同时要求 IP 和 SIP 身份,支持同一网关兼容 IP 白名单与 SIP Digest。 |
| 前置条件 |
gateway_ops_001 已登录;客户和线路组存在。 |
| 测试数据 |
authMode=MIXED,sourceIps=["100.93.185.40"],sipUsername=mixed-gw,sipDomain=sip.example.local,sipPassword=change-me-very-strong。 |
| 步骤 |
1. 创建 MIXED 网关。2. 查询详情。3. 发布配置后检查 Redis IP 和 SIP 索引。 |
| 预期结果 |
创建成功;响应同时包含 sourceIps、sipUsername、sipDomain、hasSipCredential=true;Redis 写入 auth:ip:{sourceIp} 和 auth:sip:{username}@{domain} 两类 key。 |
| 数据检查 |
网关主表 authMode=MIXED;IP 明细和 SIP HA1 均存在;生成 outbox。 |
| 安全检查 |
任一鉴权要素缺失均失败,不应降级为单一模式。 |
GW-004 切换 SIP 身份要求新密码
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证修改 SIP 用户名或域名时必须重新提供 SIP 密码,避免旧 HA1 与新身份不匹配。 |
| 前置条件 |
已存在 SIP_DIGEST 或 MIXED 网关 cgw_sip_seed_001,且 hasSipCredential=true。 |
| 测试数据 |
新 sipDomain=new.example.local;新 sipPassword=change-me-again-strong。 |
| 步骤 |
1. 不传 sipPassword 修改 sipDomain。2. 不传 sipPassword 修改 sipUsername。3. 传入新密码修改身份。4. 切换为 authMode=IP 并传 sourceIps。 |
| 预期结果 |
前两步返回 400 SIP_PASSWORD_REQUIRED;传新密码后修改成功并重新生成 HA1;切换到 IP 后 sipUsername/sipDomain 清空、hasSipCredential=false。 |
| 数据检查 |
失败请求不改变 HA1;成功修改生成配置 outbox 和 update 审计;切 IP 后不再发布 SIP auth key。 |
| 安全检查 |
新旧密码均不回显、不进入审计。 |
GW-005 主叫前缀匹配
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证客户网关可配置主叫前缀白名单,并在配置发布后进入 Redis 热路径。 |
| 前置条件 |
已存在启用客户网关 cgw_ip_seed_001。 |
| 测试数据 |
callerMatchMode=PREFIXES,callerPrefixes=["021","010","+8610","021"];另准备同 IP 其他网关已有主叫前缀 0219 或 021。 |
| 步骤 |
1. 请求 PATCH /api/v2/customer-gateways/:id 更新主叫匹配。2. 查询网关详情。3. 执行 Config Publisher。4. 检查 Redis cfg:v:{version}:customer_gateway:{id}:caller_prefixes。5. 使用 T 发起匹配和不匹配主叫呼叫。 |
| 预期结果 |
API 返回去重后的前缀列表;发布后 Redis list 包含前缀;空业务前缀场景下按最长主叫前缀选择客户网关;匹配主叫通过客户识别,不匹配主叫返回 CALLER_PREFIX_NOT_MATCHED 或等价失败原因。 |
| 数据检查 |
customer_gateway_caller_prefixes 按优先级/前缀保存;生成 outbox;manifest customerGatewayCallerPrefixCount 增加。 |
| 安全检查 |
无权限更新返回 403;非法前缀字符返回 400 CALLER_PREFIX_INVALID。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| GW-005-N1 |
PREFIXES 缺少前缀 |
callerMatchMode=PREFIXES 且列表为空 |
400 CALLER_PREFIX_REQUIRED |
| GW-005-N2 |
前缀数量超限 |
超过 20 个主叫前缀 |
400 LIST_TOO_LARGE |
| GW-005-N3 |
切回 ANY |
callerMatchMode=ANY |
前缀列表清空,发布后不写 caller_prefixes key |
| GW-005-N4 |
同网关主叫前缀覆盖 |
同一网关提交 ["021","0219"] |
400 CALLER_PREFIX_OVERLAP |
| GW-005-N5 |
同 IP 跨网关主叫前缀覆盖 |
同一 source IP 下另一个启用网关已有 021,新建/启用网关提交 0219 |
409 CUSTOMER_GATEWAY_CALLER_PREFIX_OVERLAP |
GW-006 业务前缀匹配
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证客户网关可绑定业务前缀,呼叫时先按业务前缀识别业务并剥离真实被叫。 |
| 前置条件 |
业务前缀 bp_671_seed 和 bp_672_seed 均启用;客户网关启用;A 侧 OpenSIPS 加载业务前缀热路径。 |
| 测试数据 |
calleeMatchMode=BUSINESS_PREFIXES,businessPrefixIds=["bp_671_seed","bp_672_seed"];测试被叫 67113800138000。 |
| 步骤 |
1. 更新客户网关绑定业务前缀。2. 发布配置。3. 检查 Redis business_prefix_value:671、customer_gateway:{id}:business_prefixes。4. T 发起被叫带业务前缀的呼叫。5. 同时准备一个空业务前缀/主叫前缀网关作为兜底,再次发起 671... 呼叫。6. 查询 CDR。 |
| 预期结果 |
网关详情展示绑定前缀;发布 manifest businessPrefixCount 和 customerGatewayBusinessPrefixCount 正确;呼叫按 671 优先匹配业务,实际被叫剥离为 13800138000;即使存在主叫前缀或空业务前缀兜底网关,也不得抢走已命中业务前缀的呼叫;CDR 记录业务前缀 ID/值和真实被叫。 |
| 数据检查 |
只发布启用且未删除业务前缀;禁用前缀不进入全局 business_prefixes 列表;生成 outbox。 |
| 安全检查 |
无权限绑定返回 403;跨客户绑定是否允许按产品规则固定,不得泄露其他客户敏感数据。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| GW-006-N1 |
BUSINESS_PREFIXES 缺少 ID |
calleeMatchMode=BUSINESS_PREFIXES 但不传 businessPrefixIds |
400 BUSINESS_PREFIX_REQUIRED |
| GW-006-N2 |
ID 数量超限 |
超过 50 个业务前缀 ID |
400 LIST_TOO_LARGE |
| GW-006-N3 |
切回 ANY |
calleeMatchMode=ANY |
绑定前缀清空,发布后不写 gateway business_prefixes key |
| GW-006-N4 |
被叫前缀不匹配 |
发起未绑定业务前缀被叫 |
呼叫被拒绝或进入明确失败路径,CDR 失败原因可追踪 |
| GW-006-N5 |
命中业务前缀但主叫不匹配 |
被叫 671... 命中业务前缀网关,主叫不满足该网关规则,同时存在空业务前缀兜底网关 |
返回 CALLER_PREFIX_NOT_MATCHED,不得退回兜底网关 |
| GW-006-N6 |
同 IP 同业务前缀重复 |
同一 source IP 下新建/启用另一个绑定 bp_671_seed 的网关 |
409 CUSTOMER_GATEWAY_MATCH_CONFLICT |
| GW-006-N7 |
同 IP 空业务前缀兜底重复 |
同一 source IP 下存在两个 calleeMatchMode=ANY 启用网关 |
409 CUSTOMER_GATEWAY_MATCH_CONFLICT |
GW-006A 客户网关匹配优先级与唯一性
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证最终匹配唯一,且业务前缀优先于主叫前缀、精确规则优先于空业务前缀兜底。 |
| 前置条件 |
同一测试 source IP 下准备三个候选网关:A 绑定 bp_671_seed,B 为 calleeMatchMode=ANY 且 callerPrefixes=["021"],C 为 calleeMatchMode=ANY 且 callerMatchMode=ANY;三者绑定不同线路组以便观察命中结果。 |
| 测试数据 |
呼叫 1:主叫 02160010001、被叫 67113800138000;呼叫 2:主叫 02160010001、被叫 13800138000;呼叫 3:主叫 01060010001、被叫 13800138000。 |
| 步骤 |
1. 发布配置并确认 A 加载最新热路径。2. 分别发起三通呼叫。3. 查询 CDR、HOMER、A 日志和落地线路组。 |
| 预期结果 |
呼叫 1 必须命中业务前缀网关 A,真实被叫为 13800138000;呼叫 2 命中主叫前缀网关 B;呼叫 3 命中空业务前缀兜底网关 C;任一场景不得出现随机网关或按 Redis 列表顺序误选。 |
| 数据检查 |
CDR 中客户网关、线路组、业务前缀、raw/real callee 与预期一致;客户侧计费归属客户网关,供应商成本归属落地网关。 |
| 安全检查 |
IP 可跨客户复用,但保存配置时必须保证上述三类呼叫最终只能命中一个网关。 |
GW-007 业务前缀 CRUD 与优先级
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证业务前缀创建、唯一性、状态、优先级、列表筛选和受控删除。 |
| 前置条件 |
gateway_ops_001 已登录;待创建前缀 673 不存在。 |
| 测试数据 |
prefix=673,name=业务673,description=for GW-007,priority=30,status=ENABLED。 |
| 步骤 |
1. 请求 POST /api/v2/business-prefixes 创建。2. 请求 GET /api/v2/business-prefixes?status=ENABLED&keyword=673。3. 更新 priority 和 description。4. disable/enable。5. 删除未被网关引用的前缀。 |
| 预期结果 |
创建返回 201;列表按优先级和前缀展示;状态切换成功;未引用前缀可软删除;所有写操作生成配置 outbox 和审计。 |
| 数据检查 |
prefix 唯一;priority 在 1-9999;删除为软删除;禁用或删除后不进入 Redis 活跃业务前缀快照。 |
| 安全检查 |
业务前缀接口读需要 customer_gateways.view,写需要 customer_gateways.manage;无权限返回 403。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| GW-007-N1 |
非法前缀字符 |
prefix=67# 或中文 |
400 BUSINESS_PREFIX_INVALID |
| GW-007-N2 |
前缀超长 |
长度超过 32 |
400 VALIDATION_ERROR 或 BUSINESS_PREFIX_INVALID |
| GW-007-N3 |
优先级非法 |
priority=0、10000、小数 |
400 PRIORITY_INVALID |
| GW-007-N4 |
重复前缀 |
创建已存在 671 |
409 或明确业务错误,不生成重复记录 |
| GW-007-N5 |
删除被引用前缀 |
删除已绑定网关的前缀 |
拒绝删除或软删除后自动解除/禁用,规则需固定且不破坏网关配置 |
GW-008 客户网关策略 CRUD 与优先级重排
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证客户网关策略按优先级匹配主叫/被叫并指向线路组,支持创建、更新、删除和重排。 |
| 前置条件 |
已存在客户网关 cgw_ip_seed_001,线路组 llg_gateway_seed_001,另有备用线路组 llg_backup_001。 |
| 测试数据 |
策略 1:callerMode=PREFIX、callerValue=021、calleeMode=ANY;策略 2:callerMode=ANY、calleeMode=EQUALS、calleeValue=13800138000;fallback 策略:ANY/ANY。 |
| 步骤 |
1. 请求 POST /api/v2/customer-gateways/:id/policies 创建多条策略。2. 请求列表确认按 priority 排序。3. PATCH 修改策略 match mode/value。4. 请求 POST /api/v2/customer-gateways/:id/policies/reorder 调整顺序。5. 删除一条策略。6. 发布配置并检查 Redis policy list 顺序。 |
| 预期结果 |
策略创建、更新、重排、删除成功;列表顺序与优先级一致;发布后 customer_gateway:{id}:policies 按优先级写入;呼叫策略选择符合重排结果。 |
| 数据检查 |
每次写操作生成 customer_gateway_config outbox;审计包含 policy_create、policy_update、policy_reorder、policy_delete;删除为软删除或从列表移除按实现固定。 |
| 安全检查 |
只读用户可列表不可写;无权限写返回 403。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| GW-008-N1 |
非 ANY 缺少匹配值 |
callerMode=PREFIX 但不传 callerValue |
400 VALIDATION_ERROR |
| GW-008-N2 |
匹配值非法字符 |
calleeValue=138 001 |
400 MATCH_VALUE_INVALID |
| GW-008-N3 |
非法匹配模式 |
callerMode=REGEX |
400 MATCH_MODE_INVALID |
| GW-008-N4 |
优先级非法 |
priority=0、10001、字符串 |
400 PRIORITY_INVALID |
| GW-008-N5 |
重排列表为空 |
policyIds=[] |
400 POLICY_IDS_INVALID |
| GW-008-N6 |
重排遗漏/跨网关策略 |
传入不存在或其他网关策略 ID |
返回明确业务错误或仅重排本关联网关策略,规则需固定 |
GW-009 配置发布成功
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证客户网关、业务前缀、策略变更产生 outbox 后,Config Publisher 能生成完整 Redis 配置版本并切换 active version。 |
| 前置条件 |
至少存在 1 条 pending business_prefix_config 或 customer_gateway_config outbox;Redis 可用;MySQL 可读。 |
| 测试数据 |
启用客户、业务前缀 671、IP 网关、多 IP、主叫前缀、业务前缀绑定、至少 1 条策略、线路组。 |
| 步骤 |
1. 执行 Config Publisher。2. 读取 cfg:active_version 和 cfg:previous_version。3. 读取 cfg:manifest:{version} 或等价 manifest key。4. 检查 Redis 热路径 key。5. 检查 outbox 状态。 |
| 预期结果 |
Publisher 返回 published=true;active version 更新;manifest schemaVersion=2 且 counts 正确;outbox 由 PENDING/PROCESSING 变为 PUBLISHED;lastError=null。 |
| Redis 检查 |
存在 cfg:v:{version}:auth:ip:{sourceIp}、auth:ip:{sourceIp}:gateways、auth:sip:{username}@{domain}、business_prefix_value:671、business_prefixes、customer_gateway:{id}、customer_gateway:{id}:caller_prefixes、customer_gateway:{id}:business_prefixes、customer_gateway:{id}:policies、customer_gateway:{id}:line_group。 |
| 安全检查 |
Redis 不应写入 SIP 明文密码;manifest checksum 稳定;发布失败前不应切换到半写入版本。 |
GW-010 配置发布失败与重试
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证 Redis 或 MySQL 异常时 outbox 状态、错误记录和后续恢复重试行为可控。 |
| 前置条件 |
存在 pending outbox;可在测试环境临时断开 Redis 或注入 Redis multi.exec 失败。 |
| 测试数据 |
一条客户网关配置变更 outbox。 |
| 步骤 |
1. 使 Redis 写入失败。2. 执行 Config Publisher。3. 检查 outbox 状态和 lastError。4. 恢复 Redis。5. 将失败 outbox 重置为可重试状态或按重试机制等待。6. 再次执行 Publisher。 |
| 预期结果 |
失败时 outbox 标记 FAILED 或保留可重试状态,attempts 增加,lastError 记录截断后的错误;不切换 cfg:active_version 到不完整版本;恢复后可重新发布成功。 |
| 数据检查 |
失败发布不丢 outbox;成功重试后 processedAt 设置;previous/active version 关系正确。 |
| 安全检查 |
lastError 不包含密码、连接串密码或访问令牌。 |
GW-011 网关启停、删除与发布
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证客户网关启用、禁用、删除会影响热路径配置和呼叫准入。 |
| 前置条件 |
已存在启用网关 cgw_ip_seed_001 并已发布。 |
| 测试数据 |
网关 ID;源 IP 100.93.185.30。 |
| 步骤 |
1. 请求 POST /api/v2/customer-gateways/:id/disable。2. 发布配置并发起呼叫。3. 请求 enable 后再次发布并呼叫。4. 删除未被策略/话单保护的网关并发布。 |
| 预期结果 |
禁用后网关状态为 DISABLED,呼叫被拒绝或无法匹配;启用后恢复;删除后默认列表不展示,Redis active 快照不再包含该网关可用路由。 |
| 数据检查 |
启停和删除均生成 outbox 和审计;删除为软删除;有关联策略时删除行为按产品规则固定。 |
| 安全检查 |
无权限启停/删除返回 403;删除不应物理清除历史 CDR 关联。 |
GW-012 业务前缀页面加载
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
回归 Web “业务前缀管理”页面,防止默认表单常量缺失、权限裁剪或接口错误导致页面空白。 |
| 前置条件 |
Web/API 正常;gateway_ops_001 和 gateway_viewer_001 均可登录;后端至少存在 1 条业务前缀。 |
| 测试数据 |
prefix=674,name=业务674,priority=40。 |
| 步骤 |
1. 使用 gateway_viewer_001 登录并进入业务前缀页面。2. 检查列表、筛选、空/错状态。3. 确认 viewer 无新增/编辑/启停/删除按钮。4. 使用 gateway_ops_001 登录,创建、编辑、禁用、启用一条测试前缀。5. 打开浏览器 console。 |
| 预期结果 |
页面正常渲染,无空白;viewer 只读;ops 可完成 CRUD;表单默认值正确;接口错误时显示错误提示;console 无 uncaught error。 |
| 数据检查 |
页面操作产生对应后端记录、outbox 和审计;删除测试前缀后列表状态正确。 |
| 安全检查 |
前端隐藏按钮之外,直接调用无权限写接口仍返回 403。 |
字段校验矩阵:
| 对象 |
字段 |
合法规则 |
非法示例 |
期望错误 |
| 客户网关 |
authMode |
IP、SIP_DIGEST、MIXED |
PASSWORD |
AUTH_MODE_INVALID |
| 客户网关 |
sourceIps |
IP/MIXED 必填,最多 20 个 IPv4/IPv6 |
abc、超过 20 个 |
SOURCE_IP_REQUIRED、SOURCE_IP_INVALID、SOURCE_IP_TOO_MANY |
| 客户网关 |
sipUsername |
SIP/MIXED 必填,A-Za-z0-9_.:+- |
alice gw |
SIP_USERNAME_INVALID |
| 客户网关 |
sipDomain |
SIP/MIXED 必填,字母数字点横线,保存为小写 |
sip_domain! |
SIP_DOMAIN_INVALID |
| 客户网关 |
sipPassword |
SIP/MIXED 创建或身份变更时必填,至少 12 字符 |
short |
SIP_PASSWORD_WEAK、SIP_PASSWORD_REQUIRED |
| 客户网关 |
billingCycleSec |
1-86400 整数 |
0、86401、1.5 |
INTEGER_INVALID |
| 客户网关 |
cycleRate |
非负,最多 14 位整数、6 位小数 |
-1、1.1234567 |
MONEY_INVALID |
| 客户网关 |
callerPrefixes |
PREFIXES 模式必填,最多 20 个,安全字符 |
空、含空格、超过 20 个 |
CALLER_PREFIX_REQUIRED、CALLER_PREFIX_INVALID、LIST_TOO_LARGE |
| 客户网关 |
businessPrefixIds |
BUSINESS_PREFIXES 模式必填,最多 50 个 |
空、超过 50 个 |
BUSINESS_PREFIX_REQUIRED、LIST_TOO_LARGE |
| 客户网关 |
匹配唯一性 |
同一 IP + 同一业务前缀唯一;同一 IP 主叫前缀不互相覆盖;空业务前缀兜底唯一 |
重复 bp_671_seed、021 与 0219、两个 ANY 被叫兜底 |
CUSTOMER_GATEWAY_MATCH_CONFLICT、CUSTOMER_GATEWAY_CALLER_PREFIX_OVERLAP |
| 业务前缀 |
prefix |
1-32 位字母或数字,唯一 |
67#、中文、重复 |
BUSINESS_PREFIX_INVALID 或 409 |
| 业务前缀 |
priority |
1-9999 整数 |
0、10000、1.5 |
PRIORITY_INVALID |
| 策略 |
callerMode/calleeMode |
ANY、EQUALS、PREFIX |
REGEX |
MATCH_MODE_INVALID |
| 策略 |
callerValue/calleeValue |
非 ANY 时必填,最多 64 位安全字符 |
空、138 001 |
VALIDATION_ERROR、MATCH_VALUE_INVALID |
8.4 供应商、落地网关与线路组
本节覆盖供应商主数据、供应商充值、落地网关出局参数、禁呼时段、编码、主被叫改写、主叫号码池、线路组成员、优先级、并发汇总、配置发布和真实呼叫选路。后端接口统一以 /api/v2 为前缀。
公共前置数据:
- 供应商管理账号
vendor_ops_001:包含 vendors.view、vendors.manage、vendor_gateways.view、vendor_gateways.manage、line_groups.view、line_groups.manage、recharges.view、recharges.manage、audit.view。
- 只读账号
vendor_viewer_001:包含 vendors.view、vendor_gateways.view、line_groups.view。
- 供应商
ven_seed_001:余额 20.000000,授信 200.000000。供应商不提供启用/禁用状态入口。
- 落地网关
vgw_primary_001:状态 ENABLED,host 指向 T/UAS 或可控落地模拟端。
- 落地网关
vgw_backup_001:状态 ENABLED,用于线路组故障转移。
- 线路组
llg_vendor_seed_001:状态 ENABLED,至少包含 vgw_primary_001 和 vgw_backup_001。
- 客户网关策略已指向
llg_vendor_seed_001,用于真实呼叫选路验证。
VEN-001 创建供应商
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证供应商主数据创建、默认余额/授信、可用余额、审计和权限。 |
| 前置条件 |
vendor_ops_001 已登录;供应商名 Auto Vendor VEN001 不存在。 |
| 测试数据 |
name=Auto Vendor VEN001,contactName=NOC,phone=4001000001,email=noc-ven001@example.test,creditLimit=188.5,settlement=monthly,notes=created by VEN-001。 |
| 步骤 |
1. 请求 POST /api/v2/vendors。2. 使用返回 ID 请求 GET /api/v2/vendors/:id。3. 请求 GET /api/v2/vendors。4. 查询审计。 |
| 预期结果 |
创建返回 201;余额为系统默认值;creditLimit=188.500000;availableBalance=balance+creditLimit;列表可见,gatewayCount=0;响应不暴露供应商启停状态。 |
| 数据检查 |
写入 created_by、updated_at、deleted_at=null;审计存在 module=vendors、action=create、result=SUCCESS。 |
| 安全检查 |
vendor_viewer_001 创建返回 403;响应不包含内部异常或其他供应商敏感字段。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| VEN-001-N1 |
缺少名称 |
name 为空、空格或非字符串 |
400 VALIDATION_ERROR |
| VEN-001-N2 |
字段超长 |
name 超 120、settlement 超 80、notes 超 500 |
400 VALIDATION_ERROR |
| VEN-001-N3 |
状态字段被忽略或拒绝 |
请求携带 status=DISABLED 或 status=DELETED |
不允许通过供应商接口维护启停;响应不返回供应商状态,且不会生成启停审计 |
| VEN-001-N4 |
授信金额非法 |
creditLimit=-1 或 1.1234567 |
400 MONEY_INVALID |
| VEN-001-N5 |
重复名称 |
创建已存在供应商名称 |
409 或明确业务错误,不重复写入 |
VEN-002 供应商更新与删除保护
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证供应商资料更新、软删除,以及有关联落地网关时的删除保护。 |
| 前置条件 |
已存在供应商 ven_seed_001;另有无关联供应商 ven_delete_free_001。 |
| 测试数据 |
更新 notes=primary carrier,creditLimit=200。 |
| 步骤 |
1. PATCH 更新供应商资料。2. 请求旧的 /vendors/:id/disable 和 /vendors/:id/enable。3. 删除有关联落地网关的供应商。4. 删除无关联供应商。 |
| 预期结果 |
更新成功且金额归一化;供应商启停接口不可用;有关联供应商删除被拒绝或按产品规则受控软删除;无关联供应商可软删除。 |
| 数据检查 |
updated_by、version 或更新时间更新;删除写审计;不会产生供应商 enable/disable 审计;删除不物理清除历史充值和网关引用。 |
| 安全检查 |
无 vendors.manage 的更新、删除返回 403;旧启停接口不应暴露可操作功能。 |
VEN-003 供应商充值
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证供应商人工充值会增加供应商余额、生成充值流水、幂等记录和审计。 |
| 前置条件 |
vendor_ops_001 已登录;供应商 ven_seed_001 当前余额已知。 |
| 测试数据 |
amount=3.5,idempotencyKey=ven003-20260628-0001,remark=vendor top up VEN-003。 |
| 步骤 |
1. 请求 POST /api/v2/vendors/ven_seed_001/recharges。2. 查询供应商详情。3. 请求 GET /api/v2/recharges?accountType=VENDOR&accountId=ven_seed_001。4. 重放相同请求。 |
| 预期结果 |
第一次返回 201;amount=3.500000;afterBalance=beforeBalance+3.500000;供应商详情余额同步增加;重放返回同一流水 ID,不重复加钱。 |
| 数据检查 |
充值流水 accountType=VENDOR;幂等 key 成功记录唯一;审计存在 module=recharges、action=vendor_recharge。 |
| 安全检查 |
无 recharges.manage 返回 403;响应和审计不允许前端覆盖 beforeBalance/afterBalance。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| VEN-003-N1 |
不存在供应商充值 |
POST /vendors/not-found/recharges |
404 或明确业务错误 |
| VEN-003-N2 |
幂等冲突 |
同 key 修改金额或备注 |
409 IDEMPOTENCY_KEY_CONFLICT |
| VEN-003-N3 |
金额非法 |
0、负数、超 6 位小数、超长金额 |
400 MONEY_INVALID,不写流水 |
| VEN-003-N4 |
key 非法 |
少于 8 位或含空格 |
400 IDEMPOTENCY_KEY_INVALID |
VGW-001 创建 IP 落地网关
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证基础 IP 落地网关创建、host/port/transport、CPS/并发、计费周期/费率和配置发布 outbox。 |
| 前置条件 |
vendor_ops_001 已登录;供应商 ven_seed_001 存在。 |
| 测试数据 |
vendorId=ven_seed_001,name=Auto IP Vendor Gateway,authMode=IP,host=203.0.113.10,port=5060,transport=udp,cpsLimit=30,concurrencyLimit=300,billingCycleSec=6,cycleRate=0.012,landingCalleePrefix=86。 |
| 步骤 |
1. 请求 POST /api/v2/vendor-gateways。2. 请求列表和详情。3. 查询 outbox 和审计。 |
| 预期结果 |
创建返回 201;host 小写/规范化;minuteRate=cycleRate*60/billingCycleSec=0.120000;状态默认 ENABLED;列表不返回 SIP HA1。 |
| 数据检查 |
写入 vendor_gateway_config outbox;审计存在 module=vendor_gateways、action=create;供应商 gatewayCount 增加。 |
| 安全检查 |
vendor_viewer_001 创建返回 403;响应不包含 SIP 密钥或内部错误。 |
VGW-002 创建 SIP/MIXED 落地网关
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证落地侧 SIP Digest/MIXED 鉴权、HA1 生成、密码不回显和身份变更规则。 |
| 前置条件 |
供应商存在;SIP 用户名未重复。 |
| 测试数据 |
authMode=SIP_DIGEST,host=SIP.CARRIER.LOCAL,sipUsername=carrier-user,sipPassword=change-me-very-strong。 |
| 步骤 |
1. 创建 SIP_DIGEST 落地网关。2. 查询详情和审计。3. 修改 host 或 sipUsername 但不传 sipPassword。4. 传新密码后修改。5. 切换为 IP。 |
| 预期结果 |
创建成功,host 归一化小写,hasSipCredential=true;响应/审计不含明文密码和 HA1;身份变更不传密码返回 400 SIP_PASSWORD_REQUIRED;切 IP 后 SIP 身份清空。 |
| 数据检查 |
HA1 为 md5(username:host:password);每次成功写操作生成 outbox。 |
| 安全检查 |
密码过短返回 SIP_PASSWORD_WEAK,但不回显密码。 |
VGW-003 禁呼时段校验与生效
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证落地网关可配置禁呼时段,并在呼叫选路时避开禁呼网关。 |
| 前置条件 |
落地网关 vgw_primary_001 存在;线路组有备用网关。 |
| 测试数据 |
forbiddenPeriods=[{weekdayMask:62,startTime:"23:00:00",endTime:"23:59:59"}]。 |
| 步骤 |
1. PATCH 更新禁呼时段。2. 发布配置。3. 在禁呼时段内发起呼叫或模拟时间。4. 在非禁呼时段发起呼叫。 |
| 预期结果 |
禁呼时段内该网关不被选中,若有备用则转备用,否则呼叫按规则失败;非禁呼时段可选中。 |
| 数据检查 |
Redis vendor_gateway:{id} 中包含 forbiddenPeriods;发布 manifest 版本更新。 |
| 安全检查 |
无权限更新返回 403;禁呼配置失败不应影响已发布版本。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| VGW-003-N1 |
非数组 |
forbiddenPeriods={} |
400 FORBIDDEN_PERIODS_INVALID |
| VGW-003-N2 |
星期掩码非法 |
weekdayMask=0 或 128 |
400 INTEGER_INVALID |
| VGW-003-N3 |
时间格式非法 |
startTime=24:00:00 或 23:00 |
400 TIME_INVALID |
| VGW-003-N4 |
跨午夜时段 |
startTime=23:00:00,endTime=01:00:00 |
按产品规则允许或拒绝,行为需固定 |
VGW-004 编码与媒体能力配置
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证落地网关 codec 列表、优先级唯一性和发布快照。 |
| 前置条件 |
落地网关存在。 |
| 测试数据 |
codecs=[{codec:"PCMA",priority:1},{codec:"PCMU",priority:2},{codec:"G729",priority:3}]。 |
| 步骤 |
1. PATCH 更新 codecs。2. 查询详情。3. 发布配置并检查 Redis。4. 发起呼叫检查 SDP/媒体协商是否符合预期。 |
| 预期结果 |
API 返回 codec 大写且按优先级展示;发布快照包含 codecs;媒体协商优先使用配置内 codec。 |
| 数据检查 |
codec 值唯一,priority 唯一;生成 outbox。 |
| 安全检查 |
非法 codec 不进入配置快照。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| VGW-004-N1 |
codec 非数组 |
codecs="PCMA" |
400 CODECS_INVALID |
| VGW-004-N2 |
codec 非法字符 |
codec="PCMA!" |
400 CODEC_INVALID |
| VGW-004-N3 |
codec 重复 |
两条 PCMA |
400 CODEC_DUPLICATE |
| VGW-004-N4 |
优先级重复 |
两条 priority=1 |
400 CODEC_PRIORITY_DUPLICATE |
VGW-005 被叫前缀与主被叫改写
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证落地网关被叫前缀要求、主叫/被叫前缀改写规则和真实出局号码。 |
| 前置条件 |
落地网关已绑定线路组,T/UAS 可接收并记录 INVITE。 |
| 测试数据 |
landingCalleePrefix=86;prefixRules=[{direction:"CALLEE",matchPrefix:"00",replacePrefix:"+",priority:1},{direction:"CALLER",matchPrefix:"021",replacePrefix:"",priority:1}]。 |
| 步骤 |
1. 更新落地网关前缀和改写规则。2. 发布配置。3. 发起被叫需转换的呼叫。4. 在落地侧抓取 INVITE 或检查 T 日志。5. 查询 CDR 中落地号码快照。 |
| 预期结果 |
A 发往落地侧的 Request-URI/To 被叫符合 landingCalleePrefix 和 CALLEE 改写规则;主叫符合 CALLER 改写规则;CDR 保留原始号码和改写后号码快照。 |
| 数据检查 |
Redis vendor_gateway:{id} 包含 prefixRules;同 direction 下 priority 唯一。 |
| 安全检查 |
非法前缀字符或重复优先级返回错误,不发布。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| VGW-005-N1 |
非法 landingCalleePrefix |
86+ 或中文 |
400 LANDING_CALLEE_PREFIX_INVALID |
| VGW-005-N2 |
prefixRules 非数组 |
prefixRules={} |
400 PREFIX_RULES_INVALID |
| VGW-005-N3 |
direction 非法 |
direction=BOTH |
400 PREFIX_DIRECTION_INVALID |
| VGW-005-N4 |
matchPrefix 非法 |
含空格或中文 |
400 PREFIX_INVALID |
| VGW-005-N5 |
同方向优先级重复 |
两条 CALLEE priority=1 |
400 PREFIX_RULE_PRIORITY_DUPLICATE |
VGW-006 指定主叫号码池
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证落地网关可配置主叫改写号码池,呼叫时按权重/状态选择出局主叫。 |
| 前置条件 |
落地网关启用并参与线路组。 |
| 测试数据 |
callerRewritePool=[{caller:"02160010001",weight:80},{caller:"02160010002",weight:20},{caller:"02160010003",weight:10,status:"DISABLED"}]。 |
| 步骤 |
1. 更新主叫号码池。2. 发布配置。3. 连续发起多通呼叫。4. 统计落地侧收到的 From/P-Asserted-Identity 或系统实际使用字段。 |
| 预期结果 |
只使用启用号码;权重越高被选择比例越高;禁用号码不被使用;无号码池时按默认主叫规则。 |
| 数据检查 |
Redis vendor_gateway:{id}:caller_rewrite_pool 仅包含启用号码;manifest callerRewriteCount 正确。 |
| 安全检查 |
号码池最多 50 条,重复 caller 被拒绝。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| VGW-006-N1 |
非数组 |
callerRewritePool={} |
400 CALLER_REWRITE_POOL_INVALID |
| VGW-006-N2 |
数量超限 |
超过 50 条 |
400 CALLER_REWRITE_POOL_TOO_LARGE |
| VGW-006-N3 |
caller 非法 |
含 +、空格或中文 |
400 CALLER_REWRITE_INVALID |
| VGW-006-N4 |
权重非法 |
weight=0 或超 100000 |
400 INTEGER_INVALID |
| VGW-006-N5 |
caller 重复 |
两条相同 caller |
400 CALLER_REWRITE_DUPLICATE |
VGW-007 落地网关启停、删除与发布
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证落地网关启用、禁用、软删除会影响线路组可选成员和呼叫选路。 |
| 前置条件 |
vgw_primary_001 和 vgw_backup_001 均在线并已发布。 |
| 测试数据 |
主备两个落地网关 ID。 |
| 步骤 |
1. 禁用 primary。2. 发布配置并发起呼叫。3. 启用 primary 后发布并发起呼叫。4. 删除未被保护的落地网关并发布。 |
| 预期结果 |
primary 禁用后呼叫转 backup;恢复后按优先级选择 primary;删除后快照不包含该网关可用路由;列表默认不展示软删除记录。 |
| 数据检查 |
启停/删除生成 vendor_gateway_config outbox 和审计;历史 CDR 成本快照不被破坏。 |
| 安全检查 |
无 vendor_gateways.manage 返回 403;被线路组引用的网关删除行为按产品规则受控。 |
LLG-001 创建线路组并添加成员
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证线路组创建、成员添加、并发汇总和配置发布。 |
| 前置条件 |
至少存在两个启用落地网关。 |
| 测试数据 |
线路组 name=Auto Line Group LLG001;成员 A:vendorGatewayId=vgw_primary_001、priority=1、weight=2、concurrencyCap=60;成员 B:vendorGatewayId=vgw_backup_001、priority=2、weight=1、concurrencyCap=40。 |
| 步骤 |
1. 请求 POST /api/v2/landing-line-groups。2. 添加两个成员。3. 请求详情和列表。4. 发布配置并检查 Redis。 |
| 预期结果 |
线路组创建成功;itemCount=2;enabledItemCount=2;concurrencyCapSum=100;gatewayConcurrencyLimitSum 等于启用成员网关并发限制合计;Redis 写入 line_group:{id} 和 line_group:{id}:items。 |
| 数据检查 |
每次写操作生成 line_group_config outbox;审计包含 create、item_create。 |
| 安全检查 |
只读用户可查看不可写,写返回 403。 |
LLG-002 线路组成员更新与重排
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证线路组成员优先级、权重、并发上限和状态影响路由顺序。 |
| 前置条件 |
线路组 llg_vendor_seed_001 含 primary 和 backup 两个成员。 |
| 测试数据 |
将 backup 重排到第一位;将 primary status=DISABLED;调整 concurrencyCap=20、weight=5。 |
| 步骤 |
1. 请求 POST /api/v2/landing-line-groups/:id/items/reorder。2. PATCH 更新成员状态/权重/并发。3. 发布配置。4. 发起呼叫确认选路。 |
| 预期结果 |
详情中成员顺序按新 priority;禁用成员不计入 enabledItemCount 和并发汇总;呼叫按新顺序和状态选路。 |
| 数据检查 |
Redis line_group:{id}:items 顺序正确;生成 item_update、item_reorder 审计和 outbox。 |
| 安全检查 |
重排必须使用同一线路组完整成员集合或按产品规则拒绝。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| LLG-002-N1 |
权重非法 |
weight=0 或 1001 |
400 INTEGER_INVALID |
| LLG-002-N2 |
并发上限非法 |
concurrencyCap=-1 或 100001 |
400 INTEGER_INVALID |
| LLG-002-N3 |
priority 非法 |
priority=0 或 10001 |
400 INTEGER_INVALID |
| LLG-002-N4 |
重排空列表 |
itemIds=[] |
400 LINE_GROUP_ITEM_IDS_INVALID |
| LLG-002-N5 |
删除成员 |
DELETE /landing-line-groups/:id/items/:gatewayId |
成员移除,汇总和发布快照同步更新 |
LLG-003 删除被引用线路组
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证线路组被客户网关或策略引用时不能破坏性删除。 |
| 前置条件 |
客户网关 cgw_ip_seed_001 或策略引用 llg_vendor_seed_001。 |
| 测试数据 |
被引用线路组 ID;未引用线路组 ID。 |
| 步骤 |
1. 删除被引用线路组。2. 删除未引用线路组。3. 查询客户网关/策略引用关系和配置快照。 |
| 预期结果 |
被引用线路组删除被拒绝或只能禁用,规则固定;未引用线路组可软删除;不会产生悬空 lineGroupId。 |
| 数据检查 |
删除成功设置软删除标记;被拒绝时不修改引用;审计和 outbox 按结果记录。 |
| 安全检查 |
无权限删除返回 403;错误不泄露内部约束名。 |
LLG-004 真实呼叫选路与故障转移
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证客户策略指向线路组后,OpenSIPS 能按线路组成员优先级、状态、禁呼和并发选择落地网关。 |
| 前置条件 |
A/B/T 联调环境 active;客户网关策略指向 llg_vendor_seed_001;primary 和 backup 均可被 T/UAS 验证。 |
| 测试数据 |
一通正常呼叫;一组 primary 禁用或禁呼时段内呼叫;一组并发接近上限呼叫。 |
| 步骤 |
1. 发布当前配置。2. 发起正常呼叫,确认落到 primary。3. 禁用 primary 或设置当前禁呼,发布后再呼叫。4. 配置 primary 并发上限较低,发起多路并发。5. 查询 CDR、录音和落地网关成本。 |
| 预期结果 |
正常呼叫选 primary;primary 不可用时转 backup;并发达到上限后新呼叫转备用或被拒绝;CDR 记录实际落地网关、供应商成本、失败原因。 |
| 数据检查 |
raw_cdrs/rated_cdrs 中 vendorGatewayId、lineGroupId、cost 字段正确;HOMER 可看到出局 INVITE 目标。 |
| 安全检查 |
故障转移不绕过客户鉴权、余额检查、业务前缀和主叫限制。 |
字段校验矩阵:
| 对象 |
字段 |
合法规则 |
非法示例 |
期望错误 |
| 供应商 |
name |
必填,1-120 字符,唯一 |
空、超长、重复 |
VALIDATION_ERROR 或 409 |
| 供应商 |
creditLimit |
非负,最多 14 位整数、6 位小数 |
-1、1.1234567 |
MONEY_INVALID |
| 落地网关 |
authMode |
IP、SIP_DIGEST、MIXED |
PASSWORD |
AUTH_MODE_INVALID |
| 落地网关 |
host |
IP 或小写 hostname 规则 |
bad_host! |
HOST_INVALID |
| 落地网关 |
port |
1-65535 整数 |
0、65536 |
INTEGER_INVALID |
| 落地网关 |
transport |
udp、tcp、tls |
ws |
TRANSPORT_INVALID |
| 落地网关 |
sipUsername |
SIP/MIXED 必填,安全字符 |
carrier user |
SIP_USERNAME_INVALID |
| 落地网关 |
sipPassword |
SIP/MIXED 创建或身份变更必填,至少 12 字符 |
short |
SIP_PASSWORD_WEAK、SIP_PASSWORD_REQUIRED |
| 落地网关 |
cpsLimit |
1-10000 整数,只允许数字 |
0、10001、20 CPS |
INTEGER_INVALID |
| 落地网关 |
concurrencyLimit |
0-100000 整数,0 表示不限 |
-1、100001 |
INTEGER_INVALID |
| 落地网关 |
billingCycleSec |
1-60 整数 |
0、61 |
INTEGER_INVALID |
| 落地网关 |
cycleRate |
非负,最多 14 位整数、6 位小数 |
-1、1.1234567 |
MONEY_INVALID |
| 落地网关 |
landingCalleePrefix |
可空,字母数字 |
86+ |
LANDING_CALLEE_PREFIX_INVALID |
| 落地网关 |
forbiddenPeriods |
数组,weekdayMask 1-127,时间 HH:mm:ss |
非数组、24:00:00 |
FORBIDDEN_PERIODS_INVALID、TIME_INVALID |
| 落地网关 |
codecs |
数组,codec/priority 唯一 |
重复 codec 或 priority |
CODEC_DUPLICATE、CODEC_PRIORITY_DUPLICATE |
| 落地网关 |
prefixRules |
数组,direction 为 CALLER/CALLEE,同方向 priority 唯一 |
BOTH、重复优先级 |
PREFIX_DIRECTION_INVALID、PREFIX_RULE_PRIORITY_DUPLICATE |
| 落地网关 |
callerRewritePool |
最多 50 条,caller 唯一,weight 1-100000 |
重复 caller、weight=0 |
CALLER_REWRITE_DUPLICATE、INTEGER_INVALID |
| 线路组 |
name |
必填,1-120 字符 |
空、超长 |
VALIDATION_ERROR |
| 线路组成员 |
priority |
1-10000 整数 |
0、10001 |
INTEGER_INVALID |
| 线路组成员 |
weight |
1-1000 整数 |
0、1001 |
INTEGER_INVALID |
| 线路组成员 |
concurrencyCap |
0-100000 整数,0 表示不限 |
-1、100001 |
INTEGER_INVALID |
8.5 SIP 呼叫、CDR 与计费
本节覆盖客户接入、SIP 注册/鉴权、业务前缀识别、线路组出局、Redis Stream CDR、CDR Worker 计费、客户余额扣减、失败话单、幂等、号码归属地和话单查询。三机端到端用例默认在 A/B/T 联调环境执行。
公共前置数据:
- 客户
cus_call_seed_001:状态 ENABLED,billingMode=PREPAID,余额充足,授信和最低余额按测试场景设置。
- IP 客户网关
cgw_call_ip_001:authMode=IP,T 源 IP 已在 sourceIps,启用,绑定业务前缀 671 和线路组 llg_call_seed_001。
- SIP 客户网关
cgw_call_sip_001:authMode=SIP_DIGEST,账号密码已知,启用,绑定线路组。
- 线路组
llg_call_seed_001:启用,包含 vgw_call_primary_001 和 vgw_call_backup_001。
- 落地网关
vgw_call_primary_001:启用,计费周期 6 秒,费率 0.012000,T/UAS 可接收。
- 业务前缀
bp_671_seed:prefix=671,启用。
- 号码库包含
1380013 号段,归属地和运营商字段可预测。
- Config Publisher 已成功发布,A 侧 OpenSIPS 使用最新
cfg:active_version。
CALL-001 IP 客户成功呼叫
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 IP 鉴权客户从 T 发起呼叫时,A 能识别客户网关、匹配业务前缀、选择线路组落地,并生成完整 CDR。 |
| 前置条件 |
A/B/T 服务 active;cgw_call_ip_001 启用;T 源 IP 位于网关 sourceIps;落地 UAS 可接听。 |
| 测试数据 |
主叫 02160010001;被叫 67113800138000;预期真实被叫 13800138000;Call-ID 使用唯一值 call-001-{timestamp}。 |
| 步骤 |
1. 从 T 使用 IP 客户脚本发起呼叫。2. 等待 100 Trying/Giving it a try、180/183 或 200 OK。3. 持话 6-10 秒后 BYE。4. 在 B 查询 Redis Stream、raw CDR、rated CDR、录音记录。5. 在 HOMER 查询 Call-ID。 |
| 预期结果 |
SIP 返回 100 和 200 OK,BYE 返回 200 OK;A 出局到预期落地网关;B 生成 1 条 raw CDR 和 1 条 rated CDR;CDR 包含 customerId、customerGatewayId、lineGroupId、vendorGatewayId、businessPrefix、rawCallee、callee、landingCaller、landingCallee。 |
| 数据检查 |
raw CDR ratingStatus=RATED;rated CDR billSec、费用和余额扣减正确;configVersion 为当前 active version;HOMER 中可查到入局和出局信令。 |
| 安全检查 |
非白名单源 IP 不应复用该客户网关;CDR payload 不包含 SIP 密码或内部密钥。 |
CALL-002 SIP Digest 注册和呼叫成功
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 SIP Digest 客户可使用正确账号注册/鉴权并完成呼叫。 |
| 前置条件 |
cgw_call_sip_001 启用;SIP 用户名、域、密码与 A 热路径 HA1 一致;T 脚本支持 REGISTER/INVITE Digest。 |
| 测试数据 |
SIP 用户名 sip-cus-001;域 sip.example.local;正确密码;被叫 67113800138000。 |
| 步骤 |
1. T 使用正确账号执行 REGISTER。2. 使用同一账号发起 INVITE。3. 持话后挂断。4. 查询 CDR 和客户网关识别结果。 |
| 预期结果 |
REGISTER 成功或 INVITE Digest 鉴权成功;呼叫成功;CDR 归属到 cgw_call_sip_001;source_ip 记录 T IP;业务前缀和落地路由正常。 |
| 数据检查 |
raw/rated CDR 各 1 条;客户余额扣减一次;HOMER 可看到 401 challenge 后的成功鉴权流程。 |
| 安全检查 |
CDR、日志、审计不记录明文 SIP 密码。 |
CALL-003 SIP Digest 密码错误
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证错误 SIP 密码不能注册或呼叫,且不会产生成功计费。 |
| 前置条件 |
SIP 网关启用。 |
| 测试数据 |
正确用户名和域;错误密码 WrongDigestPassword-003。 |
| 步骤 |
1. T 使用错误密码执行 REGISTER。2. 使用错误密码发起 INVITE。3. 查询 A 日志、HOMER、CDR、客户余额。 |
| 预期结果 |
REGISTER/INVITE 被 401/403 拒绝或无成功路由;不产生 rated CDR;客户余额不变;如产生失败 raw CDR,应标记 SKIPPED 且费用为 0。 |
| 数据检查 |
没有成功通话录音;失败原因可追踪;不产生余额扣减。 |
| 安全检查 |
响应不暴露正确 HA1、密码规则或账号存在性细节。 |
CALL-004 客户余额不足或低于最低余额
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证预付费客户余额不足时,热路径阻止新呼叫或按授信/最低余额规则保护。 |
| 前置条件 |
客户 cus_low_balance_001 预付费,余额、授信、最低余额已设置到触发保护条件;配置已发布。 |
| 测试数据 |
余额 0.000000 或低于 minBalance;被叫 67113800138000。 |
| 步骤 |
1. 发起呼叫。2. 查询 SIP 响应。3. 查询 raw/rated CDR、客户余额和 A 日志。 |
| 预期结果 |
呼叫被拒绝,建议返回明确 402/403/503 类业务失败响应,具体以 OpenSIPS 配置为准;不产生 rated CDR;余额不被错误扣减。 |
| 数据检查 |
如生成 raw CDR,ratingStatus=SKIPPED,customerFee=0;失败原因包含余额不足或策略拒绝。 |
| 安全检查 |
余额不足响应不泄露客户余额具体值给 SIP 对端。 |
CALL-005 禁用客户、客户网关或业务前缀呼叫
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证禁用状态会进入热路径并阻止呼叫。 |
| 前置条件 |
分别准备禁用客户、禁用客户网关、禁用业务前缀三种配置,并发布。 |
| 测试数据 |
同一主叫和被叫 67113800138000。 |
| 步骤 |
1. 禁用客户后发布并呼叫。2. 恢复客户,禁用客户网关后发布并呼叫。3. 恢复网关,禁用业务前缀后发布并呼叫。4. 查询 CDR 和日志。 |
| 预期结果 |
三种禁用均导致呼叫被拒绝或无法匹配;不产生成功 rated CDR;失败原因可追踪。 |
| 数据检查 |
Redis active snapshot 不包含禁用业务前缀;禁用网关不应作为可用入口;客户余额不变。 |
| 安全检查 |
禁用对象恢复后必须重新发布才能生效,不应使用陈旧配置误放行。 |
CALL-006 落地网关禁用故障转移
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证优先级最高落地网关不可用时,线路组可转到下一可用成员。 |
| 前置条件 |
线路组包含 primary 和 backup;两者都可被 T/UAS 接听。 |
| 测试数据 |
禁用 vgw_call_primary_001;backup vgw_call_backup_001 启用。 |
| 步骤 |
1. 正常呼叫确认 primary 被选中。2. 禁用 primary 并发布。3. 再次呼叫。4. 查询 CDR、HOMER 和落地侧日志。 |
| 预期结果 |
禁用前 CDR 记录 primary;禁用后新呼叫记录 backup;SIP 呼叫成功;供应商成本按 backup 费率计算。 |
| 数据检查 |
CDR vendorGatewayId、vendorId、lineGroupId 正确;配置版本变化可追踪。 |
| 安全检查 |
故障转移不绕过客户鉴权、主叫匹配、业务前缀匹配和余额保护。 |
CALL-007 主叫/被叫策略不匹配
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证客户网关主叫前缀、业务前缀和策略匹配失败时不会误路由。 |
| 前置条件 |
客户网关设置 callerMatchMode=PREFIXES 且只允许 021;calleeMatchMode=BUSINESS_PREFIXES 只允许 671;策略设置特定主被叫。 |
| 测试数据 |
主叫 01060010001;被叫 67213800138000 或不满足策略的号码。 |
| 步骤 |
1. 发起主叫不匹配呼叫。2. 发起业务前缀不匹配呼叫。3. 发起策略不匹配呼叫。4. 查询 CDR 和日志。 |
| 预期结果 |
呼叫被拒绝或进入明确失败路径;不产生 rated CDR;raw CDR 如存在,placeholder ID 归一为 null,失败原因可追踪。 |
| 数据检查 |
customer_gateway_policy_id=no_policy_match 等占位值入库时转换为 null;余额不扣减。 |
| 安全检查 |
不匹配失败不暴露可用前缀或策略详情。 |
CDR-001 成功话单计费
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证成功 2xx 通话按落地网关计费周期向上取整,并扣减客户余额。 |
| 前置条件 |
落地网关 billingCycleSec=6,cycleRate=0.012000;客户余额 10.000000。 |
| 测试数据 |
CDR duration_sec=28,sip_code=200。 |
| 步骤 |
1. 通过真实呼叫或注入 CDR Stream 生成 28 秒成功事件。2. 等待 CDR Worker 消费。3. 查询 raw/rated CDR 和客户余额。 |
| 预期结果 |
cycles=5;billSec=30;customerFee=0.060000;vendorCost=0.060000;grossProfit=0.000000;客户余额变为 9.940000。 |
| 数据检查 |
raw CDR ratingStatus=RATED;rated CDR customerRate.source=S23_MINIMAL_MIRROR_VENDOR_RATE,vendorRate.source=vendor_gateway;客户 updatedBy=worker-cdr,version 增加。 |
| 安全检查 |
计费不写充值流水;余额扣减在数据库事务内完成。 |
计费边界矩阵:
| durationSec |
billingCycleSec |
cycleRate |
期望 billSec |
期望金额 |
| 1 |
6 |
0.012000 |
6 |
0.012000 |
| 6 |
6 |
0.012000 |
6 |
0.012000 |
| 7 |
6 |
0.012000 |
12 |
0.024000 |
| 28 |
6 |
0.012000 |
30 |
0.060000 |
| 60 |
60 |
0.120000 |
60 |
0.120000 |
CDR-002 失败、拒接和零时长话单
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证非 2xx 或零时长事件只落 raw CDR,不扣余额。 |
| 前置条件 |
CDR Worker active;客户余额已知。 |
| 测试数据 |
sip_code=486、503、404;duration_sec=0;answered_at=none。 |
| 步骤 |
1. 模拟忙线、拒接、无路由或注入失败 CDR。2. 等待 Worker 消费。3. 查询 raw/rated CDR 和余额。 |
| 预期结果 |
raw CDR 创建成功;ratingStatus=SKIPPED;不创建 rated CDR 或 rated 金额为 0,按实现固定;客户余额不变。 |
| 数据检查 |
hangupReason、sipCode、startedAt/endedAt 保存;失败 CDR 可在话单中心筛选。 |
| 安全检查 |
失败话单不触发录音播放越权,不泄露内部路由细节。 |
CDR-003 重复 CDR 幂等
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证同一 event_id 重复进入 Redis Stream 时,raw/rated CDR 和余额扣减只发生一次。 |
| 前置条件 |
Redis Stream 和 CDR Worker active。 |
| 测试数据 |
两条完全相同 CDR,event_id=evt-cdr-003-001,duration_sec=28,sip_code=200。 |
| 步骤 |
1. 向 stream:cdr_payload 写入第一条事件。2. 写入重复事件。3. 等待 Worker 消费。4. 查询 raw CDR、rated CDR、客户余额、Redis ack/lock。 |
| 预期结果 |
只有 1 条 raw CDR 和 1 条 rated CDR;第二条处理结果为 duplicate 或被 Redis 幂等锁 ACK;客户余额只扣一次。 |
| 数据检查 |
raw_cdrs.event_id 唯一;rated_cdrs.raw_cdr_id 唯一;Redis lock:cdr:{event_id} 在 TTL 内存在。 |
| 安全检查 |
重复事件不进入 deadletter;重复处理不产生告警噪音。 |
CDR-004 CDR Stream 格式校验和死信
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证非法 CDR Stream payload 不会阻塞队列,并进入 deadletter。 |
| 前置条件 |
CDR consumer group 已创建。 |
| 测试数据 |
缺少 event_id;schema_version=999;duration_sec=-1;sip_code=999。 |
| 步骤 |
1. 向 Redis Stream 写入非法事件。2. 执行 CDR Worker 消费。3. 查询 deadletter stream、ACK 和主 stream pending。 |
| 预期结果 |
非法事件写入 stream:cdr_payload:deadletter 或配置的 deadletter stream;原消息 ACK;主消费组 pending 不增长。 |
| 数据检查 |
deadletter 包含 original_redis_id、event_id、call_id、error、payload、deadlettered_at。 |
| 安全检查 |
deadletter payload 不包含密码或访问令牌;错误信息截断且可读。 |
CDR-005 Worker 可恢复错误重试
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 MySQL 短故障等可恢复错误不会 ACK 原消息,也不会误入 deadletter。 |
| 前置条件 |
CDR Worker 正常;可临时停止 MySQL 或注入 CdrRetryableError。 |
| 测试数据 |
一条成功呼叫 CDR。 |
| 步骤 |
1. 停止 MySQL 或制造数据库连接失败。2. 写入 CDR 事件并执行 Worker。3. 检查 Redis pending 和 deadletter。4. 恢复 MySQL。5. 执行 pending reclaim/重试。 |
| 预期结果 |
故障期间消息保留 pending,幂等锁释放;不 ACK、不 deadletter;恢复后重试成功,raw/rated CDR 入库。 |
| 数据检查 |
pending 从 1 回到 0;deadletter 无新增;余额只扣一次。 |
| 安全检查 |
可恢复错误日志不包含数据库密码。 |
CDR-006 缺失计费对象
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证 CDR 中缺少客户或落地网关等关键计费对象时,系统不会错误扣费。 |
| 前置条件 |
CDR Worker active。 |
| 测试数据 |
customer_id=unknown、vendor_gateway_id=unknown、customer_gateway_policy_id=no_policy_match、vendor_gateway_id 指向不存在网关。 |
| 步骤 |
1. 分别注入上述 CDR。2. 等待 Worker 消费。3. 查询 raw/rated CDR 和余额。 |
| 预期结果 |
placeholder ID 入库时归一为 null;缺少 billable customer/vendor gateway 时 raw 标记 SKIPPED 或 FAILED,按实现固定;不扣余额。 |
| 数据检查 |
none、unknown、no_active_version、no_policy_match、single_gateway 等占位不作为外键写入;失败原因可定位。 |
| 安全检查 |
不因无效外键导致 Worker 崩溃或队列停滞。 |
CDR-007 号码归属地识别
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证号码库快照参与呼叫归属地和运营商识别,并写入 CDR。 |
| 前置条件 |
号码库已导入并发布;包含手机号段 1380013 和区号/运营商规则。 |
| 测试数据 |
被叫 67113800138000;预期 normalized_callee=13800138000。 |
| 步骤 |
1. 发起成功呼叫。2. 查询 raw CDR payload 和话单列表。3. 使用 cityCode、carrier 筛选话单。 |
| 预期结果 |
CDR 包含 calleeCityCode、calleeCityName、calleeProvinceName、calleeOperator、calleeNumberType;话单筛选能命中该记录。 |
| 数据检查 |
手机号、固话、国际号码分别识别为 MOBILE、LANDLINE、INTERNATIONAL 或 UNKNOWN;未知号码不应报错。 |
| 安全检查 |
号码库未命中时降级为 UNKNOWN,不影响话单入库。 |
CDR-008 话单查询和详情
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证话单中心 API 查询条件、分页、详情和权限。 |
| 前置条件 |
已存在多条不同主叫、被叫、客户网关、落地网关、城市、运营商和时间的话单。 |
| 测试数据 |
caller=02160010001,callee=13800138000,customerGatewayId=cgw_call_ip_001,vendorGatewayId=vgw_call_primary_001,cityCode=340100,carrier=MOBILE,take=20,skip=0。 |
| 步骤 |
1. 使用有 cdr.view 权限账号请求 GET /api/v2/cdrs。2. 分别组合号码、网关、城市、运营商和时间范围查询。3. 请求 GET /api/v2/cdrs/:id。4. 使用无权限账号请求。 |
| 预期结果 |
有权限查询返回匹配数据和分页信息;详情包含 raw/rated、录音、业务前缀、落地、费用等字段;无权限返回 403。 |
| 数据检查 |
查询默认 take=100,最大 500;skip 最大 1000000;时间范围按 startedAt 筛选。 |
| 安全检查 |
非法查询不触发 SQL 注入;无权限账号不能看到话单。 |
补充负向用例:
| 子 ID |
场景 |
步骤 |
期望结果 |
| CDR-008-N1 |
非法运营商 |
carrier=CMCC |
400 CARRIER_INVALID |
| CDR-008-N2 |
时间范围反转 |
startedFrom > startedTo |
400 TIME_RANGE_INVALID |
| CDR-008-N3 |
非法日期 |
startedFrom=not-a-date |
400 TIME_RANGE_INVALID |
| CDR-008-N4 |
分页非法 |
take=0、take=501、skip=-1 |
400 QUERY_INVALID |
| CDR-008-N5 |
查询字段超长 |
caller/callee 超 64 或 gatewayId 超 32 |
400 QUERY_INVALID |
CDR-009 长 event_id 存储兼容
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证超过数据库 event_id 长度限制的 CDR event_id 会稳定压缩存储,不导致入库失败。 |
| 前置条件 |
CDR Worker active。 |
| 测试数据 |
长度超过 64 的 event_id,其余字段为成功计费事件。 |
| 步骤 |
1. 注入长 event_id CDR。2. 等待 Worker 消费。3. 查询 raw CDR eventId 和 payload。4. 重复注入相同长 event_id。 |
| 预期结果 |
raw CDR 入库成功;存储 eventId 被稳定截断加 hash 后缀;payload 保留原始 event_id;重复事件仍按同一 event_id 幂等处理。 |
| 数据检查 |
不进入 deadletter;余额只扣一次。 |
| 安全检查 |
hash 截断逻辑稳定,不泄露额外敏感信息。 |
CDR-010 并发计费余额一致性
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证多条成功 CDR 并发处理时,客户余额通过事务锁串行扣减,不丢扣、不重复扣。 |
| 前置条件 |
客户余额足够;CDR Worker 可并发或批量消费。 |
| 测试数据 |
同一客户 5-12 条不同 event_id 成功 CDR,每条预期费用 0.012000 或固定值。 |
| 步骤 |
1. 批量写入多条 CDR。2. 启动 Worker 消费。3. 查询 rated CDR 数量和客户余额。4. 检查 Redis pending/deadletter。 |
| 预期结果 |
rated CDR 数量等于成功事件数;余额减少等于费用总和;pending=0;deadletter=0。 |
| 数据检查 |
每条 raw CDR event_id 唯一;客户 version 增加符合更新次数或按实现固定。 |
| 安全检查 |
并发下不产生负外键、重复 rated 或事务死锁长时间阻塞。 |
8.6 录音、播放与质检
本节覆盖 RTPEngine 录音 ready 文件、Recording Worker 搬运、校验、源文件清理、录音播放鉴权、Nginx X-Accel/Range、质检抽样、评分保存和审计。后端接口统一以 /api/v2 为前缀。
公共前置数据:
- 录音查看账号
recording_viewer_001:包含 cdr.view、recordings.play。
- 质检账号
quality_ops_001:包含 quality.view、quality.manage、recordings.play。
- 只读质检账号
quality_viewer_001:包含 quality.view,不包含 quality.manage、recordings.play。
- 至少 1 条成功通话 CDR,关联 READY 录音
rec_ready_seed_001,storageKey 指向 B 本地录音数据盘。
- 至少 1 条 PENDING 录音
rec_pending_seed_001,用于播放和质检不可用状态。
- A 侧
/dev/shm/voip_rec 或测试目录可生成 ready 文件,B 侧录音存储目录可写。
REC-001 录音搬运成功
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 Recording Worker 从 A 拉取 ready 文件,校验大小/hash 后写入 B,并安全删除源文件。 |
| 前置条件 |
A 侧存在测试录音文件和 ready 标记;B 侧 Recording Worker 可通过私网访问 A;关联 CDR/callId 可匹配。 |
| 测试数据 |
文件名包含 Call-ID;ready 内容包含相对路径和 size;文件大小固定,例如 55758 bytes。 |
| 步骤 |
1. 在 A 侧生成测试录音和 ready 文件。2. 启动或触发 Recording Worker。3. 查询 B 侧录音文件、数据库 recordings 表和 Worker 日志。4. 检查 A 源文件和 ready 文件。 |
| 预期结果 |
B 侧文件存在,大小/hash 与 A 源文件一致;recording 状态为 READY;storageKey 为安全相对路径;A 源文件在校验成功后删除;日志记录成功搬运。 |
| 数据检查 |
recordings 记录包含 callId、rawCdrId 或关联键、fileSize、checksum、storageKey、readyAt;状态从 PENDING 变为 READY。 |
| 安全检查 |
Worker 只处理配置目录下文件;不会跟随任意绝对路径或 ../ 路径。 |
REC-002 本地校验失败保留源文件
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证复制后大小/hash 不一致时不删除 A 源文件,避免录音丢失。 |
| 前置条件 |
可构造错误 ready size、截断文件或模拟 B 写入失败。 |
| 测试数据 |
ready size 与实际文件大小不一致。 |
| 步骤 |
1. 生成 size 不一致的 ready 文件。2. 执行 Recording Worker。3. 查询 A/B 文件和 recordings 状态。 |
| 预期结果 |
Worker 标记失败或保持待重试;A 源文件保留;B 不产生 READY 记录;错误日志可定位。 |
| 数据检查 |
recordings 不应被误标记 READY;如生成 FAILED 状态,应记录失败原因和重试信息。 |
| 安全检查 |
失败日志不泄露 SSH 私钥、完整内部路径或连接凭据。 |
REC-003 播放接口鉴权与权限
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证录音播放必须登录且具备播放权限。 |
| 前置条件 |
存在 READY 录音 rec_ready_seed_001。 |
| 测试数据 |
未登录请求;有 quality.view 但无 recordings.play 的账号;有 recordings.play 的账号。 |
| 步骤 |
1. 未登录请求录音播放接口。2. 使用 quality_viewer_001 请求播放。3. 使用 recording_viewer_001 请求播放。 |
| 预期结果 |
未登录返回 401 AUTH_REQUIRED;无播放权限返回 403;有权限返回 200/206 或 X-Accel 跳转响应,按部署方式固定。 |
| 数据检查 |
无权限请求不写播放成功审计;有权限播放可按审计策略记录访问。 |
| 安全检查 |
不允许通过猜 ID 或 storageKey 绕过权限。 |
REC-004 录音路径穿越防护
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证录音 storageKey 和播放接口防止路径穿越、绝对路径和特殊字符读取。 |
| 前置条件 |
有权限账号已登录。 |
| 测试数据 |
伪造 ID 或 storageKey:../etc/passwd、..\\windows\\win.ini、/etc/passwd、空值、超长 key。 |
| 步骤 |
1. 直接请求伪造录音 ID。2. 构造数据库中异常 storageKey 的测试记录并请求播放。3. 检查响应和日志。 |
| 预期结果 |
返回 404 或安全错误;不返回目标文件内容;Nginx internal path 不被构造到录音根目录之外。 |
| 数据检查 |
安全拒绝不改变 recording 状态。 |
| 安全检查 |
响应不泄露服务器真实路径和目录结构。 |
REC-005 Range 播放和浏览器播放
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证录音支持浏览器播放、Range 请求和常见播放器拖动。 |
| 前置条件 |
READY 录音文件存在,Nginx/X-Accel 配置已启用。 |
| 测试数据 |
Range: bytes=0-1023,完整播放请求,超出范围请求。 |
| 步骤 |
1. 使用浏览器点击播放。2. 使用 curl 请求 Range。3. 请求完整文件。4. 请求超出文件大小的 Range。 |
| 预期结果 |
合法 Range 返回 206,Content-Range 和 Content-Length 正确;完整请求返回 200 或内部跳转;浏览器可播放;超范围返回 416 或合理错误。 |
| 数据检查 |
播放不改变录音文件;访问日志可追踪 recordingId。 |
| 安全检查 |
响应头不暴露真实磁盘路径;只允许内部 X-Accel 路径。 |
QA-001 稳定抽样
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证同一抽样规则和录音在多次刷新中抽样结果稳定。 |
| 前置条件 |
存在多个 READY 录音;有启用抽样规则。 |
| 测试数据 |
抽样比例 0%、10%、100%;按客户或全局规则。 |
| 步骤 |
1. 创建或更新抽样规则。2. 多次请求质检列表。3. 调整规则 ID 或比例后再次请求。 |
| 预期结果 |
同一规则下同一 recording 抽样命中结果稳定;0% 不命中,100% 全命中;规则变化后可重新计算。 |
| 数据检查 |
列表返回 samplingScore、reviewStatus 或等价字段;不会随机抖动。 |
| 安全检查 |
无 quality.view 不能查看质检列表。 |
QA-002 保存质检评分
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证质检人员可保存评分、结果、标签和备注,并更新录音 review 状态。 |
| 前置条件 |
quality_ops_001 已登录;录音 READY 且可质检。 |
| 测试数据 |
score=88.5,result=ISSUE,issueTags=["noise","script"],remark=QA-002 review。 |
| 步骤 |
1. 打开质检中心并选择录音。2. 播放录音。3. 提交评分、结果、标签、备注。4. 刷新列表和详情。5. 查询审计。 |
| 预期结果 |
保存成功;录音 review 状态更新;列表展示评分、结果、质检人和时间;审计写入 quality 模块成功记录。 |
| 数据检查 |
重复保存按更新逻辑覆盖或产生历史版本,规则需固定;录音文件不被修改。 |
| 安全检查 |
无 quality.manage 保存返回 403;无 recordings.play 不应播放但可否评分按产品规则固定。 |
QA-003 评分边界校验
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证评分、结果枚举、标签和备注长度校验。 |
| 前置条件 |
有可质检录音。 |
| 测试数据 |
合法评分 0、100、88.55;非法评分 -1、100.01、abc;非法 result;超长备注。 |
| 步骤 |
1. 分别提交合法和非法评分。2. 提交非法 result。3. 提交超长标签/备注。 |
| 预期结果 |
合法评分保存成功;非法评分返回 400;非法 result 返回校验错误;超长字段被拒绝。 |
| 数据检查 |
非法提交不改变上一条质检结果。 |
| 安全检查 |
错误响应不包含堆栈。 |
QA-004 质检列表筛选和连续播放
| 字段 |
内容 |
| 优先级 |
P2 |
| 目的 |
验证质检中心按客户、时间、结果、待质检状态筛选,并支持连续播放工作流。 |
| 前置条件 |
多个客户、多条 READY 录音、部分已质检。 |
| 测试数据 |
客户 ID、时间范围、reviewStatus=PENDING/REVIEWED。 |
| 步骤 |
1. 按条件筛选录音。2. 打开第一条并播放。3. 保存评分后跳转下一条。4. 刷新列表。 |
| 预期结果 |
筛选结果正确;连续播放不跳过未质检录音;保存后列表状态同步。 |
| 数据检查 |
待质检数量减少;已质检数量增加。 |
| 安全检查 |
无权限账号不能看到不该访问的录音。 |
8.7 Dashboard、当前通话与操作日志
本节覆盖 Dashboard 聚合、上海时区日界、趋势桶、当前通话 OpenSIPS MI、挂断安全、审计查询和敏感字段脱敏。
公共前置数据:
- Dashboard 账号
dashboard_viewer_001:包含 dashboard.view。
- 当前通话账号
active_calls_ops_001:包含 active_calls.view、active_calls.manage。
- 审计账号
audit_viewer_001:包含 audit.view。
- 数据库中存在今天、昨天和跨上海时区边界的 CDR、录音、质检、充值和审计数据。
- A 侧 OpenSIPS MI 可访问,至少可模拟或产生 1 条进行中 Dialog。
DASH-001 今日指标聚合
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证 Dashboard 今日通话数、接通率、消费、成本、毛利、待质检等聚合正确。 |
| 前置条件 |
存在当日成功、失败和跳过话单。 |
| 测试数据 |
今日 10 通,其中 6 通成功;客户费用 12.000000,供应商成本 8.000000,待质检 3 条。 |
| 步骤 |
1. 请求 Dashboard summary 接口或打开 Dashboard。2. 与 SQL 聚合结果对比。3. 刷新页面。 |
| 预期结果 |
通话数、接通率、消费、成本、毛利、待质检与数据库一致;金额以 6 位小数或前端格式正确展示。 |
| 数据检查 |
失败话单计入总呼叫但不计入成功费用;跳过话单费用为 0。 |
| 安全检查 |
无 dashboard.view 返回 403 或菜单不可见。 |
DASH-002 上海时区日界
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证“今日”按 Asia/Shanghai 计算,不按 UTC 错分。 |
| 前置条件 |
构造 2026-06-28 00:00:00 +08:00 前后数据。 |
| 测试数据 |
UTC 2026-06-27T15:59:59Z 和 2026-06-27T16:00:00Z 两条 CDR。 |
| 步骤 |
1. 查询今日 Dashboard。2. 查询昨日 Dashboard 或手工 SQL 对比。 |
| 预期结果 |
16:00:00Z 之后的数据计入 2026-06-28;之前数据计入 2026-06-27。 |
| 数据检查 |
趋势和 summary 使用同一时区边界。 |
| 安全检查 |
不因服务器本地时区变化导致统计漂移。 |
DASH-003 趋势桶固定
| 字段 |
内容 |
| 优先级 |
P2 |
| 目的 |
验证趋势图固定桶数量,空桶补 0,时间范围校验。 |
| 前置条件 |
存在多日或多小时 CDR。 |
| 测试数据 |
7 天趋势;某天无数据。 |
| 步骤 |
1. 请求趋势接口。2. 检查桶数量、顺序和空桶。3. 请求非法范围。 |
| 预期结果 |
返回固定桶数;空桶数值为 0;非法范围返回校验错误。 |
| 数据检查 |
每个桶起止时间连续不重叠。 |
| 安全检查 |
大范围查询有上限,避免拖垮数据库。 |
ACT-001 当前通话列表
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证当前通话从 OpenSIPS Dialog 归一化后可展示主叫、被叫、客户 IP、落地 IP、时长和 SIP 信令状态。 |
| 前置条件 |
A 侧存在进行中 Dialog 或使用 MI mock。 |
| 测试数据 |
一通进行中呼叫,含 Contact、SDP、dialog ID。 |
| 步骤 |
1. 发起长持话呼叫。2. 请求当前通话接口或打开页面。3. 对比 OpenSIPS MI 输出。 |
| 预期结果 |
列表显示 callId/dialogId、caller、callee、sourceIp、landingIp、durationSec、startedAt、sipState;SIP 状态按 Dialog 归一化为 INVITE、180 Ringing、183 Session Progress、200 OK、BYE / Terminating 等可读状态;自动刷新不闪退。 |
| 数据检查 |
解析 IPv4/IPv6、Contact 和 SDP 中 IP 正确;无 Dialog 时返回空列表。 |
| 安全检查 |
无 active_calls.view 不可查看。 |
ACT-002 安全挂断
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证有权限用户可通过 OpenSIPS MI 挂断进行中通话,并处理已结束竞态。 |
| 前置条件 |
有进行中 Dialog;active_calls_ops_001 已登录。 |
| 测试数据 |
安全 dialog ID。 |
| 步骤 |
1. 点击挂断。2. 查询 SIP BYE 和当前通话列表。3. 对已结束 dialog 再次挂断。 |
| 预期结果 |
正常挂断返回成功,Dialog 从列表消失;OpenSIPS operation failed 但通话已结束时前端显示合理成功/已结束状态。 |
| 数据检查 |
CDR 正常生成,hangupReason 可追踪。 |
| 安全检查 |
无 active_calls.manage 不能挂断。 |
ACT-003 非法 Dialog ID
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证挂断接口只接受安全 dialog ID,防止 MI 命令注入。 |
| 前置条件 |
有权限账号。 |
| 测试数据 |
../x、;rm -rf、超长 ID、包含空格/换行。 |
| 步骤 |
1. 调用挂断接口传非法 ID。2. 检查 OpenSIPS MI 是否被调用。 |
| 预期结果 |
返回 400;不调用 MI;服务不崩溃。 |
| 数据检查 |
无 CDR 或 Dialog 状态被误改。 |
| 安全检查 |
错误不回显危险字符串到日志未脱敏区域。 |
AUD-001 审计列表查询
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证操作日志按用户、模块、动作、结果、对象、时间筛选。 |
| 前置条件 |
已执行客户创建、充值、权限拒绝、登录失败等操作。 |
| 测试数据 |
module=customers,action=create,result=SUCCESS,时间范围。 |
| 步骤 |
1. 使用 audit_viewer_001 查询审计列表。2. 组合筛选条件。3. 打开审计详情。4. 无权限账号查询。 |
| 预期结果 |
返回匹配审计;详情包含 actor、module、action、objectId、result、requestId、ip、userAgent、摘要。无权限返回 403。 |
| 数据检查 |
分页正确;失败审计和成功审计均可查。 |
| 安全检查 |
审计查询本身也应被审计或至少有访问日志。 |
AUD-002 敏感字段脱敏
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证密码、token、secret、HA1、验证码等敏感字段在审计和日志中脱敏。 |
| 前置条件 |
执行登录失败、重置密码、创建 SIP 网关、刷新 token 等操作。 |
| 测试数据 |
明文密码 SensitivePass-001,SIP 密码 SipSecret-001。 |
| 步骤 |
1. 执行敏感操作。2. 查询审计详情和应用日志。3. 全文搜索敏感明文。 |
| 预期结果 |
敏感字段显示 [REDACTED] 或不出现;明文密码、refresh token、sipPassword、sipHa1 不可检索。 |
| 数据检查 |
脱敏不影响非敏感字段排障。 |
| 安全检查 |
嵌套对象和数组内敏感字段也脱敏。 |
8.8 前端页面与交互
本节覆盖 Web 登录、菜单、页面切换、错误/空状态、表单、权限按钮、构建产物和浏览器控制台。建议使用真实 B 环境或本地 mock API 两种方式执行。
公共前置数据:
- 管理员账号:具备全部核心页面权限。
- 只读账号:仅具备
dashboard.view、customers.view。
- 各核心列表至少有 1 条数据,同时准备空数据环境或 mock 返回空数组。
- 浏览器打开 devtools,记录 console error/warn 和 network 失败。
WEB-001 登录态启动
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 Web 首屏、验证码、登录、refresh 和 Dashboard 初始化。 |
| 前置条件 |
API ready;Web 静态资源可访问。 |
| 测试数据 |
管理员账号。 |
| 步骤 |
1. 清空 cookie/localStorage。2. 打开首页。3. 完成验证码和登录。4. 刷新浏览器。 |
| 预期结果 |
登录成功进入 Dashboard;刷新后通过 refresh 恢复登录态或合理回到登录页;无空白页;无 uncaught error。 |
| 数据检查 |
Network 中 refresh/login 状态符合预期。 |
| 安全检查 |
access token 不应出现在 URL;退出后不能继续访问受保护页面。 |
WEB-002 API 失败态
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证 API 500、网络失败、401/403 时页面不崩溃。 |
| 前置条件 |
可通过 mock 或临时停 API 触发错误。 |
| 测试数据 |
Dashboard、客户、话单、质检页面各触发一次失败。 |
| 步骤 |
1. 打开页面。2. 让接口返回 500/超时/403。3. 点击重试或切换页面。 |
| 预期结果 |
页面显示错误提示;布局保持;可重试;401 跳登录或刷新令牌;403 显示无权限。 |
| 数据检查 |
不产生重复写请求。 |
| 安全检查 |
错误提示不展示堆栈、SQL 或 token。 |
WEB-003 空数据态
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证核心列表在空数据时可读、可继续创建或提示清晰。 |
| 前置条件 |
mock 返回空数组或测试库清空对应模块。 |
| 测试数据 |
客户、供应商、网关、线路组、话单、质检、操作日志为空。 |
| 步骤 |
1. 逐页打开空列表。2. 检查空状态和主操作。 |
| 预期结果 |
显示空状态;有权限账号可见新增/导入按钮;只读账号不显示写按钮;无表格错位。 |
| 数据检查 |
空列表不触发异常请求循环。 |
| 安全检查 |
无权限不因空状态显示写入口。 |
WEB-004 表单校验
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证前端表单必填、格式、后端错误显示和提交防抖。 |
| 前置条件 |
管理员登录。 |
| 测试数据 |
客户、客户网关、业务前缀、落地网关、线路组、充值/扣款、质检评分表单。 |
| 步骤 |
1. 提交空表单。2. 输入非法金额/IP/时间/前缀。3. 提交合法数据。4. 快速重复点击提交。 |
| 预期结果 |
非法输入有明确提示;后端错误展示在表单或通知中;合法提交关闭弹窗并刷新列表;重复点击不产生重复记录。 |
| 数据检查 |
幂等接口使用 idempotencyKey;非幂等写操作避免双提交。 |
| 安全检查 |
前端校验不能替代后端校验,绕过前端仍应被后端拒绝。 |
WEB-005 页面导航回归
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证核心菜单逐页切换无空白、标题正确、权限裁剪有效。 |
| 前置条件 |
管理员和只读账号可登录。 |
| 测试数据 |
全部核心菜单:Dashboard、当前通话、客户、网关、业务前缀、充值、供应商、落地、线路组、号码库、话单、质检、用户、角色、操作日志。 |
| 步骤 |
1. 管理员逐个点击菜单。2. 记录 h1/页面标题。3. 只读账号登录检查菜单。4. 浏览器刷新每个关键页面。 |
| 预期结果 |
每页标题正确;无 runtime error;权限菜单和按钮符合权限矩阵;刷新不丢状态。 |
| 数据检查 |
只请求当前权限允许的全局 API,避免批量 403。 |
| 安全检查 |
手动输入无权限路由显示无权限或跳转。 |
WEB-006 构建产物发布
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证前端 build 产物被正确发布到 B 当前 release,Nginx 首页引用新资源。 |
| 前置条件 |
本地 build 成功;B 当前 release 明确;有回滚点。 |
| 测试数据 |
新 JS/CSS hash。 |
| 步骤 |
1. 执行 build。2. 发布 dist。3. curl -k https://127.0.0.1/ 检查资源引用。4. 浏览器强刷首页。 |
| 预期结果 |
首页引用新 JS/CSS;旧资源不被引用;Nginx 返回 200;页面可登录。 |
| 数据检查 |
release 目录和 public 目录一致。 |
| 安全检查 |
发布不覆盖后端 env、node_modules 或用户上传录音。 |
8.9 性能、故障与安全
本节覆盖小规模并发、Worker/数据服务故障、Redis 热路径故障、SIP 安全探针、API 越权/重放和恢复性。执行故障类用例前必须确认回滚点和当前环境可中断。
PERF-001 5 路并发呼叫
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证最小并发下 SIP、CDR、计费、录音闭环稳定。 |
| 前置条件 |
A/B/T 服务 active;测试客户余额充足;T 脚本使用随机本地端口。 |
| 测试数据 |
5 个唯一 Call-ID,持话 6-10 秒。 |
| 步骤 |
1. T 并发发起 5 路呼叫。2. 等待全部挂断。3. 查询 CDR、rated、recordings。 |
| 预期结果 |
5 路均 200 OK 和 BYE 200;生成 5 raw、5 rated、5 READY 录音;无端口冲突。 |
| 数据检查 |
费用合计、余额扣减、录音大小合理。 |
| 安全检查 |
并发不绕过 CPS/并发限制。 |
PERF-002 12 路短突发
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证短突发下脚本、OpenSIPS、Worker 和录音搬运不丢事件。 |
| 前置条件 |
CPS/并发限制允许 12 路,或预期部分被限流。 |
| 测试数据 |
12 个唯一 Call-ID,短持话。 |
| 步骤 |
1. 同时发起 12 路。2. 记录成功/失败数量。3. 查询 CDR、录音、Redis pending/deadletter。 |
| 预期结果 |
若限制允许则全部成功;若限流则失败数量和原因符合规则;无端口占用错误;pending/deadletter 正常。 |
| 数据检查 |
raw CDR 数量等于成功和可记录失败事件总数。 |
| 安全检查 |
突发结束后正常呼叫恢复。 |
FAIL-001 Recording Worker 停止
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 Recording Worker 停止期间通话不受影响,恢复后录音可补搬。 |
| 前置条件 |
可停止 B lisglosips@recording-worker。 |
| 步骤 |
1. 停止 Recording Worker。2. 发起成功呼叫。3. 确认录音仍在 A ready 目录。4. 恢复 Worker。5. 查询 recordings。 |
| 预期结果 |
通话成功;恢复后录音变 READY;A 源文件校验后删除。 |
| 数据检查 |
不重复生成录音记录。 |
| 安全检查 |
停止 Worker 不影响 API 登录和 CDR 计费。 |
FAIL-002 CDR Worker 停止
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 CDR Worker 停止期间 Redis Stream 积压,恢复后自动入库计费。 |
| 前置条件 |
可停止 B lisglosips@cdr-worker。 |
| 步骤 |
1. 停止 CDR Worker。2. 发起呼叫。3. 查看 Redis stream lag/pending。4. 恢复 Worker。5. 查询 raw/rated 和余额。 |
| 预期结果 |
停止期间 SIP 通话成功但 MySQL 暂无 rated;恢复后 lag/pending 归零,CDR 入库,余额扣减一次。 |
| 数据检查 |
deadletter 无新增。 |
| 安全检查 |
积压期间不丢 CDR。 |
FAIL-003 MySQL 短故障
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 MySQL 短故障时 CDR 可重试,不误 ACK 或 deadletter。 |
| 前置条件 |
已确认停止 MySQL 的影响和恢复命令;非生产或维护窗口。 |
| 步骤 |
1. 停 MySQL。2. 发起呼叫。3. 查看 CDR Worker 日志、Redis pending/deadletter。4. 恢复 MySQL。5. 等待 pending 重试。 |
| 预期结果 |
通话可成功;CDR 暂未入库但保留 pending;恢复后入库计费;deadletter 无新增。 |
| 数据检查 |
余额只扣一次。 |
| 安全检查 |
日志不泄露数据库密码。 |
FAIL-004 Redis 短故障
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 Redis 热路径不可用时 A 保护性拒绝呼叫,恢复路径明确。 |
| 前置条件 |
已确认停止 Redis 影响;A/B/T 可操作。 |
| 步骤 |
1. 停 B Redis。2. 发起呼叫。3. 恢复 Redis。4. 按 Runbook 重试,如仍失败则重启 A OpenSIPS。5. 再发起呼叫。 |
| 预期结果 |
Redis 故障期间 A 返回保护性 503 或等价失败;恢复后按 Runbook 恢复;复测呼叫成功。 |
| 数据检查 |
故障期间不产生错误扣费。 |
| 安全检查 |
Redis 恢复不使用过期错误配置。 |
SEC-001 非法来源 SIP 探针
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证非白名单来源不能进入业务路由。 |
| 前置条件 |
准备非客户网关 IP 主机。 |
| 步骤 |
1. 从非白名单 IP 向 A 15060/udp 发送 INVITE。2. 观察响应、A 日志、HOMER、CDR。 |
| 预期结果 |
请求被静默丢弃或明确拒绝;不产生业务 CDR;不进入落地路由。 |
| 数据检查 |
无客户识别、无余额变化。 |
| 安全检查 |
不暴露可用客户 IP 或路由信息。 |
SEC-002 超 CPS 探针
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证客户侧或落地侧 CPS 限制生效,超限后可恢复。 |
| 前置条件 |
配置明确 CPS 限制,例如 2 CPS。 |
| 步骤 |
1. 短时间发起超过限制的呼叫。2. 记录成功/失败数量。3. 等待窗口恢复后发起正常呼叫。 |
| 预期结果 |
超限请求被限制;限流日志/指标可见;窗口后正常呼叫成功。 |
| 数据检查 |
超限失败不扣费。 |
| 安全检查 |
限流不影响其他客户。 |
SEC-003 API 重放和越权
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证幂等接口重放安全、非幂等接口权限保护和审计。 |
| 前置条件 |
准备只读账号、财务账号和管理员账号。 |
| 步骤 |
1. 重放客户充值/扣款 idempotencyKey。2. 使用同 key 不同 body。3. 只读账号直接 POST 写接口。4. 伪造/过期 token 请求。 |
| 预期结果 |
相同请求幂等返回同一结果;不同 body 返回 409;越权返回 403;未认证/伪造 token 返回 401。 |
| 数据检查 |
余额不重复变化;失败审计可追踪。 |
| 安全检查 |
错误响应不泄露 token 解析细节。 |
SEC-004 录音与文件安全专项
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证录音播放、X-Accel 和 Worker 文件处理无路径穿越。 |
| 前置条件 |
READY 录音存在。 |
| 步骤 |
1. 请求伪造录音 ID。2. 构造异常 storageKey。3. 检查 Nginx internal 路径。 |
| 预期结果 |
非法路径返回 404/安全错误;无法读取录音根目录外文件。 |
| 数据检查 |
无录音状态变化。 |
| 安全检查 |
不泄露真实路径。 |
8.10 发布、备份与回滚
本节覆盖 release artifact、B release preflight、MySQL/Redis 备份、隔离恢复、灰度呼叫、脚本语法、应用回滚和阿里云迁移前复验。执行前必须确认当前 release、上一 release、备份目录和回滚命令。
OPS-000 Release artifact 生成与校验
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 release 产物由固定脚本生成,包含部署所需 API/Web/Worker/packages/Prisma/infra 文件、manifest 和 SHA256,避免手工拼包。 |
| 前置条件 |
Linux 构建环境或 B 兼容环境;本地代码已通过 lint/typecheck/test/build;releaseId 已冻结。 |
| 步骤 |
1. 执行 pnpm release:artifact -- --release-id <releaseId>。2. 检查 dist/releases/<releaseId>.tar.gz 和 .sha256。3. 解包到临时目录检查 RELEASE_MANIFEST.json、public/index.html、API/Worker dist、node_modules、Prisma schema/migrations、argon2id wasm、infra 脚本。4. 执行 sha256sum -c。 |
| 预期结果 |
artifact 生成成功;manifest 记录 releaseId、git commit、git status、Node/pnpm 版本、required entries;SHA256 校验通过;正式发布包不得由 Windows 原生 node_modules 生成。 |
| 数据检查 |
解包目录文件数量合理,关键路径完整;.env、私钥、明文密码、.codex-private 不进入 artifact。 |
| 安全检查 |
脚本输出不回显数据库密码、Redis 密码、JWT secret、SSH 密钥。 |
OPS-001 B preflight
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 B 当前 release 和关键服务满足发布/验收基线。 |
| 前置条件 |
B 已部署 release;SSH 可达。 |
| 步骤 |
1. 执行 lisglosips-release-preflight.sh。2. 检查 systemd 服务。3. 请求 API ready 和 HTTPS 首页。 |
| 预期结果 |
MySQL、Redis、Nginx、API、CDR Worker、Recording Worker、Config Publisher、HEP、Prometheus、Grafana 均 active;API ready 返回 ok;Nginx 配置测试通过。 |
| 数据检查 |
current symlink 指向预期 release;最近备份目录可见。 |
| 安全检查 |
输出不包含密码、token、私钥。 |
OPS-002 MySQL 备份校验
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 MySQL 备份完整、可校验、元数据可追踪。 |
| 前置条件 |
B MySQL 正常,有业务数据。 |
| 步骤 |
1. 触发 MySQL 备份。2. 检查备份目录、metadata.tsv、SHA256SUMS。3. 执行 sha256sum -c。 |
| 预期结果 |
全库和业务库备份文件存在;SHA256 校验通过;metadata 记录时间、主机、数据库、大小。 |
| 数据检查 |
备份包含关键表:customers、raw_cdrs、rated_cdrs、recordings、users、recharges。 |
| 安全检查 |
备份文件权限限制,避免普通用户读取敏感数据。 |
OPS-003 Redis 备份校验
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证 Redis RDB 备份完整并可追踪配置版本和 Stream 数据。 |
| 前置条件 |
B Redis 正常,有 cfg:active_version 和 CDR Stream。 |
| 步骤 |
1. 触发 Redis 备份。2. 检查 dump.rdb、metadata、SHA256。3. 记录 DBSIZE、active version、stream 长度。 |
| 预期结果 |
RDB 和 metadata 存在;SHA256 校验通过;关键 key 元数据记录完整。 |
| 数据检查 |
包含配置版本、CDR Stream、幂等锁或按备份策略记录。 |
| 安全检查 |
Redis 备份权限受控。 |
OPS-004 隔离恢复演练
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证备份能恢复到隔离环境,且不覆盖生产数据。 |
| 前置条件 |
已有 MySQL 和 Redis 备份。 |
| 步骤 |
1. MySQL 恢复到临时库。2. Redis RDB 启动到临时端口或 Unix socket。3. 核对关键表和 key 数量。4. 删除临时库/实例。 |
| 预期结果 |
恢复成功;关键计数与备份时 metadata 匹配;演练不影响运行中 MySQL/Redis。 |
| 数据检查 |
raw_cdrs、recordings、recharges、users、Redis cfg 和 stream 数量合理。 |
| 安全检查 |
临时恢复数据清理干净,权限不扩大。 |
OPS-005 灰度呼叫验收
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证新 release 切换后核心 SIP-CDR-计费-录音闭环可用。 |
| 前置条件 |
B 已切到新 release;A 配置和 Redis active version 正常;T 可发起呼叫。 |
| 步骤 |
1. T 发起一通灰度呼叫。2. 记录 Call-ID。3. 查询 SIP 响应、raw/rated CDR、余额扣减、录音 READY、HOMER。 |
| 预期结果 |
呼叫成功;CDR 计费正确;录音 READY;HOMER 可查;无新增错误日志。 |
| 数据检查 |
灰度数据 ID 记录到验收报告。 |
| 安全检查 |
灰度只使用测试客户,不影响真实客户。 |
OPS-006 回滚脚本语法
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证回滚脚本和 A 配置恢复脚本语法正确。 |
| 前置条件 |
脚本已部署。 |
| 步骤 |
1. 对 B release rollback/preflight 脚本执行 bash -n。2. 对 A OpenSIPS config restore 脚本执行 bash -n。 |
| 预期结果 |
所有脚本语法通过。 |
| 数据检查 |
语法检查不执行真实回滚。 |
| 安全检查 |
不输出敏感参数。 |
OPS-007 应用回滚演练
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证可将 B current 切回上一 release 并恢复服务。 |
| 前置条件 |
有上一可用 release;当前无不可逆数据库迁移或已有数据回滚方案。 |
| 步骤 |
1. 记录当前 release。2. 切换 current symlink 到上一 release。3. 重启 API/Worker。4. 执行 preflight、API ready、HTTPS 首页。5. 如演练要求,再切回新 release。 |
| 预期结果 |
回滚后服务 active;API ready ok;Web 可访问;核心接口兼容;可再切回。 |
| 数据检查 |
无数据库 schema 不兼容;Worker 不重复处理已完成消息。 |
| 安全检查 |
回滚不覆盖备份、录音和用户上传数据。 |
OPS-008 OpenSIPS 配置恢复演练
| 字段 |
内容 |
| 优先级 |
P1 |
| 目的 |
验证 A 侧 OpenSIPS 配置可语法检查和按 Runbook 恢复。 |
| 前置条件 |
有当前运行配置备份;维护窗口或测试环境。 |
| 步骤 |
1. 执行 opensips -C 检查当前配置。2. 对恢复脚本做 dry-run 或语法检查。3. 在测试窗口执行恢复并重启/ reload。4. 发起呼叫验证。 |
| 预期结果 |
配置语法通过;恢复后 OpenSIPS active;呼叫成功。 |
| 数据检查 |
配置 SHA256 与预期记录一致。 |
| 安全检查 |
不把模板占位符配置直接覆盖生产运行配置。 |
OPS-009 阿里云迁移前复验
| 字段 |
内容 |
| 优先级 |
P0 |
| 目的 |
验证本地 KVM 结论在阿里云网络、安全组、数据盘、TLS 和正式域名下重新成立。 |
| 前置条件 |
阿里云 A/B 已部署,VPC、安全组、数据盘、DNS/TLS 准备完成。 |
| 步骤 |
1. 复查安全组端口:SIP、RTP、HTTPS、管理端口。2. 执行 B preflight。3. 执行备份恢复演练。4. 执行灰度呼叫。5. 执行故障注入最小集。 |
| 预期结果 |
正式环境全部通过;TLS 正常;RTP 双向媒体正常;备份恢复可用;灰度呼叫闭环。 |
| 数据检查 |
迁移报告记录公网/私网 IP、端口、证书、备份路径和灰度 Call-ID。 |
| 安全检查 |
SSH 不对全网开放;Redis/MySQL/Prometheus/Grafana 不公网暴露;开发 CA 和本地路径不进入生产。 |
9. 缺陷分级
| 级别 |
定义 |
示例 |
| P0 |
阻断核心链路或存在严重安全/数据风险 |
无法登录、呼叫失败、重复扣费、录音越权、发布后 API 不可用 |
| P1 |
核心功能受影响但有规避方案 |
某管理页面写操作失败、Worker 恢复需人工处理、关键指标错误 |
| P2 |
一般功能缺陷或体验问题 |
筛选条件不完整、提示文案不清晰、非关键统计延迟 |
| P3 |
低风险优化 |
样式细节、日志可读性、非核心字段排序 |
10. 回归重点
每次涉及下列模块时必须扩大回归:
apps/api/src/modules/auth、security、audit:回归 AUTH、AUD、WEB 权限相关用例。
customer-gateways、business-prefixes、worker-config-publisher:回归 GW、CALL 业务前缀和配置发布。
worker-cdr、packages/redis:回归 CDR、充值幂等、FAIL-002/FAIL-003。
worker-recording、recordings、quality:回归 REC、QA 和录音播放权限。
infra/server-a OpenSIPS/RTPEngine:回归 CALL、PERF、SEC、HEP/HOMER。
infra/server-b Nginx/systemd/release:回归 OPS、WEB-006、健康检查。
apps/web/src/App.jsx、pages、permissions.js:回归 WEB-001 至 WEB-005、AUTH-007、AUTH-008。
11. 当前已知风险与补充建议
- Redis 恢复后 OpenSIPS 热路径曾出现需要重启
opensips 才恢复的问题。上线前应补充 Redis 断连重连专项测试,并增加明确指标/告警。
- 当前真实 80 万手机号段尚未导入。号码库性能、导入耗时、批量校验和 CDR 归属地准确性需单独做数据量级测试。
- 真实 B 浏览器登录态下的逐页按钮、真实录音播放和 Nginx X-Accel 链路仍需人工复核。
- 阿里云迁移前必须重跑网络安全组、正式 TLS、数据盘、备份恢复、故障注入和灰度呼叫,不应直接沿用本地 KVM 结论。
- 建议后续增加 Playwright 前端冒烟:登录、菜单遍历、权限账号、关键表单、录音播放入口,降低页面空白类问题复发概率。