package app import ( "context" "fmt" "strings" "testing" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" "github.com/geo-platform/tenant-api/internal/tenant/repository" ) func TestResolveRetryArticleIDFromPayload(t *testing.T) { t.Parallel() cases := []struct { name string payload map[string]any wantID int64 wantOK bool }{ { name: "top level article id", payload: map[string]any{ "article_id": 78, }, wantID: 78, wantOK: true, }, { name: "nested content ref article id", payload: map[string]any{ "content_ref": map[string]any{ "article_id": 79, }, }, wantID: 79, wantOK: true, }, { name: "nested content ref generic id", payload: map[string]any{ "content_ref": map[string]any{ "id": 80, }, }, wantID: 80, wantOK: true, }, { name: "missing article id", payload: map[string]any{ "publish_record_id": 999, }, wantID: 0, wantOK: false, }, } for _, tc := range cases { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() gotID, gotOK := resolveRetryArticleIDFromPayload(tc.payload) if gotID != tc.wantID || gotOK != tc.wantOK { t.Fatalf("resolveRetryArticleIDFromPayload() = (%d, %v), want (%d, %v)", gotID, gotOK, tc.wantID, tc.wantOK) } }) } } func TestParseUniquePublishAccountIDsDedupesInRequestOrder(t *testing.T) { t.Parallel() first := uuid.MustParse("11111111-1111-1111-1111-111111111111") second := uuid.MustParse("22222222-2222-2222-2222-222222222222") got, err := parseUniquePublishAccountIDs([]CreatePublishJobAccountRequest{ {AccountID: " " + first.String() + " "}, {AccountID: second.String()}, {AccountID: first.String()}, }) if err != nil { t.Fatalf("parseUniquePublishAccountIDs() error = %v", err) } if len(got) != 2 || got[0] != first || got[1] != second { t.Fatalf("parseUniquePublishAccountIDs() = %v, want [%s %s]", got, first, second) } } func TestDesktopPublishDedupKeyUsesArticlePlatformAndAccount(t *testing.T) { t.Parallel() accountID := uuid.MustParse("33333333-3333-3333-3333-333333333333") got := desktopPublishDedupKey(7, 11, 897, " sohuhao ", accountID) want := `["publish","7","11","897","sohuhao","33333333-3333-3333-3333-333333333333"]` if got != want { t.Fatalf("desktopPublishDedupKey() = %q, want %q", got, want) } } func TestDesktopPublishDedupKeyEncodesAmbiguousPlatformIDs(t *testing.T) { t.Parallel() accountID := uuid.MustParse("33333333-3333-3333-3333-333333333333") got := desktopPublishDedupKey(7, 11, 897, "sohu:hao", accountID) want := `["publish","7","11","897","sohu:hao","33333333-3333-3333-3333-333333333333"]` if got != want { t.Fatalf("desktopPublishDedupKey() = %q, want %q", got, want) } } func TestDesktopPublishDedupKeysFromTargetsSortsAndDedupes(t *testing.T) { t.Parallel() got := desktopPublishDedupKeysFromTargets([]publishTarget{ {dedupKey: "publish:1:1:2:b:account"}, {dedupKey: "publish:1:1:2:a:account"}, {dedupKey: "publish:1:1:2:b:account"}, {dedupKey: " "}, }) want := []string{"publish:1:1:2:a:account", "publish:1:1:2:b:account"} if len(got) != len(want) { t.Fatalf("desktopPublishDedupKeysFromTargets() length = %d, want %d; got %v", len(got), len(want), got) } for i := range want { if got[i] != want[i] { t.Fatalf("desktopPublishDedupKeysFromTargets()[%d] = %q, want %q; got %v", i, got[i], want[i], got) } } } func TestPublishTargetPlatformsSortsAndDedupes(t *testing.T) { t.Parallel() got := publishTargetPlatforms([]publishTarget{ {accountSeed: &platformAccountSeed{PlatformID: "xiaohongshu"}}, {accountSeed: &platformAccountSeed{PlatformID: " weixin_channels "}}, {accountSeed: &platformAccountSeed{PlatformID: "xiaohongshu"}}, {accountSeed: &platformAccountSeed{PlatformID: " "}}, {}, }) want := []string{"weixin_channels", "xiaohongshu"} if len(got) != len(want) { t.Fatalf("publishTargetPlatforms() length = %d, want %d; got %v", len(got), len(want), got) } for i := range want { if got[i] != want[i] { t.Fatalf("publishTargetPlatforms()[%d] = %q, want %q; got %v", i, got[i], want[i], got) } } } func TestShouldRequeuePublishTaskInsteadOfCreatingNewOnlyForUnknownPublish(t *testing.T) { t.Parallel() if !shouldRequeuePublishTaskInsteadOfCreatingNew(&repository.DesktopTask{ Kind: "publish", Status: "unknown", Error: []byte(`{"publish_submit_uncertain":true,"message":"network interrupted after submit"}`), }) { t.Fatal("submit-uncertain unknown publish tasks should be retried by requeueing the existing task") } if shouldRequeuePublishTaskInsteadOfCreatingNew(&repository.DesktopTask{ Kind: "publish", Status: "unknown", Error: []byte(`{"error_code":403,"error_msg":"CSRF token invalid"}`), }) { t.Fatal("definitive failed unknown publish tasks should create a new retry record") } if shouldRequeuePublishTaskInsteadOfCreatingNew(&repository.DesktopTask{ Kind: "publish", Status: "failed", }) { t.Fatal("failed publish tasks should keep create-new retry semantics") } if shouldRequeuePublishTaskInsteadOfCreatingNew(&repository.DesktopTask{ Kind: "monitor", Status: "unknown", }) { t.Fatal("monitor retry semantics are owned by desktop task reconcile") } if shouldRequeuePublishTaskInsteadOfCreatingNew(nil) { t.Fatal("nil tasks should not be requeued") } } func TestCreatePublishJobResponseForRequeuedTaskReportsRequeuedTaskID(t *testing.T) { t.Parallel() taskID := uuid.MustParse("55555555-5555-5555-5555-555555555555") got := createPublishJobResponseForRequeuedTask(&repository.DesktopTask{DesktopID: taskID}) if got.JobID != "" { t.Fatalf("JobID = %q, want empty for requeued task", got.JobID) } if len(got.TaskIDs) != 1 || got.TaskIDs[0] != taskID.String() { t.Fatalf("TaskIDs = %v, want [%s]", got.TaskIDs, taskID) } if len(got.CreatedTaskIDs) != 0 { t.Fatalf("CreatedTaskIDs = %v, want empty", got.CreatedTaskIDs) } if len(got.ExistingTaskIDs) != 0 { t.Fatalf("ExistingTaskIDs = %v, want empty", got.ExistingTaskIDs) } if len(got.RequeuedTaskIDs) != 1 || got.RequeuedTaskIDs[0] != taskID.String() { t.Fatalf("RequeuedTaskIDs = %v, want [%s]", got.RequeuedTaskIDs, taskID) } } func TestPublishUnknownTaskHasDefinitiveFailureReleasesCSRF403(t *testing.T) { t.Parallel() task := &repository.DesktopTask{ Kind: "publish", Status: "unknown", Error: []byte(`{"error_code":403,"error_msg":"CSRF token invalid"}`), } if !publishUnknownTaskHasDefinitiveFailure(task) { t.Fatal("unknown publish task with CSRF 403 should be treated as definitive failed") } } func TestPublishUnknownTaskHasDefinitiveFailureKeepsSubmitUncertain(t *testing.T) { t.Parallel() task := &repository.DesktopTask{ Kind: "publish", Status: "unknown", Error: []byte(`{"publish_submit_uncertain":true,"message":"network interrupted after submit"}`), } if publishUnknownTaskHasDefinitiveFailure(task) { t.Fatal("submit-uncertain unknown publish task must stay in dedup set") } } func TestLoadExistingPublishRecordTargetsDedupeUnknownTasks(t *testing.T) { t.Parallel() tx := &capturePublishDedupLookupTx{rows: emptyPublishDedupRows{}} accountID := uuid.MustParse("44444444-4444-4444-4444-444444444444") targets := []publishTarget{ {accountSeed: &platformAccountSeed{ID: 77}, account: &repository.DesktopAccount{DesktopID: accountID}}, } _, err := loadExistingPublishRecordTargets(context.Background(), tx, 1, 2, 3, targets) if err != nil { t.Fatalf("loadExistingPublishRecordTargets() error = %v", err) } normalizedSQL := normalizeSQLWhitespace(tx.sql) if !strings.Contains(normalizedSQL, "dt.status IN ('queued', 'in_progress', 'succeeded', 'unknown')") { t.Fatalf("publish dedup lookup should include unknown task status; query:\n%s", tx.sql) } if !strings.Contains(normalizedSQL, "OR dt.status = 'unknown'") { t.Fatalf("publish dedup lookup should include unknown tasks even when publish record is failed; query:\n%s", tx.sql) } if !strings.Contains(normalizedSQL, "AND dt.tenant_id = $1") { t.Fatalf("publish dedup lookup should constrain desktop_tasks by tenant for the payload index; query:\n%s", tx.sql) } if strings.Contains(normalizedSQL, "desktop_publish_jobs") { t.Fatalf("publish dedup lookup should not load unused publish job metadata; query:\n%s", tx.sql) } } func TestLockDesktopPublishDedupKeysUsesSingleArrayQuery(t *testing.T) { t.Parallel() tx := &capturePublishDedupLookupTx{} err := lockDesktopPublishDedupKeys(context.Background(), tx, []string{"a", "b"}) if err != nil { t.Fatalf("lockDesktopPublishDedupKeys() error = %v", err) } if tx.execCount != 1 { t.Fatalf("lockDesktopPublishDedupKeys() exec count = %d, want 1", tx.execCount) } if !strings.Contains(tx.execSQL, "unnest($1::text[])") { t.Fatalf("lockDesktopPublishDedupKeys() should use array unnest query; query:\n%s", tx.execSQL) } gotKeys, ok := tx.execArgs[0].([]string) if !ok || len(gotKeys) != 2 || gotKeys[0] != "a" || gotKeys[1] != "b" { t.Fatalf("lockDesktopPublishDedupKeys() args = %#v, want [a b]", tx.execArgs) } } func TestLockDesktopPublishDedupKeysSkipsEmptyList(t *testing.T) { t.Parallel() tx := &capturePublishDedupLookupTx{} err := lockDesktopPublishDedupKeys(context.Background(), tx, nil) if err != nil { t.Fatalf("lockDesktopPublishDedupKeys() error = %v", err) } if tx.execCount != 0 { t.Fatalf("lockDesktopPublishDedupKeys() exec count = %d, want 0", tx.execCount) } } func TestIsPostgresUniqueViolationMatchesConstraint(t *testing.T) { t.Parallel() err := fmt.Errorf("wrapped: %w", &pgconn.PgError{ Code: "23505", ConstraintName: desktopPublishDedupActiveConstraint, }) if !isPostgresUniqueViolation(err, desktopPublishDedupActiveConstraint) { t.Fatal("isPostgresUniqueViolation() should match the dedup constraint") } if isPostgresUniqueViolation(&pgconn.PgError{Code: "23505", ConstraintName: "desktop_tasks_desktop_id_key"}, desktopPublishDedupActiveConstraint) { t.Fatal("isPostgresUniqueViolation() should ignore other unique constraints") } if isPostgresUniqueViolation(&pgconn.PgError{Code: "23503", ConstraintName: desktopPublishDedupActiveConstraint}, desktopPublishDedupActiveConstraint) { t.Fatal("isPostgresUniqueViolation() should require SQLSTATE 23505") } } type capturePublishDedupLookupTx struct { sql string args []any execSQL string execArgs []any execCount int rows pgx.Rows } func (tx *capturePublishDedupLookupTx) Begin(context.Context) (pgx.Tx, error) { return nil, nil } func (tx *capturePublishDedupLookupTx) Commit(context.Context) error { return nil } func (tx *capturePublishDedupLookupTx) Rollback(context.Context) error { return nil } func (tx *capturePublishDedupLookupTx) CopyFrom(context.Context, pgx.Identifier, []string, pgx.CopyFromSource) (int64, error) { return 0, nil } func (tx *capturePublishDedupLookupTx) SendBatch(context.Context, *pgx.Batch) pgx.BatchResults { return nil } func (tx *capturePublishDedupLookupTx) LargeObjects() pgx.LargeObjects { return pgx.LargeObjects{} } func (tx *capturePublishDedupLookupTx) Prepare(context.Context, string, string) (*pgconn.StatementDescription, error) { return nil, nil } func (tx *capturePublishDedupLookupTx) Exec(_ context.Context, sql string, arguments ...any) (pgconn.CommandTag, error) { tx.execCount++ tx.execSQL = sql tx.execArgs = arguments return pgconn.CommandTag{}, nil } func (tx *capturePublishDedupLookupTx) Query(_ context.Context, sql string, args ...any) (pgx.Rows, error) { tx.sql = sql tx.args = args return tx.rows, nil } func (tx *capturePublishDedupLookupTx) QueryRow(context.Context, string, ...any) pgx.Row { return nil } func (tx *capturePublishDedupLookupTx) Conn() *pgx.Conn { return nil } type emptyPublishDedupRows struct{} func (r emptyPublishDedupRows) Close() {} func (r emptyPublishDedupRows) Err() error { return nil } func (r emptyPublishDedupRows) CommandTag() pgconn.CommandTag { return pgconn.CommandTag{} } func (r emptyPublishDedupRows) FieldDescriptions() []pgconn.FieldDescription { return nil } func (r emptyPublishDedupRows) Next() bool { return false } func (r emptyPublishDedupRows) Scan(...any) error { return nil } func (r emptyPublishDedupRows) Values() ([]any, error) { return nil, nil } func (r emptyPublishDedupRows) RawValues() [][]byte { return nil } func (r emptyPublishDedupRows) Conn() *pgx.Conn { return nil } func normalizeSQLWhitespace(sql string) string { return strings.Join(strings.Fields(sql), " ") }