package app import ( "errors" "reflect" "regexp" "strings" "testing" "github.com/geo-platform/tenant-api/internal/shared/response" "github.com/geo-platform/tenant-api/internal/tenant/repository" ) func TestDesktopTaskRecoverySelectQueryMatchesRecoveredLeaseScanOrder(t *testing.T) { t.Parallel() query := desktopTaskRecoverySelectQuery(desktopTaskRecoveryModeStartup) gotColumns := recoverDesktopTaskSelectColumns(query) wantColumns := []string{ "desktop_id", "workspace_id", "kind", "status", "active_attempt_id", "attempts", "publish_submit_started_at", "monitor_task_id", } if len(gotColumns) != len(wantColumns) { t.Fatalf("recover select columns = %v, want %v", gotColumns, wantColumns) } for index := range wantColumns { if gotColumns[index] != wantColumns[index] { t.Fatalf("recover select columns = %v, want %v", gotColumns, wantColumns) } } } func TestDesktopTaskRecoverySelectQueryLeaseExpiryFiltersExpiredLeases(t *testing.T) { t.Parallel() query := desktopTaskRecoverySelectQuery(desktopTaskRecoveryModeLeaseExpiry) for _, fragment := range []string{ "AND lease_expires_at IS NOT NULL", "AND lease_expires_at < NOW()", "FOR UPDATE", } { if !strings.Contains(query, fragment) { t.Fatalf("recover lease-expiry query missing %q: %s", fragment, query) } } } func TestReconcileRetryPublishesTaskAvailableForAllDesktopTaskKinds(t *testing.T) { t.Parallel() for _, kind := range []string{"publish", "monitor"} { if got := reconcileDesktopTaskEventType(kind, "retry"); got != "task_available" { t.Fatalf("reconcileDesktopTaskEventType(%q, retry) = %q, want task_available", kind, got) } } } func TestDesktopTaskRecoveryPayloadPublishLeaseExpiryRequeuesInsteadOfUnknown(t *testing.T) { t.Parallel() payload, reason, message, err := desktopTaskRecoveryPayload(desktopTaskRecoveryModeLeaseExpiry, "publish") if err != nil { t.Fatalf("desktopTaskRecoveryPayload returned error: %v", err) } if reason != "lease_expired" { t.Fatalf("reason = %q, want lease_expired", reason) } if !strings.Contains(message, "re-queued for retry") { t.Fatalf("message = %q, want re-queued for retry", message) } if strings.Contains(message, "unknown") { t.Fatalf("message must not move publish recovery to unknown: %q", message) } if !strings.Contains(string(payload), "desktop_task_lease_expiry") { t.Fatalf("payload missing source: %s", payload) } } func TestDesktopTaskRecoveryPayloadPublishFinalFailsAfterMaxAttempts(t *testing.T) { t.Parallel() payload, _, message, err := desktopTaskRecoveryPayload(desktopTaskRecoveryModeLeaseExpiry, "publish_final") if err != nil { t.Fatalf("desktopTaskRecoveryPayload returned error: %v", err) } if !strings.Contains(message, "marked failed") { t.Fatalf("message = %q, want marked failed", message) } if !strings.Contains(message, "3 attempts") { t.Fatalf("message = %q, want max attempts", message) } if !strings.Contains(string(payload), "marked failed") { t.Fatalf("payload missing final failure message: %s", payload) } } func TestDesktopTaskRecoveryPayloadMonitorLeaseExpiryRequeues(t *testing.T) { t.Parallel() payload, reason, message, err := desktopTaskRecoveryPayload(desktopTaskRecoveryModeLeaseExpiry, "monitor") if err != nil { t.Fatalf("desktopTaskRecoveryPayload returned error: %v", err) } if reason != "lease_expired" { t.Fatalf("reason = %q, want lease_expired", reason) } if !strings.Contains(message, "re-queued") { t.Fatalf("message = %q, want re-queued", message) } if strings.Contains(message, "marked failed") { t.Fatalf("message must not fail expired monitor tasks: %q", message) } if !strings.Contains(string(payload), "desktop_task_lease_expiry") { t.Fatalf("payload missing source: %s", payload) } } func TestReconcileNonRetryPublishesTaskReconciled(t *testing.T) { t.Parallel() if got := reconcileDesktopTaskEventType("publish", "failed"); got != "task_reconciled" { t.Fatalf("reconcileDesktopTaskEventType(publish, failed) = %q, want task_reconciled", got) } } func TestResolvePublishRecoveryOutcome(t *testing.T) { t.Parallel() cases := []struct { name string submitStarted bool attempts int wantStatus string wantKind string }{ {"pre-submit first attempt requeues", false, 1, "queued", "publish"}, {"pre-submit just below cap requeues", false, desktopPublishMaxAttempts - 1, "queued", "publish"}, {"pre-submit at cap fails terminally", false, desktopPublishMaxAttempts, "failed", "publish_final"}, {"pre-submit above cap fails terminally", false, desktopPublishMaxAttempts + 1, "failed", "publish_final"}, // Submit may have happened: never auto-requeue or silently fail — route to unknown // (manual reconcile) regardless of remaining attempts, to avoid duplicate publishing. {"submit started first attempt is uncertain", true, 1, "unknown", "publish_uncertain"}, {"submit started at cap is still uncertain", true, desktopPublishMaxAttempts, "unknown", "publish_uncertain"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { t.Parallel() gotStatus, gotKind := resolvePublishRecoveryOutcome(tc.submitStarted, tc.attempts) if gotStatus != tc.wantStatus || gotKind != tc.wantKind { t.Fatalf("resolvePublishRecoveryOutcome(%v, %d) = (%q, %q), want (%q, %q)", tc.submitStarted, tc.attempts, gotStatus, gotKind, tc.wantStatus, tc.wantKind) } }) } } func TestResolveMonitorRecoveryOutcome(t *testing.T) { t.Parallel() cases := []struct { name string mode desktopTaskRecoveryMode attempts int wantStatus string wantKind string }{ {"startup first attempt requeues", desktopTaskRecoveryModeStartup, 1, "queued", "monitor"}, {"disconnect first attempt requeues", desktopTaskRecoveryModeDisconnect, 1, "queued", "monitor"}, {"startup repeated recovery fails", desktopTaskRecoveryModeStartup, 2, "failed", "monitor_final"}, {"disconnect repeated recovery fails", desktopTaskRecoveryModeDisconnect, 2, "failed", "monitor_final"}, {"lease expiry requeues first attempt", desktopTaskRecoveryModeLeaseExpiry, 1, "queued", "monitor"}, {"lease expiry requeues repeated attempt", desktopTaskRecoveryModeLeaseExpiry, 2, "queued", "monitor"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { t.Parallel() gotStatus, gotKind := resolveMonitorRecoveryOutcome(tc.mode, tc.attempts) if gotStatus != tc.wantStatus || gotKind != tc.wantKind { t.Fatalf("resolveMonitorRecoveryOutcome(%q, %d) = (%q, %q), want (%q, %q)", tc.mode, tc.attempts, gotStatus, gotKind, tc.wantStatus, tc.wantKind) } }) } } func TestNormalizeDesktopTaskCompletionStatusForKindFailsMonitorUnknown(t *testing.T) { t.Parallel() if got := normalizeDesktopTaskCompletionStatusForKind("monitor", "unknown"); got != "failed" { t.Fatalf("monitor completion status = %q, want failed", got) } if got := normalizeDesktopTaskCompletionStatusForKind("publish", "unknown"); got != "unknown" { t.Fatalf("publish completion status = %q, want unknown", got) } } func TestExpiredMonitorDesktopTasksCandidateSQLSelectsOnlyExpiredInProgressMonitorTasks(t *testing.T) { t.Parallel() normalized := normalizeSQLForDesktopTaskTest(expiredMonitorDesktopTasksCandidateSQL()) for _, fragment := range []string{ "t.kind = 'monitor'", "t.status = 'in_progress'", "t.monitor_task_id IS NOT NULL", "t.lease_expires_at IS NOT NULL", "t.lease_expires_at < NOW()", "ORDER BY t.lease_expires_at ASC", "FOR UPDATE OF t SKIP LOCKED", } { if !strings.Contains(normalized, fragment) { t.Fatalf("expired monitor candidate query missing %q: %s", fragment, normalized) } } } func TestRequeueExpiredMonitorDesktopTasksSQLReturnsTaskToQueue(t *testing.T) { t.Parallel() normalized := normalizeSQLForDesktopTaskTest(requeueExpiredMonitorDesktopTasksSQL()) for _, fragment := range []string{ "SET status = 'queued'", "active_attempt_id = NULL", "lease_token_hash = NULL", "lease_expires_at = NULL", "interrupt_reason = COALESCE(interrupt_reason, $3)", "enqueued_at = NOW()", "WHERE desktop_id = ANY($1::uuid[])", "AND kind = 'monitor'", "AND status = 'in_progress'", "RETURNING desktop_id", } { if !strings.Contains(normalized, fragment) { t.Fatalf("expired monitor update query missing %q: %s", fragment, normalized) } } } func TestClassifyLeaseErrorDefersQueuedMonitorTask(t *testing.T) { t.Parallel() task := &repository.DesktopTask{ Kind: "monitor", Status: "queued", } if got := classifyDesktopTaskLeaseState(task); !errors.Is(got, errDesktopTaskLeaseDeferred) { t.Fatalf("classifyDesktopTaskLeaseState() = %v, want deferred", got) } } func TestNormalizeDesktopTaskViewStatusMapsMonitorAndPublishUnknownToFailed(t *testing.T) { t.Parallel() if got := normalizeDesktopTaskViewStatus("monitor", "unknown"); got != "failed" { t.Fatalf("monitor unknown view status = %q, want failed", got) } if got := normalizeDesktopTaskViewStatus("publish", "unknown"); got != "failed" { t.Fatalf("publish unknown view status = %q, want failed", got) } } func TestIsComplianceInvalidArticleVersionError(t *testing.T) { t.Parallel() if !isComplianceInvalidArticleVersionError(response.ErrBadRequest(41005, "invalid_article_version", "article version does not belong to this article")) { t.Fatal("invalid article version compliance errors should be treated as stale publish tasks") } if isComplianceInvalidArticleVersionError(response.ErrServiceUnavailable(41004, "compliance_engine_unavailable", "compliance gate failed closed")) { t.Fatal("compliance engine failures should not be treated as stale publish tasks") } } func TestBuildListPublishTasksByStatusesQueryHidesDeletedPublishRecords(t *testing.T) { t.Parallel() query, args := buildListPublishTasksByStatusesQuery( publishTaskListOwner{TenantID: 7, WorkspaceID: 11, UserID: 23}, []string{"succeeded", "failed"}, "合肥", 10, 20, "t.updated_at DESC", ) normalized := normalizeSQLForDesktopTaskTest(query) for _, fragment := range []string{ "NOT (t.payload ? 'publish_record_id') OR EXISTS", "FROM publish_records pr", "pr.tenant_id = t.tenant_id", "t.payload->>'publish_record_id')::bigint", "pr.deleted_at IS NULL", "LIMIT $6 OFFSET $7", } { if !strings.Contains(normalized, fragment) { t.Fatalf("list publish tasks query missing %q: %s", fragment, normalized) } } if len(args) != 7 { t.Fatalf("args len = %d, want 7", len(args)) } } func TestBuildCountPublishTasksByStatusesQueryHidesDeletedPublishRecords(t *testing.T) { t.Parallel() query, args := buildCountPublishTasksByStatusesQuery( publishTaskListOwner{TenantID: 7, WorkspaceID: 11, UserID: 23}, []string{"succeeded", "failed"}, "", ) normalized := normalizeSQLForDesktopTaskTest(query) for _, fragment := range []string{ "NOT (t.payload ? 'publish_record_id') OR EXISTS", "FROM publish_records pr", "pr.deleted_at IS NULL", } { if !strings.Contains(normalized, fragment) { t.Fatalf("count publish tasks query missing %q: %s", fragment, normalized) } } if len(args) != 5 { t.Fatalf("args len = %d, want 5", len(args)) } } func TestLeaseNextQueuedMonitorTaskSQLUsesAccountIdentityAndClientSlots(t *testing.T) { t.Parallel() normalized := normalizeSQLForDesktopTaskTest(leaseNextQueuedMonitorTaskSQL()) for _, fragment := range []string{ "WITH current_accounts AS", "client_id = $3", "COALESCE(array_length($7::text[], 1), 0) = 0 OR dt.platform_id = ANY($7::text[])", "dt.target_account_id = target_account.desktop_id", "target_account.account_fingerprint", "target_account.platform_uid", "hashtextextended", "pg_try_advisory_xact_lock(account_slot_lock_key)", "SET target_client_id = $3, target_account_id = candidate.lease_account_id", "NOT EXISTS ( SELECT 1 FROM desktop_tasks AS active", "active.platform_id = dt.platform_id", "active.status = 'in_progress'", "active.desktop_id <> dt.desktop_id", "COUNT(DISTINCT active_platform.platform_id)", "active_platform.target_client_id = $3", "< $6::integer", "active_client_platform.target_client_id = $3", "active_client_platform.platform_id = dt.platform_id", "recent.status = 'succeeded'", "recent.updated_at >= now() - interval '2 seconds'", "recent.status IN ('failed', 'unknown', 'aborted')", "recent.updated_at >= now() - interval '30 seconds'", "ORDER BY dt.lane_weight DESC, dt.priority DESC, COALESCE(dt.enqueued_at, dt.created_at) ASC", } { if !strings.Contains(normalized, fragment) { t.Fatalf("monitor lease query missing %q: %s", fragment, normalized) } } } func TestNormalizeDesktopTaskLeasePlatformIDs(t *testing.T) { t.Parallel() got := normalizeDesktopTaskLeasePlatformIDs([]string{" Doubao ", "qwen", "", "DOUBAO"}) want := []string{"doubao", "qwen"} if !reflect.DeepEqual(got, want) { t.Fatalf("normalizeDesktopTaskLeasePlatformIDs() = %#v, want %#v", got, want) } if got := normalizeDesktopTaskLeasePlatformIDs([]string{" ", ""}); got != nil { t.Fatalf("normalizeDesktopTaskLeasePlatformIDs(blank) = %#v, want nil", got) } } func TestLeaseQueuedDesktopTaskByIDSQLUsesAccountIdentityAndClientSlots(t *testing.T) { t.Parallel() normalized := normalizeSQLForDesktopTaskTest(leaseQueuedDesktopTaskByIDSQL()) for _, fragment := range []string{ "WITH current_accounts AS", "client_id = $4", "dt.kind <> 'monitor' OR ca.desktop_id IS NOT NULL", "target_account.account_fingerprint", "target_account.platform_uid", "SELECT dt.desktop_id, dt.kind, ca.desktop_id AS lease_account_id", "locked_candidate AS", "pg_try_advisory_xact_lock(account_slot_lock_key)", "FROM locked_candidate WHERE t.desktop_id = locked_candidate.desktop_id", "target_account_id = CASE WHEN t.kind = 'monitor' THEN locked_candidate.lease_account_id ELSE t.target_account_id END", "dt.kind <> 'monitor' OR NOT EXISTS", "active.platform_id = dt.platform_id", "active.status = 'in_progress'", "active.desktop_id <> dt.desktop_id", "COUNT(DISTINCT active_platform.platform_id)", "active_platform.target_client_id = $4", "< $8::integer", "active_client_platform.target_client_id = $4", "active_client_platform.platform_id = dt.platform_id", "recent.status = 'succeeded'", "recent.updated_at >= now() - interval '2 seconds'", "recent.status IN ('failed', 'unknown', 'aborted')", "recent.updated_at >= now() - interval '30 seconds'", } { if !strings.Contains(normalized, fragment) { t.Fatalf("task-specific lease query missing %q: %s", fragment, normalized) } } } func recoverDesktopTaskSelectColumns(query string) []string { re := regexp.MustCompile(`(?is)select\s+(.*?)\s+from\s+desktop_tasks`) match := re.FindStringSubmatch(query) if len(match) != 2 { return nil } rawColumns := strings.Split(match[1], ",") columns := make([]string, 0, len(rawColumns)) for _, rawColumn := range rawColumns { column := strings.TrimSpace(rawColumn) column = strings.Fields(column)[0] columns = append(columns, column) } return columns } func normalizeSQLForDesktopTaskTest(sql string) string { return strings.Join(strings.Fields(sql), " ") }