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") } }