-
Notifications
You must be signed in to change notification settings - Fork 50
/
parser.go
255 lines (207 loc) · 6.78 KB
/
parser.go
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
package yaml_file
import (
"bytes"
"fmt"
"io/ioutil"
"regexp"
"strings"
"text/template"
"github.com/lamoda/gonkey/models"
"gopkg.in/yaml.v2"
)
const (
gonkeyVariableLeftPart = "{{ $"
gonkeyProtectSubstitute = "!protect!"
)
var gonkeyProtectTemplate = regexp.MustCompile(`{{\s*\$`)
func parseTestDefinitionFile(absPath string) ([]Test, error) {
data, err := ioutil.ReadFile(absPath)
if err != nil {
return nil, fmt.Errorf("failed to read file %s:\n%s", absPath, err)
}
var testDefinitions []TestDefinition
// reading the test source file
if err := yaml.Unmarshal(data, &testDefinitions); err != nil {
return nil, fmt.Errorf("failed to unmarshall %s:\n%s", absPath, err)
}
var tests []Test
for i := range testDefinitions {
testCases, err := makeTestFromDefinition(absPath, testDefinitions[i])
if err != nil {
return nil, err
}
tests = append(tests, testCases...)
}
return tests, nil
}
func substituteArgs(tmpl string, args map[string]interface{}) (string, error) {
tmpl = gonkeyProtectTemplate.ReplaceAllString(tmpl, gonkeyProtectSubstitute)
compiledTmpl, err := template.New("").Parse(tmpl)
if err != nil {
return "", err
}
buf := &bytes.Buffer{}
if err := compiledTmpl.Execute(buf, args); err != nil {
return "", err
}
tmpl = strings.ReplaceAll(buf.String(), gonkeyProtectSubstitute, gonkeyVariableLeftPart)
return tmpl, nil
}
func substituteArgsToMap(tmpl map[string]string, args map[string]interface{}) (map[string]string, error) {
res := make(map[string]string)
for key, value := range tmpl {
var err error
res[key], err = substituteArgs(value, args)
if err != nil {
return nil, err
}
}
return res, nil
}
// Make tests from the given test definition.
func makeTestFromDefinition(filePath string, testDefinition TestDefinition) ([]Test, error) {
var tests []Test
// test definition has no cases, so using request/response as is
if len(testDefinition.Cases) == 0 {
test := Test{TestDefinition: testDefinition, Filename: filePath}
test.Description = testDefinition.Description
test.Request = testDefinition.RequestTmpl
test.Responses = testDefinition.ResponseTmpls
test.ResponseHeaders = testDefinition.ResponseHeaders
test.BeforeScript = testDefinition.BeforeScriptParams.PathTmpl
test.AfterRequestScript = testDefinition.AfterRequestScriptParams.PathTmpl
test.DbQuery = testDefinition.DbQueryTmpl
test.DbResponse = testDefinition.DbResponseTmpl
test.CombinedVariables = testDefinition.Variables
dbChecks := []models.DatabaseCheck{}
for _, check := range testDefinition.DatabaseChecks {
dbChecks = append(dbChecks, &dbCheck{query: check.DbQueryTmpl, response: check.DbResponseTmpl})
}
test.DbChecks = dbChecks
return append(tests, test), nil
}
var err error
requestTmpl := testDefinition.RequestTmpl
beforeScriptPathTmpl := testDefinition.BeforeScriptParams.PathTmpl
afterRequestScriptPathTmpl := testDefinition.AfterRequestScriptParams.PathTmpl
requestURLTmpl := testDefinition.RequestURL
queryParamsTmpl := testDefinition.QueryParams
headersValTmpl := testDefinition.HeadersVal
cookiesValTmpl := testDefinition.CookiesVal
responseHeadersTmpl := testDefinition.ResponseHeaders
combinedVariables := map[string]string{}
if testDefinition.Variables != nil {
combinedVariables = testDefinition.Variables
}
// produce as many tests as cases defined
for caseIdx, testCase := range testDefinition.Cases {
test := Test{TestDefinition: testDefinition, Filename: filePath}
test.Name = fmt.Sprintf("%s #%d", test.Name, caseIdx+1)
if testCase.Description != "" {
test.Description = testCase.Description
}
// substitute RequestArgs to different parts of request
test.RequestURL, err = substituteArgs(requestURLTmpl, testCase.RequestArgs)
if err != nil {
return nil, err
}
test.Request, err = substituteArgs(requestTmpl, testCase.RequestArgs)
if err != nil {
return nil, err
}
test.QueryParams, err = substituteArgs(queryParamsTmpl, testCase.RequestArgs)
if err != nil {
return nil, err
}
test.HeadersVal, err = substituteArgsToMap(headersValTmpl, testCase.RequestArgs)
if err != nil {
return nil, err
}
test.CookiesVal, err = substituteArgsToMap(cookiesValTmpl, testCase.RequestArgs)
if err != nil {
return nil, err
}
// substitute ResponseArgs to different parts of response
test.Responses = make(map[int]string)
for status, tpl := range testDefinition.ResponseTmpls {
args, ok := testCase.ResponseArgs[status]
if ok {
// found args for response status
test.Responses[status], err = substituteArgs(tpl, args)
if err != nil {
return nil, err
}
} else {
// not found args, using response as is
test.Responses[status] = tpl
}
}
test.ResponseHeaders = make(map[int]map[string]string)
for status, respHeaders := range responseHeadersTmpl {
args, ok := testCase.ResponseArgs[status]
if ok {
// found args for response status
test.ResponseHeaders[status], err = substituteArgsToMap(respHeaders, args)
if err != nil {
return nil, err
}
} else {
// not found args, using response as is
test.ResponseHeaders[status] = respHeaders
}
}
test.BeforeScript, err = substituteArgs(beforeScriptPathTmpl, testCase.BeforeScriptArgs)
if err != nil {
return nil, err
}
test.AfterRequestScript, err = substituteArgs(afterRequestScriptPathTmpl, testCase.AfterRequestScriptArgs)
if err != nil {
return nil, err
}
test.DbQuery, err = substituteArgs(testDefinition.DbQueryTmpl, testCase.DbQueryArgs)
if err != nil {
return nil, err
}
for key, value := range testCase.Variables {
combinedVariables[key] = value.(string)
}
test.CombinedVariables = combinedVariables
// compile DbResponse
if testCase.DbResponse != nil {
// DbResponse from test case has top priority
test.DbResponse = testCase.DbResponse
} else {
if len(testDefinition.DbResponseTmpl) != 0 {
// compile DbResponse string by string
for _, tpl := range testDefinition.DbResponseTmpl {
dbResponseString, err := substituteArgs(tpl, testCase.DbResponseArgs)
if err != nil {
return nil, err
}
test.DbResponse = append(test.DbResponse, dbResponseString)
}
} else {
test.DbResponse = testDefinition.DbResponseTmpl
}
}
dbChecks := []models.DatabaseCheck{}
for _, check := range testDefinition.DatabaseChecks {
query, err := substituteArgs(check.DbQueryTmpl, testCase.DbQueryArgs)
if err != nil {
return nil, err
}
c := &dbCheck{query: query}
for _, tpl := range check.DbResponseTmpl {
responseString, err := substituteArgs(tpl, testCase.DbResponseArgs)
if err != nil {
return nil, err
}
c.response = append(c.response, responseString)
}
dbChecks = append(dbChecks, c)
}
test.DbChecks = dbChecks
tests = append(tests, test)
}
return tests, nil
}