补充AI下次时间接口异常入参与验证记录

This commit is contained in:
hello-dd-code
2026-02-28 16:24:04 +08:00
parent ec9517e248
commit c735b791cb
3 changed files with 87 additions and 1 deletions
@@ -0,0 +1,35 @@
# AI 下次抽烟时间验证记录(2026-02-28)
对应 issue`#4 [P0][T1] AI 下次抽烟时间端到端验证`
## 验证结论
- 关键场景:通过(默认模式、AI 模式、缓存命中/过期判定)。
- 边界场景:通过(空节点、非法时间、睡眠区间调整、未来计划日期最小时间)。
- 异常场景:通过(非法 `mode` 返回 `400`,错误文案明确)。
## 覆盖明细
1. 正常链路
- `internal/smoke/service/smoke_ai_next_smoke_service_test.go::TestShouldRefreshCache`
- 覆盖当天有效缓存命中。
- `internal/smoke/service/smoke_ai_next_smoke_service_test.go::TestNormalizeNodesFiltersInvalidAndDuplicate`
- 覆盖节点去重、非当天过滤、睡眠区间过滤。
2. 边界数据
- `internal/smoke/service/smoke_ai_next_smoke_service_test.go::TestComputeMinNotBeforeForFuturePlanUsesWakeUp`
- 覆盖明日计划默认 07:00 与按起床时间约束。
3. 异常输入/兜底
- `internal/smoke/handler/smoke_next_handler_test.go::TestResolveNextSmokeMode`
- 覆盖 `mode` 非法值校验,保证返回 `400` 而不是隐式降级。
## 执行命令
```bash
go test ./...
```
## 问题清单
- 当前未引入真实外部 AI 服务的在线回归(依赖环境密钥与网络),本次以可复现单元测试覆盖核心逻辑路径。
+19 -1
View File
@@ -75,7 +75,11 @@ func (h *SmokeHandler) GetNextSmokeTime(c *gin.Context) {
return return
} }
mode := strings.ToLower(strings.TrimSpace(c.DefaultQuery("mode", "auto"))) mode, ok := resolveNextSmokeMode(c.Query("mode"))
if !ok {
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "mode 参数错误,应为 auto|ai|default"))
return
}
formatPtr := func(t *time.Time) string { formatPtr := func(t *time.Time) string {
if t == nil { if t == nil {
@@ -194,6 +198,20 @@ func dateOnlyLocal(t time.Time) time.Time {
return time.Date(local.Year(), local.Month(), local.Day(), 0, 0, 0, 0, time.Local) return time.Date(local.Year(), local.Month(), local.Day(), 0, 0, 0, 0, time.Local)
} }
func resolveNextSmokeMode(raw string) (string, bool) {
mode := strings.ToLower(strings.TrimSpace(raw))
if mode == "" {
return "auto", true
}
switch mode {
case "auto", "ai", "default":
return mode, true
default:
return "", false
}
}
type nextSmokeDefaultResponse struct { type nextSmokeDefaultResponse struct {
LastSmokeAt string `json:"last_smoke_at,omitempty"` LastSmokeAt string `json:"last_smoke_at,omitempty"`
NextSmokeAt string `json:"next_smoke_at,omitempty"` NextSmokeAt string `json:"next_smoke_at,omitempty"`
@@ -0,0 +1,33 @@
package handler
import "testing"
func TestResolveNextSmokeMode(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
mode string
ok bool
}{
{name: "默认值", input: "", mode: "auto", ok: true},
{name: "自动模式", input: "auto", mode: "auto", ok: true},
{name: "AI模式", input: "ai", mode: "ai", ok: true},
{name: "默认策略模式", input: "default", mode: "default", ok: true},
{name: "大小写兼容", input: "AI", mode: "ai", ok: true},
{name: "非法值", input: "fast", mode: "", ok: false},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
gotMode, gotOK := resolveNextSmokeMode(tc.input)
if gotMode != tc.mode || gotOK != tc.ok {
t.Fatalf("resolveNextSmokeMode(%q)=(%q,%v), want=(%q,%v)", tc.input, gotMode, gotOK, tc.mode, tc.ok)
}
})
}
}