a36700076e
品牌管理:列出品牌及引用商品数,支持改名、合并重复品牌(把源品牌的商品并入目标后删除源)、删除未被引用的品牌。新建商品:商品列表新增「新建商品」入口,填写名称/条码/品牌/品类后创建并进入详情页继续补全。 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
79 lines
2.4 KiB
Go
79 lines
2.4 KiB
Go
package adminstore
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func TestBrandLifecycle(t *testing.T) {
|
|
s := newTestStore(t)
|
|
ctx := context.Background()
|
|
|
|
a, err := s.CreateBrand(ctx, "tester", "品牌A "+randomHex(4))
|
|
if err != nil {
|
|
t.Fatalf("create A: %v", err)
|
|
}
|
|
t.Cleanup(func() { _, _ = s.pool.Exec(ctx, "DELETE FROM brand WHERE id = $1", a.ID) })
|
|
|
|
b, err := s.CreateBrand(ctx, "tester", "品牌B "+randomHex(4))
|
|
if err != nil {
|
|
t.Fatalf("create B: %v", err)
|
|
}
|
|
t.Cleanup(func() { _, _ = s.pool.Exec(ctx, "DELETE FROM brand WHERE id = $1", b.ID) })
|
|
|
|
// Duplicate (normalized) name must be rejected.
|
|
if _, err := s.CreateBrand(ctx, "tester", " "+a.Name+" "); !errors.Is(err, ErrDuplicateBrand) {
|
|
t.Fatalf("expected ErrDuplicateBrand, got %v", err)
|
|
}
|
|
|
|
// Rename.
|
|
renamed, err := s.UpdateBrand(ctx, a.ID, "tester", "品牌A改名")
|
|
if err != nil {
|
|
t.Fatalf("rename: %v", err)
|
|
}
|
|
if renamed.Name != "品牌A改名" {
|
|
t.Fatalf("rename not applied: %q", renamed.Name)
|
|
}
|
|
|
|
// Attach a product to brand A so deletion is blocked and merge moves it.
|
|
var prodID string
|
|
err = s.pool.QueryRow(ctx,
|
|
"INSERT INTO product (name, brand_id, status) VALUES ($1,$2,'active') RETURNING id",
|
|
"测试商品 "+randomHex(4), a.ID).Scan(&prodID)
|
|
if err != nil {
|
|
t.Fatalf("insert product: %v", err)
|
|
}
|
|
t.Cleanup(func() { _, _ = s.pool.Exec(ctx, "DELETE FROM product WHERE id = $1", prodID) })
|
|
|
|
// Deleting an in-use brand must fail.
|
|
if err := s.DeleteBrand(ctx, a.ID, "tester"); !errors.Is(err, ErrBrandInUse) {
|
|
t.Fatalf("expected ErrBrandInUse, got %v", err)
|
|
}
|
|
|
|
// Merge A into B: product reassigned, A deleted.
|
|
merged, err := s.MergeBrands(ctx, a.ID, b.ID, "tester")
|
|
if err != nil {
|
|
t.Fatalf("merge: %v", err)
|
|
}
|
|
if merged.ID != b.ID || merged.ProductCount < 1 {
|
|
t.Fatalf("merge result wrong: %+v", merged)
|
|
}
|
|
if _, err := s.getBrand(ctx, a.ID); !errors.Is(err, ErrNotFound) {
|
|
t.Fatalf("source brand should be gone, got %v", err)
|
|
}
|
|
|
|
// Self-merge is invalid.
|
|
if _, err := s.MergeBrands(ctx, b.ID, b.ID, "tester"); !errors.Is(err, ErrInvalidMerge) {
|
|
t.Fatalf("expected ErrInvalidMerge, got %v", err)
|
|
}
|
|
|
|
// After moving the product away from B, B can be deleted.
|
|
if _, err := s.pool.Exec(ctx, "DELETE FROM product WHERE id = $1", prodID); err != nil {
|
|
t.Fatalf("cleanup product: %v", err)
|
|
}
|
|
if err := s.DeleteBrand(ctx, b.ID, "tester"); err != nil {
|
|
t.Fatalf("delete unused brand: %v", err)
|
|
}
|
|
}
|