3 const url = require( "node:url" );
4 const fs = require( "node:fs" );
5 const getRawBody = require( "raw-body" );
6 const multiparty = require( "multiparty" );
11 * Keep in sync with /test/mock.php
13 function cleanCallback( callback ) {
14 return callback.replace( /[^a-z0-9_]/gi, "" );
18 contentType: function( req, resp ) {
19 resp.writeHead( 200, {
20 "content-type": req.query.contentType
22 resp.end( req.query.response );
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" } );
30 resp.writeHead( 200, { "content-type": "text/html" } );
31 resp.end( "ERROR <script>QUnit.assert.ok( true, \"mock executed\" );</script>" );
35 name: function( req, resp, next ) {
36 resp.writeHead( 200 );
37 if ( req.query.name === "foo" ) {
41 getBody( req ).then( function( body ) {
42 if ( body === "name=peter" ) {
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" ) {
57 getBody( req ).then( function( body ) {
58 if ( body === "cal=5-2" ) {
61 resp.end( "<error>ERROR</error>" );
65 atom: function( _req, resp ) {
66 resp.writeHead( 200, { "content-type": "atom+xml" } );
67 resp.end( "<root><element /></root>" );
69 script: function( req, resp ) {
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";
76 headers[ "content-type" ] = "text/html";
79 if ( req.query.cors ) {
80 headers[ "access-control-allow-origin" ] = "*";
83 resp.writeHead( 200, headers );
85 if ( req.query.callback ) {
86 resp.end( `${ cleanCallback( req.query.callback ) }(${ JSON.stringify( {
90 resp.end( "QUnit.assert.ok( true, \"mock executed\" );" );
93 testbar: function( _req, resp ) {
94 resp.writeHead( 200 );
96 "this.testBar = 'bar'; " +
97 "jQuery('#ap').html('bar'); " +
98 "QUnit.assert.ok( true, 'mock executed');"
101 json: function( req, resp ) {
103 if ( req.query.header ) {
104 headers[ "content-type" ] = "application/json";
106 if ( req.query.cors ) {
107 headers[ "access-control-allow-origin" ] = "*";
109 resp.writeHead( 200, headers );
110 if ( req.query.array ) {
111 resp.end( JSON.stringify(
112 [ { name: "John", age: 21 }, { name: "Peter", age: 25 } ]
115 resp.end( JSON.stringify(
116 { data: { lang: "en", length: 25 } }
120 jsonp: function( req, resp, next ) {
122 if ( Array.isArray( req.query.callback ) ) {
123 callback = Promise.resolve( req.query.callback[ req.query.callback.length - 1 ] );
124 } else if ( req.query.callback ) {
125 callback = Promise.resolve( req.query.callback );
126 } else if ( req.method === "GET" ) {
127 callback = Promise.resolve( req.url.match( /^.+\/([^\/?]+)\?.+$/ )[ 1 ] );
129 callback = getBody( req ).then( function( body ) {
130 return body.trim().replace( "callback=", "" );
133 const json = req.query.array ?
135 [ { name: "John", age: 21 }, { name: "Peter", age: 25 } ]
138 { data: { lang: "en", length: 25 } }
140 callback.then( function( cb ) {
141 resp.end( `${ cleanCallback( cb ) }(${ json })` );
144 xmlOverJsonp: function( req, resp ) {
145 const callback = req.query.callback;
146 const body = fs.readFileSync( `${ __dirname }/data/with_fries.xml` ).toString();
147 resp.writeHead( 200 );
148 resp.end( `${ cleanCallback( callback ) }(${ JSON.stringify( body ) })\n` );
150 formData: function( req, resp, next ) {
151 const prefix = "multipart/form-data; boundary=--";
152 const contentTypeValue = req.headers[ "content-type" ];
153 resp.writeHead( 200 );
154 if ( ( prefix || "" ).startsWith( prefix ) ) {
155 getMultiPartContent( req ).then( function( { fields = {} } ) {
156 resp.end( `key1 -> ${ fields.key1 }, key2 -> ${ fields.key2 }` );
159 resp.end( `Incorrect Content-Type: ${ contentTypeValue
160 }\nExpected prefix: ${ prefix }` );
163 error: function( req, resp ) {
164 if ( req.query.json ) {
165 resp.writeHead( 400, { "content-type": "application/json" } );
166 resp.end( "{ \"code\": 40, \"message\": \"Bad Request\" }" );
168 resp.writeHead( 400 );
169 resp.end( "plain text message" );
172 headers: function( req, resp ) {
174 "Sample-Header": "Hello World",
176 "Sample-Header2": "Hello World 2",
177 "List-Header": "Item 1",
178 "list-header": "Item 2",
179 "constructor": "prototype collision (constructor)"
182 // Use resp.append in express to
183 // avoid overwriting List-Header
186 for ( const key in headers ) {
187 resp.append( key, headers[ key ] );
190 resp.writeHead( 200, headers );
192 req.query.keys.split( "|" ).forEach( function( key ) {
193 if ( key.toLowerCase() in req.headers ) {
194 resp.write( `${ key }: ${ req.headers[ key.toLowerCase() ] }\n` );
199 echoData: function( req, resp, next ) {
200 getBody( req ).then( function( body ) {
204 echoQuery: function( req, resp ) {
205 resp.end( req.parsed.search.slice( 1 ) );
207 echoMethod: function( req, resp ) {
208 resp.end( req.method );
210 echoHtml: function( req, resp, next ) {
211 resp.writeHead( 200, { "Content-Type": "text/html" } );
212 resp.write( `<div id='method'>${ req.method }</div>` );
213 resp.write( `<div id='query'>${ req.parsed.search.slice( 1 ) }</div>` );
214 getBody( req ).then( function( body ) {
215 resp.write( `<div id='data'>${ body }</div>` );
219 etag: function( req, resp ) {
220 const hash = Number( req.query.ts ).toString( 36 );
221 const etag = `W/"${ hash }"`;
222 if ( req.headers[ "if-none-match" ] === etag ) {
223 resp.writeHead( 304 );
227 resp.writeHead( 200, {
232 ims: function( req, resp ) {
233 const ts = req.query.ts;
234 if ( req.headers[ "if-modified-since" ] === ts ) {
235 resp.writeHead( 304 );
239 resp.writeHead( 200, {
244 status: function( req, resp ) {
245 resp.writeHead( Number( req.query.code ) );
248 testHTML: function( req, resp ) {
249 resp.writeHead( 200, { "Content-Type": "text/html" } );
251 .readFileSync( `${ __dirname }/data/test.include.html` )
253 .replace( /{{baseURL}}/g, req.query.baseURL );
256 cspFrame: function( _req, resp ) {
257 resp.writeHead( 200, {
258 "Content-Type": "text/html",
259 "Content-Security-Policy": "default-src 'self'; require-trusted-types-for 'script'; " +
260 "report-uri /test/data/mock.php?action=cspLog"
262 const body = fs.readFileSync( `${ __dirname }/data/csp.include.html` ).toString();
265 cspNonce: function( req, resp ) {
266 const testParam = req.query.test ? `-${ req.query.test }` : "";
267 resp.writeHead( 200, {
268 "Content-Type": "text/html",
269 "Content-Security-Policy": "script-src 'nonce-jquery+hardcoded+nonce'; " +
270 "report-uri /test/data/mock.php?action=cspLog"
272 const body = fs.readFileSync(
273 `${ __dirname }/data/csp-nonce${ testParam }.html` ).toString();
276 cspAjaxScript: function( _req, resp ) {
277 resp.writeHead( 200, {
278 "Content-Type": "text/html",
279 "Content-Security-Policy": "script-src 'self'; " +
280 "report-uri /test/data/mock.php?action=cspLog"
282 const body = fs.readFileSync(
283 `${ __dirname }/data/csp-ajax-script.html` ).toString();
286 cspLog: function( _req, resp ) {
288 resp.writeHead( 200 );
291 cspClean: function( _req, resp ) {
293 resp.writeHead( 200 );
296 trustedHtml: function( _req, resp ) {
297 resp.writeHead( 200, {
298 "Content-Type": "text/html",
299 "Content-Security-Policy": "require-trusted-types-for 'script'; " +
300 "report-uri /test/data/mock.php?action=cspLog"
302 const body = fs.readFileSync( `${ __dirname }/data/trusted-html.html` ).toString();
305 trustedTypesAttributes: function( _req, resp ) {
306 resp.writeHead( 200, {
307 "Content-Type": "text/html",
308 "Content-Security-Policy": "require-trusted-types-for 'script'; " +
309 "report-uri /test/data/mock.php?action=cspLog"
311 const body = fs.readFileSync(
312 `${ __dirname }/data/trusted-types-attributes.html` ).toString();
315 errorWithScript: function( req, resp ) {
316 if ( req.query.withScriptContentType ) {
317 resp.writeHead( 404, { "Content-Type": "application/javascript" } );
319 resp.writeHead( 404, { "Content-Type": "text/html; charset=UTF-8" } );
321 if ( req.query.callback ) {
322 resp.end( `${ cleanCallback( req.query.callback )
323 }( {"status": 404, "msg": "Not Found"} )` );
325 resp.end( "QUnit.assert.ok( false, \"Mock return erroneously executed\" );" );
330 "test/data/mock.php": function( req, resp, next ) {
331 if ( !mocks[ req.query.action ] ) {
332 resp.writeHead( 400 );
333 resp.end( "Invalid action query.\n" );
334 console.log( "Invalid action query:", req.method, req.url );
337 mocks[ req.query.action ]( req, resp, next );
339 "test/data/support/csp.log": function( _req, resp ) {
340 resp.writeHead( 200 );
343 "test/data/404.txt": function( _req, resp ) {
344 resp.writeHead( 404 );
350 * Connect-compatible middleware factory for mocking server responses.
351 * Used by Ajax unit tests when run via Karma.
353 * Despite Karma using Express, it uses Connect to deal with custom middleware,
354 * which passes the raw Node Request and Response objects instead of the
355 * Express versions of these (e.g. no req.path, req.query, resp.set).
357 function MockserverMiddlewareFactory() {
360 * @param {http.IncomingMessage} req
361 * @param {http.ServerResponse} resp
362 * @param {Function} next Continue request handling
364 return function( req, resp, next ) {
365 const parsed = url.parse( req.url, /* parseQuery */ true );
366 let path = parsed.pathname;
367 const query = parsed.query;
368 const subReq = Object.assign( Object.create( req ), {
373 if ( /^\/?test\/data\/mock.php\/?/.test( path ) ) {
375 // Support REST-like Apache PathInfo
376 path = "test\/data\/mock.php";
379 if ( !handlers[ path ] ) {
384 // console.log( "Mock handling", req.method, parsed.href );
385 handlers[ path ]( subReq, resp, next );
389 function getBody( req ) {
390 return req.method !== "POST" ?
391 Promise.resolve( "" ) :
397 function getMultiPartContent( req ) {
398 return new Promise( function( resolve ) {
399 if ( req.method !== "POST" ) {
404 const form = new multiparty.Form();
405 form.parse( req, function( _err, fields, files ) {
406 resolve( { fields, files } );
411 module.exports = MockserverMiddlewareFactory;