fix: 验收对照表按子项记结果不再虚报通过

This commit is contained in:
Nixevol
2026-09-30 16:24:56 +08:00
parent c72d1f0427
commit cb9512fe11
11 changed files with 1138 additions and 93 deletions
+178
View File
@@ -0,0 +1,178 @@
package report
import "strings"
func passSub(id, summary, evidence string) SubItem {
return SubItem{ID: id, Summary: summary, Status: StatusPass, Evidence: evidence}
}
func untestedSub(id, summary, note string) SubItem {
return SubItem{ID: id, Summary: summary, Status: StatusUntested, Note: note}
}
var catalogSubs = map[string][]SubItem{
"F01": {
passSub("F01a", "开通一端并可 MQTT 登录", "test/accept/accept_test.go runEndpointCreate"),
passSub("F01b", "批量开通整批校验", "internal/admin/endpoints_test.go TestEndpointImportDuplicateRejectsAll、TestEndpointImportBOMAndCreate"),
passSub("F01c", "停用", "internal/app/identity/lifecycle_test.go TestF01DisableVoidsScheduledAndRejectsNew;internal/admin/endpoints_test.go TestEndpointDisableKickAndUnlock"),
passSub("F01d", "删除群主转让", "internal/app/identity/lifecycle_test.go TestF01DeleteOwnerTransfersEarliest"),
passSub("F01e", "删除后同编号重开不串数据", "internal/app/identity/lifecycle_test.go TestF01DeleteReopenNoOldReceipts"),
},
"F02": {
passSub("F02a", "密码登录后 hello / 会话令牌", "test/accept/accept_test.go;internal/broker/f02_test.go TestF02PasswordLoginReturnsTokenAndHandshake"),
passSub("F02b", "新设备密码登录后旧设备退出", "internal/broker/f02_test.go TestF02TakenOverByPasswordLogin、TestF02OldTokenRejectedAfterPasswordLogin"),
passSub("F02c", "换 IP 用令牌重连", "internal/broker/f02_test.go TestF02TokenReconnectDifferentIPKeepsToken"),
passSub("F02d", "两种密码锁定", "internal/broker/f02_test.go TestF02IPLockDoesNotAffectOtherIP、TestF02EndpointLockAllowsTokenReconnect"),
passSub("F02e", "重置密码后被踢", "internal/broker/f02_test.go TestF02AdminResetPasswordFatal;cmd/nixmsg/fatal_revoked_integration_test.go TestUplinkResetPasswordFatal"),
passSub("F02f", "服务器故障不误报密码错误", "internal/broker/f02_test.go TestF02DBErrorClosesWithout086;internal/broker/broker_test.go TestInternalAuthErrorDoesNotReturnBadPassword"),
},
"F03": {
passSub("F03a", "断开后状态及时变离线", "test/accept/rest_accept_test.go;internal/app/presence/presence_test.go TestF03PresenceAndDirectory"),
passSub("F03b", "目录可列出", "test/accept/rest_accept_test.go;internal/app/presence/presence_test.go TestF03PresenceAndDirectory"),
untestedSub("F03c", "1000 端全表 1s 内返回", "环境/时长限制,本波不跑"),
untestedSub("F03d", "真拔网线后心跳超时离线", "环境限制;验收用关连接模拟断线"),
},
"F04": {
passSub("F04a", "只通知订阅了的端", "test/accept/rest_accept_test.go;internal/app/presence/presence_test.go TestF04PresenceWatch"),
},
"F05": {
passSub("F05a", "双端在线单聊送达与确认", "test/accept/accept_test.go runMessagingAccept"),
passSub("F05b", "崩溃不丢已提交消息", "test/chaos/q3_crash_test.go TestQ3CrashSubmitThenRestart"),
passSub("F05c", "消息号冲突", "internal/app/message/submit_test.go TestSubmitTable/conflict"),
passSub("F05d", "密码门生效", "internal/app/message/submit_test.go TestSubmitTable 对话密码用例"),
passSub("F05e", "配额生效", "internal/app/message/submit_test.go TestSubmitTable/quota_exceeded"),
},
"F06": {
passSub("F06a", "群成员收到同一份、发送者不收到自己的", "test/accept/accept_test.go;internal/app/group/group_test.go TestF06GroupSendMembership"),
passSub("F06b", "入群前不补", "internal/app/group/group_test.go TestF06GroupSendMembership(发送后入群无旧投递)"),
},
"F07": {
passSub("F07a", "256 KiB 通过", "test/accept/rest_accept_test.go"),
passSub("F07b", "超出拒绝", "test/accept/rest_accept_test.go body_too_large"),
passSub("F07c", "接收上限生效", "test/accept/rest_accept_test.go max_receive_bytes=1024"),
},
"F08": {
passSub("F08a", "重启后续传", "test/accept/accept_test.go;test/chaos/q3_crash_test.go"),
passSub("F08b", "应用层不重复(同号同指纹)", "internal/app/message/submit_test.go TestSubmitTable/idempotent_hit"),
passSub("F08c", "toxiproxy 弱网保留送达", "test/chaos/q3_weak_test.go TestQ3ToxiproxyOfflineKeepDelivered"),
untestedSub("F08d", "Linux netem 20% 丢包", "本机 Windows;test/chaos/q3_weak_test.go TestQ3NetemUntested"),
},
"F09": {
passSub("F09a", "离线保留期内上线送达", "test/accept/accept_test.go"),
passSub("F09b", "超时过期", "internal/app/message/delivery_test.go TestDeliveryStateMachine/F09_keep_ttl_expire_via_cleanup"),
},
"F10": {
passSub("F10a", "短断线送到", "test/accept/rest_accept_test.go"),
passSub("F10b", "长断线丢弃", "test/accept/rest_accept_test.go"),
passSub("F10c", "服务器重启后宽限内重连送到", "test/accept/rest_accept_test.go"),
},
"F11": {
passSub("F11a", "发送方离线后到点仍发送", "test/accept/rest_accept_test.go"),
},
"F12": {
passSub("F12a", "延迟窗口内撤回对方收不到", "test/accept/accept_test.go"),
},
"F13": {
passSub("F13a", "未推送必撤成功", "test/accept/accept_test.go"),
passSub("F13b", "群部分确认得到部分撤回", "internal/app/message/delivery_test.go TestDeliveryStateMachine/F13_recall_race_partial"),
},
"F14": {
passSub("F14a", "回执能补送给当时离线的发送方", "test/accept/rest_accept_test.go"),
},
"F15": {
passSub("F15a", "输一次记住", "test/accept/rest_accept_test.go;internal/app/identity/self_talk_test.go TestF15TalkPasswordAuth"),
passSub("F15b", "改密失效", "test/accept/rest_accept_test.go;internal/app/identity/self_talk_test.go TestF15ReplyGrantAndChange"),
passSub("F15c", "回复免密", "test/accept/rest_accept_test.go;internal/app/identity/self_talk_test.go"),
passSub("F15d", "进群仍要密码", "test/accept/rest_accept_test.go;internal/app/identity/self_talk_test.go TestF15JoinNeedsPasswordDespiteGrant"),
passSub("F15e", "防多账号轮流猜", "test/accept/rest_accept_test.go"),
},
"F16": {
passSub("F16a", "建群拉成员后可群发", "test/accept/accept_test.go"),
passSub("F16b", "群主权限", "internal/app/group/group_test.go TestF16CreateAddPasswordAndOwner"),
passSub("F16c", "退出后不再收到", "internal/app/group/group_test.go TestF16LeaveRemoveDissolve"),
passSub("F16d", "解散后同编号新群不收旧消息", "internal/app/group/group_test.go TestF16LeaveRemoveDissolve"),
},
"F17": {
passSub("F17a", "管理登录、CSRF、错误密码锁定", "test/accept/accept_test.go runAdminAuth"),
passSub("F17b", "管端开通", "test/accept/accept_test.go runEndpointCreate"),
passSub("F17c", "管注册", "test/accept/accept_test.go runRegistration;internal/admin/a3_test.go TestRegistrationToggleAndGenerate"),
passSub("F17d", "管群", "internal/admin/a3_test.go TestGroupsCRUD"),
passSub("F17e", "查记录响应无正文", "internal/admin/a3_test.go TestMessageDetailHasNoBody"),
passSub("F17f", "API 令牌可用且不能越权", "internal/admin/admin_test.go TestAPITokenAuthAndRestrictions"),
},
"F18": {
passSub("F18a", "送达后正文消失", "test/accept/rest_accept_test.go;internal/app/message/delivery_test.go TestDeliveryStateMachine/F18_body_gone_record_zero_idempotent_kept"),
passSub("F18b", "记录天数 0 时连记录消失", "test/accept/rest_accept_test.go"),
passSub("F18c", "防重仍在", "test/accept/rest_accept_test.go;internal/app/message/submit_test.go TestSubmitTable/idempotent_hit"),
},
"F19": {
passSub("F19a", "四种 SDK 接入清单(仓库内已有测试,本波不重跑全量)", "sdk/go/itest_checklist_test.go;sdk/js/test/checklist.test.ts;sdk/python/tests/test_checklist.py;sdk/java ChecklistTest"),
},
"F20": {
passSub("F20a", "裸 MQTT WebSocket 登录、收、确认、发", "test/accept/accept_test.go runMessagingAccept"),
},
"F21": {
passSub("F21a", "同一 listen 端口提供后台、WebSocket、注册", "test/accept/accept_test.go"),
untestedSub("F21b", "裸 TCP MQTT 登录收发", "验收用例只走 WebSocket;listener 首字节识别见 internal/listener/listener_test.go TestIdentifyPlainHTTPAndMQTT,不足以上升为通过"),
passSub("F21c", "后台可分到单独端口", "internal/listener/listener_test.go TestAdminSeparateMQTTClosedAndAdmin404OnListen"),
},
"F22": {
passSub("F22a", "空目录 admin init + serve 健康检查", "test/accept/accept_test.go runInitHealthz"),
passSub("F22b", "备份", "cmd/nixmsg/commands_test.go TestBackupVacuumInto"),
passSub("F22c", "升级迁移前备份", "internal/store/db_test.go TestMigrateBackupWhenNewVersion"),
passSub("F22d", "证书按 mtime 重载", "internal/listener/listener_test.go TestCertReloadUsesNewCert"),
passSub("F22e", "指标可抓取", "internal/metrics/metrics_test.go TestMetricsHandlerExposesText;internal/httpx/metrics_test.go TestMetricsGateSharedPort"),
untestedSub("F22f", "Docker 全量冒烟", "镜像未推送;本波未跑 compose 全量"),
},
"F23": {
passSub("F23a", "注册开关、安全码校验、换码不影响已注册", "test/accept/accept_test.go runRegistration;internal/app/identity/register_test.go TestRegisterF23_*"),
passSub("F23b", "输错锁定", "internal/app/identity/register_test.go TestRegisterF23_WrongCodeLock"),
},
}
// CatalogItems 按 PRD §10 拆子项;整项仅在全部子项通过时为通过。
func CatalogItems() []Item {
out := make([]Item, 0, len(Features))
for _, f := range Features {
subs := catalogSubs[f.ID]
copied := make([]SubItem, len(subs))
copy(copied, subs)
out = append(out, Rollup(Item{ID: f.ID, Subs: copied}))
}
return out
}
// CatalogResults 写出带固定时间戳的结果,便于对照表可复现。
func CatalogResults() Results {
return Results{
GeneratedAt: "2026-09-30T08:30:00Z",
Items: CatalogItems(),
}
}
// Reconcile 以目录为底;现场探测失败则整项记失败,忽略「部分覆盖却标通过」。
func Reconcile(live []Item) []Item {
byLive := make(map[string]Item, len(live))
for _, it := range live {
byLive[strings.ToUpper(strings.TrimSpace(it.ID))] = it
}
out := CatalogItems()
for i, it := range out {
lv, ok := byLive[it.ID]
if !ok {
continue
}
st, err := parseStatus(lv.Status)
if err != nil {
continue
}
if st == StatusFail {
it.Status = StatusFail
if lv.Note != "" {
it.Note = lv.Note
}
out[i] = it
}
}
return out
}