fix: the definitions migration keeps and logs every url item

Migration 202610280002 converts the Phase 17 url items through
models.ConvertLegacyItems: a relative path gets its leading slash,
spaces and the bytes " < > \ are percent-encoded, and a malformed item
no longer drops the whole definition. Each url item that cannot be
copied is logged at warn level with its label, value and reason.
This commit is contained in:
Jakub Zych
2026-11-04 23:05:45 +01:00
parent 2200e46d98
commit b37b2265ed
5 changed files with 193 additions and 22 deletions

60
models/legacy_test.go Normal file
View File

@@ -0,0 +1,60 @@
package models_test
import (
"strings"
"testing"
"git.golem15.com/golem15/sm-sitemap-plugin/models"
)
// TestConvertLegacyItems pins L2 WR-04 (D-32, P33-66): every url item the
// Phase 17 sitemap served is copied, a relative path with a leading slash
// and spaces and unsafe bytes percent-encoded; a duplicate is kept once
// and not reported; an item that still is no allowed address, and a
// malformed one, is dropped with its reason while the others are kept;
// items of another type are neither kept nor reported.
func TestConvertLegacyItems(t *testing.T) {
data := `[{"type":"url","url":"o-nas"},` +
`{"type":"url","url":"/blog/zażółć gęślą"},` +
`{"type":"url","url":"/o-nas"},` +
`{"type":"url","url":"https://site.test/x?a=1&b=2"},` +
`{"type":"url","url":["not", "text"]},` +
`{"type":"acme-post","reference":"7"},` +
`{"url":"/a\"b<c>d\\e"},` +
`{"type":"url","url":"https://site.test/kontakt"},` +
`{"type":"url","url":"javascript:alert(1)"}]`
rows, dropped := models.ConvertLegacyItems([]byte(data))
var got []string
for _, r := range rows {
got = append(got, r.URL)
if r.LastMod != "" {
t.Errorf("a copied address has a lastmod: %+v", r)
}
}
want := "/o-nas /blog/zażółć%20gęślą /a%22b%3Cc%3Ed%5Ce https://site.test/kontakt"
if strings.Join(got, " ") != want {
t.Fatalf("rows = %q, want %q", strings.Join(got, " "), want)
}
if len(dropped) != 3 {
t.Fatalf("dropped = %+v, want the & URL, the malformed item and javascript:", dropped)
}
if d := dropped[0]; d.Label != "3" || d.URL != "https://site.test/x?a=1&b=2" || !strings.Contains(d.Reason, "&") {
t.Errorf("dropped & URL = %+v", d)
}
if d := dropped[1]; d.Label != "4" || d.URL != "" || !strings.Contains(d.Reason, "url") {
t.Errorf("dropped malformed item = %+v", d)
}
if d := dropped[2]; d.Label != "8" || d.URL != "javascript:alert(1)" || d.Reason == "" {
t.Errorf("dropped javascript: = %+v", d)
}
if rows, dropped := models.ConvertLegacyItems([]byte(`{"a":{"url":"/x"},"b":{"url":"/x"}}`)); len(rows) != 1 || rows[0].URL != "/x" || len(dropped) != 0 {
t.Errorf("keyed object = %+v %+v", rows, dropped)
}
if rows, dropped := models.ConvertLegacyItems([]byte(`"not a list"`)); len(rows) != 0 || len(dropped) != 1 || dropped[0].Label != "" {
t.Errorf("not a list = %+v %+v", rows, dropped)
}
if rows, dropped := models.ConvertLegacyItems(nil); len(rows) != 0 || dropped != nil {
t.Errorf("empty = %+v %+v", rows, dropped)
}
}