Smail/test/review/pop3_action_findings_test.go
smail 6f79441585 修复审查发现的认证配置邮件协议及前端问题
补齐安全运行时清理、配置候选恢复、泛域名证书事务提交、邮件副本与协议边界、插件隐私和安装回滚回归,并更新修复状态及待验收清单。
2026-09-06 13:02:02 +08:00

159 lines
5.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package pop3_server
import (
"slices"
"testing"
"git.pcux.cn/xuans/Smail/config"
"git.pcux.cn/xuans/Smail/db"
"git.pcux.cn/xuans/Smail/internal/gopop"
"git.pcux.cn/xuans/Smail/models"
"git.pcux.cn/xuans/Smail/utils/context"
)
func TestAuditPOP3AdvertisesStandardReset(t *testing.T) {
commands, err := (action{}).Capa(&gopop.Session{InTls: true})
if err != nil || !slices.Contains(commands, "RSET") || slices.Contains(commands, "REST") {
t.Fatalf("必须公告标准 RSET 而非 REST:commands=%v err=%v", commands, err)
}
}
func TestAuditPOP3SnapshotKeepsHolesAndExcludesNewMail(t *testing.T) {
session, login := auditPOP3Session(t)
for _, id := range []int{101, 202, 303} {
auditPOP3Message(t, session, id, 0)
}
login()
if _, err := (action{}).List(session, ""); err != nil {
t.Fatal(err)
}
if err := (action{}).Delete(session, 2); err != nil {
t.Fatal(err)
}
auditPOP3Message(t, session, 404, 0)
rows, err := (action{}).List(session, "")
if err != nil || len(rows) != 2 || rows[0].Id != 1 || rows[1].Id != 3 {
t.Fatalf("会话序号变化: %v %v", rows, err)
}
if _, err := (action{}).List(session, "2"); err == nil {
t.Fatal("已删除编号仍可访问")
}
if _, err := (action{}).List(session, "4"); err == nil {
t.Fatal("新邮件进入了旧快照")
}
}
// 复用临时 SQLite 和协议授权码夹具,不访问用户运行库。
func auditPOP3Session(t *testing.T) (*gopop.Session, func()) {
t.Helper()
engine, user, code := newPOP3ProtocolAuthFixture(t)
oldDB, oldConfig := db.Instance, config.Instance
db.Instance = engine
config.Instance = &config.Config{Domain: "example.com", Domains: []string{"example.com"}}
t.Cleanup(func() {
db.Instance, config.Instance = oldDB, oldConfig
})
session := &gopop.Session{User: "alice@example.com", InTls: true, Ctx: &context.Context{UserID: user.ID}}
return session, func() {
t.Helper()
if err := (action{}).Pass(session, code); err != nil {
t.Fatal(err)
}
if session.Ctx.(*context.Context).UserID != user.ID {
t.Fatal("临时会话未关联预期用户")
}
}
}
func auditPOP3Message(t *testing.T, session *gopop.Session, id int, status int8) {
t.Helper()
message := models.Email{Id: id, Subject: "audit-pop3", Size: 100}
if _, err := db.Instance.Insert(&message); err != nil {
t.Fatal(err)
}
relation := models.UserEmail{UserID: session.Ctx.(*context.Context).UserID, EmailID: id, Status: status}
if _, err := db.Instance.Insert(&relation); err != nil {
t.Fatal(err)
}
}
func TestAuditPOP3MessageNumbersMustStartAtOne(t *testing.T) {
session, login := auditPOP3Session(t)
auditPOP3Message(t, session, 101, 0)
auditPOP3Message(t, session, 305, 0)
login()
rows, err := (action{}).List(session, "")
if err != nil || len(rows) != 2 {
t.Fatalf("列表前置条件失败: rows=%v err=%v", rows, err)
}
_, lookupErr := (action{}).List(session, "1")
t.Logf("message_count=%d list_message_numbers=%d,%d list_1_error=%v", len(rows), rows[0].Id, rows[1].Id, lookupErr)
if rows[0].Id != 1 || rows[1].Id != 2 || lookupErr != nil {
t.Fatal("POP3 消息编号使用全局邮件 ID,未建立会话级 1..N 映射")
}
}
func TestAuditPOP3MarkedDeletionMustHideMessage(t *testing.T) {
session, login := auditPOP3Session(t)
auditPOP3Message(t, session, 1, 0)
login()
if err := (action{}).Delete(session, 1); err != nil {
t.Fatal(err)
}
rows, err := (action{}).List(session, "")
if err != nil {
t.Fatal(err)
}
count, _, err := (action{}).Stat(session)
if err != nil {
t.Fatal(err)
}
_, uidlErr := (action{}).Uidl(session, "1")
duplicateErr := (action{}).Delete(session, 1)
missingErr := (action{}).Delete(session, 999999)
t.Logf("after_dele_list_count=%d stat_count=%d uidl_error=%v duplicate_dele_error=%v missing_dele_error=%v", len(rows), count, uidlErr, duplicateErr, missingErr)
if len(rows) != 0 || count != 0 || uidlErr == nil || duplicateErr == nil || missingErr == nil {
t.Fatal("已标记删除邮件仍出现在会话查询中,重复及不存在的 DELE 也未被拒绝")
}
}
func TestAuditPOP3STATMustMatchVisibleMailbox(t *testing.T) {
session, login := auditPOP3Session(t)
auditPOP3Message(t, session, 1, 0)
auditPOP3Message(t, session, 2, 5)
login()
rows, err := (action{}).List(session, "")
if err != nil {
t.Fatal(err)
}
count, _, err := (action{}).Stat(session)
if err != nil {
t.Fatal(err)
}
t.Logf("inbox_messages=1 junk_messages=1 list_count=%d stat_count=%d", len(rows), count)
if count != 1 || len(rows) != 1 {
t.Fatal("STAT 纳入垃圾箱邮件,但 LIST 排除了垃圾箱,邮箱视图不一致")
}
}
func TestAuditPOP3NegativeTOPMustNotPanic(t *testing.T) {
session, login := auditPOP3Session(t)
auditPOP3Message(t, session, 1, 0)
if _, err := db.Instance.ID(1).Cols("from_address").Update(&models.Email{FromAddress: "sender@example.com"}); err != nil {
t.Fatal(err)
}
login()
if _, err := (action{}).Top(session, 1, 0); err != nil {
t.Fatalf("正常 TOP 前置条件失败: %v", err)
}
defer func() {
if recovered := recover(); recovered != nil {
t.Logf("negative_top_panic=%v", recovered)
t.Error("负数 TOP 行参数进入切片表达式并触发 panic")
}
}()
if _, err := (action{}).Top(session, 1, -100000); err == nil {
t.Error("负数 TOP 行参数必须明确拒绝")
}
}