package memory import ( "testing" "time" "yunyan/comm" "yunyan/pb" ) func TestTaskToMemoryItem_Basic(t *testing.T) { task := &pb.DBTask{ Id: 42, Uid: "u1", TaskName: "跟进A客户", TaskDesc: "电话联系", TaskType: pb.TaskType_TaskType_Once, TriggerTime: "2026-04-08T15:00:00+08:00", Status: pb.TaskStatus_TaskStatus_Pending, CreateTime: 1700000000, } it := taskToMemoryItem(task) if it == nil { t.Fatal("不该返回 nil") } if it.HappenDate != "2026-04-08" { t.Errorf("日期期望 2026-04-08,得到 %s", it.HappenDate) } if it.HappenTime != "15:00" { t.Errorf("时刻期望 15:00,得到 %s", it.HappenTime) } if it.ClientKey != "task:42" { t.Errorf("幂等键期望 task:42,得到 %s", it.ClientKey) } if it.Category != comm.MemoryCatTodo { t.Errorf("存量任务应一律迁成 todo,得到 %s", it.Category) } // 旧体系里「服务端仅记录不触发」,用户从没被提醒过。 // 迁过来默认开提醒 = 给所有人补发一堆历史提醒。 if it.RemindAhead != 0 { t.Errorf("迁移来的任务不该默认开提醒,得到 %d", it.RemindAhead) } if !it.UserEdited { t.Error("迁移来的是用户资产,应标 user_edited 避免被自动清理碰到") } } // ⚠️ 凌晨任务:如果先转 UTC 再取日期,00:30+08:00 会退到前一天。 func TestTaskToMemoryItem_EarlyMorningKeepsLocalDate(t *testing.T) { task := &pb.DBTask{ Id: 1, Uid: "u1", TaskName: "晨会", TriggerTime: "2026-04-08T00:30:00+08:00", CreateTime: 1700000000, } it := taskToMemoryItem(task) if it == nil { t.Fatal("不该返回 nil") } if it.HappenDate != "2026-04-08" { t.Errorf("凌晨任务的日期不该退到前一天,期望 2026-04-08 得到 %s", it.HappenDate) } } func TestTaskToMemoryItem_RepeatMapping(t *testing.T) { cases := map[pb.TaskType]string{ pb.TaskType_TaskType_Once: comm.MemoryRepeatOnce, pb.TaskType_TaskType_Daily: comm.MemoryRepeatDaily, pb.TaskType_TaskType_Weekly: comm.MemoryRepeatWeekly, pb.TaskType_TaskType_Monthly: comm.MemoryRepeatMonthly, // cron 没有对应的 repeat_rule,按一次性迁:猜错的重复规则会让用户 // 每天被一个他没设过的闹钟吵醒 pb.TaskType_TaskType_Cron: comm.MemoryRepeatOnce, } for tt, want := range cases { task := &pb.DBTask{ Id: 1, Uid: "u1", TaskName: "x", TaskType: tt, TriggerTime: "2026-04-08T15:00:00+08:00", } it := taskToMemoryItem(task) if it == nil || it.RepeatRule != want { t.Errorf("TaskType %v 期望 %s,得到 %v", tt, want, it) } } } func TestTaskToMemoryItem_StatusMapping(t *testing.T) { cases := map[pb.TaskStatus]pb.MemoryState{ pb.TaskStatus_TaskStatus_Pending: pb.MemoryState_MemoryState_Pending, pb.TaskStatus_TaskStatus_Completed: pb.MemoryState_MemoryState_Done, pb.TaskStatus_TaskStatus_Cancelled: pb.MemoryState_MemoryState_Dropped, } for st, want := range cases { if got := taskStatusToState(st); got != want { t.Errorf("状态 %v 期望 %v 得到 %v", st, want, got) } } } // 没有触发时间时退回创建日期——happen_date 永不为空是拾忆页能工作的前提 func TestTaskToMemoryItem_FallsBackToCreateTime(t *testing.T) { created := time.Date(2026, 3, 1, 10, 0, 0, 0, time.Local) task := &pb.DBTask{ Id: 1, Uid: "u1", TaskName: "无时间任务", CreateTime: created.Unix(), } it := taskToMemoryItem(task) if it == nil { t.Fatal("不该返回 nil") } if it.HappenDate != comm.FormatMemoryDate(created) { t.Errorf("应退回创建日期 %s,得到 %s", comm.FormatMemoryDate(created), it.HappenDate) } if it.DateCertain { t.Error("没有具体时刻时 date_certain 应为 false") } } // 该跳过的都要跳过,别往新表里塞垃圾 func TestTaskToMemoryItem_SkipsUnusable(t *testing.T) { cases := []struct { name string task *pb.DBTask }{ {"nil", nil}, {"没有 uid", &pb.DBTask{Id: 1, TaskName: "x", CreateTime: 1}}, {"标题与描述都空", &pb.DBTask{Id: 1, Uid: "u1", CreateTime: 1}}, {"既无触发时间也无创建时间", &pb.DBTask{Id: 1, Uid: "u1", TaskName: "x"}}, } for _, c := range cases { if got := taskToMemoryItem(c.task); got != nil { t.Errorf("%s: 应跳过,却返回了 %+v", c.name, got) } } } // 描述为空时用标题;标题为空时用描述 func TestTaskToMemoryItem_TitleFallback(t *testing.T) { task := &pb.DBTask{ Id: 1, Uid: "u1", TaskDesc: "只有描述", TriggerTime: "2026-04-08T15:00:00+08:00", } it := taskToMemoryItem(task) if it == nil || it.Title != "只有描述" { t.Errorf("标题空时应退回描述,得到 %v", it) } }