121 lines
5.4 KiB
Go
121 lines
5.4 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
"github.com/gochat/gochat/internal/worker"
|
|
)
|
|
|
|
func TestSlaProcessingJobsTriggerQueuesAccountScansAndReschedules(t *testing.T) {
|
|
svc, db := setupAppliedSlaLifecycleTest(t)
|
|
now := time.Date(2026, 6, 5, 16, 0, 0, 0, time.UTC)
|
|
_, _, _, _, _ = seedAppliedSlaConversation(t, db, now.Add(-time.Hour), model.SlaPolicy{FirstResponseTimeThreshold: 60})
|
|
_ = createTestAccount(t, db)
|
|
wp := worker.NewWorkerPoolWithOptions(db, worker.WithNow(func() time.Time { return now }))
|
|
registerSlaProcessingJobsWithNow(wp, db, svc, func() time.Time { return now })
|
|
|
|
if _, err := EnqueueSlaAccountsScan(context.Background(), wp, now); err != nil {
|
|
t.Fatalf("enqueue sla scan: %v", err)
|
|
}
|
|
processed, err := wp.ProcessOne(context.Background())
|
|
if err != nil || !processed {
|
|
t.Fatalf("process trigger job: processed=%v err=%v", processed, err)
|
|
}
|
|
|
|
var accountJobCount int64
|
|
if err := db.Model(&model.BackgroundJob{}).Where("job_type = ? AND queue = ? AND status = ?", TaskTypeSlaProcessAccount, "medium", model.BackgroundJobStatusQueued).Count(&accountJobCount).Error; err != nil {
|
|
t.Fatalf("count account jobs: %v", err)
|
|
}
|
|
if accountJobCount != 1 {
|
|
t.Fatalf("expected one account scan job for accounts with SLA policies, got %d", accountJobCount)
|
|
}
|
|
|
|
var nextTrigger model.BackgroundJob
|
|
if err := db.Where("job_type = ? AND status = ?", TaskTypeSlaTriggerAccounts, model.BackgroundJobStatusQueued).First(&nextTrigger).Error; err != nil {
|
|
t.Fatalf("load next trigger job: %v", err)
|
|
}
|
|
if !nextTrigger.ScheduledAt.Equal(now.Add(slaScanInterval)) {
|
|
t.Fatalf("expected next trigger at %s, got %s", now.Add(slaScanInterval), nextTrigger.ScheduledAt)
|
|
}
|
|
}
|
|
|
|
func TestSlaProcessingJobsProcessAccountQueuesAndEvaluatesAppliedSlas(t *testing.T) {
|
|
svc, db := setupAppliedSlaLifecycleTest(t)
|
|
now := time.Date(2026, 6, 5, 16, 30, 0, 0, time.UTC)
|
|
account, _, _, policy, conversation := seedAppliedSlaConversation(t, db, time.Now().Add(-2*time.Hour), model.SlaPolicy{FirstResponseTimeThreshold: 60})
|
|
applied, err := svc.CreateFromConversation(context.Background(), account.ID, conversation.ID, policy.ID)
|
|
if err != nil {
|
|
t.Fatalf("create applied sla: %v", err)
|
|
}
|
|
wp := worker.NewWorkerPoolWithOptions(db, worker.WithNow(func() time.Time { return now }))
|
|
registerSlaProcessingJobsWithNow(wp, db, svc, func() time.Time { return now })
|
|
|
|
if _, err := wp.Enqueue(context.Background(), TaskTypeSlaProcessAccount, slaProcessAccountJob{AccountID: account.ID}, worker.WithQueue("medium")); err != nil {
|
|
t.Fatalf("enqueue account job: %v", err)
|
|
}
|
|
processed, err := wp.ProcessOne(context.Background())
|
|
if err != nil || !processed {
|
|
t.Fatalf("process account job: processed=%v err=%v", processed, err)
|
|
}
|
|
|
|
var appliedJobCount int64
|
|
if err := db.Model(&model.BackgroundJob{}).Where("job_type = ? AND status = ?", TaskTypeSlaProcessApplied, model.BackgroundJobStatusQueued).Count(&appliedJobCount).Error; err != nil {
|
|
t.Fatalf("count applied jobs: %v", err)
|
|
}
|
|
if appliedJobCount != 1 {
|
|
t.Fatalf("expected one applied SLA job, got %d", appliedJobCount)
|
|
}
|
|
|
|
processed, err = wp.ProcessOne(context.Background())
|
|
if err != nil || !processed {
|
|
t.Fatalf("process applied job: processed=%v err=%v", processed, err)
|
|
}
|
|
var reloaded model.AppliedSLA
|
|
if err := db.First(&reloaded, applied.ID).Error; err != nil {
|
|
t.Fatalf("reload applied sla: %v", err)
|
|
}
|
|
if reloaded.SLAStatus != model.SLAStatusActiveWithMisses {
|
|
t.Fatalf("expected SLA miss after durable evaluation, got %s", reloaded.SLAStatus)
|
|
}
|
|
|
|
if _, err := wp.Enqueue(context.Background(), TaskTypeSlaProcessApplied, slaProcessAppliedJob{AppliedSlaID: applied.ID}, worker.WithQueue("medium")); err != nil {
|
|
t.Fatalf("enqueue duplicate applied job: %v", err)
|
|
}
|
|
processed, err = wp.ProcessOne(context.Background())
|
|
if err != nil || !processed {
|
|
t.Fatalf("process duplicate applied job: processed=%v err=%v", processed, err)
|
|
}
|
|
var eventCount int64
|
|
if err := db.Model(&model.SlaEvent{}).Where("applied_sla_id = ?", applied.ID).Count(&eventCount).Error; err != nil {
|
|
t.Fatalf("count sla events: %v", err)
|
|
}
|
|
if eventCount != 1 {
|
|
t.Fatalf("expected durable duplicate evaluation to stay idempotent, got %d events", eventCount)
|
|
}
|
|
}
|
|
|
|
func TestSlaProcessingJobsRetryMissingAppliedSla(t *testing.T) {
|
|
svc, db := setupAppliedSlaLifecycleTest(t)
|
|
now := time.Date(2026, 6, 5, 17, 0, 0, 0, time.UTC)
|
|
wp := worker.NewWorkerPoolWithOptions(db, worker.WithNow(func() time.Time { return now }), worker.WithBackoff(func(attempt int) time.Duration { return time.Minute }))
|
|
registerSlaProcessingJobsWithNow(wp, db, svc, func() time.Time { return now })
|
|
|
|
if _, err := wp.Enqueue(context.Background(), TaskTypeSlaProcessApplied, slaProcessAppliedJob{AppliedSlaID: 9999}, worker.WithQueue("medium"), worker.WithMaxAttempts(3)); err != nil {
|
|
t.Fatalf("enqueue missing applied job: %v", err)
|
|
}
|
|
processed, err := wp.ProcessOne(context.Background())
|
|
if err == nil || !processed {
|
|
t.Fatalf("expected missing applied SLA to retry, processed=%v err=%v", processed, err)
|
|
}
|
|
var job model.BackgroundJob
|
|
if err := db.Where("job_type = ?", TaskTypeSlaProcessApplied).First(&job).Error; err != nil {
|
|
t.Fatalf("load applied job: %v", err)
|
|
}
|
|
if job.Status != model.BackgroundJobStatusRetrying || job.LastError == "" {
|
|
t.Fatalf("expected retrying applied SLA job with error, got status=%s last_error=%q", job.Status, job.LastError)
|
|
}
|
|
}
|