# NixMsg 验收对照表(PRD 第 10 节) 生成时间:2026-09-30T08:17:19Z 汇总:通过 19,部分通过 4,失败 0,未测 0 整项「通过」表示该行 PRD 一句话里的子项均有测试证据。部分覆盖不得标通过。 | 编号 | 一句话 | 结果 | 备注 | |---|---|---|---| | F01 | 批量开通整批校验、停用、删除群主转让、删除后同编号重开不串数据 | 通过 | 子项 5/5 通过 | | F02 | 新设备登录后旧设备自动退出、换 IP 用令牌重连、两种密码锁定、重置密码后被踢、服务器故障不误报密码错误 | 通过 | 子项 6/6 通过 | | F03 | 断开后状态及时变离线,全表可列出 | 部分通过 (2/4) | F03c 未测:环境/时长限制,本波不跑;F03d 未测:环境限制;验收用关连接模拟断线 | | F04 | 只通知订阅了的端 | 通过 | 子项 1/1 通过 | | F05 | 崩溃不丢已提交消息,消息号去重和冲突,密码门生效,配额生效 | 通过 | 子项 5/5 通过 | | F06 | 群成员收到同一份,入群前不补,发送者不收到自己的 | 通过 | 子项 2/2 通过 | | F07 | 256 KiB 通过,超出拒绝,接收上限生效 | 通过 | 子项 3/3 通过 | | F08 | 弱网最终送达且应用层不重复,重启后续传 | 部分通过 (3/4) | F08d 未测:本机 Windows;test/chaos/q3_weak_test.go TestQ3NetemUntested | | F09 | 保留时间从发送时刻起算,超时过期 | 通过 | 子项 2/2 通过 | | F10 | 短断线送到,长断线丢弃,服务器重启后宽限内重连送到 | 通过 | 子项 3/3 通过 | | F11 | 发送方离线后到点仍发送 | 通过 | 子项 1/1 通过 | | F12 | 延迟窗口内撤回对方收不到 | 通过 | 子项 1/1 通过 | | F13 | 未推送必撤成功;群部分确认得到部分撤回 | 通过 | 子项 2/2 通过 | | F14 | 回执能补送给当时离线的发送方 | 通过 | 子项 1/1 通过 | | F15 | 输一次记住、改密失效、回复免密、进群仍要密码、防多账号轮流猜 | 通过 | 子项 5/5 通过 | | F16 | 群主权限、退出后不再收到、解散后同编号新群不收旧消息 | 通过 | 子项 4/4 通过 | | F17 | 后台管端、管注册、管群、查记录,响应里没有正文;API 令牌可用且不能越权 | 通过 | 子项 6/6 通过 | | F18 | 送达后正文消失;记录天数 0 时连记录消失;防重仍在 | 通过 | 子项 3/3 通过 | | F19 | 四种 SDK 通过同一清单 | 通过 | 子项 1/1 通过 | | F20 | 裸 MQTT 能登录、收、确认、发 | 通过 | 子项 1/1 通过 | | F21 | 默认一个端口提供后台、WebSocket、TCP、注册;后台可分到单独端口 | 部分通过 (2/3) | F21b 未测:验收用例只走 WebSocket;listener 首字节识别见 internal/listener/listener_test.go TestIdentifyPlainHTTPAndMQTT,不足以上升为通过 | | F22 | 初始化后单文件或 Docker 启动、备份恢复、升级迁移、证书自动重载、指标可抓取 | 部分通过 (5/6) | F22f 未测:镜像未推送;本波未跑 compose 全量 | | F23 | 注册开关、安全码校验、换码不影响已注册、输错锁定 | 通过 | 子项 2/2 通过 | ## 子项 | 子项 | 摘要 | 结果 | 证据或原因 | |---|---|---|---| | F01a | 开通一端并可 MQTT 登录 | 通过 | test/accept/accept_test.go runEndpointCreate | | F01b | 批量开通整批校验 | 通过 | internal/admin/endpoints_test.go TestEndpointImportDuplicateRejectsAll、TestEndpointImportBOMAndCreate | | F01c | 停用 | 通过 | internal/app/identity/lifecycle_test.go TestF01DisableVoidsScheduledAndRejectsNew;internal/admin/endpoints_test.go TestEndpointDisableKickAndUnlock | | F01d | 删除群主转让 | 通过 | internal/app/identity/lifecycle_test.go TestF01DeleteOwnerTransfersEarliest | | F01e | 删除后同编号重开不串数据 | 通过 | internal/app/identity/lifecycle_test.go TestF01DeleteReopenNoOldReceipts | | F02a | 密码登录后 hello / 会话令牌 | 通过 | test/accept/accept_test.go;internal/broker/f02_test.go TestF02PasswordLoginReturnsTokenAndHandshake | | F02b | 新设备密码登录后旧设备退出 | 通过 | internal/broker/f02_test.go TestF02TakenOverByPasswordLogin、TestF02OldTokenRejectedAfterPasswordLogin | | F02c | 换 IP 用令牌重连 | 通过 | internal/broker/f02_test.go TestF02TokenReconnectDifferentIPKeepsToken | | F02d | 两种密码锁定 | 通过 | internal/broker/f02_test.go TestF02IPLockDoesNotAffectOtherIP、TestF02EndpointLockAllowsTokenReconnect | | F02e | 重置密码后被踢 | 通过 | internal/broker/f02_test.go TestF02AdminResetPasswordFatal;cmd/nixmsg/fatal_revoked_integration_test.go TestUplinkResetPasswordFatal | | F02f | 服务器故障不误报密码错误 | 通过 | internal/broker/f02_test.go TestF02DBErrorClosesWithout086;internal/broker/broker_test.go TestInternalAuthErrorDoesNotReturnBadPassword | | F03a | 断开后状态及时变离线 | 通过 | test/accept/rest_accept_test.go;internal/app/presence/presence_test.go TestF03PresenceAndDirectory | | F03b | 目录可列出 | 通过 | test/accept/rest_accept_test.go;internal/app/presence/presence_test.go TestF03PresenceAndDirectory | | F03c | 1000 端全表 1s 内返回 | 未测 | 环境/时长限制,本波不跑 | | F03d | 真拔网线后心跳超时离线 | 未测 | 环境限制;验收用关连接模拟断线 | | F04a | 只通知订阅了的端 | 通过 | test/accept/rest_accept_test.go;internal/app/presence/presence_test.go TestF04PresenceWatch | | F05a | 双端在线单聊送达与确认 | 通过 | test/accept/accept_test.go runMessagingAccept | | F05b | 崩溃不丢已提交消息 | 通过 | test/chaos/q3_crash_test.go TestQ3CrashSubmitThenRestart | | F05c | 消息号冲突 | 通过 | internal/app/message/submit_test.go TestSubmitTable/conflict | | F05d | 密码门生效 | 通过 | internal/app/message/submit_test.go TestSubmitTable 对话密码用例 | | F05e | 配额生效 | 通过 | internal/app/message/submit_test.go TestSubmitTable/quota_exceeded | | F06a | 群成员收到同一份、发送者不收到自己的 | 通过 | test/accept/accept_test.go;internal/app/group/group_test.go TestF06GroupSendMembership | | F06b | 入群前不补 | 通过 | internal/app/group/group_test.go TestF06GroupSendMembership(发送后入群无旧投递) | | F07a | 256 KiB 通过 | 通过 | test/accept/rest_accept_test.go | | F07b | 超出拒绝 | 通过 | test/accept/rest_accept_test.go body_too_large | | F07c | 接收上限生效 | 通过 | test/accept/rest_accept_test.go max_receive_bytes=1024 | | F08a | 重启后续传 | 通过 | test/accept/accept_test.go;test/chaos/q3_crash_test.go | | F08b | 应用层不重复(同号同指纹) | 通过 | internal/app/message/submit_test.go TestSubmitTable/idempotent_hit | | F08c | toxiproxy 弱网保留送达 | 通过 | test/chaos/q3_weak_test.go TestQ3ToxiproxyOfflineKeepDelivered | | F08d | Linux netem 20% 丢包 | 未测 | 本机 Windows;test/chaos/q3_weak_test.go TestQ3NetemUntested | | F09a | 离线保留期内上线送达 | 通过 | test/accept/accept_test.go | | F09b | 超时过期 | 通过 | internal/app/message/delivery_test.go TestDeliveryStateMachine/F09_keep_ttl_expire_via_cleanup | | F10a | 短断线送到 | 通过 | test/accept/rest_accept_test.go | | F10b | 长断线丢弃 | 通过 | test/accept/rest_accept_test.go | | F10c | 服务器重启后宽限内重连送到 | 通过 | test/accept/rest_accept_test.go | | F11a | 发送方离线后到点仍发送 | 通过 | test/accept/rest_accept_test.go | | F12a | 延迟窗口内撤回对方收不到 | 通过 | test/accept/accept_test.go | | F13a | 未推送必撤成功 | 通过 | test/accept/accept_test.go | | F13b | 群部分确认得到部分撤回 | 通过 | internal/app/message/delivery_test.go TestDeliveryStateMachine/F13_recall_race_partial | | F14a | 回执能补送给当时离线的发送方 | 通过 | test/accept/rest_accept_test.go | | F15a | 输一次记住 | 通过 | test/accept/rest_accept_test.go;internal/app/identity/self_talk_test.go TestF15TalkPasswordAuth | | F15b | 改密失效 | 通过 | test/accept/rest_accept_test.go;internal/app/identity/self_talk_test.go TestF15ReplyGrantAndChange | | F15c | 回复免密 | 通过 | test/accept/rest_accept_test.go;internal/app/identity/self_talk_test.go | | F15d | 进群仍要密码 | 通过 | test/accept/rest_accept_test.go;internal/app/identity/self_talk_test.go TestF15JoinNeedsPasswordDespiteGrant | | F15e | 防多账号轮流猜 | 通过 | test/accept/rest_accept_test.go | | F16a | 建群拉成员后可群发 | 通过 | test/accept/accept_test.go | | F16b | 群主权限 | 通过 | internal/app/group/group_test.go TestF16CreateAddPasswordAndOwner | | F16c | 退出后不再收到 | 通过 | internal/app/group/group_test.go TestF16LeaveRemoveDissolve | | F16d | 解散后同编号新群不收旧消息 | 通过 | internal/app/group/group_test.go TestF16LeaveRemoveDissolve | | F17a | 管理登录、CSRF、错误密码锁定 | 通过 | test/accept/accept_test.go runAdminAuth | | F17b | 管端开通 | 通过 | test/accept/accept_test.go runEndpointCreate | | F17c | 管注册 | 通过 | test/accept/accept_test.go runRegistration;internal/admin/a3_test.go TestRegistrationToggleAndGenerate | | F17d | 管群 | 通过 | internal/admin/a3_test.go TestGroupsCRUD | | F17e | 查记录响应无正文 | 通过 | internal/admin/a3_test.go TestMessageDetailHasNoBody | | F17f | API 令牌可用且不能越权 | 通过 | internal/admin/admin_test.go TestAPITokenAuthAndRestrictions | | F18a | 送达后正文消失 | 通过 | test/accept/rest_accept_test.go;internal/app/message/delivery_test.go TestDeliveryStateMachine/F18_body_gone_record_zero_idempotent_kept | | F18b | 记录天数 0 时连记录消失 | 通过 | test/accept/rest_accept_test.go | | F18c | 防重仍在 | 通过 | test/accept/rest_accept_test.go;internal/app/message/submit_test.go TestSubmitTable/idempotent_hit | | F19a | 四种 SDK 接入清单(仓库内已有测试,本波不重跑全量) | 通过 | sdk/go/itest_checklist_test.go;sdk/js/test/checklist.test.ts;sdk/python/tests/test_checklist.py;sdk/java ChecklistTest | | F20a | 裸 MQTT WebSocket 登录、收、确认、发 | 通过 | test/accept/accept_test.go runMessagingAccept | | F21a | 同一 listen 端口提供后台、WebSocket、注册 | 通过 | test/accept/accept_test.go | | F21b | 裸 TCP MQTT 登录收发 | 未测 | 验收用例只走 WebSocket;listener 首字节识别见 internal/listener/listener_test.go TestIdentifyPlainHTTPAndMQTT,不足以上升为通过 | | F21c | 后台可分到单独端口 | 通过 | internal/listener/listener_test.go TestAdminSeparateMQTTClosedAndAdmin404OnListen | | F22a | 空目录 admin init + serve 健康检查 | 通过 | test/accept/accept_test.go runInitHealthz | | F22b | 备份 | 通过 | cmd/nixmsg/commands_test.go TestBackupVacuumInto | | F22c | 升级迁移前备份 | 通过 | internal/store/db_test.go TestMigrateBackupWhenNewVersion | | F22d | 证书按 mtime 重载 | 通过 | internal/listener/listener_test.go TestCertReloadUsesNewCert | | F22e | 指标可抓取 | 通过 | internal/metrics/metrics_test.go TestMetricsHandlerExposesText;internal/httpx/metrics_test.go TestMetricsGateSharedPort | | F22f | Docker 全量冒烟 | 未测 | 镜像未推送;本波未跑 compose 全量 | | F23a | 注册开关、安全码校验、换码不影响已注册 | 通过 | test/accept/accept_test.go runRegistration;internal/app/identity/register_test.go TestRegisterF23_* | | F23b | 输错锁定 | 通过 | internal/app/identity/register_test.go TestRegisterF23_WrongCodeLock |