- 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 逃逸)
109 lines
3.6 KiB
Go
109 lines
3.6 KiB
Go
package creator
|
||
|
||
import (
|
||
"errors"
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
func TestNormalizeCompetitorShareJobInput(t *testing.T) {
|
||
job, err := normalizeCompetitorShareJobInput(CompetitorShareJobInput{
|
||
Platform: " douyin ",
|
||
ShareURL: " https://v.douyin.com/share-a/ ",
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if job.Platform != PlatformDouyin || job.ShareURL != "https://v.douyin.com/share-a/" || job.Tags == nil {
|
||
t.Fatalf("normalized job = %+v", job)
|
||
}
|
||
|
||
for _, input := range []CompetitorShareJobInput{
|
||
{Platform: PlatformDouyin, ShareURL: "http://v.douyin.com/share-a/"},
|
||
{Platform: "unsupported", ShareURL: "https://v.douyin.com/share-a/"},
|
||
{Platform: PlatformDouyin, ShareURL: "https://v.douyin.com/share-a/#fragment"},
|
||
} {
|
||
if _, err := normalizeCompetitorShareJobInput(input); !errors.Is(err, ErrInvalid) {
|
||
t.Fatalf("input %+v returned err=%v", input, err)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 集成测试: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)
|
||
}
|
||
}
|