返回 DeepSeek-Reasonix
frontmatter_test.go
根目录 / internal / frontmatter / frontmatter_test.go
1 package frontmatter
2
3 import (
4 "strings"
5 "testing"
6 )
7
8 func TestSplitNoFence(t *testing.T) {
9 fm, body := Split("just body text\nno fence")
10 if len(fm) != 0 {
11 t.Errorf("expected empty fm, got %v", fm)
12 }
13 if !strings.Contains(body, "just body text") {
14 t.Errorf("body = %q", body)
15 }
16 }
17
18 func TestSplitUnclosedFence(t *testing.T) {
19 fm, body := Split("---\nkey: val\n\nno closing fence")
20 if len(fm) != 0 {
21 t.Errorf("unclosed fence should return empty fm, got %v", fm)
22 }
23 if !strings.Contains(body, "---") {
24 t.Errorf("body should contain original content: %q", body)
25 }
26 }
27
28 func TestSplitEmptyBody(t *testing.T) {
29 fm, body := Split("---\nkey: val\n---\n")
30 if fm["key"] != "val" {
31 t.Errorf("key = %q", fm["key"])
32 }
33 if strings.TrimSpace(body) != "" {
34 t.Errorf("expected empty body, got %q", body)
35 }
36 }
37
38 func TestSplitNestedMetadata(t *testing.T) {
39 fm, body := Split("---\nname: test\ndescription: desc\nmetadata:\n type: user\n---\n\nbody here")
40 if fm["name"] != "test" {
41 t.Errorf("name = %q", fm["name"])
42 }
43 if fm["description"] != "desc" {
44 t.Errorf("description = %q", fm["description"])
45 }
46 if fm["type"] != "user" {
47 t.Errorf("type = %q, expected flattened from metadata", fm["type"])
48 }
49 if !strings.Contains(body, "body here") {
50 t.Errorf("body = %q", body)
51 }
52 }
53
54 func TestSplitCRLF(t *testing.T) {
55 fm, body := Split("---\r\nname: test\r\n---\r\nbody\r\n")
56 if fm["name"] != "test" {
57 t.Errorf("name = %q", fm["name"])
58 }
59 if !strings.Contains(body, "body") {
60 t.Errorf("body = %q", body)
61 }
62 }
63
64 func TestSplitQuotedValues(t *testing.T) {
65 fm, _ := Split("---\nname: test\ndescription: \"quoted desc\"\n---\n")
66 if fm["description"] != "quoted desc" {
67 t.Errorf("description should be unquoted: %q", fm["description"])
68 }
69 }
70
71 func TestSplitSingleQuotes(t *testing.T) {
72 fm, _ := Split("---\nname: test\ndescription: 'single quoted'\n---\n")
73 if fm["description"] != "single quoted" {
74 t.Errorf("description should be unquoted: %q", fm["description"])
75 }
76 }
77
78 func TestSplitEmptyInput(t *testing.T) {
79 fm, body := Split("")
80 if len(fm) != 0 {
81 t.Errorf("empty input should return empty fm, got %v", fm)
82 }
83 if body != "" {
84 t.Errorf("body = %q", body)
85 }
86 }
87
88 func TestSplitOnlyFence(t *testing.T) {
89 fm, body := Split("---\n---\n")
90 if len(fm) != 0 {
91 t.Errorf("empty fence should return empty fm, got %v", fm)
92 }
93 if strings.TrimSpace(body) != "" {
94 t.Errorf("body = %q", body)
95 }
96 }
97
98 func TestSplitMultipleKeys(t *testing.T) {
99 fm, _ := Split("---\na: 1\nb: 2\nc: 3\n---\n")
100 if fm["a"] != "1" || fm["b"] != "2" || fm["c"] != "3" {
101 t.Errorf("fm = %v", fm)
102 }
103 }
104
105 func TestSplitCaseInsensitive(t *testing.T) {
106 fm, _ := Split("---\nName: Test\nDESCRIPTION: desc\n---\n")
107 if fm["name"] != "Test" {
108 t.Errorf("name = %q", fm["name"])
109 }
110 if fm["description"] != "desc" {
111 t.Errorf("description = %q", fm["description"])
112 }
113 }
114
115 func TestSplitYAMLScalarsWithColonAndMultiline(t *testing.T) {
116 fm, body := Split("---\n" +
117 "name: test\n" +
118 "description: \"run: with colon\"\n" +
119 "notes: |\n" +
120 " first line\n" +
121 " second line\n" +
122 "---\n" +
123 "body")
124 if fm["description"] != "run: with colon" {
125 t.Fatalf("description = %q", fm["description"])
126 }
127 if fm["notes"] != "first line\nsecond line" {
128 t.Fatalf("notes = %q", fm["notes"])
129 }
130 if body != "body" {
131 t.Fatalf("body = %q", body)
132 }
133 }
134
135 func TestDecodeTypedFrontmatter(t *testing.T) {
136 var meta struct {
137 Name string `yaml:"name"`
138 Description string `yaml:"description"`
139 AllowedTools []string `yaml:"allowed-tools"`
140 }
141 body, err := Decode("---\nname: review\ndescription: \"run: checks\"\nallowed-tools:\n - read_file\n - grep\n---\nbody", &meta, DecodeOptions{KnownFields: true})
142 if err != nil {
143 t.Fatalf("Decode: %v", err)
144 }
145 if meta.Name != "review" || meta.Description != "run: checks" {
146 t.Fatalf("meta = %+v", meta)
147 }
148 if strings.Join(meta.AllowedTools, ",") != "read_file,grep" {
149 t.Fatalf("AllowedTools = %#v", meta.AllowedTools)
150 }
151 if body != "body" {
152 t.Fatalf("body = %q", body)
153 }
154 }
155
156 func TestDecodeKnownFieldsReportsUnknownKey(t *testing.T) {
157 var meta struct {
158 Name string `yaml:"name"`
159 }
160 _, err := Decode("---\nname: ok\nextra: nope\n---\nbody", &meta, DecodeOptions{KnownFields: true})
161 if err == nil {
162 t.Fatal("Decode accepted unknown frontmatter key")
163 }
164 if !strings.Contains(err.Error(), "extra") {
165 t.Fatalf("error = %v, want unknown key name", err)
166 }
167 }
168
169 func TestDecodeReportsMalformedYAML(t *testing.T) {
170 var meta struct {
171 Name string `yaml:"name"`
172 }
173 _, err := Decode("---\nname: [unterminated\n---\nbody", &meta, DecodeOptions{})
174 if err == nil {
175 t.Fatal("Decode accepted malformed YAML")
176 }
177 if !strings.Contains(strings.ToLower(err.Error()), "line") {
178 t.Fatalf("error = %v, want YAML location detail", err)
179 }
180 }
181
181 lines GO