feat: publisher, local business and home crumb settings
- Publisher and Local business tabs with the C20 refusals (sameAs lines, person as local business, address, times, country, coordinates) - the publisher node from the settings, as an Organization, a Person or the chosen local business type with address, geo and opening hours - P8 publisher preview with Google's required and recommended findings (classes/schema Validate) - the home crumb name on the General tab; every new key in the settings API and seo_update_settings
This commit is contained in:
@@ -3,6 +3,8 @@ package seo_test
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -365,3 +367,113 @@ func crumbNames(res map[string]any) string {
|
||||
}
|
||||
return strings.Join(names, " > ")
|
||||
}
|
||||
|
||||
// refusal asserts a 422 save answer naming field with message.
|
||||
func refusal(t *testing.T, rec *httptest.ResponseRecorder, field, message string) {
|
||||
t.Helper()
|
||||
body := rec.Body.String()
|
||||
if rec.Code != http.StatusUnprocessableEntity || !strings.Contains(body, `"`+field+`"`) || !strings.Contains(body, message) {
|
||||
t.Fatalf("save = %d %s, want 422 on %s with %q", rec.Code, body, field, message)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPublisherSettings covers the Publisher and Local business tabs
|
||||
// (UI-SPEC C20, D-45, D-03): the saved values, every C20 refusal (422) in
|
||||
// the admin and the API, the local business node and the P8 preview.
|
||||
func TestPublisherSettings(t *testing.T) {
|
||||
env := bootBlog(t)
|
||||
token := env.admin(t, true)
|
||||
fields := env.formSchema(t, token, "golem15/seo/settings")
|
||||
for _, name := range []string{"breadcrumb_home", "publisher_type", "publisher_name", "publisher_logo", "publisher_same_as", "local_enabled", "local_hours", "_publisher_preview"} {
|
||||
findField(t, fields, name)
|
||||
}
|
||||
settings := adminAPI("/golem15/seo/settings/1")
|
||||
env.expect(t, http.StatusOK, http.MethodPut, settings, token, map[string]any{
|
||||
"publisher_type": "organization", "publisher_name": "Acme Sp. z o.o.", "publisher_url": "https://acme.example.test/",
|
||||
"publisher_email": "office@acme.example.test", "publisher_phone": "+48 600 000 000",
|
||||
"publisher_same_as": "https://www.facebook.com/acme\nhttps://pl.wikipedia.org/wiki/Acme",
|
||||
})
|
||||
show := env.expect(t, http.StatusOK, http.MethodGet, "/_seo/api/v1/settings", token, nil).Body.String()
|
||||
for _, want := range []string{`"publisher_name":"Acme Sp. z o.o."`, `"publisher_phone":"+48 600 000 000"`, `"local_enabled":false`} {
|
||||
if !jsonContains(show, want) {
|
||||
t.Fatalf("settings lack %s: %s", want, show)
|
||||
}
|
||||
}
|
||||
|
||||
// P8 without a logo: the node and a recommended-logo warning.
|
||||
preview := env.expect(t, http.StatusOK, http.MethodGet, adminAPI("/golem15/seo/settings/partials/publisher_preview?id=1"), token, nil).Body.String()
|
||||
if !strings.Contains(preview, "Acme Sp. z o.o.") || !strings.Contains(preview, `Missing recommended property “logo” (Organization)`) {
|
||||
t.Fatalf("P8 = %s", preview)
|
||||
}
|
||||
env.expect(t, http.StatusOK, http.MethodPut, settings, token, map[string]any{"publisher_logo": "/media/logo.png"})
|
||||
preview = env.expect(t, http.StatusOK, http.MethodGet, adminAPI("/golem15/seo/settings/partials/publisher_preview?id=1"), token, nil).Body.String()
|
||||
if !strings.Contains(preview, "No problems") || strings.Contains(preview, "Missing") {
|
||||
t.Fatalf("P8 with a logo = %s", preview)
|
||||
}
|
||||
|
||||
// The C20 refusals, in the admin form.
|
||||
refusal(t, env.call(t, http.MethodPut, settings, token, map[string]any{"publisher_same_as": "https://ok.example.test\nx.pl"}), "publisher_same_as", "Line 2: use a full address starting with https://.")
|
||||
refusal(t, env.call(t, http.MethodPut, settings, token, map[string]any{"publisher_same_as": "x.pl"}), "publisher_same_as", "Line 1: use a full address")
|
||||
refusal(t, env.call(t, http.MethodPut, settings, token, map[string]any{"publisher_type": "person", "local_enabled": true, "local_street": "Prosta 1", "local_postal_code": "00-001", "local_city": "Warszawa"}), "local_enabled", "A person cannot be a local business.")
|
||||
local := map[string]any{"publisher_type": "organization", "local_enabled": true, "local_street": "Prosta 1", "local_postal_code": "00-001", "local_city": "Warszawa"}
|
||||
with := func(k string, v any) map[string]any {
|
||||
out := map[string]any{}
|
||||
for key, value := range local {
|
||||
out[key] = value
|
||||
}
|
||||
out[k] = v
|
||||
return out
|
||||
}
|
||||
refusal(t, env.call(t, http.MethodPut, settings, token, with("local_street", "")), "local_street", "A local business needs a street, a postal code and a city.")
|
||||
refusal(t, env.call(t, http.MethodPut, settings, token, with("local_hours", []map[string]any{{"day": "mon", "opens": "9:00", "closes": "17:00"}})), "local_hours.0.opens", "Use 24-hour time, e.g. 09:00.")
|
||||
refusal(t, env.call(t, http.MethodPut, settings, token, with("local_hours", []map[string]any{{"day": "mon", "opens": "17:00", "closes": "09:00"}})), "local_hours.0.closes", "Closing must come after opening.")
|
||||
refusal(t, env.call(t, http.MethodPut, settings, token, with("local_country", "POL")), "local_country", "Use a two-letter country code, e.g. PL.")
|
||||
refusal(t, env.call(t, http.MethodPut, settings, token, with("local_latitude", "91")), "local_latitude", "Enter decimal degrees")
|
||||
// The same checks through the API (MCP seo_update_settings).
|
||||
refusal(t, env.call(t, http.MethodPut, "/_seo/api/v1/settings", token, map[string]any{"local_enabled": true, "publisher_type": "person"}), "local_enabled", "A person cannot be a local business.")
|
||||
refusal(t, env.call(t, http.MethodPut, "/_seo/api/v1/settings", token, map[string]any{"publisher_same_as": "x.pl"}), "publisher_same_as", "Line 1: use a full address")
|
||||
|
||||
// A valid local business: the publisher node becomes its type with
|
||||
// the address, geo, opening hours and price range.
|
||||
env.expect(t, http.StatusOK, http.MethodPut, "/_seo/api/v1/settings", token, map[string]any{
|
||||
"local_enabled": true, "local_type": "FoodEstablishment", "local_street": "Prosta 1", "local_postal_code": "00-001",
|
||||
"local_city": "Warszawa", "local_country": "pl", "local_latitude": "52.2297", "local_longitude": "21.0122",
|
||||
"local_price_range": "$$", "local_hours": []map[string]any{{"day": "mon", "opens": "09:00", "closes": "13:00"}, {"day": "mon", "opens": "14:00", "closes": "18:00"}},
|
||||
})
|
||||
nodes := graphNodes(t, env.resolve(t, "/"))
|
||||
biz := nodes["FoodEstablishment"]
|
||||
if biz == nil || biz["@id"] != siteURL+"/#organization" || biz["priceRange"] != "$$" || biz["telephone"] != "+48 600 000 000" {
|
||||
t.Fatalf("local business node = %v", nodes)
|
||||
}
|
||||
if got := asJSON(t, biz["address"]); got != `{"@type":"PostalAddress","addressCountry":"PL","addressLocality":"Warszawa","postalCode":"00-001","streetAddress":"Prosta 1"}` {
|
||||
t.Fatalf("address = %s", got)
|
||||
}
|
||||
if got := asJSON(t, biz["geo"]); got != `{"@type":"GeoCoordinates","latitude":52.2297,"longitude":21.0122}` {
|
||||
t.Fatalf("geo = %s", got)
|
||||
}
|
||||
if hours, _ := biz["openingHoursSpecification"].([]any); len(hours) != 2 || hours[1].(map[string]any)["opens"] != "14:00" || hours[0].(map[string]any)["dayOfWeek"] != "Monday" {
|
||||
t.Fatalf("hours = %v", biz["openingHoursSpecification"])
|
||||
}
|
||||
if got := asJSON(t, biz["sameAs"]); got != `["https://www.facebook.com/acme","https://pl.wikipedia.org/wiki/Acme"]` {
|
||||
t.Fatalf("sameAs = %s", got)
|
||||
}
|
||||
if biz["url"] != "https://acme.example.test/" || asJSON(t, biz["logo"]) != `{"@id":"https://www.example.test/#logo","@type":"ImageObject","contentUrl":"https://www.example.test/media/logo.png","url":"https://www.example.test/media/logo.png"}` {
|
||||
t.Fatalf("url/logo = %v %v", biz["url"], biz["logo"])
|
||||
}
|
||||
|
||||
// A person publisher: #person, no local business.
|
||||
env.expect(t, http.StatusOK, http.MethodPut, "/_seo/api/v1/settings", token, map[string]any{"local_enabled": false, "publisher_type": "person", "publisher_name": "Jan Kowalski"})
|
||||
nodes = graphNodes(t, env.resolve(t, "/"))
|
||||
if p := nodes["Person"]; p == nil || p["@id"] != siteURL+"/#person" || p["name"] != "Jan Kowalski" || idOf(nodes["WebSite"]["publisher"]) != siteURL+"/#person" {
|
||||
t.Fatalf("person publisher = %v", nodes)
|
||||
}
|
||||
|
||||
// The home crumb name.
|
||||
if got := crumbNames(env.resolve(t, "/blog/solo")); got != "Home > Solo" {
|
||||
t.Fatalf("default home crumb trail = %s", got)
|
||||
}
|
||||
env.expect(t, http.StatusOK, http.MethodPut, settings, token, map[string]any{"breadcrumb_home": "Start"})
|
||||
if got := crumbNames(env.resolve(t, "/blog/solo")); got != "Start > Solo" {
|
||||
t.Fatalf("home crumb trail = %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user