2026-06-26 08:37:04 +00:00
|
|
|
package usecase
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"haixun-backend/internal/model/job/domain/entity"
|
|
|
|
|
"haixun-backend/internal/model/job/domain/enum"
|
|
|
|
|
domusecase "haixun-backend/internal/model/job/domain/usecase"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestCreateRun_BlocksNonRepeatableAfterSuccess(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
template := demoTemplate()
|
|
|
|
|
template.Repeatable = false
|
|
|
|
|
template.DedupeKeys = []string{"scope_id"}
|
|
|
|
|
|
|
|
|
|
runs := newMemoryRunRepo(nil)
|
|
|
|
|
queue := newMemoryQueueRepo()
|
|
|
|
|
uc := testUseCaseFull(template, runs, nil, queue)
|
|
|
|
|
|
|
|
|
|
first, err := uc.CreateRun(ctx, domusecase.CreateRunRequest{
|
|
|
|
|
TemplateType: template.Type,
|
|
|
|
|
Scope: "user",
|
|
|
|
|
ScopeID: "u1",
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("first CreateRun() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
first.Status = enum.RunStatusSucceeded
|
|
|
|
|
first.DedupeKey = buildDedupeKey(template, "user", "u1", nil)
|
|
|
|
|
_, _ = runs.Update(ctx, first)
|
|
|
|
|
|
|
|
|
|
_, err = uc.CreateRun(ctx, domusecase.CreateRunRequest{
|
|
|
|
|
TemplateType: template.Type,
|
|
|
|
|
Scope: "user",
|
|
|
|
|
ScopeID: "u1",
|
|
|
|
|
})
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected duplicate completed job error")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCreateRun_DedupeLeaseBlocksParallel(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
template := demoTemplate()
|
|
|
|
|
template.Repeatable = true
|
|
|
|
|
template.DedupeKeys = []string{"scope_id"}
|
|
|
|
|
|
|
|
|
|
queue := newMemoryQueueRepo()
|
|
|
|
|
uc := testUseCaseFull(template, newMemoryRunRepo(nil), nil, queue)
|
|
|
|
|
|
|
|
|
|
first, err := uc.CreateRun(ctx, domusecase.CreateRunRequest{
|
|
|
|
|
TemplateType: template.Type,
|
|
|
|
|
Scope: "user",
|
|
|
|
|
ScopeID: "u1",
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("first CreateRun() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if first.Status != enum.RunStatusQueued {
|
|
|
|
|
t.Fatalf("first status = %s, want queued", first.Status)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = uc.CreateRun(ctx, domusecase.CreateRunRequest{
|
|
|
|
|
TemplateType: template.Type,
|
|
|
|
|
Scope: "user",
|
|
|
|
|
ScopeID: "u1",
|
|
|
|
|
})
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected in-progress dedupe error")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-01 08:42:51 +00:00
|
|
|
func TestBuildDedupeKey_IncludesScanPostID(t *testing.T) {
|
|
|
|
|
template := &entity.Template{
|
|
|
|
|
Type: "generate-outreach-draft",
|
|
|
|
|
DedupeKeys: []string{"scan_post_id"},
|
|
|
|
|
}
|
|
|
|
|
key1 := buildDedupeKey(template, "placement_topic", "topic-a", map[string]any{"scan_post_id": "post-1"})
|
|
|
|
|
key2 := buildDedupeKey(template, "placement_topic", "topic-a", map[string]any{"scan_post_id": "post-2"})
|
|
|
|
|
if key1 == "" || key2 == "" || key1 == key2 {
|
|
|
|
|
t.Fatalf("dedupe keys should differ by scan_post_id: %q vs %q", key1, key2)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-26 08:37:04 +00:00
|
|
|
func TestBuildDedupeKey_IncludesScopeID(t *testing.T) {
|
|
|
|
|
template := &entity.Template{
|
|
|
|
|
Type: "demo",
|
|
|
|
|
DedupeKeys: []string{"scope_id", "target"},
|
|
|
|
|
}
|
|
|
|
|
key1 := buildDedupeKey(template, "user", "a", map[string]any{"target": "x"})
|
|
|
|
|
key2 := buildDedupeKey(template, "user", "b", map[string]any{"target": "x"})
|
|
|
|
|
if key1 == key2 {
|
|
|
|
|
t.Fatal("dedupe keys should differ by scope_id")
|
|
|
|
|
}
|
|
|
|
|
}
|