test: unit tests for the schema builder, crawler and page helpers
- schema: types per kind, @id links, every node kind, the publisher and local business, every validator rule, custom JSON-LD caps and merge - crawl: HTML parse, each check's positive and negative case, fix routes with the fitting tool, the rendered comparison, allowRequest, resolverRules and NewRenderer - pagination, alternates and x-default, primary parents, the filter's addresses, registry paths and fields, auto-link addresses
This commit is contained in:
86
classes/schema/validate_test.go
Normal file
86
classes/schema/validate_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func findings(r Report) string {
|
||||
var out []string
|
||||
for _, f := range r.Findings {
|
||||
out = append(out, f.Level+":"+f.Property)
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
// TestValidateNodeRules pins every rule of the table: the required and
|
||||
// recommended properties per type, the LocalBusiness subtypes checked as
|
||||
// LocalBusiness, and a type without Google rules reported as Rules false
|
||||
// with no findings (RESEARCH Pitfall 11).
|
||||
func TestValidateNodeRules(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
node map[string]any
|
||||
rules bool
|
||||
want string
|
||||
}{
|
||||
{map[string]any{"@type": TypeBlogPosting}, true, "recommended:author recommended:datePublished recommended:dateModified recommended:headline recommended:image"},
|
||||
{map[string]any{"@type": TypeArticle, "headline": "x", "author": []any{map[string]any{"name": "a"}}, "datePublished": "d", "dateModified": "d", "image": []any{"i"}}, true, ""},
|
||||
{map[string]any{"@type": TypeNewsArticle, "headline": " "}, true, "recommended:author recommended:datePublished recommended:dateModified recommended:headline recommended:image"},
|
||||
{map[string]any{"@type": TypeBreadcrumbList, "itemListElement": []any{}}, true, "required:itemListElement"},
|
||||
{map[string]any{"@type": TypeVideoObject, "name": "v"}, true, "required:thumbnailUrl required:uploadDate recommended:description recommended:embedUrl"},
|
||||
{map[string]any{"@type": TypeOrganization, "name": "o", "url": "u", "logo": map[string]any{"url": "l"}}, true, ""},
|
||||
{map[string]any{"@type": TypePerson}, true, "required:name recommended:url recommended:image"},
|
||||
{map[string]any{"@type": TypeWebSite, "url": "u"}, true, "required:name"},
|
||||
{map[string]any{"@type": TypeProfilePage, "mainEntity": map[string]any{}}, true, "required:mainEntity"},
|
||||
{map[string]any{"@type": "Store", "name": "s"}, true, "required:address recommended:telephone recommended:url recommended:geo recommended:openingHoursSpecification recommended:priceRange recommended:image"},
|
||||
{map[string]any{"@type": "FinancialService", "name": "s", "address": map[string]any{"a": 1}, "telephone": "1", "url": "u", "geo": map[string]any{"x": 1}, "openingHoursSpecification": []any{1}, "priceRange": "$", "image": "i"}, true, ""},
|
||||
{map[string]any{"@type": TypeWebPage}, false, ""},
|
||||
{map[string]any{"@type": TypeService}, false, ""},
|
||||
{map[string]any{"@type": "FAQPage"}, false, ""},
|
||||
{map[string]any{"@type": TypeCollectionPage}, false, ""},
|
||||
} {
|
||||
r := ValidateNode(tc.node)
|
||||
if r.Rules != tc.rules || findings(r) != tc.want || r.Findings == nil {
|
||||
t.Errorf("%v: rules=%v findings=%q, want rules=%v %q", tc.node["@type"], r.Rules, findings(r), tc.rules, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestNodeType pins the first @type of a node in each JSON form.
|
||||
func TestNodeType(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
node map[string]any
|
||||
want string
|
||||
}{
|
||||
{map[string]any{"@type": "Article"}, "Article"},
|
||||
{map[string]any{"@type": []any{1, "Store", "LocalBusiness"}}, "Store"},
|
||||
{map[string]any{"@type": []string{"Person"}}, "Person"},
|
||||
{map[string]any{"@type": []string{}}, ""},
|
||||
{map[string]any{}, ""},
|
||||
{map[string]any{"@type": 5}, ""},
|
||||
} {
|
||||
if got := NodeType(tc.node); got != tc.want {
|
||||
t.Errorf("NodeType(%v) = %q, want %q", tc.node, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateGraph pins Validate over a graph: one report per node with
|
||||
// its @id, in order, and Nodes reading both graph forms and skipping
|
||||
// non-objects.
|
||||
func TestValidateGraph(t *testing.T) {
|
||||
g := map[string]any{"@graph": []any{map[string]any{"@type": TypeWebSite, "@id": "w", "name": "n", "url": "u"}, "junk", map[string]any{"@type": TypePerson, "@id": "p"}}}
|
||||
reps := Validate(g)
|
||||
if len(reps) != 2 || reps[0].ID != "w" || len(reps[0].Findings) != 0 || reps[1].ID != "p" || findings(reps[1]) != "required:name recommended:url recommended:image" {
|
||||
t.Fatalf("reports = %+v", reps)
|
||||
}
|
||||
if len(Nodes(map[string]any{"@graph": []map[string]any{{"@id": "a"}}})) != 1 || Nodes(map[string]any{}) != nil {
|
||||
t.Fatal("Nodes is wrong")
|
||||
}
|
||||
if got := Validate(map[string]any{}); got == nil || len(got) != 0 {
|
||||
t.Fatalf("empty graph = %v, want []", got)
|
||||
}
|
||||
if r, rec, ok := RulesFor("Unknown"); ok || r != nil || rec != nil {
|
||||
t.Fatal("an unknown type has no rules")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user