code.pablomurad.com

patchwork

Publishes the revisions of your pages as a feed, with notes and readable diffs, written in Go

internal/render/schema_test.go raw
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)
		}
	}
}