package creator import ( "errors" "os" "path/filepath" "testing" "time" ) func TestWorkCoversUseUIDWorkIDFilesForOwnedAndCompetitor(t *testing.T) { store, accounts, ctx := openCreatorIntegrationStore(t) accountID := createIntegrationAccount(t, ctx, accounts, "cover-files") owned, err := accounts.GetAccount(ctx, accountID) if err != nil { t.Fatal(err) } competitor, err := store.CreateCompetitor(ctx, CompetitorInput{Platform: PlatformDouyin, PlatformAccountKey: "cover-target-uid", Nickname: "封面账号", HomepageURL: "https://www.douyin.com/user/cover-target"}) if err != nil { t.Fatal(err) } for _, source := range []struct{ kind, id, uid string }{{SourceOwned, owned.ID, owned.PlatformAccountKey}, {SourceCompetitor, competitor.ID, competitor.PlatformAccountKey}} { work, _, err := store.UpsertWork(ctx, WorkInput{SourceType: source.kind, SourceID: source.id, Platform: PlatformDouyin, WorkKey: "7312345678901234567-" + source.kind, Title: "封面作品", CoverURL: "https://p3.douyinpic.com/cover"}, time.Now().UTC()) if err != nil { t.Fatal(err) } missing, err := store.ListWorksMissingCover(ctx, source.kind, source.id) if err != nil || len(missing) != 1 { t.Fatalf("missing: %v %v", missing, err) } if _, err := store.GetWorkCover(ctx, work.ID); !errors.Is(err, ErrNotFound) { t.Fatalf("missing cover: %v", err) } if err := store.SaveWorkCover(ctx, work.ID, "image/png", []byte("first-cover")); err != nil { t.Fatal(err) } path, err := store.GetWorkCover(ctx, work.ID) if err != nil { t.Fatal(err) } want := filepath.Join(store.coverDirectory, source.uid, work.WorkKey+".png") if path != want { t.Fatalf("cover path=%q want=%q", path, want) } data, err := os.ReadFile(path) if err != nil || string(data) != "first-cover" { t.Fatalf("file: %q %v", data, err) } missing, err = store.ListWorksMissingCover(ctx, source.kind, source.id) if err != nil || len(missing) != 0 { t.Fatalf("cached work still missing: %v %v", missing, err) } if err := store.SaveWorkCover(ctx, work.ID, "image/webp", []byte("replacement")); err != nil { t.Fatal(err) } if _, err := os.Stat(want); !errors.Is(err, os.ErrNotExist) { t.Fatalf("old extension still exists: %v", err) } path, err = store.GetWorkCover(ctx, work.ID) if err != nil || filepath.Ext(path) != ".webp" { t.Fatalf("replacement path: %q %v", path, err) } data, err = os.ReadFile(path) if err != nil || string(data) != "replacement" { t.Fatalf("replacement: %q %v", data, err) } } var obsoleteTableExists bool if err := store.db.QueryRowContext(ctx, `SELECT to_regclass('creator_work_cover') IS NOT NULL`).Scan(&obsoleteTableExists); err != nil || obsoleteTableExists { t.Fatalf("image-byte table remains: %v %v", obsoleteTableExists, err) } } func TestWorkCoverFilesExposeFailuresAndRejectInvalidNames(t *testing.T) { store, accounts, ctx := openCreatorIntegrationStore(t) accountID := createIntegrationAccount(t, ctx, accounts, "cover-errors") work, _, err := store.UpsertWork(ctx, WorkInput{SourceType: SourceOwned, SourceID: accountID, Platform: PlatformDouyin, WorkKey: "file-errors", CoverURL: "https://p3.douyinpic.com/cover"}, time.Now().UTC()) if err != nil { t.Fatal(err) } for _, item := range []struct { contentType string data []byte }{{"text/html", []byte("wrong")}, {"image/png", nil}, {"image/png", make([]byte, maxWorkCoverBytes+1)}} { if err := store.SaveWorkCover(ctx, work.ID, item.contentType, item.data); !errors.Is(err, ErrInvalid) { t.Fatalf("invalid image accepted: %v", err) } } if _, err := store.GetWorkCover(ctx, "missing-work"); !errors.Is(err, ErrNotFound) { t.Fatalf("unknown work: %v", err) } blocked := filepath.Join(t.TempDir(), "not-a-directory") if err := os.WriteFile(blocked, []byte("blocked"), 0600); err != nil { t.Fatal(err) } store.coverDirectory = blocked if err := store.SaveWorkCover(ctx, work.ID, "image/png", []byte("cover")); err == nil { t.Fatal("filesystem error was swallowed") } if _, err := store.GetWorkCover(ctx, work.ID); err == nil || errors.Is(err, ErrNotFound) { t.Fatalf("filesystem error treated as normal cache miss: %v", err) } bad, _, err := store.UpsertWork(ctx, WorkInput{SourceType: SourceOwned, SourceID: accountID, Platform: PlatformDouyin, WorkKey: "../outside"}, time.Now().UTC()) if err != nil { t.Fatal(err) } if err := store.SaveWorkCover(ctx, bad.ID, "image/png", []byte("cover")); !errors.Is(err, ErrInvalid) { t.Fatalf("invalid work filename accepted: %v", err) } if _, err := store.ListWorksMissingCover(ctx, "wrong-source", accountID); !errors.Is(err, ErrInvalid) { t.Fatalf("invalid source: %v", err) } }