-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathposition_test.go
More file actions
190 lines (164 loc) · 5.41 KB
/
Copy pathposition_test.go
File metadata and controls
190 lines (164 loc) · 5.41 KB
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package raml
// TestPositions verifies that the Line stored on every named model element
// matches the 1-based line in the RAML source where that element's NAME
// (the YAML key) appears — not the line where its value body begins.
//
// Run with: go test -run TestPositions -v
import (
"path/filepath"
"strings"
"testing"
)
// posLibraryRAML is a self-contained Library document covering every type and
// property kind that TestPositions inspects. Keep it small and tightly
// formatted so the source lines are obvious at a glance.
const posLibraryRAML = `#%RAML 1.0 Library
types:
Paging:
type: object
properties:
cursor: string
Obj1: object
StringShape:
type: string
ObjectWithPropertiesType:
type: object
properties:
required?:
required: true
type: object
optional:
required: false
type: object
ArrayShape:
type: array
items: string
UnionType:
type: string | number
InlineType: string
A:
properties:
a: string
B:
type: A
properties:
b: string
`
// posAPIRAML is the minimal API document needed to assert endpoint/operation
// positions. It deliberately has no includes so it parses standalone.
const posAPIRAML = `#%RAML 1.0
title: Test API
/:
get:
responses:
200:
`
// splitLines returns the 1-based-indexable lines of a RAML source string.
func splitLines(s string) []string {
return strings.Split(strings.ReplaceAll(s, "\r\n", "\n"), "\n")
}
// parseInlineRAML parses content directly from memory via ParseFromString.
// baseDir is required to be absolute by the parser but is used only to derive
// the document's synthetic file URI — no files are written. The synthetic
// path is returned so callers can look the parsed fragment up via
// GetFragment / GetFragmentTypePtrs.
func parseInlineRAML(t *testing.T, baseDir, fileName, content string) (*RAML, string) {
t.Helper()
r, err := ParseFromString(content, fileName, baseDir, OptWithValidate())
if err != nil {
t.Logf("%s parse warnings: %v", fileName, err)
}
return r, filepath.Join(baseDir, fileName)
}
// checkLine asserts that lines[gotLine-1] contains needle and logs the result.
func checkLine(t *testing.T, lines []string, label string, gotLine int, needle string) {
t.Helper()
if gotLine < 1 || gotLine > len(lines) {
t.Errorf("%-50s Line=%d out of range (file has %d lines)", label, gotLine, len(lines))
return
}
actual := lines[gotLine-1]
if !strings.Contains(actual, needle) {
// find where needle actually is
for i, l := range lines {
if strings.Contains(l, needle) {
t.Errorf("%-50s Line=%d %q — want line %d which has %q", label, gotLine, actual, i+1, needle)
return
}
}
t.Errorf("%-50s Line=%d %q — needle %q not found in file", label, gotLine, actual, needle)
} else {
t.Logf("%-50s Line=%d OK (%q)", label, gotLine, strings.TrimSpace(actual))
}
}
func TestPositions(t *testing.T) {
tmp := t.TempDir()
// ── library ────────────────────────────────────────────────────────────
libRAML, libPath := parseInlineRAML(t, tmp, "library.raml", posLibraryRAML)
libLines := splitLines(posLibraryRAML)
libTypes := libRAML.GetFragmentTypePtrs(libPath)
libChecks := []struct{ name, needle string }{
{"Paging", " Paging:"},
{"Obj1", " Obj1:"},
{"StringShape", " StringShape:"},
{"ObjectWithPropertiesType", " ObjectWithPropertiesType:"},
{"ArrayShape", " ArrayShape:"},
{"UnionType", " UnionType:"},
{"A", " A:"},
{"B", " B:"},
{"InlineType", " InlineType:"},
}
for _, tc := range libChecks {
bs := libTypes[tc.name]
if bs == nil {
t.Errorf("type %q not found in library", tc.name)
continue
}
checkLine(t, libLines, "lib type "+tc.name, bs.KeyPos.Line, tc.needle)
}
// Properties inside ObjectWithPropertiesType
owpt := libTypes["ObjectWithPropertiesType"]
if owpt != nil {
if obj, ok := owpt.Shape.(*ObjectShape); ok {
propChecks := []struct{ name, needle string }{
{"required?", " required?:"},
{"optional", " optional:"},
}
for _, pc := range propChecks {
if p, ok := obj.Properties.Get(pc.name); ok {
checkLine(t, libLines, "lib prop "+pc.name, p.Base.KeyPos.Line, pc.needle)
} else {
t.Errorf("property %q not found", pc.name)
}
}
}
}
// Properties inside Paging
paging := libTypes["Paging"]
if paging != nil {
if obj, ok := paging.Shape.(*ObjectShape); ok {
if p, ok := obj.Properties.Get("cursor"); ok {
checkLine(t, libLines, "lib prop Paging.cursor", p.Base.KeyPos.Line, " cursor:")
}
}
}
// ── api ────────────────────────────────────────────────────────────────
apiRAML, apiPath := parseInlineRAML(t, tmp, "api.raml", posAPIRAML)
apiLines := splitLines(posAPIRAML)
apiFrag, ok := apiRAML.GetFragment(apiPath).(*APIFragment)
if !ok || apiFrag == nil {
t.Fatal("api is not an APIFragment")
}
// Root endpoint
root, ok := apiFrag.EndPoints.Get("/")
if !ok {
t.Fatal("endpoint / not found")
}
checkLine(t, apiLines, "api endpoint /", root.KeyPos.Line, "/:")
// GET operation on /
get, ok := root.Operations.Get("get")
if !ok {
t.Fatal("operation GET / not found")
}
checkLine(t, apiLines, "api operation GET /", get.KeyPos.Line, " get:")
}