package feeds import ( "strings" "testing" ) func TestParseSpecificationsA1BrokenClosers(t *testing.T) { raw := "" 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 := `` 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 := `` 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{"", " ", "", "", ""} { 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 := ` 5901234123457 Washer Janus A 8 kg 72 ` 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 := ` 111
  • Color: Blue
  • Finish: Matte
  • ]]>
    ` 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 := ` 222 Width: 10; Height: 20 333 ]]> ` 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": ``, "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(` 1 RedM `) 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) } }