package scheduler import ( "strings" "testing" ) func TestBatchDeleteSQLUsesBoundedSkipLockedDelete(t *testing.T) { sql := batchDeleteSQL("monitoring_collect_tasks", "id", "business_date < $1::date", "business_date ASC, id ASC") for _, want := range []string{ "LIMIT $2", "FOR UPDATE SKIP LOCKED", "DELETE FROM monitoring_collect_tasks t", "USING doomed", "WHERE t.id = doomed.id", } { if !strings.Contains(sql, want) { t.Fatalf("batchDeleteSQL missing %q in SQL:\n%s", want, sql) } } if strings.Contains(sql, "COUNT(*) FROM monitoring_collect_tasks") { t.Fatalf("batchDeleteSQL must not count full candidate set:\n%s", sql) } } func TestBatchProbeSQLCapsDryRunProbe(t *testing.T) { sql := batchProbeSQL("monitoring_collect_tasks", "id", "business_date < $1::date", "business_date ASC, id ASC") if !strings.Contains(sql, "LIMIT $2") { t.Fatalf("batchProbeSQL should cap dry-run probe:\n%s", sql) } if !strings.Contains(sql, "SELECT COUNT(*)::bigint") { t.Fatalf("batchProbeSQL should count only the limited candidate subquery:\n%s", sql) } } func TestMarkedArticleRetentionClearsManualAttributionBeforeDelete(t *testing.T) { var target retentionTarget for _, item := range retentionTargets() { if item.Name == "monitoring_user_marked_articles" { target = item break } } if target.Name == "" { t.Fatal("monitoring_user_marked_articles retention target missing") } if target.PreDeleteSQL == "" { t.Fatal("monitoring_user_marked_articles retention target must clear citation attribution before delete") } for _, want := range []string{ "SELECT tenant_id, id", "UPDATE monitoring_citation_facts cf", "cf.tenant_id = doomed.tenant_id", "content_source_type = 'manual_mark'", "content_source_id = doomed.id", "SET content_source_type = NULL", "LIMIT $2", "FOR UPDATE SKIP LOCKED", } { if !strings.Contains(target.PreDeleteSQL, want) { t.Fatalf("marked article attribution cleanup SQL missing %q in SQL:\n%s", want, target.PreDeleteSQL) } } }