api-tuner 0.5.4 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -0
- package/bin/jsonpath.sh +16 -0
- package/lib/jsonpath.js +23 -0
- package/package.json +4 -3
- package/rules/assertions.n3 +25 -0
- package/rules/curl-body.n3 +8 -0
package/README.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
**API** **T**ests **U**sing **n3** **R**ules
|
|
4
4
|
|
|
5
|
+
- [Prerequisites](#prerequisites)
|
|
6
|
+
- [Installation](#installation)
|
|
7
|
+
- [Usage](#usage)
|
|
8
|
+
- [Example](#example)
|
|
9
|
+
- [Documentation](#documentation)
|
|
10
|
+
- [Namespaces](#namespaces)
|
|
11
|
+
- [Defining a Test Case](#defining-a-test-case)
|
|
12
|
+
- [HTTP Requests](#http-requests)
|
|
13
|
+
- [Assertions](#assertions)
|
|
14
|
+
- [Utility Rules](#utility-rules)
|
|
15
|
+
- [Debugging](#debugging)
|
|
16
|
+
|
|
5
17
|
## Prerequisites
|
|
6
18
|
|
|
7
19
|
- [SWI Prolog](https://www.swi-prolog.org/Download.html)
|
|
@@ -77,6 +89,183 @@ api-tuner test.n3
|
|
|
77
89
|
|
|
78
90
|
## More examples
|
|
79
91
|
|
|
92
|
+
See the [Documentation](#documentation) section below for more examples and detailed documentation of the N3 rules.
|
|
93
|
+
|
|
94
|
+
## Documentation
|
|
95
|
+
|
|
96
|
+
This section describes the N3 rules and vocabulary used by `api-tuner` for defining and executing API tests.
|
|
97
|
+
|
|
98
|
+
### Namespaces
|
|
99
|
+
|
|
100
|
+
Commonly used prefixes in `api-tuner` tests:
|
|
101
|
+
|
|
102
|
+
```turtle
|
|
103
|
+
PREFIX tuner: <https://api-tuner.described.at/>
|
|
104
|
+
PREFIX resource: <https://api-tuner.described.at/resource#>
|
|
105
|
+
PREFIX earl: <http://www.w3.org/ns/earl#>
|
|
106
|
+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
107
|
+
PREFIX log: <http://www.w3.org/2000/10/swap/log#>
|
|
108
|
+
PREFIX string: <http://www.w3.org/2000/10/swap/string#>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Defining a Test Case
|
|
112
|
+
|
|
113
|
+
A test case is defined as an `earl:TestCase`. The core logic of the test resides in `tuner:formula`.
|
|
114
|
+
|
|
115
|
+
```turtle
|
|
116
|
+
<#myTest>
|
|
117
|
+
a earl:TestCase ;
|
|
118
|
+
rdfs:label "Description of my test" ;
|
|
119
|
+
tuner:formula {
|
|
120
|
+
# Test logic goes here
|
|
121
|
+
} .
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If the formula evaluates to true (all statements inside match), the test is considered `earl:passed`.
|
|
125
|
+
|
|
126
|
+
### HTTP Requests
|
|
127
|
+
|
|
128
|
+
#### Request Object
|
|
129
|
+
|
|
130
|
+
You can define a detailed request using the `tuner:Request` class.
|
|
131
|
+
|
|
132
|
+
```turtle
|
|
133
|
+
<#test> tuner:request [
|
|
134
|
+
a tuner:Request ;
|
|
135
|
+
tuner:method "POST" ;
|
|
136
|
+
tuner:url <http://example.com/api> ;
|
|
137
|
+
tuner:header ( "Accept" "application/json" ) ;
|
|
138
|
+
tuner:query ( "verbose" "true" ) ;
|
|
139
|
+
tuner:body { <#s> <#p> <#o> }
|
|
140
|
+
] .
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
To execute the request and get a response:
|
|
144
|
+
```turtle
|
|
145
|
+
<#test> tuner:request ?req .
|
|
146
|
+
?req tuner:response ?res .
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### Simplified Helpers
|
|
150
|
+
|
|
151
|
+
The `resource:` namespace provides shortcuts for common HTTP methods. These helpers automatically assert a `200 OK` response status unless used in a way that captures the response for further assertions.
|
|
152
|
+
|
|
153
|
+
- `( <url> ?res ) resource:getIn []`
|
|
154
|
+
- `( <url> ?body ?res ) resource:postIn []`
|
|
155
|
+
- `( <url> ?res ) resource:postIn []` (no body)
|
|
156
|
+
- `( <url> ?body ?res ) resource:putIn []`
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
```turtle
|
|
160
|
+
( <http://example.com> ?res ) resource:getIn [] .
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### Request Bodies
|
|
164
|
+
|
|
165
|
+
`api-tuner` supports different types of request bodies:
|
|
166
|
+
|
|
167
|
+
1. **Inline RDF**: Uses an N3 formula. It is serialized as Turtle and sent with `Content-Type: text/turtle`.
|
|
168
|
+
```turtle
|
|
169
|
+
tuner:body { <#s> <#p> <#o> }
|
|
170
|
+
```
|
|
171
|
+
2. **File Reference**: Sends the contents of a local file.
|
|
172
|
+
```turtle
|
|
173
|
+
tuner:body <file:data.json>
|
|
174
|
+
```
|
|
175
|
+
3. **Multipart Form**:
|
|
176
|
+
```turtle
|
|
177
|
+
tuner:body [
|
|
178
|
+
tuner:form ( "field1" "value1" ) ;
|
|
179
|
+
tuner:form ( "fileField" <file:photo.jpg> "image/jpeg" ) ;
|
|
180
|
+
]
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### Query Parameters
|
|
184
|
+
|
|
185
|
+
Query parameters can be added to a request using `tuner:query`.
|
|
186
|
+
|
|
187
|
+
```turtle
|
|
188
|
+
?req tuner:query ( "name" "value" ) .
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Assertions
|
|
192
|
+
|
|
193
|
+
Assertions are performed on the `tuner:Response` object (usually captured in a variable like `?res`).
|
|
194
|
+
|
|
195
|
+
#### Status Code
|
|
196
|
+
|
|
197
|
+
Assert the HTTP status code:
|
|
198
|
+
```turtle
|
|
199
|
+
?res tuner:http_code 200 .
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### Headers
|
|
203
|
+
|
|
204
|
+
Assert the presence and value of an HTTP header. Header names are case-insensitive.
|
|
205
|
+
|
|
206
|
+
- **Exact match**:
|
|
207
|
+
```turtle
|
|
208
|
+
?res tuner:header ( "Content-Type" "application/json" ) .
|
|
209
|
+
```
|
|
210
|
+
- **Regex match**:
|
|
211
|
+
```turtle
|
|
212
|
+
?res tuner:header ( "Content-Type" "application/.*" string:matches ) .
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### Body
|
|
216
|
+
|
|
217
|
+
- **Raw body string**:
|
|
218
|
+
```turtle
|
|
219
|
+
?res tuner:body ?body .
|
|
220
|
+
?body string:contains "Expected Text" .
|
|
221
|
+
```
|
|
222
|
+
- **RDF Semantics**: If the response is RDF, you can use `log:includes` to check its content.
|
|
223
|
+
```turtle
|
|
224
|
+
?res tuner:body ?body.
|
|
225
|
+
?body log:includes { <#s> <#p> <#o> } .
|
|
226
|
+
```
|
|
227
|
+
⚠️ Be careful when using `?res!log:includes` resource path shorthand which will not work inside `tuner:formula`. Please refer to [this discussion](https://github.com/eyereasoner/eye/issues/148#issuecomment-2810940959).
|
|
228
|
+
|
|
229
|
+
<<<<<<< json-assertions
|
|
230
|
+
- **JSON Path**: If the response is JSON, you can use `tuner:jsonPath` to assert values within the body.
|
|
231
|
+
```turtle
|
|
232
|
+
?res tuner:body ?body .
|
|
233
|
+
# Exact match
|
|
234
|
+
?body tuner:jsonPath ( "$.foo" "bar" ) .
|
|
235
|
+
# Custom assertion (e.g. regex, contains, math)
|
|
236
|
+
?body tuner:jsonPath ( "$.baz" "42" string:contains ) .
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
=======
|
|
240
|
+
>>>>>>> master
|
|
241
|
+
#### Generic Assertions
|
|
242
|
+
|
|
243
|
+
Use `tuner:assertThat` to fail a test with a custom message if a condition is not met.
|
|
244
|
+
|
|
245
|
+
```turtle
|
|
246
|
+
{ ?value math:greaterThan 10 }!tuner:assertThat "Value should be greater than 10" .
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Utility Rules
|
|
250
|
+
|
|
251
|
+
#### Logging
|
|
252
|
+
|
|
253
|
+
Print messages to the console during test execution (depending on log level).
|
|
254
|
+
|
|
255
|
+
- `?message^tuner:info`: Prints an INFO message.
|
|
256
|
+
- `?message^tuner:trace`: Prints a DEBUG message.
|
|
257
|
+
|
|
258
|
+
Example:
|
|
259
|
+
```turtle
|
|
260
|
+
"Starting request"^tuner:info .
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
#### File Operations
|
|
264
|
+
|
|
265
|
+
- `() file:temp ?path`: Generates a temporary file path.
|
|
266
|
+
- `?path file:rm ?any`: Deletes a file.
|
|
267
|
+
- `?relative file:libPath ?absolute`: Resolves a path relative to the `api-tuner` library.
|
|
268
|
+
|
|
80
269
|
### Debugging
|
|
81
270
|
|
|
82
271
|
Setting the `--debug` flag will print verbose response information. The `--raw` flag will print
|
|
@@ -87,6 +276,9 @@ with `api-tuner`. Thus, you can list them with `ls -l "${TMPDIR:-/tmp}"/api-tune
|
|
|
87
276
|
in the GitHub Workflow step example below.
|
|
88
277
|
|
|
89
278
|
```yaml
|
|
279
|
+
- run: npx api-tuner ...
|
|
280
|
+
env:
|
|
281
|
+
TMPDIR: ${{ runner.temp }}
|
|
90
282
|
- if: failure()
|
|
91
283
|
name: upload api-tuner response data
|
|
92
284
|
uses: actions/upload-artifact@v7
|
package/bin/jsonpath.sh
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
SCRIPT_DIR=$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)
|
|
4
|
+
|
|
5
|
+
# find JS entrypoint
|
|
6
|
+
executable="$SCRIPT_DIR/../lib/jsonpath.js"
|
|
7
|
+
|
|
8
|
+
# if tsx exists in path
|
|
9
|
+
if command -v tsx > /dev/null 2>&1
|
|
10
|
+
then
|
|
11
|
+
# use tsx
|
|
12
|
+
node --import tsx --no-warnings "$executable" "$@"
|
|
13
|
+
else
|
|
14
|
+
# use plain node
|
|
15
|
+
node "$executable" "$@"
|
|
16
|
+
fi
|
package/lib/jsonpath.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { JSONPath } from 'jsonpath-plus';
|
|
3
|
+
import getStream from 'get-stream';
|
|
4
|
+
async function main() {
|
|
5
|
+
const path = process.argv[2];
|
|
6
|
+
if (!path) {
|
|
7
|
+
console.error('Usage: node jsonpath.js <path>');
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
const jsonString = await getStream(process.stdin);
|
|
11
|
+
try {
|
|
12
|
+
const json = JSON.parse(jsonString);
|
|
13
|
+
const result = JSONPath({ path, json, wrap: false });
|
|
14
|
+
if (result !== undefined && result !== null) {
|
|
15
|
+
process.stdout.write(JSON.stringify(result));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
console.error('Error parsing JSON or executing JSONPath:', e.message);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api-tuner",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
"rules"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@changesets/cli": "^2.29.7",
|
|
32
31
|
"@jeswr/pretty-turtle": "^1.8.2",
|
|
33
32
|
"@sindresorhus/merge-streams": "^4.0.0",
|
|
34
33
|
"@zazuko/env-node": "^3",
|
|
@@ -36,11 +35,13 @@
|
|
|
36
35
|
"get-stream": "^9.0.1",
|
|
37
36
|
"is-absolute-url": "^4.0.1",
|
|
38
37
|
"jsonld": "^9",
|
|
38
|
+
"jsonpath-plus": "^10.2.0",
|
|
39
39
|
"rdf-validate-shacl": "^0.6.5",
|
|
40
40
|
"replacestream": "^4.0.3",
|
|
41
41
|
"tar": "^7.5.7"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
+
"@changesets/cli": "^2.31.0",
|
|
44
45
|
"@rdfjs/types": "^2",
|
|
45
46
|
"@tpluscode/eslint-config": "^0.5.0",
|
|
46
47
|
"@types/jsonld": "^1.5.15",
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
"eslint": "^8.57.1",
|
|
54
55
|
"eslint-import-resolver-typescript": "^4.3.4",
|
|
55
56
|
"husky": "^9.1.7",
|
|
56
|
-
"lint-staged": "^
|
|
57
|
+
"lint-staged": "^16.4.0",
|
|
57
58
|
"tsx": "^4.19.3",
|
|
58
59
|
"typescript": "^5.8.3"
|
|
59
60
|
},
|
package/rules/assertions.n3
CHANGED
|
@@ -73,6 +73,31 @@ prefix log: <http://www.w3.org/2000/10/swap/log#>
|
|
|
73
73
|
) log:ifThenElseIn [] .
|
|
74
74
|
} .
|
|
75
75
|
|
|
76
|
+
{
|
|
77
|
+
?body tuner:jsonPath ( ?path ?expected ) .
|
|
78
|
+
} <= {
|
|
79
|
+
?body tuner:jsonPath ( ?path ?expected log:equalTo ) .
|
|
80
|
+
} .
|
|
81
|
+
|
|
82
|
+
{
|
|
83
|
+
?body tuner:jsonPath ( ?path ?expected ?builtIn ) .
|
|
84
|
+
} <= {
|
|
85
|
+
?body log:rawType log:Literal .
|
|
86
|
+
( "echo " ?body " | bin/jsonpath.sh '" ?path "' " )!string:concatenation log:shell ?actualRaw .
|
|
87
|
+
|
|
88
|
+
# remove surrounding quotes added by the shell
|
|
89
|
+
( ( ?actualRaw '^\"' "")!string:replace '\"$' "") string:replace ?actual .
|
|
90
|
+
|
|
91
|
+
(
|
|
92
|
+
{ ?actual ?builtIn ?expected }
|
|
93
|
+
true
|
|
94
|
+
{
|
|
95
|
+
("Expected JSON path '" ?path "' to satisfy '" ?builtIn " " ?expected "' but got '" ?actual "'")!string:concatenation^tuner:info .
|
|
96
|
+
true log:equalTo false .
|
|
97
|
+
}
|
|
98
|
+
) log:ifThenElseIn ?SCOPE .
|
|
99
|
+
} .
|
|
100
|
+
|
|
76
101
|
{
|
|
77
102
|
?res tuner:header ( ?name ?value ) .
|
|
78
103
|
} <= {
|
package/rules/curl-body.n3
CHANGED
|
@@ -30,6 +30,14 @@ prefix math: <http://www.w3.org/2000/10/swap/math#>
|
|
|
30
30
|
( " --data-binary " ?fileUrl!file:curlFileReference ) string:concatenation ?curlArgs .
|
|
31
31
|
} .
|
|
32
32
|
|
|
33
|
+
{
|
|
34
|
+
?literalBody </#body> ( ?curlArgs [] ) .
|
|
35
|
+
} <= {
|
|
36
|
+
?literalBody log:rawType log:Literal .
|
|
37
|
+
|
|
38
|
+
( " -d '" ?literalBody "'" ) string:concatenation ?curlArgs .
|
|
39
|
+
} .
|
|
40
|
+
|
|
33
41
|
{
|
|
34
42
|
?multipartBody </#body> ( ?curlArgs [] ) .
|
|
35
43
|
} <= {
|