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) } }