api-tuner 0.1.3 → 0.2.0
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 +4 -4
- package/bin/tuner.sh +6 -10
- package/lib/parse-test-case.js +39 -0
- package/lib/summarise-results.js +29 -8
- package/package.json +6 -3
- package/rules/assertions.n3 +33 -2
- package/rules/logging.n3 +10 -0
package/README.md
CHANGED
|
@@ -46,13 +46,13 @@ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
|
46
46
|
PREFIX log: <http://www.w3.org/2000/10/swap/log#>
|
|
47
47
|
PREFIX string: <http://www.w3.org/2000/10/swap/string#>
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
<#getExampleDotCom>
|
|
50
50
|
a earl:TestCase ;
|
|
51
51
|
rdfs:label "Simple GET test" ;
|
|
52
52
|
.
|
|
53
53
|
|
|
54
54
|
# Configure a request
|
|
55
|
-
:req
|
|
55
|
+
_:req
|
|
56
56
|
a tuner:Request ;
|
|
57
57
|
tuner:url <http://localhost:1080/example.com> ;
|
|
58
58
|
tuner:method "GET" ;
|
|
@@ -60,7 +60,7 @@ PREFIX string: <http://www.w3.org/2000/10/swap/string#>
|
|
|
60
60
|
|
|
61
61
|
{
|
|
62
62
|
# Execute the request and capture its response
|
|
63
|
-
:req tuner:response ?res .
|
|
63
|
+
_:req tuner:response ?res .
|
|
64
64
|
|
|
65
65
|
# Check the response status code and content type
|
|
66
66
|
?res tuner:http_code 200 ;
|
|
@@ -71,7 +71,7 @@ PREFIX string: <http://www.w3.org/2000/10/swap/string#>
|
|
|
71
71
|
?res!tuner:body string:contains "Example Domain" .
|
|
72
72
|
} => {
|
|
73
73
|
# Use te EARL vocabulary to assert the test passed
|
|
74
|
-
|
|
74
|
+
<#getExampleDotCom> earl:outcome earl:passed .
|
|
75
75
|
} .
|
|
76
76
|
```
|
|
77
77
|
|
package/bin/tuner.sh
CHANGED
|
@@ -79,14 +79,10 @@ if [ "$SILENT" != true ]; then
|
|
|
79
79
|
ARGS="$ARGS ${SCRIPT_PATH}/../logging/info.n3"
|
|
80
80
|
fi
|
|
81
81
|
|
|
82
|
-
MERGED="$(mktemp)"
|
|
83
|
-
if [ -n "$BASE_IRI" ]; then
|
|
84
|
-
echo "base <$BASE_IRI>" > "$MERGED"
|
|
85
|
-
fi
|
|
86
|
-
|
|
87
|
-
for path in "${PATHS[@]}"; do
|
|
88
|
-
cat "$path" >> "$MERGED"
|
|
89
|
-
done
|
|
90
|
-
|
|
91
82
|
set -o pipefail
|
|
92
|
-
|
|
83
|
+
for path in "${PATHS[@]}"; do
|
|
84
|
+
(
|
|
85
|
+
node "${SCRIPT_PATH}/../lib/parse-test-case.js" --base-iri "$BASE_IRI" -- "${path}" \
|
|
86
|
+
| $eye $ARGS "${SCRIPT_PATH}"/../rules/*.n3 -
|
|
87
|
+
) &
|
|
88
|
+
done | $SUMMARY
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as url from 'node:url'
|
|
2
|
+
import { createReadStream } from 'node:fs'
|
|
3
|
+
import yargs from 'yargs'
|
|
4
|
+
import { hideBin } from 'yargs/helpers'
|
|
5
|
+
import replaceStream from 'replacestream'
|
|
6
|
+
import isAbsoluteUrl from 'is-absolute-url'
|
|
7
|
+
import StreamConcat from 'stream-concat'
|
|
8
|
+
|
|
9
|
+
const argv = yargs(hideBin(process.argv)).argv
|
|
10
|
+
|
|
11
|
+
const baseIri = argv['base-iri'] || 'http://example.org/'
|
|
12
|
+
const testCases = argv._
|
|
13
|
+
|
|
14
|
+
function replacer(fileUrl) {
|
|
15
|
+
return (_, match) => {
|
|
16
|
+
if (match.startsWith('#')) {
|
|
17
|
+
return `<${fileUrl}${match}>`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (isAbsoluteUrl(match)) {
|
|
21
|
+
return `<${match}>`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return `<${baseIri}${match}>`
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let fileIndex = 0
|
|
29
|
+
function nextStream() {
|
|
30
|
+
if (fileIndex === testCases.length) {
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
const testCase = testCases[fileIndex++]
|
|
34
|
+
const testCaseUrl = url.pathToFileURL(testCase).toString()
|
|
35
|
+
return createReadStream(testCase)
|
|
36
|
+
.pipe(replaceStream(/<([^>]*)>(?=([^"\\]*(\\.|"([^"\\]*\\.)*[^"\\]*"))*[^"]*$)/g, replacer(testCaseUrl)))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
new StreamConcat(nextStream).pipe(process.stdout)
|
package/lib/summarise-results.js
CHANGED
|
@@ -28,15 +28,36 @@ const shapesTtl = new URL('./shapes.ttl', import.meta.url)
|
|
|
28
28
|
const validationReport = validator.validate(data)
|
|
29
29
|
|
|
30
30
|
if (argv.summary) {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
const resultMap = rdf.clownface({ dataset: data })
|
|
32
|
+
.has(rdf.ns.rdf.type, rdf.ns.earl.TestCase)
|
|
33
|
+
.toArray()
|
|
34
|
+
.reduce((map, testCase) => {
|
|
35
|
+
const { pathname } = new URL(testCase)
|
|
36
|
+
|
|
37
|
+
const testCases = map.get(pathname) || []
|
|
38
|
+
testCases.push(testCase)
|
|
39
|
+
|
|
40
|
+
return map.set(pathname, testCases)
|
|
41
|
+
}, new Map())
|
|
42
|
+
|
|
43
|
+
for (const [pathname, testCases] of resultMap.entries()) {
|
|
44
|
+
const summary = ['']
|
|
45
|
+
summary.push(`🔎 SUITE <file://${pathname}>`)
|
|
46
|
+
|
|
47
|
+
for (const testCase of testCases) {
|
|
48
|
+
const { hash } = new URL(testCase.value)
|
|
49
|
+
const result = validationReport.results.find(result => result.focusNode.equals(testCase.term))
|
|
50
|
+
const label = testCase.out(rdf.ns.rdfs.label).value
|
|
51
|
+
const resultLine = label ? `${label} (<${hash}>)` : `<${hash}>`
|
|
52
|
+
|
|
53
|
+
if (result?.severity.equals(rdf.ns.sh.Violation)) {
|
|
54
|
+
summary.push(`❌ FAIL ${resultLine}`)
|
|
55
|
+
} else {
|
|
56
|
+
summary.push(`✅ PASS ${resultLine}`)
|
|
57
|
+
}
|
|
39
58
|
}
|
|
59
|
+
|
|
60
|
+
process.stderr.write(summary.join('\n') + '\n')
|
|
40
61
|
}
|
|
41
62
|
}
|
|
42
63
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api-tuner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
"api-tuner": "./bin/tuner.sh"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"download-eye": "EYE_VERSION=11.
|
|
11
|
+
"download-eye": "EYE_VERSION=11.10.0 ./lib/download-eye.sh",
|
|
12
12
|
"postinstall": "([ -d eye ] || npm run download-eye) && eye/install.sh --prefix=eye",
|
|
13
13
|
"prepare": "husky",
|
|
14
14
|
"lint": "eslint . --quiet --ignore-path .gitignore",
|
|
15
|
-
"test": "./bin/tuner.sh tests/*.n3 tests/**/*.n3",
|
|
15
|
+
"test": "./bin/tuner.sh tests/*.n3 tests/**/*.n3 --base-iri http://localhost:1080/",
|
|
16
16
|
"release": "changeset publish"
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
@@ -25,8 +25,11 @@
|
|
|
25
25
|
"@changesets/cli": "^2.27.12",
|
|
26
26
|
"@jeswr/pretty-turtle": "^1.5.0",
|
|
27
27
|
"@zazuko/env-node": "^2.1.4",
|
|
28
|
+
"is-absolute-url": "^4.0.1",
|
|
28
29
|
"jsonld": "^8.3.3",
|
|
29
30
|
"rdf-validate-shacl": "^0.5.6",
|
|
31
|
+
"replacestream": "^4.0.3",
|
|
32
|
+
"stream-concat": "^2.0.0",
|
|
30
33
|
"yargs": "^17.7.2"
|
|
31
34
|
},
|
|
32
35
|
"devDependencies": {
|
package/rules/assertions.n3
CHANGED
|
@@ -2,19 +2,50 @@ PREFIX tuner: <https://api-tuner.described.at/>
|
|
|
2
2
|
prefix string: <http://www.w3.org/2000/10/swap/string#>
|
|
3
3
|
prefix log: <http://www.w3.org/2000/10/swap/log#>
|
|
4
4
|
|
|
5
|
+
{
|
|
6
|
+
?expr tuner:assertThat ?msg .
|
|
7
|
+
} <= {
|
|
8
|
+
(
|
|
9
|
+
?expr
|
|
10
|
+
true
|
|
11
|
+
{
|
|
12
|
+
(
|
|
13
|
+
{ ?msg log:rawType log:ForAll }
|
|
14
|
+
{ ?expr^tuner:logFailedAssertion }
|
|
15
|
+
{ ?msg^tuner:logFailedAssertion }
|
|
16
|
+
) log:ifThenElseIn [].
|
|
17
|
+
true log:equalTo false .
|
|
18
|
+
}
|
|
19
|
+
) log:ifThenElseIn [] .
|
|
20
|
+
} .
|
|
21
|
+
|
|
5
22
|
{
|
|
6
23
|
?res tuner:http_code ?value .
|
|
24
|
+
} <= {
|
|
25
|
+
( ?res tuner:http_code ?value ) <#curl-field> [] .
|
|
26
|
+
} .
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
?res tuner:url.host ?value .
|
|
30
|
+
} <= {
|
|
31
|
+
( ?res tuner:url.host ?value ) <#curl-field> [] .
|
|
32
|
+
} .
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
( ?res ?prop ?value ) <#curl-field> [] .
|
|
7
36
|
} <= {
|
|
8
37
|
?res log:includes {
|
|
9
38
|
[] a tuner:Response ;
|
|
10
|
-
|
|
39
|
+
?prop ?actualValue .
|
|
11
40
|
} .
|
|
12
41
|
|
|
42
|
+
?prop log:localName ?localName .
|
|
43
|
+
|
|
13
44
|
(
|
|
14
45
|
{ ?actualValue log:equalTo ?value }
|
|
15
46
|
true
|
|
16
47
|
{
|
|
17
|
-
("Expected
|
|
48
|
+
("Expected " ?localName " " ?value " but got " ?actualValue)!string:concatenation^tuner:info .
|
|
18
49
|
true log:equalTo false .
|
|
19
50
|
}
|
|
20
51
|
) log:ifThenElseIn ?SCOPE .
|
package/rules/logging.n3
CHANGED
|
@@ -22,3 +22,13 @@ prefix string: <http://www.w3.org/2000/10/swap/string#>
|
|
|
22
22
|
true
|
|
23
23
|
) log:ifThenElseIn ?SCOPE .
|
|
24
24
|
} .
|
|
25
|
+
|
|
26
|
+
{
|
|
27
|
+
?left tuner:logFailedAssertion ?right .
|
|
28
|
+
} <= {
|
|
29
|
+
(
|
|
30
|
+
{ [] tuner:logLevel 'info' }
|
|
31
|
+
{ 'Failed assertion' log:trace ?right . }
|
|
32
|
+
true
|
|
33
|
+
) log:ifThenElseIn ?SCOPE .
|
|
34
|
+
} .
|