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