916bdd9c7c
- 新增 category.archive_kind 与 kind_field 字段模板表(迁移 0010) - 种子 electronics 字段模板 + 3C 品类树(手机/笔记本/平板等) - 后端按档案模式动态计算完整度/合格:食品沿用 food_detail, 其它模式走 product.attributes + kind_field - 新增 GET /api/kind-fields?kind= 接口 - 后台编辑页按品类模式动态渲染规格参数表单 - 公开详情页/接口输出带标签的规格表 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
120 lines
3.6 KiB
Go
120 lines
3.6 KiB
Go
package adminstore
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
// contains reports whether s holds v.
|
|
func contains(s []string, v string) bool {
|
|
for _, x := range s {
|
|
if x == v {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// electronicsCategoryID returns the seeded electronics.phone category id,
|
|
// skipping the test when the archive-kind migration is not applied.
|
|
func electronicsCategoryID(t *testing.T, s *Store) string {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
var hasTable bool
|
|
if err := s.pool.QueryRow(ctx, "SELECT to_regclass('public.kind_field') IS NOT NULL").Scan(&hasTable); err != nil || !hasTable {
|
|
t.Skip("archive-kind migration not applied (kind_field missing)")
|
|
}
|
|
var id string
|
|
err := s.pool.QueryRow(ctx, "SELECT id FROM category WHERE path = 'electronics.phone'::ltree").Scan(&id)
|
|
if err != nil {
|
|
t.Skipf("electronics.phone category not seeded: %v", err)
|
|
}
|
|
return id
|
|
}
|
|
|
|
func TestListKindFieldsElectronics(t *testing.T) {
|
|
s := newTestStore(t)
|
|
electronicsCategoryID(t, s) // ensures migration applied
|
|
fields, err := s.ListKindFields(context.Background(), "electronics")
|
|
if err != nil {
|
|
t.Fatalf("list kind fields: %v", err)
|
|
}
|
|
if len(fields) == 0 {
|
|
t.Fatal("expected seeded electronics fields, got none")
|
|
}
|
|
var sawQualified bool
|
|
for _, f := range fields {
|
|
if f.FieldKey == "model_number" && f.Qualified {
|
|
sawQualified = true
|
|
}
|
|
}
|
|
if !sawQualified {
|
|
t.Fatal("expected model_number to be a qualified field")
|
|
}
|
|
}
|
|
|
|
// TestElectronicsArchiveKind verifies a non-food product uses the electronics
|
|
// completeness rules: food fields (nutriments/ingredients) are not required,
|
|
// and qualified spec fields drive both "missing" and the quality score.
|
|
func TestElectronicsArchiveKind(t *testing.T) {
|
|
s := newTestStore(t)
|
|
ctx := context.Background()
|
|
catID := electronicsCategoryID(t, s)
|
|
|
|
created, err := s.CreateProduct(ctx, "tester", ProductInput{
|
|
Name: "测试手机 " + randomHex(6),
|
|
CategoryID: &catID,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create product: %v", err)
|
|
}
|
|
t.Cleanup(func() { _, _ = s.pool.Exec(ctx, "DELETE FROM product WHERE id = $1", created.ID) })
|
|
|
|
if created.ArchiveKind != "electronics" {
|
|
t.Fatalf("archive_kind = %q, want electronics", created.ArchiveKind)
|
|
}
|
|
// Food-only completeness fields must not be required for electronics.
|
|
if contains(created.Missing, "nutriments") || contains(created.Missing, "ingredients") {
|
|
t.Fatalf("electronics product should not require food fields: missing=%v", created.Missing)
|
|
}
|
|
// Qualified spec fields should appear as missing while empty.
|
|
for _, k := range []string{"model_number", "ccc_cert", "screen_size"} {
|
|
if !contains(created.Missing, k) {
|
|
t.Fatalf("expected %q in missing, got %v", k, created.Missing)
|
|
}
|
|
}
|
|
|
|
scoreBefore := created.QualityScore
|
|
|
|
gtin := "69" + randomHex(11)
|
|
brand := "TestPhoneCo"
|
|
updated, err := s.UpdateProduct(ctx, created.ID, "tester", ProductInput{
|
|
Name: created.Name,
|
|
GTIN: >in,
|
|
BrandName: &brand,
|
|
CategoryID: &catID,
|
|
Status: "active",
|
|
Attributes: map[string]any{
|
|
"model_number": "X-100",
|
|
"ccc_cert": "2024010101234567",
|
|
"screen_size": "6.1",
|
|
"color": "黑色",
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("update product: %v", err)
|
|
}
|
|
|
|
if got := updated.Attributes["model_number"]; got != "X-100" {
|
|
t.Fatalf("attributes not persisted: %v", updated.Attributes)
|
|
}
|
|
for _, k := range []string{"model_number", "ccc_cert", "screen_size"} {
|
|
if contains(updated.Missing, k) {
|
|
t.Fatalf("%q should be filled, still missing: %v", k, updated.Missing)
|
|
}
|
|
}
|
|
if updated.QualityScore <= scoreBefore {
|
|
t.Fatalf("quality should rise after filling fields: before=%v after=%v", scoreBefore, updated.QualityScore)
|
|
}
|
|
}
|