Skip to content

Commit 005428a

Browse files
authored
Add tests for CORS support (#72)
* Add tests for CORS support * Use CSS from dockerhub image. * Simplify tests and add higher level tests for server-cors requirement
1 parent 9e1cbf4 commit 005428a

11 files changed

Lines changed: 423 additions & 13 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Solid Specification Conformance Tests
22

33
## Release 0.0.7
4+
* Test CORS support.
45
* Change all github-related URIs to use accessible versions
56

67
## Release 0.0.6

protocol/cors/acao-vary.feature

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
Feature: Server returns correct Access-Control-Allow-Origin and Vary headers
2+
3+
Background: Set up test container
4+
* def testContainer = rootTestContainer.createContainer()
5+
* def resource = testContainer.createResource('.txt', 'Hello', 'text/plain')
6+
7+
Scenario Outline: Access-Control-Allow-Origin header is set to correct origin for <method> on container
8+
Given url testContainer.url
9+
And headers clients.alice.getAuthHeaders('<method>', testContainer.url)
10+
And header Origin = 'https://tester'
11+
When method <method>
12+
Then match <statuses> contains responseStatus
13+
And match header Access-Control-Allow-Origin == 'https://tester'
14+
And match response <check>
15+
Examples:
16+
| method | statuses | check |
17+
| OPTIONS | [200, 204] | == '' |
18+
| GET | [200] | != '' |
19+
| HEAD | [200] | == '' |
20+
21+
Scenario Outline: Vary header includes Origin for <method> on container
22+
Given url testContainer.url
23+
And headers clients.alice.getAuthHeaders('<method>', testContainer.url)
24+
And header Origin = 'https://tester'
25+
When method <method>
26+
Then match <statuses> contains responseStatus
27+
And match header Vary contains 'Origin'
28+
And match response <check>
29+
Examples:
30+
| method | statuses | check |
31+
| OPTIONS | [200, 204] | == '' |
32+
| GET | [200] | != '' |
33+
| HEAD | [200] | == '' |
34+
35+
Scenario Outline: Access-Control-Allow-Origin header is set to correct origin for <method> on resource
36+
Given url resource.url
37+
And headers clients.alice.getAuthHeaders('<method>', resource.url)
38+
And header Origin = 'https://tester'
39+
When method <method>
40+
Then match <statuses> contains responseStatus
41+
And match header Access-Control-Allow-Origin == 'https://tester'
42+
And match response <check>
43+
Examples:
44+
| method | statuses | check |
45+
| OPTIONS | [200, 204] | == '' |
46+
| GET | [200] | == 'Hello' |
47+
| HEAD | [200] | == '' |
48+
49+
Scenario Outline: Vary header includes Origin for <method> on resource
50+
Given url resource.url
51+
And headers clients.alice.getAuthHeaders('<method>', resource.url)
52+
And header Origin = 'https://tester'
53+
When method <method>
54+
Then match <statuses> contains responseStatus
55+
And match header Vary contains 'Origin'
56+
And match response <check>
57+
Examples:
58+
| method | statuses | check |
59+
| OPTIONS | [200, 204] | == '' |
60+
| GET | [200] | == 'Hello' |
61+
| HEAD | [200] | == '' |

protocol/cors/accept-acah.feature

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# This only applies to pre-flight checks since that is where Access-Control-Allow-Headers is used
2+
Feature: Server should explicitly list Accept under Access-Control-Allow-Headers
3+
4+
Background: Set up test container
5+
* def testContainer = rootTestContainer.createContainer()
6+
* def resource = testContainer.createResource('.txt', 'Hello', 'text/plain')
7+
8+
Scenario: OPTIONS request doesn't return Accept in Access-Control-Allow-Headers for GET pre-flight if not requested
9+
Given url testContainer.url
10+
And headers clients.alice.getAuthHeaders('OPTIONS', testContainer.url)
11+
And header Origin = 'https://tester'
12+
And header Access-Control-Request-Method = 'GET'
13+
And header Access-Control-Request-Headers = 'X-CUSTOM, Content-Type'
14+
When method OPTIONS
15+
Then match [200, 204] contains responseStatus
16+
And match response == ''
17+
And match header Access-Control-Allow-Headers !contains 'Accept'
18+
19+
Scenario: OPTIONS request returns Accept in Access-Control-Allow-Headers for POST pre-flight
20+
Given url testContainer.url
21+
And headers clients.alice.getAuthHeaders('OPTIONS', testContainer.url)
22+
And header Origin = 'https://tester'
23+
And header Access-Control-Request-Method = 'POST'
24+
And header Access-Control-Request-Headers = 'X-CUSTOM, Content-Type, Accept'
25+
When method OPTIONS
26+
Then match [200, 204] contains responseStatus
27+
And match response == ''
28+
And match header Access-Control-Allow-Headers contains 'Accept'
29+
30+
Scenario: OPTIONS request returns Accept in Access-Control-Allow-Headers for GET pre-flight with long Accept
31+
Given url testContainer.url
32+
And headers clients.alice.getAuthHeaders('OPTIONS', testContainer.url)
33+
And header Origin = 'https://tester'
34+
And header Access-Control-Request-Method = 'GET'
35+
And header Access-Control-Request-Headers = 'X-CUSTOM, Content-Type, Accept'
36+
When method OPTIONS
37+
Then match [200, 204] contains responseStatus
38+
And match response == ''
39+
And match header Access-Control-Allow-Headers contains 'Accept'
40+
41+
Given url testContainer.url
42+
And headers clients.alice.getAuthHeaders('GET', testContainer.url)
43+
And header Accept = 'text/turtle;q=0.9, application/rdf+xml;q=0.8, application/n-triples;q=0.8, application/n-quads;q=0.8, text/x-nquads;q=0.8, application/trig;q=0.8, text/n3;q=0.8, application/ld+json;q=0.8, application/x-binary-rdf;q=0.8, text/plain;q=0.7'
44+
When method GET
45+
Then status 200
46+
And match response != ''
47+
And match header Content-Type contains 'text/turtle'
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Simple requests
2+
# - Methods: GET, HEAD, POST
3+
# - Extra headers: Accept, Accept-Language, Content-Language, Content-Type (application/x-www-form-urlencoded, multipart/form-data, text/plain)
4+
# Pre-flight requests - handled in a separate test since it has its own requirement
5+
Feature: Server must respond to requests sending Origin with the appropriate Access-Control-* headers
6+
7+
Background: Set up test container and test data
8+
* def testContainer = rootTestContainer.createContainer()
9+
10+
Scenario Outline: Simple request: <method> request returns access control headers
11+
Given url testContainer.url
12+
And header Origin = 'https://tester'
13+
And headers <headers>
14+
* <body>
15+
When method <method>
16+
Then match <statuses> contains responseStatus
17+
And match header Access-Control-Allow-Origin == 'https://tester'
18+
And match header Access-Control-Allow-Credentials == 'true'
19+
Examples:
20+
| method | headers! | body | statuses |
21+
| GET | {Accept: 'text/turtle'} | def ignore = 1 | [401] |
22+
| HEAD | {} | def ignore = 1 | [401] |
23+
| POST | {'Content-Type': 'text/plain'} | request "Hello" | [401] |
24+
25+
Scenario Outline: Requests with credentials: <method> request returns access control headers
26+
Given url testContainer.url
27+
And headers clients.alice.getAuthHeaders('<method>', testContainer.url)
28+
And header Origin = 'https://tester'
29+
And headers <headers>
30+
* <body>
31+
When method <method>
32+
Then match <statuses> contains responseStatus
33+
And match header Access-Control-Allow-Origin == 'https://tester'
34+
And match header Access-Control-Allow-Credentials == 'true'
35+
Examples:
36+
| method | headers! | body | statuses |
37+
| GET | {Accept: 'text/turtle'} | def ignore = 1 | [200] |
38+
| HEAD | {} | def ignore = 1 | [200] |
39+
| POST | {'Content-Type': 'text/plain'} | request "Hello" | [200, 201, 204, 205] |
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Feature: Server should enumerate headers in Access-Control-Expose-Headers
2+
3+
Background: Set up test container (not empty)
4+
* def testContainer = rootTestContainer.createContainer()
5+
* def resource = testContainer.createResource('.txt', 'Hello', 'text/plain')
6+
7+
Scenario: Access-Control-Expose-Headers is present but not *
8+
Given url testContainer.url
9+
And headers clients.alice.getAuthHeaders('GET', testContainer.url)
10+
And header Origin = 'https://tester'
11+
And header Accept = 'text/turtle'
12+
When method GET
13+
Then status 200
14+
And match header Access-Control-Expose-Headers != null
15+
And match header Access-Control-Expose-Headers != '*'
16+
And match response != ''
17+
18+
# To test this enumerates all headers we could split it on ', ' and compare to the set of all keys in responseHeaders
19+
# The FETCH spec says
20+
# "A response will typically get its CORS-exposed header-name list set by extracting header values from the
21+
# `Access-Control-Expose-Headers` header. This list is used by a CORS filtered response to determine which headers to expose."
22+
# This makes it sound as though the important thing is the intersection - it doesn't matter if headers are in one list but not the other.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
Feature: Server must implement the CORS protocol for preflight requests
2+
3+
Background: Set up test container and resource
4+
* def testContainer = rootTestContainer.createContainer()
5+
* def resource = testContainer.createResource('.txt', 'Hello', 'text/plain')
6+
7+
Scenario Outline: Pre-flight CORS request for <method> request
8+
Given url testContainer.url
9+
And header Origin = 'https://tester'
10+
And header Access-Control-Request-Method = '<method>'
11+
And header Access-Control-Request-Headers = 'X-CUSTOM, Content-Type, Accept'
12+
When method OPTIONS
13+
Then match [200, 204] contains responseStatus
14+
And match header Access-Control-Allow-Origin == 'https://tester'
15+
And match header Access-Control-Allow-Methods contains '<method>'
16+
And match header Access-Control-Allow-Headers contains 'X-CUSTOM'
17+
And match header Access-Control-Allow-Headers contains 'Content-Type'
18+
And match header Access-Control-Allow-Headers contains 'Accept'
19+
And match header Access-Control-Allow-Credentials == 'true'
20+
And match header Access-Control-Expose-Headers != null
21+
And match response == ''
22+
23+
Given url testContainer.url
24+
And header Origin = 'https://tester'
25+
And headers clients.alice.getAuthHeaders('<method>', testContainer.url)
26+
# Demonstrates the case where a long Accept header is allowed
27+
And header Accept = 'text/turtle;q=0.9, application/rdf+xml;q=0.8, application/n-triples;q=0.8, application/n-quads;q=0.8, text/x-nquads;q=0.8, application/trig;q=0.8, text/n3;q=0.8, application/ld+json;q=0.8, application/x-binary-rdf;q=0.8, text/plain;q=0.7'
28+
* <body>
29+
When method <method>
30+
Then match <statuses> contains responseStatus
31+
And match header Access-Control-Allow-Origin == 'https://tester'
32+
And match header Access-Control-Allow-Credentials == 'true'
33+
And match header Access-Control-Expose-Headers != null
34+
And match header Access-Control-Expose-Headers != '*'
35+
# Check Content-Type on GET request only
36+
And <check>
37+
And match header Vary contains 'Origin'
38+
Examples:
39+
| method | body | statuses | check |
40+
| GET | def ignore = 1 | [200] | match header Content-Type contains 'text/turtle' |
41+
| HEAD | def ignore = 1 | [200] | def ignore = 1 |
42+
| POST | request "Hello" | [200, 201, 204, 205] | def ignore = 1 |
43+
44+
@http-redirect
45+
Scenario: OPTIONS request returns headers for pre-flight check after redirect from http
46+
* configure followRedirects = false
47+
Given url testContainer.url.replace(/^https:/, 'http:')
48+
And header Origin = 'https://tester'
49+
And header Access-Control-Request-Method = 'POST'
50+
And header Access-Control-Request-Headers = 'X-CUSTOM, Content-Type'
51+
When method OPTIONS
52+
Then match [301, 308] contains responseStatus
53+
* def location = responseHeaders['Location'][0]
54+
55+
Given url location
56+
And header Origin = 'https://tester'
57+
And header Access-Control-Request-Method = 'POST'
58+
And header Access-Control-Request-Headers = 'X-CUSTOM, Content-Type'
59+
When method OPTIONS
60+
Then match [200, 204] contains responseStatus
61+
And match header Access-Control-Allow-Origin == 'https://tester'
62+
And match header Access-Control-Allow-Methods contains 'POST'
63+
And match header Access-Control-Allow-Headers contains 'X-CUSTOM'
64+
And match header Access-Control-Allow-Headers contains 'Content-Type'
65+
And match header Access-Control-Allow-Credentials == 'true'
66+
And match header Access-Control-Expose-Headers != null
67+
And match response == ''

protocol/cors/preflight.feature

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
Feature: Server must support HTTP OPTIONS for CORS preflight requests
2+
3+
Background: Set up test container
4+
* def testContainer = rootTestContainer.createContainer()
5+
6+
Scenario: OPTIONS request returns headers for pre-flight check
7+
Given url testContainer.url
8+
And header Origin = 'https://tester'
9+
And header Access-Control-Request-Method = 'POST'
10+
And header Access-Control-Request-Headers = 'X-CUSTOM, Content-Type'
11+
When method OPTIONS
12+
Then match [200, 204] contains responseStatus
13+
And match header Access-Control-Allow-Origin == 'https://tester'
14+
And match header Access-Control-Allow-Methods contains 'POST'
15+
And match header Access-Control-Allow-Headers contains 'X-CUSTOM'
16+
And match header Access-Control-Allow-Headers contains 'Content-Type'
17+
And match header Access-Control-Allow-Credentials == 'true'
18+
# We should check the list of headers exposed but what is the required list
19+
And match header Access-Control-Expose-Headers != null
20+
And match response == ''
21+
22+
@http-redirect
23+
Scenario: OPTIONS request returns headers for pre-flight check after redirect from http
24+
Given url testContainer.url.replace(/^https:/, 'http:')
25+
And header Origin = 'https://tester'
26+
And header Access-Control-Request-Method = 'POST'
27+
And header Access-Control-Request-Headers = 'X-CUSTOM, Content-Type'
28+
When method OPTIONS
29+
Then match [301, 308] contains responseStatus
30+
* def location = responseHeaders['Location'][0]
31+
32+
Given url location
33+
And header Origin = 'https://tester'
34+
And header Access-Control-Request-Method = 'POST'
35+
And header Access-Control-Request-Headers = 'X-CUSTOM, Content-Type'
36+
When method OPTIONS
37+
Then match [200, 204] contains responseStatus
38+
And match header Access-Control-Allow-Origin == 'https://tester'
39+
And match header Access-Control-Allow-Methods contains 'POST'
40+
And match header Access-Control-Allow-Headers contains 'X-CUSTOM'
41+
And match header Access-Control-Allow-Headers contains 'Content-Type'
42+
And match header Access-Control-Allow-Credentials == 'true'
43+
# We should check the list of headers exposed but what is the required list
44+
And match header Access-Control-Expose-Headers != null
45+
And match response == ''
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Simple requests
2+
# - Methods: GET, HEAD, POST
3+
# - Safe headers: Accept, Accept-Language, Content-Language, Content-Type (application/x-www-form-urlencoded, multipart/form-data, text/plain)
4+
Feature: Server must implement the CORS protocol for simple requests
5+
6+
Background: Set up test container and test data
7+
* def testContainer = rootTestContainer.createContainer()
8+
* def resource = testContainer.createResource('.txt', 'Hello', 'text/plain')
9+
10+
Scenario Outline: Simple container request: <method> request returns access control headers
11+
Given url testContainer.url
12+
And header Origin = 'https://tester'
13+
And headers <headers>
14+
* <body>
15+
When method <method>
16+
Then match <statuses> contains responseStatus
17+
And match header Access-Control-Allow-Origin == 'https://tester'
18+
And match header Access-Control-Allow-Credentials == 'true'
19+
And match header Access-Control-Expose-Headers != null
20+
And match header Access-Control-Expose-Headers != '*'
21+
And match header Vary contains 'Origin'
22+
Examples:
23+
| method | headers! | body | statuses |
24+
| GET | {Accept: 'text/turtle'} | def ignore = 1 | [401] |
25+
| HEAD | {} | def ignore = 1 | [401] |
26+
| POST | {'Content-Type': 'text/plain'} | request "Hello" | [401] |
27+
28+
Scenario Outline: Simple resource request: <method> request returns access control headers
29+
Given url resource.url
30+
And header Origin = 'https://tester'
31+
And headers <headers>
32+
* <body>
33+
When method <method>
34+
Then match <statuses> contains responseStatus
35+
And match header Access-Control-Allow-Origin == 'https://tester'
36+
And match header Access-Control-Allow-Credentials == 'true'
37+
And match header Access-Control-Expose-Headers != null
38+
And match header Access-Control-Expose-Headers != '*'
39+
And match header Vary contains 'Origin'
40+
Examples:
41+
| method | headers! | body | statuses |
42+
| GET | {Accept: 'text/plain'} | def ignore = 1 | [401] |
43+
| HEAD | {} | def ignore = 1 | [401] |
44+
45+
Scenario Outline: Requests container with credentials: <method> request returns access control headers
46+
Given url testContainer.url
47+
And headers clients.alice.getAuthHeaders('<method>', testContainer.url)
48+
And header Origin = 'https://tester'
49+
And headers <headers>
50+
* <body>
51+
When method <method>
52+
Then match <statuses> contains responseStatus
53+
And match header Access-Control-Allow-Origin == 'https://tester'
54+
And match header Access-Control-Allow-Credentials == 'true'
55+
And match header Access-Control-Expose-Headers != null
56+
And match header Access-Control-Expose-Headers != '*'
57+
And match header Vary contains 'Origin'
58+
Examples:
59+
| method | headers! | body | statuses |
60+
| GET | {Accept: 'text/turtle'} | def ignore = 1 | [200] |
61+
| HEAD | {} | def ignore = 1 | [200] |
62+
| POST | {'Content-Type': 'text/plain'} | request "Hello" | [200, 201, 204, 205] |
63+
64+
Scenario Outline: Requests resource with credentials: <method> request returns access control headers
65+
Given url resource.url
66+
And headers clients.alice.getAuthHeaders('<method>', resource.url)
67+
And header Origin = 'https://tester'
68+
And headers <headers>
69+
* <body>
70+
When method <method>
71+
Then match <statuses> contains responseStatus
72+
And match header Access-Control-Allow-Origin == 'https://tester'
73+
And match header Access-Control-Allow-Credentials == 'true'
74+
And match header Access-Control-Expose-Headers != null
75+
And match header Access-Control-Expose-Headers != '*'
76+
And match header Vary contains 'Origin'
77+
Examples:
78+
| method | headers! | body | statuses |
79+
| GET | {Accept: 'text/plain'} | def ignore = 1 | [200] |
80+
| HEAD | {} | def ignore = 1 | [200] |

0 commit comments

Comments
 (0)