package pact import ( "encoding/json" "reflect" "testing" "time" ) // TestContentKindAndToolValues pins the wire values of the content kinds // and tools: plugins store and compare them as strings, so a value never // changes once released, and each is distinct and lower-case. func TestContentKindAndToolValues(t *testing.T) { kinds := map[string]string{ ContentKindArticle: "article", ContentKindCollection: "collection", ContentKindProfile: "profile", ContentKindPage: "page", } tools := map[string]string{ ContentToolEdit: "edit", ContentToolHeadings: "headings", ContentToolImageAlt: "image_alt", } for _, set := range []map[string]string{kinds, tools} { seen := map[string]bool{} for got, want := range set { if got != want { t.Errorf("constant = %q, want %q", got, want) } if seen[got] { t.Errorf("%q is used twice", got) } seen[got] = true } } if len(kinds) != 4 || len(tools) != 3 { t.Fatalf("%d kinds and %d tools, want 4 and 3 distinct values", len(kinds), len(tools)) } } // TestContentRecordZeroFacts pins the additive contract: a record and a // locale built without the new facts carry their zero values (no date, no // parents or authors, TopHeading 0 for "not known", no images, videos or // headings), and a descriptor without Kind or Tools says nothing. func TestContentRecordZeroFacts(t *testing.T) { rec := ContentRecord{ID: "1", Published: true, Locales: []ContentLocale{{Locale: "en", URL: "/a", Title: "A"}}} if rec.PublishedAt != nil || rec.Parents != nil || rec.Authors != nil || rec.TopHeading != 0 { t.Fatalf("record facts = %+v, want zero", rec) } l := rec.Locales[0] if l.Images != nil || l.Videos != nil || l.Headings != nil { t.Fatalf("locale facts = %+v, want zero", l) } var d ContentDescriptor if d.Kind != "" || d.Tools != nil { t.Fatalf("descriptor = %+v, want no kind and no tools", d) } } // TestContentValueTypes pins the value types' fields: a record with every // fact set round-trips through a copy, and the value types are plain data // (comparable where they hold no slice), so plugins can key maps by a ref. func TestContentValueTypes(t *testing.T) { at := time.Date(2026, 11, 4, 10, 0, 0, 0, time.UTC) rec := ContentRecord{ ID: "7", Published: true, PublishedAt: &at, Parents: []ContentRef{{Type: "acme-category", ID: "3"}}, Authors: []ContentPerson{{Name: "Ada", Ref: ContentRef{Type: "acme-author", ID: "1"}}, {Name: "Guest"}}, TopHeading: 2, Locales: []ContentLocale{{ Locale: "en", URL: "/posts/x", Images: []ContentImage{{URL: "/m/a.jpg", Alt: "A cap", Width: 800, Height: 600}, {URL: "/m/line.png", Decorative: true}}, Videos: []ContentVideo{{Provider: "youtube", ID: "abc"}}, Headings: []ContentHeading{{Level: 2, Text: "Intro"}, {Level: 3, Text: "Detail"}}, }}, } raw, err := json.Marshal(rec) if err != nil { t.Fatal(err) } var back ContentRecord if err := json.Unmarshal(raw, &back); err != nil { t.Fatal(err) } if !reflect.DeepEqual(back.Parents, rec.Parents) || !reflect.DeepEqual(back.Authors, rec.Authors) || back.TopHeading != 2 || !back.PublishedAt.Equal(at) || !reflect.DeepEqual(back.Locales[0].Images, rec.Locales[0].Images) || !reflect.DeepEqual(back.Locales[0].Videos, rec.Locales[0].Videos) || !reflect.DeepEqual(back.Locales[0].Headings, rec.Locales[0].Headings) { t.Fatalf("round trip = %+v, want %+v", back, rec) } refs := map[ContentRef]bool{{Type: "acme-category", ID: "3"}: true} if !refs[rec.Parents[0]] || refs[rec.Authors[0].Ref] { t.Fatal("ContentRef must work as a map key") } if (ContentHeading{Level: 2, Text: "Intro"}) != rec.Locales[0].Headings[0] || (ContentVideo{Provider: "youtube", ID: "abc"}) != rec.Locales[0].Videos[0] { t.Fatal("headings and videos must compare by value") } }