diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/annotation-protocol | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
58 files changed, 1699 insertions, 0 deletions
diff --git a/testing/web-platform/tests/annotation-protocol/.editorconfig b/testing/web-platform/tests/annotation-protocol/.editorconfig new file mode 100644 index 0000000000..842ce7b6bf --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + diff --git a/testing/web-platform/tests/annotation-protocol/CONTRIBUTING.md b/testing/web-platform/tests/annotation-protocol/CONTRIBUTING.md new file mode 100644 index 0000000000..cc585fd7d1 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/CONTRIBUTING.md @@ -0,0 +1,38 @@ +Annotation-Protocol: Guidelines for Contributing Tests +====================================================== + +This document describes the method people should use for authoring tests and +integrating them into the repository. Anyone is welcome to submit new tests to +this collection. If you do, please create the tests following the guidelines +below. Then submit them as a pull request so they can be evaluated + +Structure +--------- + +Tests are organized by client or server, and then by major section of the Annotation +Protocol specification. The folders associated with these are: + +* client - tests a client needs to run +* server - tests to be run against a server + +Within these folders, special files ending with the suffix ".html" provide the source +for the test as a set javascript calls to perform the test. + +* scripts - JavaScript that are included by tests +* tools - supporting scripts and files + +Client Test Cases +----------------- + +@@@TODO@@@ describe the structure of client test cases. + +Server Test Cases +----------------- + +@@@TODO@@@ describe the structure of server test cases. + + +Command Line Tools +------------------ + +### Stand-alone Annotation Server ### diff --git a/testing/web-platform/tests/annotation-protocol/META.yml b/testing/web-platform/tests/annotation-protocol/META.yml new file mode 100644 index 0000000000..f953586526 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/META.yml @@ -0,0 +1,3 @@ +spec: https://www.w3.org/TR/annotation-protocol/ +suggested_reviewers: + - halindrome diff --git a/testing/web-platform/tests/annotation-protocol/README.md b/testing/web-platform/tests/annotation-protocol/README.md new file mode 100644 index 0000000000..d0ec93573f --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/README.md @@ -0,0 +1,86 @@ +Annotation-Protocol: Tests for the Web Annotation Protocol +========================================================== + +The [Web Annotation Protocol](https://www.w3.org/TR/annotation-protocol) +specification presents set of messages to allow Annotation clients and servers +to interact seamlessly. + +The purpose of these tests is to help validate that clients send and are +capable of receiving correctly formatted messages, and that servers are +able to receive and respond to correctly structured requests. + +The general approach for this testing is to enable both manual and +automated testing. However, since the specification has no actual user +interface requirements, there is no general automation mechanism that +can be presented for testing clients. Also the server tests need to be +pointed at a server implementation to exercise. However, once provided +the basic information, testing is automated. + +Implementors could take advantage of the plumbing we provide here to +help their implementations talk to the endpoint we provide or exercise +their endpoint with the provided server tests. This assumes knowledge +of the requirements of each test / collection of tests so that the input +data is relevant. Each test or test collection contains information +sufficient for the task. + +With regard to server tests, the browser tests we provide can be +pointed at an endpoint and will exercise that endpoint using well +defined messages. This is done semi-automatically, although some set-up +is required. + +Running Tests +------------- + +In the case of this test collection, we will be initially creating manual +tests. These will automatically determine pass or fail and generate output for +the main WPT window. The plan is to minimize the number of such tests to +ease the burden on the testers while still exercising all the features. + +The workflow for running these tests is something like: + +1. Start up the test driver window and select the annotation-protocol tests - + either client or server - then click "Start". +2. A window pops up that shows a test - the description of which tells the + tester what is required. The window will contain fields into which some + information is provided. +3. In the case of client testing the tester (presumably in another window) brings up their + annotation client and points it at the supplied endpoint. They they perform the + action specified (annotating content in the test window, requesting an annotation from the server, etc.). +4. The server receives the information from the client, evaluates it, and reports the result of testing. + In the event of multi-step messages, the cycle repeats until complete. +5. Repeat steps 2-4 until done. +6. Download the JSON format report of test results, which can then be visually + inspected, reported on using various tools, or passed on to W3C for + evaluation and collection in the Implementation Report via github. + +**Remember that while these tests are written to help exercise implementations, +their other (important) purpose is to increase confidence that there are +interoperable implementations.** So, implementers are our audience, but these +tests are not meant to be a comprehensive collection of tests for an implementor. +The bulk of the tests are manual because there are no UI requirements in the +Recommendation that would make it possible to effectively stimulate every client portably. + +Having said that, because the structure of these "manual" tests is very rigid, +it is possible for an implementer who understands test automation to use an +open source tool such as [Selenium](http://www.seleniumhq.org/) to run these +"manual" tests against their implementation - exercising their implementation +against content they provide to create annotations and feed the data into our +test input field and run the test. + +Capturing and Reporting Results +------------------------------- + +As tests are run against implementations, if the results of testing are +submitted to [test-results](https://github.com/w3c/test-results/) then they will +be automatically included in documents generated by +[wptreport](https://www.github.com/w3c/wptreport). The same tool can be used +locally to view reports about recorded results. + +Automating Test Execution +------------------------- + +Writing Tests +------------- + +If you are interested in writing tests for this environment, see the +associated [CONTRIBUTING](CONTRIBUTING.md) document. diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno1.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno1.json new file mode 100644 index 0000000000..905349bb46 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno1.json @@ -0,0 +1,7 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno1", + "type": "Annotation", + "body": "http://example.org/post1", + "target": "http://example.com/page1" +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno1.jsonld b/testing/web-platform/tests/annotation-protocol/files/annotations/anno1.jsonld new file mode 100644 index 0000000000..0e1729ae72 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno1.jsonld @@ -0,0 +1,11 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "/annotations/anno1.jsonld", + "type": "Annotation", + "created": "2015-01-31T12:03:45Z", + "body": { + "type": "TextualBody", + "value": "I like this page!" + }, + "target": "/index.html" +} diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno10.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno10.json new file mode 100644 index 0000000000..766fddf3ac --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno10.json @@ -0,0 +1,19 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno10", + "type": "Annotation", + "body": { + "type": "Choice", + "items": [ + { + "id": "http://example.org/note1", + "language": "en" + }, + { + "id": "http://example.org/note2", + "language": "fr" + } + ] + }, + "target": "http://example.org/website1" +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno11.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno11.json new file mode 100644 index 0000000000..7461493f46 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno11.json @@ -0,0 +1,18 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno11", + "type": "Annotation", + "motivation": "commenting", + "body": { + "type": "TextualBody", + "value": "These pages together provide evidence of the conspiracy" + }, + "target": { + "type": "Composite", + "items": [ + "http://example.com/page1", + "http://example.org/page6", + "http://example.net/page4" + ] + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno12.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno12.json new file mode 100644 index 0000000000..a8039b207d --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno12.json @@ -0,0 +1,19 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno12", + "type": "Annotation", + "motivation": "tagging", + "body": { + "type": "TextualBody", + "value": "important" + }, + "target": { + "type": "List", + "items": [ + "http://example.com/book/page1", + "http://example.com/book/page2", + "http://example.com/book/page3", + "http://example.com/book/page4" + ] + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno13.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno13.json new file mode 100644 index 0000000000..8bf663330d --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno13.json @@ -0,0 +1,16 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno13", + "type": "Annotation", + "motivation": "classifying", + "body": "http://example.org/vocab/art/portrait", + "target": { + "type": "Independents", + "items": [ + "http://example.com/image1", + "http://example.net/image2", + "http://example.com/image4", + "http://example.org/image9" + ] + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno14.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno14.json new file mode 100644 index 0000000000..102225b729 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno14.json @@ -0,0 +1,16 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno14", + "type": "Annotation", + "creator": "http://example.org/user1", + "created": "2015-01-28T12:00:00Z", + "modified": "2015-01-29T09:00:00Z", + "generator": "http://example.org/client1", + "generated": "2015-02-04T12:00:00Z", + "body": { + "id": "http://example.net/review1", + "creator": "http://example.net/user2", + "created": "2014-06-02T17:00:00Z" + }, + "target": "http://example.com/restaurant1" +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno15.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno15.json new file mode 100644 index 0000000000..1280eef4cd --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno15.json @@ -0,0 +1,20 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno15", + "type": "Annotation", + "creator": { + "id": "http://example.org/user1", + "type": "Person", + "name": "My Pseudonym", + "nickname": "pseudo", + "email_sha1": "58bad08927902ff9307b621c54716dcc5083e339" + }, + "generator": { + "id": "http://example.org/client1", + "type": "Software", + "name": "Code v2.1", + "homepage": "http://example.org/client1/homepage1" + }, + "body": "http://example.net/review1", + "target": "http://example.com/restaurant1" +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno16.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno16.json new file mode 100644 index 0000000000..f110169c34 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno16.json @@ -0,0 +1,12 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno16", + "type": "Annotation", + "audience": { + "id": "http://example.edu/roles/teacher", + "type": "schema:EducationalAudience", + "schema:educationalRole": "teacher" + }, + "body": "http://example.net/classnotes1", + "target": "http://example.com/textbook1" +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno17.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno17.json new file mode 100644 index 0000000000..056b8e4f53 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno17.json @@ -0,0 +1,12 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno17", + "type": "Annotation", + "motivation": "commenting", + "body": "http://example.net/comment1", + "target": { + "id": "http://example.com/video1", + "type": "Video", + "accessibility": "captions" + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno18.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno18.json new file mode 100644 index 0000000000..cdf50fcd8f --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno18.json @@ -0,0 +1,19 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno18", + "type": "Annotation", + "motivation": "bookmarking", + "body": [ + { + "type": "TextualBody", + "value": "readme", + "purpose": "tagging" + }, + { + "type": "TextualBody", + "value": "A good description of the topic that bears further investigation", + "purpose": "describing" + } + ], + "target": "http://example.com/page1" +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno19.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno19.json new file mode 100644 index 0000000000..bd572ce688 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno19.json @@ -0,0 +1,11 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno19", + "type": "Annotation", + "rights": "https://creativecommons.org/publicdomain/zero/1.0/", + "body": { + "id": "http://example.net/review1", + "rights": "http://creativecommons.org/licenses/by-nc/4.0/" + }, + "target": "http://example.com/product1" +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno2.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno2.json new file mode 100644 index 0000000000..f950f2db8d --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno2.json @@ -0,0 +1,17 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno2", + "type": "Annotation", + "body": { + "id": "http://example.org/analysis1.mp3", + "format": "audio/mpeg", + "language": "fr" + }, + "target": { + "id": "http://example.gov/patent1.pdf", + "format": "application/pdf", + "language": ["en", "ar"], + "textDirection": "ltr", + "processingLanguage": "en" + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno2.jsonld b/testing/web-platform/tests/annotation-protocol/files/annotations/anno2.jsonld new file mode 100644 index 0000000000..22e96e11c8 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno2.jsonld @@ -0,0 +1,11 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "/annotations/anno2.jsonld", + "type": "Annotation", + "created": "2015-02-31T12:03:45Z", + "body": { + "type": "TextualBody", + "value": "Great resource!" + }, + "target": "http://www.w3c.org/" +} diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno20.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno20.json new file mode 100644 index 0000000000..17fcc7185a --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno20.json @@ -0,0 +1,12 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno20", + "type": "Annotation", + "canonical": "urn:uuid:dbfb1861-0ecf-41ad-be94-a584e5c4f1df", + "via": "http://other.example.org/anno1", + "body": { + "id": "http://example.net/review1", + "rights": "http://creativecommons.org/licenses/by/4.0/" + }, + "target": "http://example.com/product1" +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno21.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno21.json new file mode 100644 index 0000000000..9ae284df04 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno21.json @@ -0,0 +1,14 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno21", + "type": "Annotation", + "body": { + "type": "SpecificResource", + "purpose": "tagging", + "source": "http://example.org/city1" + }, + "target": { + "id": "http://example.org/photo1", + "type": "Image" + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno22.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno22.json new file mode 100644 index 0000000000..767080c05e --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno22.json @@ -0,0 +1,13 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno22", + "type": "Annotation", + "body": { + "source": "http://example.org/page1", + "selector": "http://example.org/paraselector1" + }, + "target": { + "source": "http://example.com/dataset1", + "selector": "http://example.org/dataselector1" + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno23.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno23.json new file mode 100644 index 0000000000..53b6c676b1 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno23.json @@ -0,0 +1,15 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno23", + "type": "Annotation", + "body": { + "source": "http://example.org/video1", + "purpose": "describing", + "selector": { + "type": "FragmentSelector", + "conformsTo": "http://www.w3.org/TR/media-frags/", + "value": "t=30,60" + } + }, + "target": "http://example.org/image1" +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno24.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno24.json new file mode 100644 index 0000000000..6cca6cb0a6 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno24.json @@ -0,0 +1,13 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno24", + "type": "Annotation", + "body": "http://example.org/note1", + "target": { + "source": "http://example.org/page1.html", + "selector": { + "type": "CssSelector", + "value": "#elemid > .elemclass + p" + } + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno25.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno25.json new file mode 100644 index 0000000000..0e48d3be6a --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno25.json @@ -0,0 +1,13 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno25", + "type": "Annotation", + "body": "http://example.org/note1", + "target": { + "source": "http://example.org/page1.html", + "selector": { + "type": "XPathSelector", + "value": "/html/body/p[2]/table/tr[2]/td[3]/span" + } + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno26.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno26.json new file mode 100644 index 0000000000..1777fd781b --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno26.json @@ -0,0 +1,15 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno26", + "type": "Annotation", + "body": "http://example.org/comment1", + "target": { + "source": "http://example.org/page1", + "selector": { + "type": "TextQuoteSelector", + "exact": "anotation", + "prefix": "this is an ", + "suffix": " that has some" + } + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno27.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno27.json new file mode 100644 index 0000000000..4844979a24 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno27.json @@ -0,0 +1,14 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno27", + "type": "Annotation", + "body": "http://example.org/review1", + "target": { + "source": "http://example.org/ebook1", + "selector": { + "type": "TextPositionSelector", + "start": 412, + "end": 795 + } + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno28.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno28.json new file mode 100644 index 0000000000..2dd1bc7afc --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno28.json @@ -0,0 +1,14 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno28", + "type": "Annotation", + "body": "http://example.org/note1", + "target": { + "source": "http://example.org/diskimg1", + "selector": { + "type": "DataPositionSelector", + "start": 4096, + "end": 4104 + } + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno29.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno29.json new file mode 100644 index 0000000000..77914acaeb --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno29.json @@ -0,0 +1,13 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno29", + "type": "Annotation", + "body": "http://example.org/road1", + "target": { + "source": "http://example.org/map1", + "selector": { + "id": "http://example.org/svg1", + "type": "SvgSelector" + } + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno3.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno3.json new file mode 100644 index 0000000000..548825c76b --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno3.json @@ -0,0 +1,13 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno3", + "type": "Annotation", + "body": { + "id": "http://example.org/video1", + "type": "Video" + }, + "target": { + "id": "http://example.org/website1", + "type": "Text" + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno3.jsonld b/testing/web-platform/tests/annotation-protocol/files/annotations/anno3.jsonld new file mode 100644 index 0000000000..ef1bc83b55 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno3.jsonld @@ -0,0 +1,8 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "/annotations/anno3.jsonld", + "type": "Annotation", + "target": "http://w3c.org/", + "body": "https://en.wikipedia.org/wiki/World_Wide_Web_Consortium", + "movivation": "describing" +} diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno30.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno30.json new file mode 100644 index 0000000000..1a7619afc1 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno30.json @@ -0,0 +1,13 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno30", + "type": "Annotation", + "body": "http://example.org/road1", + "target": { + "source": "http://example.org/map1", + "selector": { + "type": "SvgSelector", + "value": "<svg:svg> ... </svg:svg>" + } + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno31.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno31.json new file mode 100644 index 0000000000..ee6902c396 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno31.json @@ -0,0 +1,20 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno31", + "type": "Annotation", + "body": "http://example.org/comment1", + "target": { + "source": "http://example.org/page1.html", + "selector": { + "type": "RangeSelector", + "startSelector": { + "type": "XPathSelector", + "value": "//table[1]/tr[1]/td[2]" + }, + "endSelector": { + "type": "XPathSelector", + "value": "//table[1]/tr[1]/td[4]" + } + } + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno32.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno32.json new file mode 100644 index 0000000000..f7c189ceb6 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno32.json @@ -0,0 +1,19 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno32", + "type": "Annotation", + "body": "http://example.org/comment1", + "target": { + "source": "http://example.org/page1", + "selector": { + "type": "FragmentSelector", + "value": "para5", + "refinedBy": { + "type": "TextQuoteSelector", + "exact": "Selected Text", + "prefix": "text before the ", + "suffix": " and text after it" + } + } + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno33.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno33.json new file mode 100644 index 0000000000..f2866eef56 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno33.json @@ -0,0 +1,12 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno33", + "type": "Annotation", + "body": "http://example.org/note1", + "target": { + "source": "http://example.org/page1", + "state": { + "id": "http://example.org/state1" + } + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno34.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno34.json new file mode 100644 index 0000000000..c3df38c282 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno34.json @@ -0,0 +1,14 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno34", + "type": "Annotation", + "body": "http://example.org/note1", + "target": { + "source": "http://example.org/page1", + "state": { + "type": "TimeState", + "cached": "http://archive.example.org/copy1", + "sourceDate": "2015-07-20T13:30:00Z" + } + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno35.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno35.json new file mode 100644 index 0000000000..39050abb13 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno35.json @@ -0,0 +1,13 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno35", + "type": "Annotation", + "body": "http://example.org/description1", + "target": { + "source": "http://example.org/resource1", + "state": { + "type": "HttpRequestState", + "value": "Accept: application/pdf" + } + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno36.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno36.json new file mode 100644 index 0000000000..77581efb3d --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno36.json @@ -0,0 +1,22 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno36", + "type": "Annotation", + "body": "http://example.org/comment1", + "target": { + "source": "http://example.org/ebook1", + "state": { + "type": "TimeState", + "sourceDate": "2016-02-01T12:05:23Z", + "refinedBy": { + "type": "HttpRequestState", + "value": "Accept: application/pdf", + "refinedBy": { + "type": "FragmentSelector", + "value": "page=10", + "conformsTo": "http://tools.ietf.org/rfc/rfc3778" + } + } + } + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno37.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno37.json new file mode 100644 index 0000000000..96796db9a0 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno37.json @@ -0,0 +1,11 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno37", + "type": "Annotation", + "stylesheet": "http://example.org/style1", + "body": "http://example.org/comment1", + "target": { + "source": "http://example.org/document1", + "styleClass": "red" + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno38.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno38.json new file mode 100644 index 0000000000..67f24b80ea --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno38.json @@ -0,0 +1,14 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno38", + "type": "Annotation", + "stylesheet": { + "type": "CssStylesheet", + "value": ".red { color: red }" + }, + "body": "http://example.org/body1", + "target": { + "source": "http://example.org/target1", + "styleClass": "red" + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno39.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno39.json new file mode 100644 index 0000000000..27e639cd34 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno39.json @@ -0,0 +1,15 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno39", + "type": "Annotation", + "body": "http://example.org/comment1", + "target": { + "source": "http://example.edu/article.pdf", + "selector": "http://example.org/selectors/html-selector1", + "renderedVia": { + "id": "http://example.com/pdf-to-html-library", + "type": "Software", + "schema:softwareVersion": "2.5" + } + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno4.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno4.json new file mode 100644 index 0000000000..5655fb39e1 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno4.json @@ -0,0 +1,11 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno4", + "type": "Annotation", + "body": "http://example.org/description1", + "target": { + "id": "http://example.com/image1#xywh=100,100,300,300", + "type": "Image", + "format": "image/jpeg" + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno40.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno40.json new file mode 100644 index 0000000000..63b47f145c --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno40.json @@ -0,0 +1,10 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno40", + "type": "Annotation", + "body": "http://example.org/note1", + "target": { + "source": "http://example.org/image1", + "scope": "http://example.org/page1" + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno41.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno41.json new file mode 100644 index 0000000000..80afc74894 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno41.json @@ -0,0 +1,82 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno41", + "type": "Annotation", + "motivation": "commenting", + "creator": { + "id": "http://example.org/user1", + "type": "Person", + "name": "A. Person", + "nickname": "user1" + }, + "created": "2015-10-13T13:00:00Z", + "generator": { + "id": "http://example.org/client1", + "type": "Software", + "name": "Code v2.1", + "homepage": "http://example.org/homepage1" + }, + "generated": "2015-10-14T15:13:28Z", + "stylesheet": { + "id": "http://example.org/stylesheet1", + "type": "CssStylesheet" + }, + "body": [ + { + "type": "TextualBody", + "purpose": "tagging", + "value": "love" + }, + { + "type": "Choice", + "items": [ + { + "type": "TextualBody", + "purpose": "describing", + "value": "I really love this particular bit of text in this XML. No really.", + "format": "text/plain", + "language": "en", + "creator": "http://example.org/user1" + }, + { + "type": "SpecificResource", + "purpose": "describing", + "source": { + "id": "http://example.org/comment1", + "type": "Audio", + "format": "audio/mpeg", + "language": "de", + "creator": { + "id": "http://example.org/user2", + "type": "Person" + } + } + } + ] + } + ], + "target": { + "type": "SpecificResource", + "styleClass": "mystyle", + "source": "http://example.com/document1", + "state": [ + { + "type": "HttpRequestState", + "value": "Accept: application/xml", + "refinedBy": { + "type": "TimeState", + "sourceDate": "2015-09-25T12:00:00Z" + } + } + ], + "selector": { + "type": "FragmentSelector", + "value": "xpointer(/doc/body/section[2]/para[1])", + "refinedBy": { + "type": "TextPositionSelector", + "start": 6, + "end": 27 + } + } + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno5.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno5.json new file mode 100644 index 0000000000..85532e8785 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno5.json @@ -0,0 +1,12 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno5", + "type":"Annotation", + "body": { + "type" : "TextualBody", + "value" : "<p>j'adore !</p>", + "format" : "text/html", + "language" : "fr" + }, + "target": "http://example.org/photo1" +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno6.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno6.json new file mode 100644 index 0000000000..9ae0d88eae --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno6.json @@ -0,0 +1,7 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno6", + "type":"Annotation", + "bodyValue": "Comment text", + "target": "http://example.org/target1" +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno7.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno7.json new file mode 100644 index 0000000000..9f821d22fb --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno7.json @@ -0,0 +1,11 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno7", + "type":"Annotation", + "body": { + "type": "TextualBody", + "value": "Comment text", + "format": "text/plain" + }, + "target": "http://example.org/target1" +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno8.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno8.json new file mode 100644 index 0000000000..3187af6574 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno8.json @@ -0,0 +1,6 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno8", + "type": "Annotation", + "target": "http://example.org/ebook1" +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/anno9.json b/testing/web-platform/tests/annotation-protocol/files/annotations/anno9.json new file mode 100644 index 0000000000..8d72da3563 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/anno9.json @@ -0,0 +1,16 @@ +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://example.org/anno9", + "type": "Annotation", + "body": [ + "http://example.org/description1", + { + "type": "TextualBody", + "value": "tag1" + } + ], + "target": [ + "http://example.org/image1", + "http://example.org/image2" + ] +}
\ No newline at end of file diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/annotation.headers b/testing/web-platform/tests/annotation-protocol/files/annotations/annotation.headers new file mode 100644 index 0000000000..e69ef1cde2 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/annotation.headers @@ -0,0 +1,4 @@ +Content-Type: application/ld+json; profile="http://www.w3.org/ns/anno.jsonld" +Link: <http://www.w3.org/ns/ldp#Resource>; rel="type" +Allow: GET,HEAD,OPTIONS,DELETE,PUT +Vary: Accept diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/annotation.options.headers b/testing/web-platform/tests/annotation-protocol/files/annotations/annotation.options.headers new file mode 100644 index 0000000000..48973d7459 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/annotation.options.headers @@ -0,0 +1,2 @@ +Allow: GET,HEAD,OPTIONS,DELETE,PUT +Vary: Accept diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/collection.headers b/testing/web-platform/tests/annotation-protocol/files/annotations/collection.headers new file mode 100644 index 0000000000..a6dc59ecb2 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/collection.headers @@ -0,0 +1,5 @@ +Content-Type: application/ld+json; profile="http://www.w3.org/ns/anno.jsonld" +Link: <http://www.w3.org/ns/ldp#BasicContainer>; rel="type" +Link: <http://www.w3.org/TR/annotation-protocol/>; rel="http://www.w3.org/ns/ldp#constrainedBy" +Allow: POST,GET,OPTIONS,HEAD +Vary: Accept, Prefer diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/collection.options.headers b/testing/web-platform/tests/annotation-protocol/files/annotations/collection.options.headers new file mode 100644 index 0000000000..7bc301662c --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/collection.options.headers @@ -0,0 +1,2 @@ +Allow: POST,GET,OPTIONS,HEAD,DELETE,PUT +Vary: Accept, Prefer diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/cors.headers b/testing/web-platform/tests/annotation-protocol/files/annotations/cors.headers new file mode 100644 index 0000000000..816bc214a5 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/cors.headers @@ -0,0 +1,4 @@ +Access-Control-Allow-Headers: Content-Type, Prefer +Access-Control-Allow-Methods: GET,HEAD,OPTIONS,DELETE,PUT +Access-Control-Allow-Origin: * +Access-Control-Expose-Headers: ETag, Allow, Vary, Link, Content-Type, Location, Content-Location, Prefer diff --git a/testing/web-platform/tests/annotation-protocol/files/annotations/page.headers b/testing/web-platform/tests/annotation-protocol/files/annotations/page.headers new file mode 100644 index 0000000000..7cb1c19c65 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/annotations/page.headers @@ -0,0 +1,3 @@ +Content-Type: application/ld+json; profile="http://www.w3.org/ns/anno.jsonld" +Allow: GET,OPTIONS,HEAD +Vary: Accept, Prefer diff --git a/testing/web-platform/tests/annotation-protocol/files/index.html b/testing/web-platform/tests/annotation-protocol/files/index.html new file mode 100644 index 0000000000..146fd3aed4 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/index.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width"> + <title>Annotate This Page</title> +</head> +<body> + + <h4>Test Your Annotation Client <em>Today!</em></h4> + + <blockquote>A conforming Web Annotation Protocol client SHOULD be able to: + <ul> + <li>start from this page</li> + <li>discover the <em>recommended</em> AnnotationCollection</li> + <li>read existing annotations from that AnnotationCollection</li> + <li>create, update, and delete annotations from that AnnotationCollection</li> + </ul> + </blockquote> + + <p>Interoperability between systems has two basic aspects: the syntax and semantics of the data that is moved between the systems, and the transport mechanism for that movement. The HTTP protocol and the Web architecture provides us with a great starting point for a standardized transport layer, and can be used to move content between systems easily and effectively. Building upon these foundations allows us to make use of existing technology and patterns to ensure consistency and ease of development.</p> + + <p>The Web Annotation Protocol describes a transport mechanism for creating, managing, and retrieving Annotations. Annotations in this specification are assumed to follow the requirements of the Web Annotation Data Model [<cite><a href="#bib-annotation-model" class="bibref">annotation-model</a></cite>] and Web Annotation Vocabulary [<cite><a href="#bib-annotation-vocab" class="bibref">annotation-vocab</a></cite>]. This specification builds upon REST principles and the Linked Data Platform [<cite><a href="#bib-ldp" class="bibref">ldp</a></cite>] recommendation, and familiarity with it is recommended.</p> + +</body> +</html> diff --git a/testing/web-platform/tests/annotation-protocol/files/index.html.headers b/testing/web-platform/tests/annotation-protocol/files/index.html.headers new file mode 100644 index 0000000000..2b5c176a69 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/files/index.html.headers @@ -0,0 +1 @@ +Link: </annotations/>; rel="http://www.w3.org/ns/oa#annotationService" diff --git a/testing/web-platform/tests/annotation-protocol/server/server-manual.html b/testing/web-platform/tests/annotation-protocol/server/server-manual.html new file mode 100644 index 0000000000..cc9fbd9105 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/server/server-manual.html @@ -0,0 +1,413 @@ +<!doctype html> +<html> +<head> +<title>Annotation Protocol Must Tests</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script type="application/javascript"> +/* globals header, assert_equals, promise_test, assert_true, uuid, assert_regexp_match */ + +/* jshint unused: false, strict: false */ + +setup( { explicit_timeout: true, explicit_done: true } ); + +// just ld+json here as the full profile'd media type is a SHOULD +var MEDIA_TYPE = 'application/ld+json'; +var MEDIA_TYPE_REGEX = /application\/ld\+json/; +// a request timeout if there is not one specified in the parent window + +var myTimeout = 5000; + +function request(method, url, headers, content) { + if (method === undefined) { + method = "GET"; + } + + return new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + + // this gets returned when the request completes + var resp = { + xhr: xhr, + headers: null, + status: 0, + body: null, + text: "" + }; + + xhr.open(method, url); + + // headers? + if (headers !== undefined) { + headers.forEach(function(ref, idx) { + xhr.setRequestHeader(ref[0], ref[1]); + }); + } + + // xhr.timeout = myTimeout; + + xhr.ontimeout = function() { + resp.timeout = myTimeout; + resolve(resp); + }; + + xhr.onerror = function() { + resolve(resp); + }; + + xhr.onload = function () { + resp.status = this.status; + if (this.status >= 200 && this.status < 300) { + var d = xhr.response; + resp.text = d; + // we have it; what is it? + var type = xhr.getResponseHeader('Content-Type'); + if (type) { + resp.type = type.split(';')[0]; + if (resp.type === MEDIA_TYPE) { + try { + d = JSON.parse(d); + resp.body = d; + } + catch(err) { + resp.body = null; + } + } + } else { + resp.type = null; + resp.body = null; + } + + } + resolve(resp); + }; + + if (content !== undefined) { + if ("object" === typeof(content)) { + xhr.send(JSON.stringify(content)); + } else if ("function" === typeof(content)) { + xhr.send(content()); + } else if ("string" === typeof(content)) { + xhr.send(content); + } + } else { + xhr.send(); + } + }); +} + +function checkBody(res, pat, isRE) { + if (isRE === undefined) { + isRE = true; + } + if (!res.body) { + if (isRE) { + assert_regexp_match("", pat, header + " not found in body"); + } else { + assert_equals("", pat, header + " not found in body") ; + } + } else { + if (isRE) { + assert_regexp_match(res.body, pat, pat + " not found in body "); + } else { + assert_equals(res.body, pat, pat + " not found in body"); + } + } +} + +function checkHeader(res, header, pat, isRE) { + if (isRE === undefined) { + isRE = true; + } + if (!res.xhr.getResponseHeader(header)) { + if (isRE) { + assert_regexp_match("", pat, header + " not found in response"); + } else { + assert_equals("", pat, header + " not found in response") ; + } + } else { + var val = res.xhr.getResponseHeader(header) ; + if (isRE) { + assert_regexp_match(val, pat, pat + " not found in " + header); + } else { + assert_equals(val, pat, pat + " not found in " + header); + } + } +} + +/* makePromiseTests + * + * thennable - Promise that when resolved will send data into the test + * criteria - Array of assertions + */ + +function makePromiseTests( thennable, criteria ) { + // loop over the array of criteria + // + // create a promise_test for each one + criteria.forEach(function(ref) { + promise_test(function() { + return thennable.then(function(res) { + if (ref.header !== undefined) { + // it is a header check + if (ref.pat !== undefined) { + checkHeader(res, ref.header, ref.pat, true); + } else if (ref.string !== undefined) { + checkHeader(res, ref.header, ref.string, false); + } else if (ref.test !== undefined) { + assert_true(ref.test(res)); + } + } else { + if (ref.pat !== undefined) { + checkBody(res, ref.pat, true); + } else if (ref.string !== undefined) { + checkBody(res, ref.string, false); + } else if (ref.test !== undefined) { + assert_true(ref.test(res)); + } + } + }); + }, ref.assertion); + }); +} + +function runTests( container_url, annotation_url ) { + // trim whitespace from incoming variables + container_url = container_url.trim(); + annotation_url = annotation_url.trim(); + + // Section 4 has a requirement that the URL end in a slash, so... + // ensure the url has a length + test(function() { + assert_regexp_match(container_url, /\/$/, 'Container URL did not end in a "/" character'); + }, 'Container MUST end in a "/" character'); + + // Container tests + var theContainer = request("GET", container_url); + + makePromiseTests( theContainer, [ + { header: 'Allow', pat: /GET/, assertion: "Containers MUST support GET (check Allow on GET)" }, + { header: 'Allow', pat: /HEAD/, assertion: "Containers MUST support HEAD (check Allow on GET)" }, + { header: 'Allow', pat: /OPTIONS/, assertion: "Containers MUST support OPTIONS (check Allow on GET)" }, + { header: 'Content-Type', pat: MEDIA_TYPE_REGEX, assertion: 'Containers MUST have a Content-Type header with the application/ld+json media type'}, + { header: 'Content-Type', pat: MEDIA_TYPE_REGEX, assertion: 'Containers MUST response with the JSON-LD representation (by default)'}, + { test: function(res) { return ( 'type' in res.body && res.body.type.indexOf('BasicContainer') > -1 ); }, assertion: 'Containers MUST return a description of the container with BasicContainer' }, + { test: function(res) { return ( 'type' in res.body && res.body.type.indexOf('AnnotationCollection') > -1 ); }, assertion: 'Containers MUST return a description of the container with AnnotationCollection' }, + { header: 'Link', pat: /(.*)/, assertion: 'Containers MUST return a Link header (rfc5988) on all responses' }, + { header: 'ETag', pat: /(.*)/, assertion: 'Containers MUST have an ETag header'}, + { header: 'Vary', pat: /Accept/, assertion: 'Containers MUST have a Vary header with Accept in the value'}, + { header: 'Link', pat: /rel\=\"type\"|\/ns\/ldp#|Container/, assertion: 'Containers MUST advertise its type by including a link where the rel parameter value is type and the target IRI is the appropriate Container Type'}, + { header: 'Link', pat: /rel\=\"type\"|\/ns\/ldp#|Container/, + assertion: 'Containers MUST advertise that it imposes Annotation protocol specific' + + ' constraints by including a link where the target IRI is' + + ' http://www.w3.org/TR/annotation-protocol/, and the rel parameter' + + ' value is the IRI http://www.w3.org/ns/ldp#constrainedBy'}, + ] ); + + + promise_test(function() { + return request("HEAD", container_url).then(function(res) { + assert_equals(res.status, 200, "HEAD request returned " + res.status); + }); + }, "Containers MUST support HEAD method"); + + promise_test(function() { + return request("OPTIONS", container_url).then(function(res) { + assert_equals(res.status, 200, "OPTIONS request returned " + res.status); + }); + }, "Containers MUST support OPTIONS method"); + + // Container representation tests + + + makePromiseTests( theContainer, [ + { header: 'Content-Location', pat: /(.*)/, assertion: "Containers MUST include a Content-Location header with the IRI as its value" }, + { header: 'Content-Location', test: function(res) { if (res.xhr.getResponseHeader('content-location') === res.body.id ) { return true; } else { return false;} }, assertion: "Container's Content-Location and `id` MUST match" } + ]); + + promise_test(function() { + return theContainer.then(function(res) { + var f = res.body.first; + if (f !== undefined && f !== "") { + request("GET", f).then(function(lres) { + assert_true(('partOf' in lres.body) || ('id' in lres.body.partOf), "No partOf in response"); + }); + } else { + assert_true(false, "no 'first' in response from Container"); + } + }); + }, "Annotation Pages must have a link to the container they are part of, using the partOf property"); + + promise_test(function() { + return theContainer.then(function(res) { + var l = res.body.last; + request("GET", l).then(function(lres) { + assert_true(('prev' in lres.body), "No link to the previous page in response"); + }); + }); + }, "Annotation Pages MUST have a link to the previous page in the sequence, using the prev property (if not the first page)"); + + promise_test(function() { + return theContainer.then(function(res) { + var f = res.body.first; + request("GET", f).then(function(lres) { + assert_true(('next' in lres.body), "No link to the next page in response"); + }); + }); + }, "Annotation Pages MUST have a link to the next page in the sequence, using the next property (if not the last page)"); + + // Annotation Tests + var theRequest = request("GET", annotation_url); + + makePromiseTests( theRequest, [ + { header: 'Allow', pat: /GET/, assertion: "Annotations MUST support GET (check Allow on GET)" }, + { header: 'Allow', pat: /HEAD/, assertion: "Annotations MUST support HEAD (check Allow on GET)" }, + { header: 'Allow', pat: /OPTIONS/, assertion: "Annotations MUST support OPTIONS (check Allow on GET)" }, + { header: 'Content-Type', pat: MEDIA_TYPE_REGEX, assertion: 'Annotations MUST have a Content-Type header with the application/ld+json media type'}, + { header: 'Link', string: '<http://www.w3.org/ns/ldp#Resource>; rel="type"', assertion: 'Annotations MUST have a Link header entry where the target IRI is http://www.w3.org/ns/ldp#Resource and the rel parameter value is type'}, + { header: 'ETag', pat: /(.*)/, assertion: 'Annotations MUST have an ETag header'}, + { header: 'Vary', pat: /Accept/, assertion: 'Annotations MUST have a Vary header with Accept in the value'}, + ] ); + + promise_test(function() { + return request("HEAD", annotation_url).then(function(res) { + assert_equals(res.status, 200, "HEAD request returned " + res.status); + }); + }, "Annotations MUST support HEAD method"); + + promise_test(function() { + return request("OPTIONS", annotation_url).then(function(res) { + assert_equals(res.status, 200, "OPTIONS request returned " + res.status); + }); + }, "Annotations MUST support OPTIONS method"); + + + // creation and deletion tests + + var theAnnotation = { + "@context": "http://www.w3.org/ns/anno.jsonld", + "type": "Annotation", + "body": { + "type": "TextualBody", + "value": "I like this page!" + }, + "target": "http://www.example.com/index.html", + "canonical": 'urn:uuid:' + token() + }; + + var theCreation = request("POST", container_url, [ [ 'Content-Type', MEDIA_TYPE ] ], theAnnotation); + + makePromiseTests( theCreation, [ + { test: function(res) { return ('id' in res.body); }, assertion: "Created Annotation MUST have an id property" }, + { test: function(res) { return (('id' in res.body) && (res.body.id.search(container_url) > -1));}, assertion: "Created Annotation MUST have an id that starts with the Container IRI" }, + { test: function(res) { return ( 'canonical' in res.body && res.body.canonical === theAnnotation.canonical ); }, assertion: "Created Annotation MUST preserve any canonical IRI" }, + { test: function(res) { return ( res.status === 201 ) ; }, assertion: "Annotation Server MUST respond with a 201 Created code if the creation is successful" }, + { header: "Location", test: function(res) { return res.body.id === res.xhr.getResponseHeader('location') ; } , assertion: "Location header SHOULD match the id of the new Annotation" }, + ]); + + promise_test(function() { + return theCreation.then(function(res) { + var newAnnotation = res.body ; + newAnnotation.target = "http://other.example/"; + return request("PUT", res.body.id, [['Content-Type', MEDIA_TYPE]], newAnnotation) + .then(function(lres) { + assert_equals(lres.body.target, newAnnotation.target, "Annotation did not update"); + }) + .catch(function(err) { + assert_true(false, "Update of annotation failed"); + }); + }); + }, "Annotation update must be done with the PUT method"); + + promise_test(function() { + return theCreation.then(function(res) { + request("DELETE", res.body.id) + .then(function(lres) { + assert_equals(lres.status, 204, "DELETE of " + res.body.id + " did not return a 204 Status" ); + }); + }); + }, "Annotation deletion with DELETE method MUST return a 204 status" ); + + // SHOULD tests + + test(function() { + assert_equals(container_url.toLowerCase().substr(0,5), "https", "Server is not using HTTPS"); + }, "Annotation server SHOULD use HTTPS rather than HTTP"); + + var thePrefRequest = request("GET", container_url, + [['Prefer', 'return=representation;include="http://www.w3.org/ns/ldp#PreferMinimalContainer"']]); + + promise_test(function() { + return thePrefRequest + .then(function(res) { + var f = res.body.first; + request("GET", f).then(function(fres) { + fres.body.items.forEach(function(item) { + assert_true('@context' in item, "Annotation does not contain `@context`"); + }); + }); + }); + }, "SHOULD return the full annotation descriptions"); + + + makePromiseTests( thePrefRequest, [ + { test: function(res) { return ('total' in res.body); }, assertion: "SHOULD include the total property with the total number of annotations in the container" }, + { test: function(res) { return ('first' in res.body); }, assertion: "SHOULD have a link to the first page of its contents using `first`" }, + { test: function(res) { return ('last' in res.body); }, assertion: "SHOULD have a link to the last page of its contents using `last`" }, + { test: function(res) { return (!('items' in res.body)); }, assertion: "Response contains annotations via `items` when it SHOULD NOT"}, + { test: function(res) { return (!('ldp:contains' in res.body)); }, assertion: "Response contains annotations via `ldp:contains` when it SHOULD NOT" }, + { header: 'Vary', pat: /Prefer/, assertion: "SHOULD include Prefer in the Vary header" } + ]); + + promise_test(function() { + return thePrefRequest + .then(function(res) { + var h = res.xhr.getResponseHeader('Prefer'); + assert_equals(h, null, "Reponse contains the `Prefer` header when it SHOULD NOT"); + }); + }, 'SHOULD NOT [receive] the Prefer header when requesting the page'); + +} + +// set up an event handler one the document is loaded that will run the tests once we +// have a URI to run against +on_event(document, "DOMContentLoaded", function() { + var serverURI = document.getElementById("uri") ; + var annotationURI = document.getElementById("annotation") ; + var runButton = document.getElementById("endpoint-submit-button") ; + on_event(runButton, "click", function() { + // user clicked + var URI = serverURI.value; + var ANN = annotationURI.value; + runButton.disabled = true; + + // okay, they clicked. run the tests with that URI + runTests(URI, ANN); + done(); + }); + }); +</script> +</head> +<body> +<p>The scripts associated with this test will exercise all of the MUST and SHOULD requirements +for an Annotation Protocol server implementation. In order to do so, the server must have +its CORS settings configured such that your test machine can access the annotations and containers +and such that it can get certain information from the headers. In particular, the container and +annotations within the container +under test must permit access to the Allow, Content-Location, Content-Type, ETag, Link, Location, Prefer, and Vary headers. +Correct CORS access can be achieved with headers like:</p> +<pre> +Access-Control-Allow-Headers: Content-Type, Prefer +Access-Control-Allow-Methods: GET,HEAD,OPTIONS,DELETE,PUT +Access-Control-Allow-Origin: * +Access-Control-Expose-Headers: ETag, Allow, Vary, Link, Content-Type, Location, Content-Location, Prefer +</pre> +<p>Provide endpoint and annotation URIs and select "Go" to start testing.</p> +<form name="endpoint"> + <p><label for="uri">Endpoint URI:</label> <input type="text" size="50" id="uri" name="uri"></p> + <p><label for="uri">Annotation URI:</label> <input type="text" size="50" id="annotation" name="annotation"></p> + <input type="button" id="endpoint-submit-button" value="Go"> +</form> +</body> +</html> diff --git a/testing/web-platform/tests/annotation-protocol/tools/protocol-server.py b/testing/web-platform/tests/annotation-protocol/tools/protocol-server.py new file mode 100644 index 0000000000..e5d1210001 --- /dev/null +++ b/testing/web-platform/tests/annotation-protocol/tools/protocol-server.py @@ -0,0 +1,429 @@ +# protocol-server +# +# a reference implementation of the Web Annotation Protocol +# +# Developed by Benjamin Young (@bigbulehat) and Shane McCarron (@halindrome). +# Sponsored by Spec-Ops (https://spec-ops.io) + +from __future__ import print_function + +import os +import sys + +here = os.path.abspath(os.path.dirname(__file__)) +repo_root = os.path.abspath(os.path.join(here, os.pardir, os.pardir)) + +sys.path.insert(0, os.path.join(repo_root, "tools")) +sys.path.insert(0, os.path.join(repo_root, "tools", "six")) +sys.path.insert(0, os.path.join(repo_root, "tools", "html5lib")) +sys.path.insert(0, os.path.join(repo_root, "tools", "wptserve")) +sys.path.insert(0, os.path.join(repo_root, "tools", "pywebsocket", "src")) +sys.path.insert(0, os.path.join(repo_root, "tools", "py")) +sys.path.insert(0, os.path.join(repo_root, "tools", "pytest")) +sys.path.insert(0, os.path.join(repo_root, "tools", "webdriver")) + +import hashlib +import json +import urlparse +import uuid + +import wptserve + +myprotocol = 'http' +myhost = 'localhost' +port = 8080 +doc_root = os.path.join(repo_root, "annotation-protocol", "files", "") +container_path = doc_root + 'annotations/' + +URIroot = myprotocol + '://' + myhost + ':{0}'.format(port) + +per_page = 10 + +MEDIA_TYPE = 'application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"' +# Prefer header variants +PREFER_MINIMAL_CONTAINER = "http://www.w3.org/ns/ldp#PreferMinimalContainer" +PREFER_CONTAINED_IRIS = "http://www.w3.org/ns/oa#PreferContainedIRIs" +PREFER_CONTAINED_DESCRIPTIONS = \ + "http://www.w3.org/ns/oa#PreferContainedDescriptions" + + +# dictionary for annotations that we create on the fly +tempAnnotations = {} + +def extract_preference(prefer): + """Extracts the parameters from a Prefer header's value + >>> extract_preferences('return=representation;include="http://www.w3.org/ns/ldp#PreferMinimalContainer http://www.w3.org/ns/oa#PreferContainedIRIs"') + {"return": "representation", "include": ["http://www.w3.org/ns/ldp#PreferMinimalContainer", "http://www.w3.org/ns/oa#PreferContainedIRIs"]} + """ + obj = {} + if prefer: + params = prefer.split(';') + for p in params: + key, value = p.split('=') + obj[key] = value.strip('"').split(' ') + return obj + + +def dump_json(obj): + return json.dumps(obj, indent=4, sort_keys=True) + +def add_cors_headers(resp): + headers_file = doc_root + 'annotations/cors.headers' + resp.headers.update(load_headers_from_file(headers_file)) + +def load_headers_from_file(path): + headers = [] + with open(path, 'r') as header_file: + data = header_file.read() + headers = [tuple(item.strip() for item in line.split(":", 1)) + for line in data.splitlines() if line] + return headers + +def annotation_files(): + files = [] + for file in os.listdir(container_path): + if file.endswith('.jsonld') or file.endswith('.json'): + files.append(file) + for item in list(tempAnnotations.keys()): + files.append(item) + return files + + +def annotation_iris(skip=0): + iris = [] + for filename in annotation_files(): + iris.append(URIroot + '/annotations/' + filename) + return iris[skip:][:per_page] + + +def annotations(skip=0): + annotations = [] + files = annotation_files() + for file in files: + if file.startswith("temp-"): + annotations.append(json.loads(tempAnnotations[file])) + else: + with open(container_path + file, 'r') as annotation: + annotations.append(json.load(annotation)) + return annotations + + +def total_annotations(): + return len(annotation_files()) + + +@wptserve.handlers.handler +def collection_get(request, response): + """Annotation Collection handler. NOTE: This also routes paging requests""" + + # Paginate if requested + qs = urlparse.parse_qs(request.url_parts.query) + if 'page' in qs: + return page(request, response) + + # stub collection + collection_json = { + "@context": [ + "http://www.w3.org/ns/anno.jsonld", + "http://www.w3.org/ns/ldp.jsonld" + ], + "id": URIroot + "/annotations/", + "type": ["BasicContainer", "AnnotationCollection"], + "total": 0, + "label": "A Container for Web Annotations", + "first": URIroot + "/annotations/?page=0" + } + + last_page = (total_annotations() / per_page) - 1 + collection_json['last'] = URIroot + "/annotations/?page={0}".format(last_page) + + # Default Container format SHOULD be PreferContainedDescriptions + preference = extract_preference(request.headers.get('Prefer')) + if 'include' in preference: + preference = preference['include'] + else: + preference = None + + collection_json['total'] = total_annotations() + # TODO: calculate last page and add it's page number + + if (qs.get('iris') and qs.get('iris')[0] is '1') \ + or (preference and PREFER_CONTAINED_IRIS in preference): + return_iris = True + else: + return_iris = False + + # only PreferContainedIRIs has unqiue content + if return_iris: + collection_json['id'] += '?iris=1' + collection_json['first'] += '&iris=1' + collection_json['last'] += '&iris=1' + + if preference and PREFER_MINIMAL_CONTAINER not in preference: + if return_iris: + collection_json['first'] = annotation_iris() + else: + collection_json['first'] = annotations() + + collection_headers_file = doc_root + 'annotations/collection.headers' + add_cors_headers(response) + response.headers.update(load_headers_from_file(collection_headers_file)) + # this one's unique per request + response.headers.set('Content-Location', collection_json['id']) + return dump_json(collection_json) + + +@wptserve.handlers.handler +def collection_head(request, response): + container_path = doc_root + request.request_path + if os.path.isdir(container_path): + response.status = 200 + else: + response.status = 404 + + add_cors_headers(response) + headers_file = doc_root + 'annotations/collection.headers' + for header, value in load_headers_from_file(headers_file): + response.headers.append(header, value) + + response.content = None + + +@wptserve.handlers.handler +def collection_options(request, response): + container_path = doc_root + request.request_path + if os.path.isdir(container_path): + response.status = 200 + else: + response.status = 404 + + add_cors_headers(response) + headers_file = doc_root + 'annotations/collection.options.headers' + for header, value in load_headers_from_file(headers_file): + response.headers.append(header, value) + +def page(request, response): + page_json = { + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": URIroot + "/annotations/", + "type": "AnnotationPage", + "partOf": { + "id": URIroot + "/annotations/", + "total": 42023 + }, + "next": URIroot + "/annotations/", + "items": [ + ] + } + + add_cors_headers(response) + headers_file = doc_root + 'annotations/collection.headers' + response.headers.update(load_headers_from_file(headers_file)) + + qs = urlparse.parse_qs(request.url_parts.query) + page_num = int(qs.get('page')[0]) + page_json['id'] += '?page={0}'.format(page_num) + + total = total_annotations() + so_far = (per_page * (page_num+1)) + remaining = total - so_far + + if page_num != 0: + page_json['prev'] = URIroot + '/annotations/?page={0}'.format(page_num-1) + + page_json['partOf']['total'] = total + + if remaining > per_page: + page_json['next'] += '?page={0}'.format(page_num+1) + else: + del page_json['next'] + + if qs.get('iris') and qs.get('iris')[0] is '1': + page_json['items'] = annotation_iris(so_far) + page_json['id'] += '&iris=1' + if 'prev' in page_json: + page_json['prev'] += '&iris=1' + if 'next' in page_json: + page_json['next'] += '&iris=1' + else: + page_json['items'] = annotations(so_far) + + return dump_json(page_json) + + +@wptserve.handlers.handler +def annotation_get(request, response): + """Individual Annotations""" + requested_file = doc_root + request.request_path[1:] + base = os.path.basename( requested_file ) + + headers_file = doc_root + 'annotations/annotation.headers' + + if base.startswith("temp-") and tempAnnotations[base]: + response.headers.update(load_headers_from_file(headers_file)) + response.headers.set('Etag', hashlib.sha1(base).hexdigest()) + data = dump_json(tempAnnotations[base]) + if data != "" : + response.content = data + response.status = 200 + else: + response.content = "" + response.status = 404 + elif os.path.isfile(requested_file): + response.headers.update(load_headers_from_file(headers_file)) + # Calculate ETag using Apache httpd's default method (more or less) + # http://www.askapache.info//2.3/mod/core.html#fileetag + statinfo = os.stat(requested_file) + etag = "{0}{1}{2}".format(statinfo.st_ino, statinfo.st_mtime, + statinfo.st_size) + # obfuscate so we don't leak info; hexdigest for string compatibility + response.headers.set('Etag', hashlib.sha1(etag).hexdigest()) + + with open(requested_file, 'r') as data_file: + data = data_file.read() + response.content = data + response.status = 200 + else: + response.content = 'Not Found' + response.status = 404 + + add_cors_headers(response) + + +@wptserve.handlers.handler +def annotation_head(request, response): + requested_file = doc_root + request.request_path[1:] + base = os.path.basename(requested_file) + + headers_file = doc_root + 'annotations/annotation.options.headers' + + if base.startswith("temp-") and tempAnnotations[base]: + response.status = 200 + response.headers.update(load_headers_from_file(headers_file)) + elif os.path.isfile(requested_file): + response.status = 200 + response.headers.update(load_headers_from_file(headers_file)) + else: + response.status = 404 + + add_cors_headers(response) + +@wptserve.handlers.handler +def annotation_options(request, response): + requested_file = doc_root + request.request_path[1:] + base = os.path.basename(requested_file) + + headers_file = doc_root + 'annotations/annotation.options.headers' + + if base.startswith("temp-") and tempAnnotations[base]: + response.status = 200 + response.headers.update(load_headers_from_file(headers_file)) + elif os.path.isfile(requested_file): + response.status = 200 + response.headers.update(load_headers_from_file(headers_file)) + else: + response.status = 404 + + add_cors_headers(response) + +def create_annotation(body): + # TODO: verify media type is JSON of some kind (at least) + incoming = json.loads(body) + id = "temp-"+str(uuid.uuid4()) + if 'id' in incoming: + incoming['canonical'] = incoming['id'] + incoming['id'] = URIroot + '/annotations/' + id + + return incoming + + +@wptserve.handlers.handler +def annotation_post(request, response): + incoming = create_annotation(request.body) + newID = incoming['id'] + key = os.path.basename(newID) + + print("post:" + newID) + print("post:" + key) + + tempAnnotations[key] = dump_json(incoming) + + headers_file = doc_root + 'annotations/annotation.headers' + response.headers.update(load_headers_from_file(headers_file)) + response.headers.append('Location', newID) + add_cors_headers(response) + response.content = dump_json(incoming) + response.status = 201 + +@wptserve.handlers.handler +def annotation_put(request, response): + incoming = create_annotation(request.body) + + # remember it in our local cache too + # tempAnnotations[request.request_path[1:]] = dump_jason(incoming) + newID = incoming['id'] + key = os.path.basename(newID) + + print("put:" + newID) + print("put:" + key) + + tempAnnotations[key] = dump_json(incoming) + + headers_file = doc_root + 'annotations/annotation.headers' + response.headers.update(load_headers_from_file(headers_file)) + response.headers.append('Location', incoming['id']) + add_cors_headers(response) + response.content = dump_json(incoming) + response.status = 200 + + +@wptserve.handlers.handler +def annotation_delete(request, response): + base = os.path.basename(request.request_path[1:]) + requested_file = doc_root + request.request_path[1:] + + add_cors_headers(response) + + headers_file = doc_root + 'annotations/annotation.headers' + + try: + if base.startswith("temp-"): + if tempAnnotations[base]: + del tempAnnotations[base] + else: + os.remove(requested_file) + response.headers.update(load_headers_from_file(headers_file)) + response.status = 204 + response.content = '' + except OSError: + response.status = 404 + response.content = 'Not Found' + +if __name__ == '__main__': + print('http://' + myhost + ':{0}/'.format(port)) + print('container URI is http://' + myhost + ':{0}/'.format(port) + "/annotations/") + print('example annotation URI is http://' + myhost + ':{0}/'.format(port) + "/annotations/anno1.json") + + routes = [ + ("GET", "", wptserve.handlers.file_handler), + ("GET", "index.html", wptserve.handlers.file_handler), + + # container/collection responses + ("HEAD", "annotations/", collection_head), + ("OPTIONS", "annotations/", collection_options), + ("GET", "annotations/", collection_get), + + # create annotations in the collection + ("POST", "annotations/", annotation_post), + + # single annotation responses + ("HEAD", "annotations/*", annotation_head), + ("OPTIONS", "annotations/*", annotation_options), + ("GET", "annotations/*", annotation_get), + ("PUT", "annotations/*", annotation_put), + ("DELETE", "annotations/*", annotation_delete) + ] + + httpd = wptserve.server.WebTestHttpd(host=myhost, bind_hostname=myhost, port=port, doc_root=doc_root, + routes=routes) + httpd.start(block=True) |