Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .existdb.json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"Dockerfile",
"devel/**",
"test/**",
"schemathesis-results.xml",
"*.xar"
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Test API
name: Schemathesis Tests

on:
push:
Expand All @@ -8,17 +8,19 @@ on:

permissions:
contents: read
pull-requests: write

env:
COMPOSE_FILE: compose.yml:compose.t.yml
DOCKER_BUILDKIT: 1
FORCE_COLOR: 1

jobs:
test_api:
schemathesis:
name: Schemathesis
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- run: docker --version
- name: Start the docker-compose stack
run: docker compose up --detach --wait
Expand All @@ -31,7 +33,7 @@ jobs:
--url http://localhost:8081/exist/restxq/v1/corpora \
--user admin: \
--header 'Content-Type: application/json' \
--data @./corpora/test.json
--data @./test/test.json
- name: Load data for test corpus
run: |
curl \
Expand All @@ -46,21 +48,13 @@ jobs:
run: curl --verbose http://localhost:8081/exist/restxq/v1/corpora/test

- name: Test API with schemathesis
# Refs:
# https://github.com/schemathesis/action#configuration
# https://schemathesis.readthedocs.io/en/stable/cli.html
uses: schemathesis/action@95849c1d1e806909cca98b7bf031bf47d552cd5b # v1.1.1
uses: schemathesis/action@806cace2053cbbac93188e1281ff7da415643160 # v3.0.0
with:
schema: http://localhost:8081/exist/restxq/v1/openapi.yaml
base-url: http://localhost:8081/exist/restxq/v1/
# OPTIONAL. List of Schemathesis checks to run. Defaults to `all`
# if multiple checks should be used, use a comma seperated string,
# e.g. "not_a_server_error,status_code_conformance"
checks: not_a_server_error,content_type_conformance,response_headers_conformance,response_schema_conformance
# OPTIONAL. Maximum number of generated examples for each endpoint
authorization: 'Basic YWRtaW46'
max-examples: 100
# OPTIONAL. Extra arguments to pass to Schemathesis
args: "--auth=admin: --workers=2 --include-method=GET"
args: "--include-method GET --mode positive --request-timeout 30"

