diff --git a/internal/creator/competitor_share_jobs.go b/internal/creator/competitor_share_jobs.go index df56085..e499d0a 100644 --- a/internal/creator/competitor_share_jobs.go +++ b/internal/creator/competitor_share_jobs.go @@ -95,8 +95,8 @@ func (s *Store) ListCompetitorShareJobsWithAuthor(ctx context.Context, platform, query, args := competitorShareJobFilter(`SELECT share_job.id, share_job.platform, share_job.share_url, share_job.tags, share_job.status, share_job.attempts, share_job.competitor_id, share_job.failure_reason, share_job.lease_until, share_job.last_attempt_at, share_job.completed_at, share_job.created_at, share_job.updated_at, competitor.nickname, competitor.avatar_url - FROM creator_competitor_share_job share_job - LEFT JOIN creator_competitor ON competitor.id = share_job.competitor_id`, platform, status) + FROM creator_competitor_share_job AS share_job + LEFT JOIN creator_competitor AS competitor ON competitor.id = share_job.competitor_id`, platform, status) rows, err := s.db.QueryContext(ctx, query+` ORDER BY share_job.created_at DESC, share_job.id`, args...) if err != nil { return nil, databaseError(err) @@ -140,11 +140,11 @@ func competitorShareJobFilter(query, platform, status string) (string, []any) { args := make([]any, 0, 2) if platform != "" { args = append(args, platform) - conditions = append(conditions, "creator_competitor_share_job.platform = $"+strconv.Itoa(len(args))) + conditions = append(conditions, "share_job.platform = $"+strconv.Itoa(len(args))) } if status != "" { args = append(args, status) - conditions = append(conditions, "creator_competitor_share_job.status = $"+strconv.Itoa(len(args))) + conditions = append(conditions, "share_job.status = $"+strconv.Itoa(len(args))) } if len(conditions) > 0 { query += ` WHERE ` + strings.Join(conditions, ` AND `) diff --git a/internal/creator/competitor_share_jobs_test.go b/internal/creator/competitor_share_jobs_test.go index 4c606b1..416ac0c 100644 --- a/internal/creator/competitor_share_jobs_test.go +++ b/internal/creator/competitor_share_jobs_test.go @@ -3,6 +3,7 @@ package creator import ( "errors" "testing" + "time" ) func TestNormalizeCompetitorShareJobInput(t *testing.T) { @@ -27,3 +28,81 @@ func TestNormalizeCompetitorShareJobInput(t *testing.T) { } } } + +// 集成测试:ListCompetitorShareJobsWithAuthor 的 SQL(join 别名 + 过滤条件别名)必须真实可执行。 +// 回归背景:LEFT JOIN creator_competitor ON competitor.id=... 缺 AS competitor 别名, +// 过滤条件误用原表名而非 share_job 别名,导入页一进来就 500 "creator operation failed"。 +func TestListCompetitorShareJobsWithAuthorSQL(t *testing.T) { + store, _, ctx := openCreatorIntegrationStore(t) + if err := store.Ping(ctx); err != nil { + t.Fatal(err) + } + if err := store.EnsureSchema(ctx); err != nil { + t.Fatal(err) + } + + job, err := store.CreateCompetitorShareJob(ctx, CompetitorShareJobInput{ + Platform: PlatformDouyin, ShareURL: "https://v.douyin.com/import-author-sql/", + }) + if err != nil { + t.Fatal(err) + } + competitor, err := store.UpsertCompetitor(ctx, CompetitorInput{ + Platform: PlatformDouyin, PlatformAccountKey: "sec_uid_import-author-sql", + Nickname: "解析作者", AvatarURL: "https://example.com/a.jpg", + HomepageURL: "https://www.douyin.com/user/import-author-sql", + }) + if err != nil { + t.Fatal(err) + } + _, token, ok, err := store.ClaimCompetitorShareJob(ctx, job.ID, time.Now().UTC()) + if err != nil || !ok { + t.Fatalf("claim job: ok=%v err=%v", ok, err) + } + if err := store.MarkCompetitorShareJob(ctx, job.ID, token, CompetitorShareJobSucceeded, competitor.ID, ""); err != nil { + t.Fatal(err) + } + if _, err := store.CreateCompetitorShareJob(ctx, CompetitorShareJobInput{ + Platform: PlatformDouyin, ShareURL: "https://v.douyin.com/import-author-sql-other/", + }); err != nil { + t.Fatal(err) + } + + // 无过滤:两条任务都能列出,成功任务带作者信息。 + items, err := store.ListCompetitorShareJobsWithAuthor(ctx, "", "") + if err != nil { + t.Fatalf("list with author: %v", err) + } + if len(items) != 2 { + t.Fatalf("items = %d, want 2", len(items)) + } + var matched bool + for _, item := range items { + if item.ID != job.ID { + continue + } + matched = true + if item.AuthorName != "解析作者" || item.AuthorAvatarURL != "https://example.com/a.jpg" || item.CompetitorID != competitor.ID { + t.Fatalf("author view = %+v", item) + } + } + if !matched { + t.Fatalf("job %s missing in list", job.ID) + } + + // platform+status 过滤:条件引用 join 别名,不能报缺列。 + filtered, err := store.ListCompetitorShareJobsWithAuthor(ctx, PlatformDouyin, CompetitorShareJobSucceeded) + if err != nil { + t.Fatalf("list filtered: %v", err) + } + if len(filtered) != 1 || filtered[0].ID != job.ID { + t.Fatalf("filtered = %+v", filtered) + } + none, err := store.ListCompetitorShareJobsWithAuthor(ctx, PlatformDouyin, CompetitorShareJobFailed) + if err != nil { + t.Fatalf("list filtered empty: %v", err) + } + if len(none) != 0 { + t.Fatalf("expected empty, got %+v", none) + } +}