Files
backend_v2/internal/service/manual_article_service_test.go
toom1996 1de1ec46a6 update
2026-10-18 17:42:10 +08:00

137 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"context"
"testing"
"fashionapi/internal/dto"
"fashionapi/internal/model"
"fashionapi/internal/repository"
)
// stubManualIngest 只实现建稿用到的两个方法(其余经嵌入接口转发,本测试不会调用)。
type stubManualIngest struct {
repository.IngestRepository
entityHit bool
created bool
gotRW *model.BrandRunway
gotImgs []model.BrandRunwayImage
}
func (s *stubManualIngest) RunwayEntityState(_ context.Context, _ uint32, _, _ string) (uint32, string, bool, bool, error) {
return 0, "", s.entityHit, false, nil
}
func (s *stubManualIngest) CreateRunwayWithImages(_ context.Context, rw *model.BrandRunway, imgs []model.BrandRunwayImage) (uint32, error) {
s.created, s.gotRW, s.gotImgs = true, rw, imgs
return 99, nil
}
// stubManualBrands 提供品牌精确匹配所需的一行。
type stubManualBrands struct {
repository.BrandRepository
name string
}
func (s *stubManualBrands) ListAdmin(_ context.Context, _ dto.BrandQuery, _ []uint32) ([]model.Brand, int64, error) {
if s.name == "" {
return []model.Brand{}, 0, nil
}
return []model.Brand{{ID: 7, NameEn: s.name}}, 1, nil
}
// stubManualUploader 记录上传的 key,用于断言 sha1 内容寻址与去重。
type stubManualUploader struct{ keys []string }
func (s *stubManualUploader) Upload(_ context.Context, key string, _ []byte, _ string) (string, error) {
s.keys = append(s.keys, key)
return "https://cdn/" + key, nil
}
func (s *stubManualUploader) Enabled() bool { return true }
func manualRunwayInput(images []ManualImage) ManualArticleInput {
return ManualArticleInput{
Kind: manualKindRunway, BrandName: "Gucci", TitleEn: "Spring 2027 RTW",
Year: 2027, Season: "spring", CollectionType: "rtw",
Images: images,
}
}
// 一张最小 JPEG 头,http.DetectContentType 会识别为 image/jpeg。
var fakeJPEG = append([]byte{0xFF, 0xD8, 0xFF, 0xE0}, []byte("manual-test-payload")...)
// TestManualCreateRequiresImages 没有图片必须直接拒绝,且既不上传也不写库。
func TestManualCreateRequiresImages(t *testing.T) {
up := &stubManualUploader{}
ing := &stubManualIngest{}
svc := NewManualArticleService(ing, &stubManualBrands{name: "Gucci"}, up, nil, nil, nil)
if _, err := svc.Create(context.Background(), manualRunwayInput(nil)); err == nil {
t.Fatal("没有图片应报错")
}
if len(up.keys) != 0 || ing.created {
t.Fatalf("校验失败不应上传/写库,实际 uploads=%d created=%v", len(up.keys), ing.created)
}
}
// TestManualCreateBrandNotFound 品牌名匹配不到时必须报错且不写库。
func TestManualCreateBrandNotFound(t *testing.T) {
up := &stubManualUploader{}
ing := &stubManualIngest{}
svc := NewManualArticleService(ing, &stubManualBrands{name: "SomeOtherBrand"}, up, nil, nil, nil)
in := manualRunwayInput([]ManualImage{{Filename: "a.jpg", Data: fakeJPEG}})
if _, err := svc.Create(context.Background(), in); err == nil {
t.Fatal("品牌不存在应报错")
}
if ing.created || len(up.keys) != 0 {
t.Fatalf("品牌不存在不应写库/上传,实际 created=%v uploads=%d", ing.created, len(up.keys))
}
}
// TestManualCreateRunwayUploadsAndInserts 正常提交:sha1 内容寻址、同内容去重、
// 封面取第一张、image_count 与实际行数一致。
func TestManualCreateRunwayUploadsAndInserts(t *testing.T) {
up := &stubManualUploader{}
ing := &stubManualIngest{}
svc := NewManualArticleService(ing, &stubManualBrands{name: "gucci"}, up, nil, nil, nil)
// 两张同内容(应去重成 1 张)+ 一张不同内容 → 共 2 张
other := append([]byte{0xFF, 0xD8, 0xFF, 0xE0}, []byte("another-payload")...)
in := manualRunwayInput([]ManualImage{
{Filename: "a.jpg", Data: fakeJPEG},
{Filename: "a-copy.jpg", Data: fakeJPEG},
{Filename: "b.jpg", Data: other},
})
id, err := svc.Create(context.Background(), in)
if err != nil {
t.Fatalf("正常提交不应报错: %v", err)
}
if id != 99 {
t.Fatalf("应返回仓储给的主键,实际 %d", id)
}
if len(up.keys) != 2 {
t.Fatalf("同内容图片应去重为 2 张上传,实际 %d: %v", len(up.keys), up.keys)
}
for _, k := range up.keys {
if len(k) < len("runway/")+40+len(".jpg") || k[:7] != "runway/" {
t.Fatalf("key 应为 runway/<sha1>.jpg,实际 %q", k)
}
}
if !ing.created {
t.Fatal("应写入正式表")
}
if len(ing.gotImgs) != 2 || ing.gotRW.ImageCount != 2 {
t.Fatalf("图片行数与 image_count 都应等于 2,实际 rows=%d count=%d", len(ing.gotImgs), ing.gotRW.ImageCount)
}
if ing.gotRW.Cover != up.keys[0] {
t.Fatalf("封面应取第一张,实际 cover=%q keys=%v", ing.gotRW.Cover, up.keys)
}
if ing.gotRW.Status != model.StatusPending {
t.Fatalf("新建记录必须是 pending(走审核),实际 %s", ing.gotRW.Status)
}
if ing.gotRW.BrandID != 7 {
t.Fatalf("品牌应按英文名匹配到 id=7(不区分大小写),实际 %d", ing.gotRW.BrandID)
}
}