Files
descrybe/apps/api/internal/feeds/specs_test.go
T

281 lines
7.4 KiB
Go
Raw Normal View History

package feeds
import (
"strings"
"testing"
)
func TestParseSpecificationsA1BrokenClosers(t *testing.T) {
raw := "<ul><li>Energijski razred: E</><li>Dimenzije: 605x95x395 </></ul>"
pairs := ParseSpecifications(raw)
if len(pairs) != 2 {
t.Fatalf("got %d pairs: %#v", len(pairs), pairs)
}
if pairs[0].Label != "Energijski razred" || pairs[0].Value != "E" {
t.Fatalf("first=%#v", pairs[0])
}
if pairs[1].Label != "Dimenzije" || pairs[1].Value != "605x95x395" {
t.Fatalf("second=%#v", pairs[1])
}
m := specsToMap(pairs)
if m["energy_class"] != "E" || m["dimenzije"] != "605x95x395" {
t.Fatalf("map=%#v", m)
}
}
func TestAttributeKeyFromLabel(t *testing.T) {
if got := AttributeKeyFromLabel("Energijski razred"); got != "energijski-razred" {
t.Fatalf("got %q", got)
}
if got := AttributeKeyFromLabel("Širina"); got != "sirina" {
t.Fatalf("got %q", got)
}
}
func TestCanonicalAttributeKey(t *testing.T) {
cases := map[string]string{
"Visina": "net_height",
"netheight": "net_height",
"net_height": "net_height",
"Širina": "net_width",
"Globina": "net_depth",
"netMass": "net_mass",
"Teža": "net_mass",
"Energijski razred": "energy_class",
":": "",
"true": "",
"": "",
}
for in, want := range cases {
if got := CanonicalAttributeKey(in); got != want {
t.Fatalf("%q → %q want %q", in, got, want)
}
}
}
func TestParseSpecificationsSkipsBareListItems(t *testing.T) {
raw := `<ul><li>:</li><li>Color: Red</li><li>Waterproof</li></ul>`
pairs := ParseSpecifications(raw)
if len(pairs) != 1 || pairs[0].Label != "Color" || pairs[0].Value != "Red" {
t.Fatalf("got %#v", pairs)
}
m := specsToMap(pairs)
if _, ok := m[":"]; ok {
t.Fatalf("junk key present: %#v", m)
}
if m["color"] != "Red" {
t.Fatalf("map=%#v", m)
}
}
func TestParseSpecificationsHTML(t *testing.T) {
raw := `<ul><li>Color: Red</li><li>Size: Large</li><li>Material: Cotton</li></ul>`
pairs := ParseSpecifications(raw)
if len(pairs) != 3 {
t.Fatalf("got %d pairs: %#v", len(pairs), pairs)
}
if pairs[0].Label != "Color" || pairs[0].Value != "Red" {
t.Fatalf("first=%#v", pairs[0])
}
}
func TestParseSpecificationsHTMLEmpty(t *testing.T) {
for _, raw := range []string{"", " ", "<ul></ul>", "<ul><li></li></ul>", "<![CDATA[]]>"} {
if got := ParseSpecifications(raw); got != nil {
t.Fatalf("raw=%q got %#v", raw, got)
}
}
}
func TestParseSpecificationsFlat(t *testing.T) {
raw := "Color: Red; Size: L; Weight: 1.2 kg"
pairs := ParseSpecifications(raw)
if len(pairs) != 3 {
t.Fatalf("got %d: %#v", len(pairs), pairs)
}
pipe := ParseSpecifications("Voltage: 230V | Frequency: 50Hz")
if len(pipe) != 2 {
t.Fatalf("pipe=%#v", pipe)
}
nl := ParseSpecifications("Width: 10\nHeight: 20")
if len(nl) != 2 {
t.Fatalf("nl=%#v", nl)
}
}
func TestParseSpecificationsCSVLike(t *testing.T) {
raw := `"Brand","Acme","Model","X1"`
// Single chunk without strong separators — treat as one line; may not split.
// Use semicolon CSV-ish pairs:
raw = `"Brand","Acme"; "Model","X1"`
pairs := ParseSpecifications(raw)
if len(pairs) < 2 {
t.Fatalf("got %#v", pairs)
}
}
func TestExpandSpecificationFieldsNestedXML(t *testing.T) {
xmlBody := `<?xml version="1.0"?>
<Export>
<Item>
<EAN>5901234123457</EAN>
<name>Washer</name>
<brand>Janus</brand>
<specifications>
<EnergyClass>A</EnergyClass>
<Capacity>8 kg</Capacity>
</specifications>
<EPRELID></EPRELID>
<netMass>72</netMass>
</Item>
</Export>`
var row feedRow
n, err := parseXMLItems(strings.NewReader(xmlBody), "Item", func(r feedRow) error {
row = r
return nil
})
if err != nil {
t.Fatal(err)
}
if n != 1 {
t.Fatalf("n=%d", n)
}
if row["specifications/EnergyClass"] != "A" {
t.Fatalf("nested energy=%q row=%v", row["specifications/EnergyClass"], row)
}
if row["specifications/Capacity"] != "8 kg" {
t.Fatalf("capacity=%q", row["specifications/Capacity"])
}
v, ok := lookupRow(row, "specifications/EnergyClass")
if !ok || v != "A" {
t.Fatalf("lookup nested: ok=%v v=%q", ok, v)
}
}
func TestExpandSpecificationFieldsCDATA(t *testing.T) {
xmlBody := `<?xml version="1.0"?>
<products>
<item>
<gtin>111</gtin>
<specifications><![CDATA[<ul><li>Color: Blue</li><li>Finish: Matte</li></ul>]]></specifications>
</item>
</products>`
var row feedRow
_, err := parseXMLItems(strings.NewReader(xmlBody), "item", func(r feedRow) error {
row = r
return nil
})
if err != nil {
t.Fatal(err)
}
if row["specifications/Color"] != "Blue" {
t.Fatalf("color=%q row=%v", row["specifications/Color"], row)
}
if row["specifications/Finish"] != "Matte" {
t.Fatalf("finish=%q", row["specifications/Finish"])
}
}
func TestExpandSpecificationFieldsFlatAndEmpty(t *testing.T) {
xmlBody := `<?xml version="1.0"?>
<products>
<item>
<ean>222</ean>
<specifications>Width: 10; Height: 20</specifications>
</item>
<item>
<ean>333</ean>
<specifications><![CDATA[<ul></ul>]]></specifications>
</item>
</products>`
var rows []feedRow
_, err := parseXMLItems(strings.NewReader(xmlBody), "item", func(r feedRow) error {
cp := make(feedRow, len(r))
for k, v := range r {
cp[k] = v
}
rows = append(rows, cp)
return nil
})
if err != nil {
t.Fatal(err)
}
if len(rows) != 2 {
t.Fatalf("rows=%d", len(rows))
}
if rows[0]["specifications/Width"] != "10" {
t.Fatalf("width=%q", rows[0]["specifications/Width"])
}
// Empty HTML must not invent children.
for k := range rows[1] {
if strings.HasPrefix(k, "specifications/") {
t.Fatalf("unexpected child %q", k)
}
}
}
func TestApplyMappingsSpecificationsObject(t *testing.T) {
row := feedRow{
"EAN": "5901234123457",
"specifications": `<ul><li>Color: Red</li></ul>`,
"specifications/Color": "Red",
"specifications/EnergyClass": "B",
}
expandSpecificationFields(row)
mappings := parseMappings([]any{
map[string]any{"source": "EAN", "target": "gtin"},
map[string]any{"source": "specifications", "target": "specifications"},
map[string]any{"source": "specifications/EnergyClass", "target": "energy_class"},
})
mapped, gtin := applyMappings(row, mappings)
if gtin != "5901234123457" {
t.Fatalf("gtin=%q", gtin)
}
specs, ok := mapped["specifications"].(map[string]string)
if !ok || specs["color"] != "Red" {
t.Fatalf("specs=%#v", mapped["specifications"])
}
if mapped["energy_class"] != "B" {
t.Fatalf("energy=%v", mapped["energy_class"])
}
}
func TestExtractXMLSchemaNestedSpecs(t *testing.T) {
data := []byte(`<?xml version="1.0"?>
<Export><Item>
<EAN>1</EAN>
<specifications><Color>Red</Color><Size>M</Size></specifications>
</Item></Export>`)
fields, rows, err := extractXMLSchema(data, "Item")
if err != nil {
t.Fatal(err)
}
if rows != 1 {
t.Fatalf("rows=%d", rows)
}
paths := map[string]bool{}
for _, f := range fields {
paths[f.Path] = true
}
if !paths["specifications/Color"] || !paths["specifications/Size"] {
t.Fatalf("missing nested paths: %#v", paths)
}
// Bare Color leaf should be suppressed when nested exists.
if paths["Color"] {
t.Fatalf("bare Color should be dropped: %#v", paths)
}
}
func TestLookupRowNestedPrefersPath(t *testing.T) {
row := feedRow{
"name": "Product",
"specifications": "ignored",
"specifications/foo": "nested",
"other/foo": "other",
}
v, ok := lookupRow(row, "specifications/foo")
if !ok || v != "nested" {
t.Fatalf("got ok=%v v=%q", ok, v)
}
}