Build: Fix pre release matching in compare size regex
[jquery.git] / test / middleware-mockserver.cjs
bloba07cb4798454718f61d3d0de04511261cc8cf2e1
1 "use strict";
3 const url = require( "node:url" );
4 const fs = require( "node:fs" );
5 const getRawBody = require( "raw-body" );
6 const multiparty = require( "multiparty" );
8 let cspLog = "";
10 /**
11  * Like `readFileSync`, but on error returns "ERROR"
12  * without crashing.
13  * @param path
14  */
15 function readFileSync( path ) {
16         try {
17                 return fs.readFileSync( path );
18         } catch ( e ) {
19                 return "ERROR";
20         }
23 /**
24  * Keep in sync with /test/mock.php
25  */
26 function cleanCallback( callback ) {
27         return callback.replace( /[^a-z0-9_]/gi, "" );
30 const mocks = {
31         contentType: function( req, resp ) {
32                 resp.writeHead( 200, {
33                         "content-type": req.query.contentType
34                 } );
35                 resp.end( req.query.response );
36         },
37         wait: function( req, resp ) {
38                 const wait = Number( req.query.wait ) * 1000;
39                 setTimeout( function() {
40                         if ( req.query.script ) {
41                                 resp.writeHead( 200, { "content-type": "text/javascript" } );
42                         } else {
43                                 resp.writeHead( 200, { "content-type": "text/html" } );
44                                 resp.end( "ERROR <script>QUnit.assert.ok( true, \"mock executed\" );</script>" );
45                         }
46                 }, wait );
47         },
48         name: function( req, resp, next ) {
49                 resp.writeHead( 200 );
50                 if ( req.query.name === "foo" ) {
51                         resp.end( "bar" );
52                         return;
53                 }
54                 getBody( req ).then( function( body ) {
55                         if ( body === "name=peter" ) {
56                                 resp.end( "pan" );
57                         } else {
58                                 resp.end( "ERROR" );
59                         }
60                 }, next );
61         },
62         xml: function( req, resp, next ) {
63                 const content = "<math><calculation>5-2</calculation><result>3</result></math>";
64                 resp.writeHead( 200, { "content-type": "text/xml" } );
66                 if ( req.query.cal === "5-2" ) {
67                         resp.end( content );
68                         return;
69                 }
70                 getBody( req ).then( function( body ) {
71                         if ( body === "cal=5-2" ) {
72                                 resp.end( content );
73                         } else {
74                                 resp.end( "<error>ERROR</error>" );
75                         }
76                 }, next );
77         },
78         atom: function( _req, resp ) {
79                 resp.writeHead( 200, { "content-type": "atom+xml" } );
80                 resp.end( "<root><element /></root>" );
81         },
82         script: function( req, resp ) {
83                 const headers = {};
84                 if ( req.query.header === "ecma" ) {
85                         headers[ "content-type" ] = "application/ecmascript";
86                 } else if ( "header" in req.query ) {
87                         headers[ "content-type" ] = "text/javascript";
88                 } else {
89                         headers[ "content-type" ] = "text/html";
90                 }
92                 if ( req.query.cors ) {
93                         headers[ "access-control-allow-origin" ] = "*";
94                 }
96                 resp.writeHead( 200, headers );
98                 if ( req.query.callback ) {
99                         resp.end( `${ cleanCallback( req.query.callback ) }(${ JSON.stringify( {
100                                 headers: req.headers
101                         } ) })` );
102                 } else {
103                         resp.end( "QUnit.assert.ok( true, \"mock executed\" );" );
104                 }
105         },
106         testbar: function( _req, resp ) {
107                 resp.writeHead( 200 );
108                 resp.end(
109                         "this.testBar = 'bar'; " +
110                         "jQuery('#ap').html('bar'); " +
111                         "QUnit.assert.ok( true, 'mock executed');"
112                 );
113         },
114         json: function( req, resp ) {
115                 const headers = {};
116                 if ( req.query.header ) {
117                         headers[ "content-type" ] = "application/json";
118                 }
119                 if ( req.query.cors ) {
120                         headers[ "access-control-allow-origin" ] = "*";
121                 }
122                 resp.writeHead( 200, headers );
123                 if ( req.query.array ) {
124                         resp.end( JSON.stringify(
125                                 [ { name: "John", age: 21 }, { name: "Peter", age: 25 } ]
126                         ) );
127                 } else {
128                         resp.end( JSON.stringify(
129                                 { data: { lang: "en", length: 25 } }
130                         ) );
131                 }
132         },
133         jsonp: function( req, resp, next ) {
134                 let callback;
135                 if ( Array.isArray( req.query.callback ) ) {
136                         callback = Promise.resolve( req.query.callback[ req.query.callback.length - 1 ] );
137                 } else if ( req.query.callback ) {
138                         callback = Promise.resolve( req.query.callback );
139                 } else if ( req.method === "GET" ) {
140                         callback = Promise.resolve( req.url.match( /^.+\/([^\/?]+)\?.+$/ )[ 1 ] );
141                 } else {
142                         callback = getBody( req ).then( function( body ) {
143                                 return body.trim().replace( "callback=", "" );
144                         } );
145                 }
146                 const json = req.query.array ?
147                         JSON.stringify(
148                                 [ { name: "John", age: 21 }, { name: "Peter", age: 25 } ]
149                         ) :
150                         JSON.stringify(
151                                 { data: { lang: "en", length: 25 } }
152                         );
153                 callback.then( function( cb ) {
154                         resp.end( `${ cleanCallback( cb ) }(${ json })` );
155                 }, next );
156         },
157         xmlOverJsonp: function( req, resp ) {
158                 const callback = req.query.callback;
159                 const body = readFileSync( `${ __dirname }/data/with_fries.xml` ).toString();
160                 resp.writeHead( 200 );
161                 resp.end( `${ cleanCallback( callback ) }(${ JSON.stringify( body ) })\n` );
162         },
163         formData: function( req, resp, next ) {
164                 const prefix = "multipart/form-data; boundary=--";
165                 const contentTypeValue = req.headers[ "content-type" ];
166                 resp.writeHead( 200 );
167                 if ( ( prefix || "" ).startsWith( prefix ) ) {
168                         getMultiPartContent( req ).then( function( { fields = {} } ) {
169                                 resp.end( `key1 -> ${ fields.key1 }, key2 -> ${ fields.key2 }` );
170                         }, next );
171                 } else {
172                         resp.end( `Incorrect Content-Type: ${ contentTypeValue
173                                 }\nExpected prefix: ${ prefix }` );
174                 }
175         },
176         error: function( req, resp ) {
177                 if ( req.query.json ) {
178                         resp.writeHead( 400, { "content-type": "application/json" } );
179                         resp.end( "{ \"code\": 40, \"message\": \"Bad Request\" }" );
180                 } else {
181                         resp.writeHead( 400 );
182                         resp.end( "plain text message" );
183                 }
184         },
185         headers: function( req, resp ) {
186                 const headers = {
187                         "Sample-Header": "Hello World",
188                         "Empty-Header": "",
189                         "Sample-Header2": "Hello World 2",
190                         "List-Header": "Item 1",
191                         "list-header": "Item 2",
192                         "constructor": "prototype collision (constructor)"
193                 };
195                 resp.writeHead( 200, headers );
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 = readFileSync(
255                                 `${ __dirname }/data/test.include.html`
256                         )
257                         .toString()
258                         .replace( /{{baseURL}}/g, req.query.baseURL );
259                 resp.end( body );
260         },
261         cspFrame: function( _req, resp ) {
262                 resp.writeHead( 200, {
263                         "Content-Type": "text/html",
264                         "Content-Security-Policy": "default-src 'self'; require-trusted-types-for 'script'; " +
265                                 "report-uri /test/data/mock.php?action=cspLog"
266                 } );
267                 const body = readFileSync( `${ __dirname }/data/csp.include.html` ).toString();
268                 resp.end( body );
269         },
270         cspNonce: function( req, resp ) {
271                 const testParam = req.query.test ?
272                         `-${ req.query.test.replace( /[^a-z0-9]/gi, "" ) }` :
273                         "";
274                 resp.writeHead( 200, {
275                         "Content-Type": "text/html",
276                         "Content-Security-Policy": "script-src 'nonce-jquery+hardcoded+nonce'; " +
277                                 "report-uri /test/data/mock.php?action=cspLog"
278                 } );
279                 const body = readFileSync(
280                         `${ __dirname }/data/csp-nonce${ testParam }.html` ).toString();
281                 resp.end( body );
282         },
283         cspAjaxScript: function( _req, resp ) {
284                 resp.writeHead( 200, {
285                         "Content-Type": "text/html",
286                         "Content-Security-Policy": "script-src 'self'; " +
287                                 "report-uri /test/data/mock.php?action=cspLog"
288                 } );
289                 const body = readFileSync(
290                         `${ __dirname }/data/csp-ajax-script.html` ).toString();
291                 resp.end( body );
292         },
293         cspLog: function( _req, resp ) {
294                 cspLog = "error";
295                 resp.writeHead( 200 );
296                 resp.end();
297         },
298         cspClean: function( _req, resp ) {
299                 cspLog = "";
300                 resp.writeHead( 200 );
301                 resp.end();
302         },
303         trustedHtml: function( _req, resp ) {
304                 resp.writeHead( 200, {
305                         "Content-Type": "text/html",
306                         "Content-Security-Policy": "require-trusted-types-for 'script'; " +
307                                 "report-uri /test/data/mock.php?action=cspLog"
308                 } );
309                 const body = readFileSync( `${ __dirname }/data/trusted-html.html` ).toString();
310                 resp.end( body );
311         },
312         trustedTypesAttributes: function( _req, resp ) {
313                 resp.writeHead( 200, {
314                         "Content-Type": "text/html",
315                         "Content-Security-Policy": "require-trusted-types-for 'script'; " +
316                                 "report-uri /test/data/mock.php?action=cspLog"
317                 } );
318                 const body = readFileSync(
319                         `${ __dirname }/data/trusted-types-attributes.html` ).toString();
320                 resp.end( body );
321         },
322         errorWithScript: function( req, resp ) {
323                 if ( req.query.withScriptContentType ) {
324                         resp.writeHead( 404, { "Content-Type": "application/javascript" } );
325                 } else {
326                         resp.writeHead( 404, { "Content-Type": "text/html; charset=UTF-8" } );
327                 }
328                 if ( req.query.callback ) {
329                         resp.end( `${ cleanCallback( req.query.callback )
330                                 }( {"status": 404, "msg": "Not Found"} )` );
331                 } else {
332                         resp.end( "QUnit.assert.ok( false, \"Mock return erroneously executed\" );" );
333                 }
334         }
336 const handlers = {
337         "test/data/mock.php": function( req, resp, next ) {
338                 if ( !mocks[ req.query.action ] ) {
339                         resp.writeHead( 400 );
340                         resp.end( "Invalid action query.\n" );
341                         console.log( "Invalid action query:", req.method, req.url );
342                         return;
343                 }
344                 mocks[ req.query.action ]( req, resp, next );
345         },
346         "test/data/support/csp.log": function( _req, resp ) {
347                 resp.writeHead( 200 );
348                 resp.end( cspLog );
349         },
350         "test/data/404.txt": function( _req, resp ) {
351                 resp.writeHead( 404 );
352                 resp.end( "" );
353         }
357  * Connect-compatible middleware factory for mocking server responses.
358  * Used by Ajax tests run in Node.
359  */
360 function MockserverMiddlewareFactory() {
362         /**
363          * @param {http.IncomingMessage} req
364          * @param {http.ServerResponse} resp
365          * @param {Function} next Continue request handling
366          */
367         return function( req, resp, next ) {
368                 const parsed = url.parse( req.url, /* parseQuery */ true );
369                 let path = parsed.pathname;
370                 const query = parsed.query;
371                 const subReq = Object.assign( Object.create( req ), {
372                         query: query,
373                         parsed: parsed
374                 } );
376                 if ( /^\/?test\/data\/mock.php\/?/.test( path ) ) {
378                         // Support REST-like Apache PathInfo
379                         path = "test\/data\/mock.php";
380                 }
382                 if ( !handlers[ path ] ) {
383                         next();
384                         return;
385                 }
387                 // console.log( "Mock handling", req.method, parsed.href );
388                 handlers[ path ]( subReq, resp, next );
389         };
392 function getBody( req ) {
393         return req.method !== "POST" ?
394                 Promise.resolve( "" ) :
395                 getRawBody( req, {
396                         encoding: true
397                 } );
400 function getMultiPartContent( req ) {
401         return new Promise( function( resolve ) {
402                 if ( req.method !== "POST" ) {
403                         resolve( "" );
404                         return;
405                 }
407                 const form = new multiparty.Form();
408                 form.parse( req, function( _err, fields, files ) {
409                         resolve( { fields, files } );
410                 } );
411         } );
414 module.exports = MockserverMiddlewareFactory;