@quantcdn/edge-conformance 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,238 @@
1
+ {
2
+ "cases": [
3
+ {
4
+ "id": "functions-filter-transforms-html",
5
+ "description": "A content filter changes the body of a text/html response.",
6
+ "tags": [
7
+ "functions"
8
+ ],
9
+ "op": "runFunction",
10
+ "input": {
11
+ "uuid": "b2c3d4e5-0003-4000-8000-000000000003",
12
+ "type": "filter",
13
+ "body": "class contentFilter extends TransformStream {\n constructor(event, responseHeaders) {\n super({\n transform: (chunk, controller) => {\n const text = this.textDecoder.decode(chunk);\n controller.enqueue(this.textEncoder.encode(text.replace(/example/g, 'test')));\n }\n });\n this.textEncoder = new TextEncoder();\n this.textDecoder = new TextDecoder();\n }\n}",
14
+ "request": {
15
+ "method": "GET",
16
+ "url": "https://www.example.com/page"
17
+ },
18
+ "response": {
19
+ "status": 200,
20
+ "headers": {
21
+ "content-type": "text/html; charset=utf-8"
22
+ },
23
+ "body": "<p>example page</p>"
24
+ }
25
+ },
26
+ "expect": {
27
+ "status": 200,
28
+ "bodyText": "<p>test page</p>"
29
+ }
30
+ },
31
+ {
32
+ "id": "functions-filter-sets-a-response-header",
33
+ "description": "A content filter can set a response header on a text/html response.",
34
+ "tags": [
35
+ "functions"
36
+ ],
37
+ "op": "runFunction",
38
+ "input": {
39
+ "uuid": "b2c3d4e5-0003-4000-8000-000000000003",
40
+ "type": "filter",
41
+ "body": "class contentFilter extends TransformStream {\n constructor(event, responseHeaders) {\n responseHeaders.set('x-filtered', 'yes');\n super({ transform: (chunk, controller) => controller.enqueue(chunk) });\n }\n}",
42
+ "request": {
43
+ "method": "GET",
44
+ "url": "https://www.example.com/page"
45
+ },
46
+ "response": {
47
+ "status": 200,
48
+ "headers": {
49
+ "content-type": "text/html; charset=utf-8"
50
+ },
51
+ "body": "<p>page</p>"
52
+ }
53
+ },
54
+ "expect": {
55
+ "status": 200,
56
+ "headers": {
57
+ "x-filtered": "yes"
58
+ },
59
+ "bodyText": "<p>page</p>"
60
+ }
61
+ },
62
+ {
63
+ "id": "functions-filter-keeps-the-status",
64
+ "description": "A content filter keeps the status of the response.",
65
+ "tags": [
66
+ "functions"
67
+ ],
68
+ "op": "runFunction",
69
+ "input": {
70
+ "uuid": "b2c3d4e5-0003-4000-8000-000000000003",
71
+ "type": "filter",
72
+ "body": "class contentFilter extends TransformStream {\n constructor(event, responseHeaders) {\n super({\n transform: (chunk, controller) => {\n const text = this.textDecoder.decode(chunk);\n controller.enqueue(this.textEncoder.encode(text.replace(/example/g, 'test')));\n }\n });\n this.textEncoder = new TextEncoder();\n this.textDecoder = new TextDecoder();\n }\n}",
73
+ "request": {
74
+ "method": "GET",
75
+ "url": "https://www.example.com/page"
76
+ },
77
+ "response": {
78
+ "status": 404,
79
+ "headers": {
80
+ "content-type": "text/html; charset=utf-8"
81
+ },
82
+ "body": "<p>example missing</p>"
83
+ }
84
+ },
85
+ "expect": {
86
+ "status": 404,
87
+ "bodyText": "<p>test missing</p>"
88
+ }
89
+ },
90
+ {
91
+ "id": "functions-filter-does-not-change-a-non-html-body",
92
+ "description": "A content filter does not change the body of a response that is not text/html.",
93
+ "tags": [
94
+ "functions",
95
+ "unverified"
96
+ ],
97
+ "op": "runFunction",
98
+ "input": {
99
+ "uuid": "b2c3d4e5-0003-4000-8000-000000000003",
100
+ "type": "filter",
101
+ "body": "class contentFilter extends TransformStream {\n constructor(event, responseHeaders) {\n super({\n transform: (chunk, controller) => {\n const text = this.textDecoder.decode(chunk);\n controller.enqueue(this.textEncoder.encode(text.replace(/example/g, 'test')));\n }\n });\n this.textEncoder = new TextEncoder();\n this.textDecoder = new TextDecoder();\n }\n}",
102
+ "request": {
103
+ "method": "GET",
104
+ "url": "https://www.example.com/data"
105
+ },
106
+ "response": {
107
+ "status": 200,
108
+ "headers": {
109
+ "content-type": "application/json"
110
+ },
111
+ "body": "{\"word\":\"example\"}"
112
+ }
113
+ },
114
+ "expect": {
115
+ "status": 200,
116
+ "bodyText": "{\"word\":\"example\"}"
117
+ }
118
+ },
119
+ {
120
+ "id": "functions-filter-needs-a-content-filter-class",
121
+ "description": "A filter body that does not define contentFilter gives a 500 response.",
122
+ "tags": [
123
+ "functions"
124
+ ],
125
+ "op": "runFunction",
126
+ "input": {
127
+ "uuid": "b2c3d4e5-0003-4000-8000-000000000003",
128
+ "type": "filter",
129
+ "body": "const notAFilter = 1;",
130
+ "request": {
131
+ "method": "GET",
132
+ "url": "https://www.example.com/page"
133
+ },
134
+ "response": {
135
+ "status": 200,
136
+ "headers": {
137
+ "content-type": "text/html; charset=utf-8"
138
+ },
139
+ "body": "<p>page</p>"
140
+ }
141
+ },
142
+ "expect": {
143
+ "status": 500
144
+ }
145
+ },
146
+ {
147
+ "id": "functions-filter-applies-to-static-html",
148
+ "description": "A filter rule changes a static text/html page.",
149
+ "tags": [
150
+ "functions"
151
+ ],
152
+ "op": "serveSite",
153
+ "input": {
154
+ "files": {
155
+ "index.html": "<p>example home</p>"
156
+ },
157
+ "functions": [
158
+ {
159
+ "uuid": "b2c3d4e5-0003-4000-8000-000000000003",
160
+ "id": "rewrite",
161
+ "type": "filter",
162
+ "body": "class contentFilter extends TransformStream {\n constructor(event, responseHeaders) {\n super({\n transform: (chunk, controller) => {\n const text = this.textDecoder.decode(chunk);\n controller.enqueue(this.textEncoder.encode(text.replace(/example/g, 'test')));\n }\n });\n this.textEncoder = new TextEncoder();\n this.textDecoder = new TextDecoder();\n }\n}"
163
+ }
164
+ ],
165
+ "rules": {
166
+ "version": 1,
167
+ "namespace": "conformance",
168
+ "rules": [
169
+ {
170
+ "id": "rewrite",
171
+ "type": "filter",
172
+ "urls": [
173
+ "/",
174
+ "/*"
175
+ ],
176
+ "weight": 0,
177
+ "function_uuid": "b2c3d4e5-0003-4000-8000-000000000003"
178
+ }
179
+ ]
180
+ },
181
+ "request": {
182
+ "method": "GET",
183
+ "url": "https://www.example.com/"
184
+ }
185
+ },
186
+ "expect": {
187
+ "status": 200,
188
+ "bodyText": "<p>test home</p>"
189
+ }
190
+ },
191
+ {
192
+ "id": "functions-filter-skips-static-css",
193
+ "description": "A filter rule does not change a static file that is not text/html.",
194
+ "tags": [
195
+ "functions"
196
+ ],
197
+ "op": "serveSite",
198
+ "input": {
199
+ "files": {
200
+ "index.html": "<p>example home</p>",
201
+ "site.css": ".example { color: red; }"
202
+ },
203
+ "functions": [
204
+ {
205
+ "uuid": "b2c3d4e5-0003-4000-8000-000000000003",
206
+ "id": "rewrite",
207
+ "type": "filter",
208
+ "body": "class contentFilter extends TransformStream {\n constructor(event, responseHeaders) {\n super({\n transform: (chunk, controller) => {\n const text = this.textDecoder.decode(chunk);\n controller.enqueue(this.textEncoder.encode(text.replace(/example/g, 'test')));\n }\n });\n this.textEncoder = new TextEncoder();\n this.textDecoder = new TextDecoder();\n }\n}"
209
+ }
210
+ ],
211
+ "rules": {
212
+ "version": 1,
213
+ "namespace": "conformance",
214
+ "rules": [
215
+ {
216
+ "id": "rewrite",
217
+ "type": "filter",
218
+ "urls": [
219
+ "/",
220
+ "/*"
221
+ ],
222
+ "weight": 0,
223
+ "function_uuid": "b2c3d4e5-0003-4000-8000-000000000003"
224
+ }
225
+ ]
226
+ },
227
+ "request": {
228
+ "method": "GET",
229
+ "url": "https://www.example.com/site.css"
230
+ }
231
+ },
232
+ "expect": {
233
+ "status": 200,
234
+ "bodyText": ".example { color: red; }"
235
+ }
236
+ }
237
+ ]
238
+ }
@@ -0,0 +1,304 @@
1
+ {
2
+ "cases": [
3
+ {
4
+ "id": "functions-response-status-headers-and-body",
5
+ "description": "The Response that a function returns gives the status, headers and body.",
6
+ "tags": [
7
+ "functions"
8
+ ],
9
+ "op": "runFunction",
10
+ "input": {
11
+ "uuid": "b2c3d4e5-0001-4000-8000-000000000001",
12
+ "type": "function",
13
+ "body": "return new Response('Hello World', { status: 200, headers: { 'content-type': 'text/html', 'x-powered-by': 'QuantCDN' } });",
14
+ "request": {
15
+ "method": "GET",
16
+ "url": "https://www.example.com/hello"
17
+ }
18
+ },
19
+ "expect": {
20
+ "status": 200,
21
+ "headers": {
22
+ "content-type": {
23
+ "startsWith": "text/html"
24
+ },
25
+ "x-powered-by": "QuantCDN"
26
+ },
27
+ "bodyText": "Hello World"
28
+ }
29
+ },
30
+ {
31
+ "id": "functions-response-status",
32
+ "description": "A function can return any status.",
33
+ "tags": [
34
+ "functions"
35
+ ],
36
+ "op": "runFunction",
37
+ "input": {
38
+ "uuid": "b2c3d4e5-0001-4000-8000-000000000001",
39
+ "type": "function",
40
+ "body": "return new Response('Accepted', { status: 202 });",
41
+ "request": {
42
+ "method": "POST",
43
+ "url": "https://www.example.com/api",
44
+ "body": "x"
45
+ }
46
+ },
47
+ "expect": {
48
+ "status": 202,
49
+ "bodyText": "Accepted"
50
+ }
51
+ },
52
+ {
53
+ "id": "functions-await",
54
+ "description": "A function body can use await.",
55
+ "tags": [
56
+ "functions"
57
+ ],
58
+ "op": "runFunction",
59
+ "input": {
60
+ "uuid": "b2c3d4e5-0001-4000-8000-000000000001",
61
+ "type": "function",
62
+ "body": "const value = await new Promise((resolve) => setTimeout(() => resolve('later'), 5));\nreturn new Response(value);",
63
+ "request": {
64
+ "method": "GET",
65
+ "url": "https://www.example.com/api"
66
+ }
67
+ },
68
+ "expect": {
69
+ "status": 200,
70
+ "bodyText": "later"
71
+ }
72
+ },
73
+ {
74
+ "id": "functions-json-response",
75
+ "description": "A function can return JSON with its content type.",
76
+ "tags": [
77
+ "functions"
78
+ ],
79
+ "op": "runFunction",
80
+ "input": {
81
+ "uuid": "b2c3d4e5-0001-4000-8000-000000000001",
82
+ "type": "function",
83
+ "body": "return new Response(JSON.stringify({ ok: true }), { headers: { 'content-type': 'application/json' } });",
84
+ "request": {
85
+ "method": "GET",
86
+ "url": "https://www.example.com/api"
87
+ }
88
+ },
89
+ "expect": {
90
+ "status": 200,
91
+ "headers": {
92
+ "content-type": {
93
+ "startsWith": "application/json"
94
+ }
95
+ },
96
+ "bodyJson": {
97
+ "ok": true
98
+ }
99
+ }
100
+ },
101
+ {
102
+ "id": "functions-non-response-value",
103
+ "description": "A value that is not a Response becomes a 200 response with the value as the body.",
104
+ "tags": [
105
+ "functions"
106
+ ],
107
+ "op": "runFunction",
108
+ "input": {
109
+ "uuid": "b2c3d4e5-0001-4000-8000-000000000001",
110
+ "type": "function",
111
+ "body": "return 'plain value';",
112
+ "request": {
113
+ "method": "GET",
114
+ "url": "https://www.example.com/api"
115
+ }
116
+ },
117
+ "expect": {
118
+ "status": 200,
119
+ "bodyText": "plain value"
120
+ }
121
+ },
122
+ {
123
+ "id": "functions-default-cache-control",
124
+ "description": "A function response without cache-control gets private, max-age=0.",
125
+ "tags": [
126
+ "functions"
127
+ ],
128
+ "op": "runFunction",
129
+ "input": {
130
+ "uuid": "b2c3d4e5-0001-4000-8000-000000000001",
131
+ "type": "function",
132
+ "body": "return new Response('ok');",
133
+ "request": {
134
+ "method": "GET",
135
+ "url": "https://www.example.com/api"
136
+ }
137
+ },
138
+ "expect": {
139
+ "status": 200,
140
+ "headers": {
141
+ "cache-control": "private, max-age=0"
142
+ }
143
+ }
144
+ },
145
+ {
146
+ "id": "functions-keeps-its-cache-control",
147
+ "description": "A cache-control that the function sets is kept.",
148
+ "tags": [
149
+ "functions"
150
+ ],
151
+ "op": "runFunction",
152
+ "input": {
153
+ "uuid": "b2c3d4e5-0001-4000-8000-000000000001",
154
+ "type": "function",
155
+ "body": "return new Response('ok', { headers: { 'cache-control': 'public, max-age=60' } });",
156
+ "request": {
157
+ "method": "GET",
158
+ "url": "https://www.example.com/api"
159
+ }
160
+ },
161
+ "expect": {
162
+ "status": 200,
163
+ "headers": {
164
+ "cache-control": "public, max-age=60"
165
+ }
166
+ }
167
+ },
168
+ {
169
+ "id": "functions-set-cookie-group",
170
+ "description": "Each set-cookie header that the function sets reaches the client.",
171
+ "tags": [
172
+ "functions"
173
+ ],
174
+ "op": "runFunction",
175
+ "input": {
176
+ "uuid": "b2c3d4e5-0001-4000-8000-000000000001",
177
+ "type": "function",
178
+ "body": "const headers = new Headers();\nheaders.append('set-cookie', 'a=1; Path=/');\nheaders.append('set-cookie', 'b=2; Path=/');\nreturn new Response('ok', { headers });",
179
+ "request": {
180
+ "method": "GET",
181
+ "url": "https://www.example.com/api"
182
+ }
183
+ },
184
+ "expect": {
185
+ "status": 200,
186
+ "headers": {
187
+ "set-cookie": [
188
+ "a=1; Path=/",
189
+ "b=2; Path=/"
190
+ ]
191
+ }
192
+ }
193
+ },
194
+ {
195
+ "id": "functions-thrown-error-500",
196
+ "description": "A function that throws gives a 500 response without the error message.",
197
+ "tags": [
198
+ "functions"
199
+ ],
200
+ "op": "runFunction",
201
+ "input": {
202
+ "uuid": "b2c3d4e5-0001-4000-8000-000000000001",
203
+ "type": "function",
204
+ "body": "throw new Error('function broke');",
205
+ "request": {
206
+ "method": "GET",
207
+ "url": "https://www.example.com/api"
208
+ }
209
+ },
210
+ "expect": {
211
+ "status": 500,
212
+ "bodyExcludes": [
213
+ "function broke"
214
+ ]
215
+ }
216
+ },
217
+ {
218
+ "id": "functions-thrown-error-empty-body",
219
+ "description": "The 500 response for a thrown error has an empty body.",
220
+ "tags": [
221
+ "functions",
222
+ "unverified"
223
+ ],
224
+ "op": "runFunction",
225
+ "input": {
226
+ "uuid": "b2c3d4e5-0001-4000-8000-000000000001",
227
+ "type": "function",
228
+ "body": "throw new Error('function broke');",
229
+ "request": {
230
+ "method": "GET",
231
+ "url": "https://www.example.com/api"
232
+ }
233
+ },
234
+ "expect": {
235
+ "status": 500,
236
+ "bodyText": ""
237
+ }
238
+ },
239
+ {
240
+ "id": "functions-rejected-promise-500",
241
+ "description": "A function whose promise rejects gives a 500 response.",
242
+ "tags": [
243
+ "functions"
244
+ ],
245
+ "op": "runFunction",
246
+ "input": {
247
+ "uuid": "b2c3d4e5-0001-4000-8000-000000000001",
248
+ "type": "function",
249
+ "body": "await Promise.reject(new Error('rejected'));",
250
+ "request": {
251
+ "method": "GET",
252
+ "url": "https://www.example.com/api"
253
+ }
254
+ },
255
+ "expect": {
256
+ "status": 500,
257
+ "bodyExcludes": [
258
+ "rejected"
259
+ ]
260
+ }
261
+ },
262
+ {
263
+ "id": "functions-auth-deny-response",
264
+ "description": "An auth function response that is not 201 is the response to the client.",
265
+ "tags": [
266
+ "functions"
267
+ ],
268
+ "op": "runFunction",
269
+ "input": {
270
+ "uuid": "b2c3d4e5-0002-4000-8000-000000000002",
271
+ "type": "auth",
272
+ "body": "return new Response('Forbidden', { status: 403 });",
273
+ "request": {
274
+ "method": "GET",
275
+ "url": "https://www.example.com/members/area"
276
+ }
277
+ },
278
+ "expect": {
279
+ "status": 403,
280
+ "bodyText": "Forbidden"
281
+ }
282
+ },
283
+ {
284
+ "id": "functions-auth-needs-a-response",
285
+ "description": "An auth function that does not return a Response gives a 500 response.",
286
+ "tags": [
287
+ "functions"
288
+ ],
289
+ "op": "runFunction",
290
+ "input": {
291
+ "uuid": "b2c3d4e5-0002-4000-8000-000000000002",
292
+ "type": "auth",
293
+ "body": "return 'yes';",
294
+ "request": {
295
+ "method": "GET",
296
+ "url": "https://www.example.com/members/area"
297
+ }
298
+ },
299
+ "expect": {
300
+ "status": 500
301
+ }
302
+ }
303
+ ]
304
+ }