fix(creator): 修复导入页列表 SQL —— join 缺别名导致 /accounts/import 一进来就 500
- ListCompetitorShareJobsWithAuthor 的 LEFT JOIN creator_competitor 缺 AS competitor 别名,ON 条件引用 competitor.id 直接报 missing FROM-clause entry(42P01) - competitorShareJobFilter 过滤条件误用原表名 creator_competitor_share_job, 与查询别名 share_job 不符,带 platform/status 参数时会再报缺列 - 新增集成测试 TestListCompetitorShareJobsWithAuthorSQL 覆盖 join 作者回填 与 platform/status 过滤(此前该 SQL 无任何测试,bug 逃逸)
This commit is contained in:
@@ -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 `)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user