3 const url = require( "url" );
4 const fs = require( "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 ) {
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" } );
75 resp.writeHead( 200, { "content-type": "text/html" } );
78 if ( req.query.cors ) {
79 resp.writeHead( 200, { "access-control-allow-origin": "*" } );
82 if ( req.query.callback ) {
83 resp.end( `${ cleanCallback( req.query.callback ) }(${ JSON.stringify( {
87 resp.end( "QUnit.assert.ok( true, \"mock executed\" );" );
90 testbar: function( _req, resp ) {
91 resp.writeHead( 200 );
93 "this.testBar = 'bar'; " +
94 "jQuery('#ap').html('bar'); " +
95 "QUnit.assert.ok( true, 'mock executed');"
98 json: function( req, resp ) {
99 if ( req.query.header ) {
100 resp.writeHead( 200, { "content-type": "application/json" } );
102 if ( req.query.cors ) {
103 resp.writeHead( 200, { "access-control-allow-origin": "*" } );
105 if ( req.query.array ) {
106 resp.end( JSON.stringify(
107 [ { name: "John", age: 21 }, { name: "Peter", age: 25 } ]
110 resp.end( JSON.stringify(
111 { data: { lang: "en", length: 25 } }
115 jsonp: function( req, resp, next ) {
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 ] );
124 callback = getBody( req ).then( function( body ) {
125 return body.trim().replace( "callback=", "" );
128 const json = req.query.array ?
130 [ { name: "John", age: 21 }, { name: "Peter", age: 25 } ]
133 { data: { lang: "en", length: 25 } }
135 callback.then( function( cb ) {
136 resp.end( `${ cleanCallback( cb ) }(${ json })` );
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` );
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 }` );
154 resp.end( `Incorrect Content-Type: ${ contentTypeValue
155 }\nExpected prefix: ${ prefix }` );
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\" }" );
163 resp.writeHead( 400 );
164 resp.end( "plain text message" );
167 headers: function( req, resp ) {
168 resp.writeHead( 200, {
169 "Sample-Header": "Hello World",
171 "Sample-Header2": "Hello World 2",
172 "List-Header": "Item 1",
173 "list-header": "Item 2",
174 "constructor": "prototype collision (constructor)"
176 req.query.keys.split( "|" ).forEach( function( key ) {
177 if ( key.toLowerCase() in req.headers ) {
178 resp.write( `${ key }: ${ req.headers[ key.toLowerCase() ] }\n` );
183 echoData: function( req, resp, next ) {
184 getBody( req ).then( function( body ) {
188 echoQuery: function( req, resp ) {
189 resp.end( req.parsed.search.slice( 1 ) );
191 echoMethod: function( req, resp ) {
192 resp.end( req.method );
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>` );
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 );
211 resp.writeHead( 200, {
216 ims: function( req, resp ) {
217 const ts = req.query.ts;
218 if ( req.headers[ "if-modified-since" ] === ts ) {
219 resp.writeHead( 304 );
223 resp.writeHead( 200, {
228 status: function( req, resp ) {
229 resp.writeHead( Number( req.query.code ) );
232 testHTML: function( req, resp ) {
233 resp.writeHead( 200, { "Content-Type": "text/html" } );
235 .readFileSync( `${ __dirname }/data/test.include.html` )
237 .replace( /{{baseURL}}/g, req.query.baseURL );
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"
246 const body = fs.readFileSync( `${ __dirname }/data/csp.include.html` ).toString();
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"
256 const body = fs.readFileSync(
257 `${ __dirname }/data/csp-nonce${ testParam }.html` ).toString();
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"
266 const body = fs.readFileSync(
267 `${ __dirname }/data/csp-ajax-script.html` ).toString();
270 cspLog: function( _req, resp ) {
272 resp.writeHead( 200 );
275 cspClean: function( _req, resp ) {
277 resp.writeHead( 200 );
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"
286 const body = fs.readFileSync( `${ __dirname }/data/trusted-html.html` ).toString();
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"
295 const body = fs.readFileSync(
296 `${ __dirname }/data/trusted-types-attributes.html` ).toString();
299 errorWithScript: function( req, resp ) {
300 if ( req.query.withScriptContentType ) {
301 resp.writeHead( 404, { "Content-Type": "application/javascript" } );
303 resp.writeHead( 404, { "Content-Type": "text/html; charset=UTF-8" } );
305 if ( req.query.callback ) {
306 resp.end( `${ cleanCallback( req.query.callback )
307 }( {"status": 404, "msg": "Not Found"} )` );
309 resp.end( "QUnit.assert.ok( false, \"Mock return erroneously executed\" );" );
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 );
321 mocks[ req.query.action ]( req, resp, next );
323 "test/data/support/csp.log": function( _req, resp ) {
324 resp.writeHead( 200 );
327 "test/data/404.txt": function( _req, resp ) {
328 resp.writeHead( 404 );
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).
341 function MockserverMiddlewareFactory() {
344 * @param {http.IncomingMessage} req
345 * @param {http.ServerResponse} resp
346 * @param {Function} next Continue request handling
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 ), {
357 if ( /^test\/data\/mock.php\//.test( path ) ) {
359 // Support REST-like Apache PathInfo
360 path = "test\/data\/mock.php";
363 if ( !handlers[ path ] ) {
368 handlers[ path ]( subReq, resp, next );
372 function getBody( req ) {
373 return req.method !== "POST" ?
374 Promise.resolve( "" ) :
380 function getMultiPartContent( req ) {
381 return new Promise( function( resolve ) {
382 if ( req.method !== "POST" ) {
387 const form = new multiparty.Form();
388 form.parse( req, function( _err, fields, files ) {
389 resolve( { fields, files } );
394 module.exports = MockserverMiddlewareFactory;