thread-master/apps/backend/internal/module/crm/usecase/followup_scan_test.go

115 lines
3.4 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 usecase
import (
"context"
"testing"
"time"
"apps/backend/internal/module/crm/domain"
"apps/backend/internal/module/crm/repository"
)
const day = int64(24 * time.Hour)
func seedFollowUp(t *testing.T, repo domain.Repository, days int) (*domain.Contact, *domain.FollowUp) {
t.Helper()
ctx := context.Background()
c, err := repo.UpsertContactByIdentity(ctx, &domain.Contact{
OwnerUID: 7, AuthorHandle: "buyer", FollowUpDays: days,
})
if err != nil {
t.Fatal(err)
}
f := &domain.FollowUp{
ID: domain.NewID(), OwnerUID: 7, ContactID: c.ID,
DueAt: 1, Status: domain.FollowUpScheduled,
CreatedAt: 1, UpdatedAt: 1,
}
if err := repo.SaveFollowUp(ctx, f); err != nil {
t.Fatal(err)
}
return c, f
}
// FU-01FU-03第一次通知後要再等一個間隔才第二次通知第二次之後才 escalated。
func TestScanFollowUpsNotifiesTwiceThenEscalates(t *testing.T) {
ctx := context.Background()
repo := repository.NewMemory()
svc := New(repo)
_, f := seedFollowUp(t, repo, 3)
now := int64(10 * day)
if n, err := svc.ScanFollowUps(ctx, now); err != nil || n != 1 {
t.Fatalf("first scan n=%d err=%v", n, err)
}
first, err := repo.GetFollowUp(ctx, f.ID)
if err != nil || first.Status != domain.FollowUpNotified || first.NotifiedCount != 1 {
t.Fatalf("after first scan=%+v err=%v", first, err)
}
if first.DueAt != now+3*day {
t.Fatalf("first notify must push due_at by the contact interval, got %d", first.DueAt)
}
// 還沒到下一次到期:不可重複通知
if n, err := svc.ScanFollowUps(ctx, now+day); err != nil || n != 0 {
t.Fatalf("premature rescan n=%d err=%v", n, err)
}
if n, err := svc.ScanFollowUps(ctx, now+3*day); err != nil || n != 1 {
t.Fatalf("second scan n=%d err=%v", n, err)
}
second, err := repo.GetFollowUp(ctx, f.ID)
if err != nil || second.Status != domain.FollowUpEscalated || second.NotifiedCount != 2 {
t.Fatalf("after second scan=%+v err=%v", second, err)
}
// escalated 是終點:不再被掃到
if n, err := svc.ScanFollowUps(ctx, now+30*day); err != nil || n != 0 {
t.Fatalf("escalated must stop scanning n=%d err=%v", n, err)
}
}
// FU-04延後把到期日後移並回到 scheduled。
func TestSnoozeReturnsToScheduled(t *testing.T) {
ctx := context.Background()
repo := repository.NewMemory()
svc := New(repo)
_, f := seedFollowUp(t, repo, 3)
if _, err := svc.ScanFollowUps(ctx, 10*day); err != nil {
t.Fatal(err)
}
before := domain.NowNano()
got, err := svc.SnoozeFollowUp(ctx, 7, f.ID, 5)
if err != nil {
t.Fatal(err)
}
if got.Status != domain.FollowUpScheduled {
t.Fatalf("snooze status = %s, want scheduled", got.Status)
}
if got.DueAt < before+5*day {
t.Fatalf("snooze must push due_at at least 5 days out, got %d", got.DueAt)
}
}
func TestScanFollowUpsFallsBackToDefaultInterval(t *testing.T) {
ctx := context.Background()
repo := repository.NewMemory()
svc := New(repo)
f := &domain.FollowUp{
ID: domain.NewID(), OwnerUID: 7, ContactID: "missing-contact",
DueAt: 1, Status: domain.FollowUpScheduled, CreatedAt: 1, UpdatedAt: 1,
}
if err := repo.SaveFollowUp(ctx, f); err != nil {
t.Fatal(err)
}
now := int64(10 * day)
if _, err := svc.ScanFollowUps(ctx, now); err != nil {
t.Fatal(err)
}
got, err := repo.GetFollowUp(ctx, f.ID)
if err != nil || got.DueAt != now+int64(domain.DefaultFollowUpDays)*day {
t.Fatalf("missing contact should use default interval, got %+v err=%v", got, err)
}
}