Selector: Make `selector.js` module depend on `attributes/attr.js`
[jquery.git] / test / middleware-mockserver.cjs
bloba7a7a47b93b964f3a3afa86bf763c47a94b188ed
1 "use strict";
3 const url = require( "url" );
4 const fs = require( "fs" );
5 const getRawBody = require( "raw-body" );
6 const multiparty = require( "multiparty" );
8 let cspLog = "";
10 /**
11  * Keep in sync with /test/mock.php
12  */
13 function cleanCallback( callback ) {
14         return callback.replace( /[^a-z0-9_]/gi, "" );
17 const mocks = {
18         contentType: function( req, resp ) {
19                 resp.writeHead( 200, {
20                         "content-type": req.query.contentType
21                 } );
22                 resp.end( req.query.response );
23         },
24         wait: function( req, resp ) {
25                 const wait = Number( req.query.wait ) * 1000;
26                 setTimeout( function() {
27                         if ( req.query.script ) {
28                                 resp.writeHead( 200, { "content-type": "text/javascript" } );
29                         } else {
30                                 resp.writeHead( 200, { "content-type": "text/html" } );
31                                 resp.end( "ERROR <script>QUnit.assert.ok( true, \"mock executed\" );</script>" );
32                         }
33                 }, wait );
34         },
35         name: function( req, resp, next ) {
36                 resp.writeHead( 200 );
37                 if ( req.query.name === "foo" ) {
38                         resp.end( "bar" );
39                         return;
40                 }
41                 getBody( req ).then( function( body ) {
42                         if ( body === "name=peter" ) {
43                                 resp.end( "pan" );
44                         } else {
45                                 resp.end( "ERROR" );
46                         }
47                 }, next );
48         },
49         xml: function( req, resp, next ) {
50                 const content = "<math><calculation>5-2</calculation><result>3</result></math>";
51                 resp.writeHead( 200, { "content-type": "text/xml" } );
53                 if ( req.query.cal === "5-2" ) {
54                         resp.end( content );
55                         return;
56                 }
57                 getBody( req ).then( function( body ) {
58                         if ( body === "cal=5-2" ) {
59                                 resp.end( content );
60                         } else {
61                                 resp.end( "<error>ERROR</error>" );
62                         }
63                 }, next );
64         },
65         atom: function( _req, resp ) {
66                 resp.writeHead( 200, { "content-type": "atom+xml" } );
67                 resp.end( "<root><element /></root>" );
68         },
69         script: function( req, resp ) {
70                 const headers = {};
71                 if ( req.query.header === "ecma" ) {
72                         headers[ "content-type" ] = "application/ecmascript";
73                 } else if ( "header" in req.query ) {
74                         headers[ "content-type" ] = "text/javascript";
75                 } else {
76                         headers[ "content-type" ] = "text/html";
77                 }
79                 if ( req.query.cors ) {
80                         headers[ "access-control-allow-origin" ] = "*";
81                 }
83                 if ( resp.set ) {
84                         resp.set( headers );
85                 } else {
86                         for ( const key in headers ) {
87                                 resp.writeHead( 200, { [ key ]: headers[ key ] } );
88                         }
89                 }
91                 if ( req.query.callback ) {
92                         resp.end( `${ cleanCallback( req.query.callback ) }(${ JSON.stringify( {
93                                 headers: req.headers
94                         } ) })` );
95                 } else {
96                         resp.end( "QUnit.assert.ok( true, \"mock executed\" );" );
97                 }
98         },
99         testbar: function( _req, resp ) {
100                 resp.writeHead( 200 );
101                 resp.end(
102                         "this.testBar = 'bar'; " +
103                         "jQuery('#ap').html('bar'); " +
104                         "QUnit.assert.ok( true, 'mock executed');"
105                 );
106         },
107         json: function( req, resp ) {
108                 if ( req.query.header ) {
109                         resp.writeHead( 200, { "content-type": "application/json" } );
110                 }
111                 if ( req.query.cors ) {
112                         resp.writeHead( 200, { "access-control-allow-origin": "*" } );
113                 }
114                 if ( req.query.array ) {
115                         resp.end( JSON.stringify(
116                                 [ { name: "John", age: 21 }, { name: "Peter", age: 25 } ]
117                         ) );
118                 } else {
119                         resp.end( JSON.stringify(
120                                 { data: { lang: "en", length: 25 } }
121                         ) );
122                 }
123         },
124         jsonp: function( req, resp, next ) {
125                 let callback;
126                 if ( Array.isArray( req.query.callback ) ) {
127                         callback = Promise.resolve( req.query.callback[ req.query.callback.length - 1 ] );
128                 } else if ( req.query.callback ) {
129                         callback = Promise.resolve( req.query.callback );
130                 } else if ( req.method === "GET" ) {
131                         callback = Promise.resolve( req.url.match( /^.+\/([^\/?]+)\?.+$/ )[ 1 ] );
132                 } else {
133                         callback = getBody( req ).then( function( body ) {
134                                 return body.trim().replace( "callback=", "" );
135                         } );
136                 }
137                 const json = req.query.array ?
138                         JSON.stringify(
139                                 [ { name: "John", age: 21 }, { name: "Peter", age: 25 } ]
140                         ) :
141                         JSON.stringify(
142                                 { data: { lang: "en", length: 25 } }
143                         );
144                 callback.then( function( cb ) {
145                         resp.end( `${ cleanCallback( cb ) }(${ json })` );
146                 }, next );
147         },
148         xmlOverJsonp: function( req, resp ) {
149                 const callback = req.query.callback;
150                 const body = fs.readFileSync( `${ __dirname }/data/with_fries.xml` ).toString();
151                 resp.writeHead( 200 );
152                 resp.end( `${ cleanCallback( callback ) }(${ JSON.stringify( body ) })\n` );
153         },
154         formData: function( req, resp, next ) {
155                 const prefix = "multipart/form-data; boundary=--";
156                 const contentTypeValue = req.headers[ "content-type" ];
157                 resp.writeHead( 200 );
158                 if ( ( prefix || "" ).startsWith( prefix ) ) {
159                         getMultiPartContent( req ).then( function( { fields = {} } ) {
160                                 resp.end( `key1 -> ${ fields.key1 }, key2 -> ${ fields.key2 }` );
161                         }, next );
162                 } else {
163                         resp.end( `Incorrect Content-Type: ${ contentTypeValue
164                                 }\nExpected prefix: ${ prefix }` );
165                 }
166         },
167         error: function( req, resp ) {
168                 if ( req.query.json ) {
169                         resp.writeHead( 400, { "content-type": "application/json" } );
170                         resp.end( "{ \"code\": 40, \"message\": \"Bad Request\" }" );
171                 } else {
172                         resp.writeHead( 400 );
173                         resp.end( "plain text message" );
174                 }
175         },
176         headers: function( req, resp ) {
177                 const headers = {
178                         "Sample-Header": "Hello World",
179                         "Empty-Header": "",
180                         "Sample-Header2": "Hello World 2",
181                         "List-Header": "Item 1",
182                         "list-header": "Item 2",
183                         "constructor": "prototype collision (constructor)"
184                 };
186                 // Use resp.append in express to
187                 // avoid overwriting List-Header
188                 if ( resp.append ) {
190                         for ( const key in headers ) {
191                                 resp.append( key, headers[ key ] );
192                         }
193                 } else {
194                         resp.writeHead( 200, headers );
195                 }
196                 req.query.keys.split( "|" ).forEach( function( key ) {
197                         if ( key.toLowerCase() in req.headers ) {
198                                 resp.write( `${ key }: ${ req.headers[ key.toLowerCase() ] }\n` );
199                         }
200                 } );
201                 resp.end();
202         },
203         echoData: function( req, resp, next ) {
204                 getBody( req ).then( function( body ) {
205                         resp.end( body );
206                 }, next );
207         },
208         echoQuery: function( req, resp ) {
209                 resp.end( req.parsed.search.slice( 1 ) );
210         },
211         echoMethod: function( req, resp ) {
212                 resp.end( req.method );
213         },
214         echoHtml: function( req, resp, next ) {
215                 resp.writeHead( 200, { "Content-Type": "text/html" } );
216                 resp.write( `<div id='method'>${ req.method }</div>` );
217                 resp.write( `<div id='query'>${ req.parsed.search.slice( 1 ) }</div>` );
218                 getBody( req ).then( function( body ) {
219                         resp.write( `<div id='data'>${ body }</div>` );
220                         resp.end( body );
221                 }, next );
222         },
223         etag: function( req, resp ) {
224                 const hash = Number( req.query.ts ).toString( 36 );
225                 const etag = `W/"${ hash }"`;
226                 if ( req.headers[ "if-none-match" ] === etag ) {
227                         resp.writeHead( 304 );
228                         resp.end();
229                         return;
230                 }
231                 resp.writeHead( 200, {
232                         "Etag": etag
233                 } );
234                 resp.end();
235         },
236         ims: function( req, resp ) {
237                 const ts = req.query.ts;
238                 if ( req.headers[ "if-modified-since" ] === ts ) {
239                         resp.writeHead( 304 );
240                         resp.end();
241                         return;
242                 }
243                 resp.writeHead( 200, {
244                         "Last-Modified": ts
245                 } );
246                 resp.end();
247         },
248         status: function( req, resp ) {
249                 resp.writeHead( Number( req.query.code ) );
250                 resp.end();
251         },
252         testHTML: function( req, resp ) {
253                 resp.writeHead( 200, { "Content-Type": "text/html" } );
254                 const body = fs
255                         .readFileSync( `${ __dirname }/data/test.include.html` )
256                         .toString()
257                         .replace( /{{baseURL}}/g, req.query.baseURL );
258                 resp.end( body );
259         },
260         cspFrame: function( _req, resp ) {
261                 resp.writeHead( 200, {
262                         "Content-Type": "text/html",
263                         "Content-Security-Policy": "default-src 'self'; require-trusted-types-for 'script'; " +
264                                 "report-uri /base/test/data/mock.php?action=cspLog"
265                 } );
266                 const body = fs.readFileSync( `${ __dirname }/data/csp.include.html` ).toString();
267                 resp.end( body );
268         },
269         cspNonce: function( req, resp ) {
270                 const testParam = req.query.test ? `-${ req.query.test }` : "";
271                 resp.writeHead( 200, {
272                         "Content-Type": "text/html",
273                         "Content-Security-Policy": "script-src 'nonce-jquery+hardcoded+nonce'; " +
274                                 "report-uri /base/test/data/mock.php?action=cspLog"
275                 } );
276                 const body = fs.readFileSync(
277                         `${ __dirname }/data/csp-nonce${ testParam }.html` ).toString();
278                 resp.end( body );
279         },
280         cspAjaxScript: function( _req, resp ) {
281                 resp.writeHead( 200, {
282                         "Content-Type": "text/html",
283                         "Content-Security-Policy": "script-src 'self'; " +
284                                 "report-uri /base/test/data/mock.php?action=cspLog"
285                 } );
286                 const body = fs.readFileSync(
287                         `${ __dirname }/data/csp-ajax-script.html` ).toString();
288                 resp.end( body );
289         },
290         cspLog: function( _req, resp ) {
291                 cspLog = "error";
292                 resp.writeHead( 200 );
293                 resp.end();
294         },
295         cspClean: function( _req, resp ) {
296                 cspLog = "";
297                 resp.writeHead( 200 );
298                 resp.end();
299         },
300         trustedHtml: function( _req, resp ) {
301                 resp.writeHead( 200, {
302                         "Content-Type": "text/html",
303                         "Content-Security-Policy": "require-trusted-types-for 'script'; " +
304                                 "report-uri /base/test/data/mock.php?action=cspLog"
305                 } );
306                 const body = fs.readFileSync( `${ __dirname }/data/trusted-html.html` ).toString();
307                 resp.end( body );
308         },
309         trustedTypesAttributes: function( _req, resp ) {
310                 resp.writeHead( 200, {
311                         "Content-Type": "text/html",
312                         "Content-Security-Policy": "require-trusted-types-for 'script'; " +
313                                 "report-uri /base/test/data/mock.php?action=cspLog"
314                 } );
315                 const body = fs.readFileSync(
316                         `${ __dirname }/data/trusted-types-attributes.html` ).toString();
317                 resp.end( body );
318         },
319         errorWithScript: function( req, resp ) {
320                 if ( req.query.withScriptContentType ) {
321                         resp.writeHead( 404, { "Content-Type": "application/javascript" } );
322                 } else {
323                         resp.writeHead( 404, { "Content-Type": "text/html; charset=UTF-8" } );
324                 }
325                 if ( req.query.callback ) {
326                         resp.end( `${ cleanCallback( req.query.callback )
327                                 }( {"status": 404, "msg": "Not Found"} )` );
328                 } else {
329                         resp.end( "QUnit.assert.ok( false, \"Mock return erroneously executed\" );" );
330                 }
331         }
333 const handlers = {
334         "test/data/mock.php": function( req, resp, next ) {
335                 if ( !mocks[ req.query.action ] ) {
336                         resp.writeHead( 400 );
337                         resp.end( "Invalid action query.\n" );
338                         console.log( "Invalid action query:", req.method, req.url );
339                         return;
340                 }
341                 mocks[ req.query.action ]( req, resp, next );
342         },
343         "test/data/support/csp.log": function( _req, resp ) {
344                 resp.writeHead( 200 );
345                 resp.end( cspLog );
346         },
347         "test/data/404.txt": function( _req, resp ) {
348                 resp.writeHead( 404 );
349                 resp.end( "" );
350         }
354  * Connect-compatible middleware factory for mocking server responses.
355  * Used by Ajax unit tests when run via Karma.
357  * Despite Karma using Express, it uses Connect to deal with custom middleware,
358  * which passes the raw Node Request and Response objects instead of the
359  * Express versions of these (e.g. no req.path, req.query, resp.set).
360  */
361 function MockserverMiddlewareFactory() {
363         /**
364          * @param {http.IncomingMessage} req
365          * @param {http.ServerResponse} resp
366          * @param {Function} next Continue request handling
367          */
368         return function( req, resp, next ) {
369                 const parsed = url.parse( req.url, /* parseQuery */ true );
370                 let path = parsed.pathname.replace( /^\/base\//, "" );
371                 const query = parsed.query;
372                 const subReq = Object.assign( Object.create( req ), {
373                         query: query,
374                         parsed: parsed
375                 } );
377                 if ( /^\/?test\/data\/mock.php\/?/.test( path ) ) {
379                         // Support REST-like Apache PathInfo
380                         path = "test\/data\/mock.php";
381                 }
383                 if ( !handlers[ path ] ) {
384                         next();
385                         return;
386                 }
388                 // console.log( "Mock handling", req.method, parsed.href );
389                 handlers[ path ]( subReq, resp, next );
390         };
393 function getBody( req ) {
394         return req.method !== "POST" ?
395                 Promise.resolve( "" ) :
396                 getRawBody( req, {
397                         encoding: true
398                 } );
401 function getMultiPartContent( req ) {
402         return new Promise( function( resolve ) {
403                 if ( req.method !== "POST" ) {
404                         resolve( "" );
405                         return;
406                 }
408                 const form = new multiparty.Form();
409                 form.parse( req, function( _err, fields, files ) {
410                         resolve( { fields, files } );
411                 } );
412         } );
415 module.exports = MockserverMiddlewareFactory;