115 lines
3.4 KiB
Go
115 lines
3.4 KiB
Go
|
|
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-01/FU-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)
|
|||
|
|
}
|
|||
|
|
}
|