- name: Stop the docker-compose stack
run: docker compose down
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ devel/
expath-pkg.xml
local.build.properties
*.xar
schemathesis-results.*
.hypothesis
.claude
5 changes: 4 additions & 1 deletion api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,7 @@ paths:
required: true
schema:
type: string
pattern: '^Q[0-9]+$'
example: Q131412
responses:
'200':
Expand Down Expand Up @@ -1031,7 +1032,7 @@ paths:
- name: id
in: query
required: false
description: Identifier for a collection or document. Preferably the full URI of a corpus or a play, but shorter DraCor IDs are also supported.
description: Identifier for a collection. Preferably the full URI of a corpus, but shorter DraCor corpus IDs are also supported.
schema:
type: string
examples:
Expand Down Expand Up @@ -1113,6 +1114,7 @@ paths:
description: The unique identifier for the Resource whose tree or subtree must be returned. Should be the full URI of a play (recommended) or the DraCor ID.
schema:
type: string
minLength: 1
example: https://dracor.org/id/ger000088
- name: ref
in: query
Expand Down Expand Up @@ -1181,6 +1183,7 @@ paths:
description: The unique identifier for the Resource whose tree or subtree must be returned. Should be the full URI of a play (recommended) or the DraCor ID.
schema:
type: string
minLength: 1
example: https://dracor.org/id/ger000088
- name: ref
in: query
Expand Down
15 changes: 14 additions & 1 deletion modules/api.xqm
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,12 @@ declare
%output:media-type("application/json")
%output:method("json")
function api:corpora($include) {
if ($include[1] and not($include[1] = "metrics")) then
(
<rest:response><http:response status="400"/></rest:response>,
map {"error": "invalid value for parameter 'include'"}
)
else
array {
(: DEPRECATED: remove teiCorpus support in v2 :)
for $corpus in collection($config:corpora-root)/(tei:dracorCorpus|tei:teiCorpus)
Expand All @@ -266,7 +272,7 @@ function api:corpora($include) {
return map:merge ((
$info,
map:entry("uri", $config:api-base || '/corpora/' || $name),
if ($include = "metrics") then (
if ($include[1] = "metrics") then (
map:entry("metrics", local:get-corpus-metrics($name))
) else ()

Expand Down Expand Up @@ -1270,6 +1276,11 @@ function api:spoken-text(
$corpusname, $playname, $sex, $role, $relation, $relation-active,
$relation-passive
) {
let $sex := $sex[1]
let $role := $role[1]
let $relation := $relation[1]
let $relation-active := $relation-active[1]
let $relation-passive := $relation-passive[1]
let $doc := dutil:get-doc($corpusname, $playname)
let $sexes := tokenize($sex, ',')
return
Expand Down Expand Up @@ -1500,6 +1511,8 @@ declare
%output:media-type("application/json")
%output:method("json")
function api:authorInfo($id, $lang) {
let $lang := $lang[1]
return
if (not(matches($id, '^Q[0-9]+$'))) then
(
<rest:response>
Expand Down
101 changes: 60 additions & 41 deletions modules/dts.xqm
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,16 @@ declare
%output:method("json")
function ddts:collections($id as xs:string*, $page as xs:string*, $nav as xs:string*)
as item()+ {
let $id := $id[1]
let $page := $page[1]
let $nav := $nav[1]
return
if ($nav and $nav != "parents") then
(
<rest:response><http:response status="400"/></rest:response>,
"The value '" || $nav || "' of the parameter 'nav' is not allowed. Use the single allowed value 'parents' if you want to request the parent collection."
)
else
(: check, if param $id is set -- request a certain collection :)
if ( $id ) then

Expand Down Expand Up @@ -349,9 +359,9 @@ as item()+ {
(: test: http://localhost:8088/api/v1/dts/collection?id=http://localhost:8088/id/ger12345 :)
(
<rest:response>
<http:response status="400"/>
<http:response status="404"/>
</rest:response>,
"The value '" || $id || "' of the parameter 'id' is not in a valid format: Either provide the id or URI of a corpus or play."
"The value '" || $id || "' of the parameter 'id' does not identify a known collection. Provide the name or URI of a corpus."
)

else
Expand Down Expand Up @@ -652,28 +662,35 @@ as item()+

:)
declare function local:child-readable-collection-with-parent-by-id($id as xs:string)
as map()
as item()+
{
let $self := local:child-readable-collection-by-id($id)

(: get parent collection and remove the members :)

(: TODO: maybe use a dutil:function instead; this is very custom; not sure how this will
work when something is changed in the general API code
:)
let $file-db-path := util:collection-name(collection($config:corpora-root)/tei:TEI[@xml:id eq $id])
let $corpusname := tokenize(replace($file-db-path, "/db/dracor/corpora/",""),"/")[1]
return
if (not($corpusname)) then
(
<rest:response><http:response status="404"/></rest:response>,
"Resource '" || $id || "' does not exist!"
)
else
let $self := local:child-readable-collection-by-id($id)

(: get the parent by the function to generate a collection :)
let $parent := local:corpus-to-collection($corpusname)
(: get parent collection and remove the members :)

(: remove "members" and "@context" :)
let $parent-without-members := map:remove($parent,"member")
let $parent-without-context := map:remove($parent-without-members, "@context")
let $members := map {"member" : array { $parent-without-context }}
(: TODO: maybe use a dutil:function instead; this is very custom; not sure how this will
work when something is changed in the general API code
:)

return
map:merge(($self, $members))
(: get the parent by the function to generate a collection :)
let $parent := local:corpus-to-collection($corpusname)

(: remove "members" and "@context" :)
let $parent-without-members := map:remove($parent,"member")
let $parent-without-context := map:remove($parent-without-members, "@context")
let $members := map {"member" : array { $parent-without-context }}

return
map:merge(($self, $members))
};


Expand Down Expand Up @@ -894,7 +911,7 @@ as item() {
: $tree
: $mediaType
:
: Params used in POST, PUT, DELETE requests are not availiable
: Params used in POST, PUT, DELETE requests are not available

:)

Expand Down Expand Up @@ -925,23 +942,22 @@ declare
%output:media-type("application/xml")
%output:method("xml")
function ddts:document($resource, $ref, $start, $end, $tree, $media-type) {
let $resource := $resource[1]
let $ref := $ref[1]
(: ref takes precedence: if ref is set, start and end are ignored :)
let $start := if ($ref) then () else $start[1]
let $end := if ($ref) then () else $end[1]
(: start and end must both be present for a range; if only one is given, ignore both :)
let $start := if ($start and $end) then $start else ()
let $end := if ($start and $end) then $end else ()
let $media-type := $media-type[1]
return

(: TODO: to properly implement a possibility to request a document or fragment in a different media type
we would need to set the serialization parameters rest:produces, output: ... dynamically :)
(: check, if valid request :)

(: In GET requests one may either provide a ref parameter or a pair of start and end parameters. A request cannot combine ref with the other two. If, say, a ref and a start are both provided this should cause the request to fail. :)
if ( $ref and ( $start or $end ) ) then
(
<rest:response>
<http:response status="400"/>
</rest:response>,
<error statusCode="400" xmlns="https://w3id.org/dts/api#">
<title>Bad Request</title>
<description>GET requests may either have a 'ref' parameter or a pair of 'start' and 'end' parameters. A request cannot combine 'ref' with the other two.</description>
</error>
)
else if ( ($start and not($end) ) or ( $end and not($start) ) ) then
if ( ($start and not($end) ) or ( $end and not($start) ) ) then
(: requesting a range, should check, if start and end is present :)
(
<rest:response>
Expand Down Expand Up @@ -1405,7 +1421,7 @@ declare function local:collections-self-describe() {
</rest:response>,
<error statusCode="400" xmlns="https://w3id.org/dts/api#">
<title>Bad Request</title>
<description>Should at least use the required parameter 'id'. Automatic self description is not availiable.</description>
<description>Should at least use the required parameter 'id'. Automatic self description is not available.</description>
</error>
)
};
Expand Down Expand Up @@ -1788,6 +1804,17 @@ declare function local:link-header-of-fragment($tei as element(tei:TEI), $ref as
%output:media-type("application/ld+json")
%output:method("json")
function ddts:navigation($resource, $ref, $start, $end, $level, $down) {
let $resource := $resource[1]
let $ref := $ref[1]
(: ref takes precedence: if ref is set, start and end are ignored :)
let $start := if ($ref) then () else $start[1]
let $end := if ($ref) then () else $end[1]
(: start and end must both be present for a range; if only one is given, ignore both :)
let $start := if ($start and $end) then $start else ()
let $end := if ($start and $end) then $end else ()
(: default to top-level navigation when no navigation parameter is specified :)
let $down := if ($down[1]) then $down[1] else if (not($ref) and not($start)) then "1" else ()
return
(: parameter $id is mandatory :)
if ( not($resource) ) then
(
Expand All @@ -1796,14 +1823,6 @@ declare function local:link-header-of-fragment($tei as element(tei:TEI), $ref as
</rest:response>,
"Mandatory parameter 'resource' is missing."
)
(: both ref and either start or end is specified should return an error :)
else if ( $ref and ($start or $end) ) then
(
<rest:response>
<http:response status="400"/>
</rest:response>,
"Bad Request: Use of both parameters 'ref' and 'start' or 'end' is not allowed."
)
(: should not use start without end or vice versa :)
else if ( ($start and not($end)) or ($end and not($start)) ) then
(
Expand Down Expand Up @@ -2521,7 +2540,7 @@ declare function local:snippet($tei-fragment as element()) {

(: end of level2 :)
else
(: this level is not implemented/not availiable :)
(: this level is not implemented/not available :)
(
<rest:response>
<http:response status="400"/>
Expand Down
10 changes: 6 additions & 4 deletions test/schemathesis.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# This should reproduce the test in the GitHub workflow.

schemathesis run http://localhost:8081/exist/restxq/v1/openapi.yaml \
--exclude-checks status_code_conformance \
--report \
--hypothesis-max-examples 50 \
--include-method GET \
--mode positive \
--max-examples 50 \
--request-timeout 30 \
--auth admin: \
--method GET
--report junit \
--report-junit-path schemathesis-results.xml
5 changes: 5 additions & 0 deletions test/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "test",
"title": "Test Corpus",
"repository": "https://github.com/dracor-org/testdracor"
}
Loading