1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92 |
package render
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
"github.com/santhosh-tekuri/jsonschema/v6"
)
const schemaBase = "https://git.pablomurad.com/pablo/patchwork/raw/branch/main/schema/v1/"
// TestOutputMatchesSchema validates every JSON file of the tree against
// schema/v1, the contract published for sites that consume the API. The
// schemas are strict about unknown fields, so adding a field without
// documenting it fails here.
func TestOutputMatchesSchema(t *testing.T) {
c := jsonschema.NewCompiler()
c.AssertFormat()
files, err := filepath.Glob(filepath.Join("..", "..", "schema", "v1", "*.json"))
if err != nil || len(files) == 0 {
t.Fatalf("schemas: %v", err)
}
for _, f := range files {
raw, err := os.ReadFile(f)
if err != nil {
t.Fatal(err)
}
doc, err := jsonschema.UnmarshalJSON(bytes.NewReader(raw))
if err != nil {
t.Fatalf("%s: %v", f, err)
}
if err := c.AddResource(schemaBase+filepath.Base(f), doc); err != nil {
t.Fatal(err)
}
}
compile := func(name string) *jsonschema.Schema {
s, err := c.Compile(schemaBase + name)
if err != nil {
t.Fatalf("%s: %v", name, err)
}
return s
}
schemas := map[string]*jsonschema.Schema{
"pages": compile("pages.json"), "page": compile("page.json"),
"version": compile("version.json"), "revisions": compile("revisions.json"),
}
tree := build(t)
checked := 0
for _, p := range tree.Paths() {
var name string
switch {
case p == "api/v1/pages.json":
name = "pages"
case p == "api/v1/revisions.json":
name = "revisions"
case strings.Contains(p, "/versions/"):
name = "version"
case strings.HasPrefix(p, "api/v1/pages/"):
name = "page"
default:
continue
}
inst, err := jsonschema.UnmarshalJSON(bytes.NewReader(tree.Files[p].Body))
if err != nil {
t.Fatalf("%s: %v", p, err)
}
if err := schemas[name].Validate(inst); err != nil {
t.Errorf("%s does not match %s.json:\n%v", p, name, err)
}
checked++
}
if checked < 8 {
t.Fatalf("only %d files checked", checked)
}
// The schemas must actually reject drift.
reject := map[string]string{
"pages": `{"pages":[{"uid":"u","slug":"s","path":"p","title":"t","url":"/s","history_url":"/h","original_date":"2026-01-01","first_seen_at":"2026-01-01T00:00:00Z","revision_count":0,"latest_number":1,"bogus":1}]}`,
"page": `{"uid":"u","slug":"s","path":"p","title":"t","url":"/s","history_url":"/h","original_date":"2026-01-01","first_seen_at":"2026-01-01T00:00:00Z","revision_count":0,"latest_number":1,"versions":[],"bogus":1}`,
"version": `{"page":{"uid":"u","slug":"s","title":"t","url":"/s"},"number":1,"latest_number":1,"published_at":"yesterday","updated_at":"2026-01-01T00:00:00Z","url":"/s","title":"t","body_markdown":"","frontmatter":{},"diff":null}`,
}
for name, body := range reject {
inst, _ := jsonschema.UnmarshalJSON(strings.NewReader(body))
if err := schemas[name].Validate(inst); err == nil {
t.Errorf("%s.json accepted an invalid document", name)
}
}
}
|