~bigbes/sr-ht-spec

ref: f8a7a1743ad9704ff46f9e9319f8cc22b75bbdef sr-ht-spec/doc/front_test.go -rw-r--r-- 5.3 KiB
f8a7a174 — Eugene Blikh feat(graph): wire the proposals read to service.ListProposals (Phase 3) 26 days ago
                                                                                
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package doc

import (
	"strings"
	"testing"

	"sourcecraft.dev/bigbes/sr-ht-spec/core"
)

func TestParseFront(t *testing.T) {
	cases := []struct {
		name       string
		src        string
		wantTitle  string
		wantType   string
		wantStatus core.Status
		wantID     string
		wantTags   []string
		wantProps  int
		wantBody   string
	}{
		{
			name: "full document",
			src: "---\nid: SPEC-0007\ntitle: Proposal storage model\nstatus: draft\n" +
				"type: concept\nsummary: \"Write-optimized storage\"\n" +
				"tags:\n  - databases\n  - storage\n---\n\nA Log-Structured Merge tree.\n",
			wantTitle: "Proposal storage model", wantType: "concept",
			wantStatus: core.StatusDraft, wantID: "SPEC-0007",
			wantTags: []string{"databases", "storage"}, wantProps: 6,
			wantBody: "A Log-Structured Merge tree.\n",
		},
		{
			// Pushed with --push-option=skip-validation, or written by hand.
			name:     "no frontmatter at all",
			src:      "Loading... [13 kB]\n\nProse begins here.\n",
			wantBody: "Loading... [13 kB]\n\nProse begins here.\n",
		},
		{
			// A `**Source:**` bold-label header is body text, not frontmatter,
			// and must be left exactly alone.
			name:     "bold-label header is not frontmatter",
			src:      "**Source:** https://example.com/a\n\nArticle text.\n",
			wantBody: "**Source:** https://example.com/a\n\nArticle text.\n",
		},
		{
			name:     "unterminated block stays body",
			src:      "---\ntitle: broken\n\nno closing fence\n",
			wantBody: "---\ntitle: broken\n\nno closing fence\n",
		},
		{
			name:     "malformed yaml degrades to no properties",
			src:      "---\ntitle: [unclosed\n---\n\nBody.\n",
			wantBody: "Body.\n",
		},
		{
			// core rejects a duplicated key rather than taking the last one:
			// two `id:` lines is exactly the typo that corrupts the registry.
			// On the read path that degrades to an untitled document, not to a
			// document with one of the two ids silently chosen.
			name:     "duplicate key degrades to no properties",
			src:      "---\nid: SPEC-0007\nid: SPEC-0008\ntitle: Two ids\n---\n\nBody.\n",
			wantBody: "Body.\n",
		},
		{
			// A thematic break must not be mistaken for an opening fence.
			name:     "leading thematic break is not frontmatter",
			src:      "---\n\nJust a rule.\n",
			wantBody: "---\n\nJust a rule.\n",
		},
		{
			name:      "explicit yaml end marker closes the block",
			src:       "---\ntitle: Ends early\n...\nBody.\n",
			wantTitle: "Ends early", wantProps: 1,
			wantBody: "Body.\n",
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			front, body := ParseFront([]byte(c.src))
			if front.Title != c.wantTitle {
				t.Errorf("Title = %q, want %q", front.Title, c.wantTitle)
			}
			if front.Type != c.wantType {
				t.Errorf("Type = %q, want %q", front.Type, c.wantType)
			}
			if front.Status != c.wantStatus {
				t.Errorf("Status = %q, want %q", front.Status, c.wantStatus)
			}
			if front.ID != c.wantID {
				t.Errorf("ID = %q, want %q", front.ID, c.wantID)
			}
			if strings.Join(front.Tags, ",") != strings.Join(c.wantTags, ",") {
				t.Errorf("Tags = %v, want %v", front.Tags, c.wantTags)
			}
			if len(front.Props) != c.wantProps {
				t.Errorf("Props = %+v, want %d entries", front.Props, c.wantProps)
			}
			if string(body) != c.wantBody {
				t.Errorf("body = %q, want %q", body, c.wantBody)
			}
		})
	}
}

// The modelled fields must come from core and nowhere else, so that what the
// read plane believes a header says is what the push hook validated.
func TestParseFrontUsesCoreForModelledFields(t *testing.T) {
	src := "---\nid: SPEC-0007\ntitle: T\nstatus: review\nsupersedes: SPEC-0003\n" +
		"owners: [~bigbes]\nparent: \"[[storage]]\"\naliases: [storage-model]\n" +
		"planned: [SPEC-0009]\n---\n\nbody\n"
	front, _ := ParseFront([]byte(src))

	if err := core.DefaultSchema().ValidateFrontmatter(front.Frontmatter); err != nil {
		t.Fatalf("core rejects the frontmatter this package parsed: %v", err)
	}
	if front.Supersedes != "SPEC-0003" || len(front.Owners) != 1 {
		t.Errorf("core fields lost: %+v", front.Frontmatter)
	}
	if !front.Has("parent") {
		t.Errorf("Present must cover every key, including ones core does not model")
	}
	if front.Parent != "[[storage]]" {
		t.Errorf("Parent = %q", front.Parent)
	}
	if strings.Join(front.Aliases, ",") != "storage-model" {
		t.Errorf("Aliases = %v", front.Aliases)
	}
	if strings.Join(front.Planned, ",") != "SPEC-0009" {
		t.Errorf("Planned = %v", front.Planned)
	}
}

func TestFrontSearchTextAndProp(t *testing.T) {
	front, _ := ParseFront([]byte("---\nid: SPEC-0007\ntitle: T\nparent: \"[[storage]]\"\n---\n\nbody\n"))

	if got := front.Prop("Parent"); got != "[[storage]]" {
		t.Errorf("Prop(Parent) = %q", got)
	}
	st := front.SearchText()
	if strings.Contains(st, "[[") {
		t.Errorf("search text kept wikilink brackets: %q", st)
	}
	if !strings.Contains(st, "id: SPEC-0007") {
		t.Errorf("search text missing the id line: %q", st)
	}
}

func TestLinkTarget(t *testing.T) {
	cases := map[string]string{
		`"[[storage]]"`:       "storage",
		`[[storage]]`:         "storage",
		`[[storage|Storage]]`: "storage",
		`  [[specs/index]]  `: "specs/index",
		`storage`:             "storage",
		`"[[a-b]]"`:           "a-b",
		``:                    "",
	}
	for in, want := range cases {
		if got := LinkTarget(in); got != want {
			t.Errorf("LinkTarget(%q) = %q, want %q", in, got, want)
		}
	}
}