package repo import ( "context" "testing" "github.com/agentmail/gateway/internal/models" ) // 待办类型(permission / question)与多选语义必须在**每一条读路径**上透出。 // // # 为什么值得单独钉一个测试 // // 这几个字段(mails.permission_kind / permission_multi_select)曾经只存在于 // 结构体与写入路径:模型里有字段、INSERT 里有列,但五个读路径的 SELECT/Scan // 都没带上它们。 // // 这类遗漏不会报错 —— SQL 照常返回,只是那个字段永远为零值。前端于是拿到 // permission_kind="" 与 permission_multi_select=false,把「模型问了一个问题」 // 渲染成「批准 / 拒绝」两个按钮,人点出来的答案对模型毫无意义。 // // 所以判据必须落在「五个不同的读函数都看得见」上,而不是只看写入那一侧。 func TestPermissionFieldsVisibleOnEveryReadPath(t *testing.T) { setupTestDB(t) ctx := context.Background() sid, err := CreateSession(ctx, nil, "agent-q", "问与答", "") if err != nil { t.Fatal(err) } // 一个自由文本问题:无预设选项、不允许多选。 mailID, err := CreatePermissionMail(ctx, sid, "agent-q", "alice", "配置文件名是什么", "请直接回答", nil, "question", false) if err != nil { t.Fatal(err) } if err := CreatePermissionRequest(ctx, mailID, sid, "agent-q", "配置文件名是什么", nil, "请直接回答", "question", false); err != nil { t.Fatal(err) } // 一个多选问题:单独造一封,验证 multi_select 也能透出。 multiID, err := CreatePermissionMail(ctx, sid, "agent-q", "alice", "要哪些环境", "勾选", []string{"dev", "prod"}, "question", true) if err != nil { t.Fatal(err) } if err := CreatePermissionRequest(ctx, multiID, sid, "agent-q", "要哪些环境", []string{"dev", "prod"}, "勾选", "question", true); err != nil { t.Fatal(err) } // 一封普通审批:kind 必须是 permission,multi 必须是 false。 approvalID, err := CreatePermissionMail(ctx, sid, "agent-q", "alice", "删除 build/", "rm -rf build/", []string{"同意", "拒绝"}, "permission", false) if err != nil { t.Fatal(err) } assertQuestion := func(what string, m *models.Mail) { t.Helper() if m == nil { t.Fatalf("%s 返回 nil", what) } if m.PermissionKind != "question" { t.Errorf("%s:PermissionKind = %q,期望 question"+ "(字段没进 SELECT/Scan 时会静默变成空串,前端就会渲染成批准/拒绝)", what, m.PermissionKind) } } // 1) GetMailByID —— 单封详情(前端点开邮件走这条)。 got, err := GetMailByID(ctx, mailID) if err != nil { t.Fatal(err) } assertQuestion("GetMailByID", got) // 2) ListInbox —— 授权页的列表来源。 inbox, err := ListInbox(ctx, "alice", "all", 50) if err != nil { t.Fatal(err) } var found *models.Mail for i := range inbox { if inbox[i].ID == mailID { found = &inbox[i] } } if found == nil { t.Fatal("ListInbox 没返回那封询问邮件") } assertQuestion("ListInbox", found) // 3) GetSessionMails —— 会话视图整树。 thread, err := GetSessionMails(ctx, sid) if err != nil { t.Fatal(err) } var inThread *models.Mail for i := range thread { if thread[i].ID == mailID { inThread = &thread[i] } } if inThread == nil { t.Fatal("GetSessionMails 没返回那封询问邮件") } assertQuestion("GetSessionMails", inThread) // 4) GetSessionMailByID —— 会话内单封。 one, err := GetSessionMailByID(ctx, sid, mailID) if err != nil { t.Fatal(err) } assertQuestion("GetSessionMailByID", one) // 5) ListSentBy —— 发件箱(Agent 自查「我问过什么」)。 sent, err := ListSentBy(ctx, "agent-q", 50) if err != nil { t.Fatal(err) } var inSent *models.Mail for i := range sent { if sent[i].ID == mailID { inSent = &sent[i] } } if inSent == nil { t.Fatal("ListSentBy 没返回那封询问邮件") } assertQuestion("ListSentBy", inSent) // multi_select 也要透出:勾选语义决定前端渲染单选还是多选。 multi, err := GetMailByID(ctx, multiID) if err != nil { t.Fatal(err) } if !multi.PermissionMulti { t.Error("多选问题:PermissionMulti = false,期望 true(字段漏读会让多选退化成单选)") } // 审批型不能被误标成 question。 approval, err := GetMailByID(ctx, approvalID) if err != nil { t.Fatal(err) } if approval.PermissionKind != "permission" { t.Errorf("审批邮件:PermissionKind = %q,期望 permission", approval.PermissionKind) } if approval.PermissionMulti { t.Error("审批邮件不该带多选语义") } } // 无选项的 question 不能被塞上「同意 / 拒绝」默认选项。 // // 那是 handler 层的默认值逻辑:审批型没有选项时确实该给两个按钮, // 但自由文本问题给它们只会让人点出一个毫无意义的答案。 // 这里从 repo 侧确认「传什么就存什么」,handler 的默认值规则另有测试。 func TestQuestionKeepsEmptyOptions(t *testing.T) { setupTestDB(t) ctx := context.Background() sid, err := CreateSession(ctx, nil, "agent-q2", "自由文本", "") if err != nil { t.Fatal(err) } mailID, err := CreatePermissionMail(ctx, sid, "agent-q2", "alice", "你的名字", "请回答", nil, "question", false) if err != nil { t.Fatal(err) } // GetPermissionByMailID 读的是 permission_requests 表(不是 mails), // 所以必须把那一行也插上 —— 否则拿到的是「没有这条请求」。 if err := CreatePermissionRequest(ctx, mailID, sid, "agent-q2", "你的名字", nil, "请回答", "question", false); err != nil { t.Fatal(err) } pr, err := GetPermissionByMailID(ctx, mailID) if err != nil { t.Fatal(err) } if pr.Kind != "question" { t.Errorf("Kind = %q,期望 question", pr.Kind) } if len(pr.Options) != 0 { t.Errorf("无选项问题被写入了选项 %v,期望空", pr.Options) } }