| /* eslint-env node */ |
| var url = require( "url" ); |
| var fs = require( "fs" ); |
| var getRawBody = require( "raw-body" ); |
| |
| var cspLog = ""; |
| /** |
| * Keep in sync with /test/mock.php |
| */ |
| function cleanCallback( callback ) { |
| return callback.replace( /[^a-z0-9_]/gi, "" ); |
| } |
| |
| var mocks = { |
| contentType: function( req, resp ) { |
| resp.writeHead( 200, { |
| "content-type": req.query.contentType |
| } ); |
| resp.end( req.query.response ); |
| }, |
| wait: function( req, resp ) { |
| var wait = Number( req.query.wait ) * 1000; |
| setTimeout( function() { |
| if ( req.query.script ) { |
| resp.writeHead( 200, { "content-type": "text/javascript" } ); |
| } else { |
| resp.writeHead( 200, { "content-type": "text/html" } ); |
| resp.end( "ERROR <script>QUnit.assert.ok( true, \"mock executed\" );</script>" ); |
| } |
| }, wait ); |
| }, |
| name: function( req, resp, next ) { |
| resp.writeHead( 200 ); |
| if ( req.query.name === "foo" ) { |
| resp.end( "bar" ); |
| return; |
| } |
| getBody( req ).then( function( body ) { |
| if ( body === "name=peter" ) { |
| resp.end( "pan" ); |
| } else { |
| resp.end( "ERROR" ); |
| } |
| }, next ); |
| }, |
| xml: function( req, resp, next ) { |
| var content = "<math><calculation>5-2</calculation><result>3</result></math>"; |
| resp.writeHead( 200, { "content-type": "text/xml" } ); |
| |
| if ( req.query.cal === "5-2" ) { |
| resp.end( content ); |
| return; |
| } |
| getBody( req ).then( function( body ) { |
| if ( body === "cal=5-2" ) { |
| resp.end( content ); |
| } else { |
| resp.end( "<error>ERROR</error>" ); |
| } |
| }, next ); |
| }, |
| atom: function( req, resp, next ) { |
| resp.writeHead( 200, { "content-type": "atom+xml" } ); |
| resp.end( "<root><element /></root>" ); |
| }, |
| script: function( req, resp ) { |
| if ( req.query.header === "ecma" ) { |
| resp.writeHead( 200, { "content-type": "application/ecmascript" } ); |
| } else if ( "header" in req.query ) { |
| resp.writeHead( 200, { "content-type": "text/javascript" } ); |
| } else { |
| resp.writeHead( 200, { "content-type": "text/html" } ); |
| } |
| resp.end( "QUnit.assert.ok( true, \"mock executed\" );" ); |
| }, |
| testbar: function( req, resp ) { |
| resp.writeHead( 200 ); |
| resp.end( |
| "this.testBar = 'bar'; " + |
| "jQuery('#ap').html('bar'); " + |
| "QUnit.assert.ok( true, 'mock executed');" |
| ); |
| }, |
| json: function( req, resp ) { |
| if ( req.query.header ) { |
| resp.writeHead( 200, { "content-type": "application/json" } ); |
| } |
| if ( req.query.array ) { |
| resp.end( JSON.stringify( |
| [ { name: "John", age: 21 }, { name: "Peter", age: 25 } ] |
| ) ); |
| } else { |
| resp.end( JSON.stringify( |
| { data: { lang: "en", length: 25 } } |
| ) ); |
| } |
| }, |
| jsonp: function( req, resp, next ) { |
| var callback; |
| if ( Array.isArray( req.query.callback ) ) { |
| callback = Promise.resolve( req.query.callback[ req.query.callback.length - 1 ] ); |
| } else if ( req.query.callback ) { |
| callback = Promise.resolve( req.query.callback ); |
| } else if ( req.method === "GET" ) { |
| callback = Promise.resolve( req.url.match( /^.+\/([^\/?]+)\?.+$/ )[ 1 ] ); |
| } else { |
| callback = getBody( req ).then( function( body ) { |
| return body.trim().replace( "callback=", "" ); |
| } ); |
| } |
| var json = req.query.array ? |
| JSON.stringify( |
| [ { name: "John", age: 21 }, { name: "Peter", age: 25 } ] |
| ) : |
| JSON.stringify( |
| { data: { lang: "en", length: 25 } } |
| ); |
| callback.then( function( cb ) { |
| resp.end( cleanCallback( cb ) + "(" + json + ")" ); |
| }, next ); |
| }, |
| xmlOverJsonp: function( req, resp ) { |
| var callback = req.query.callback; |
| var body = fs.readFileSync( __dirname + "/data/with_fries.xml" ).toString(); |
| resp.writeHead( 200 ); |
| resp.end( cleanCallback( callback ) + "(" + JSON.stringify( body ) + ")\n" ); |
| }, |
| error: function( req, resp ) { |
| if ( req.query.json ) { |
| resp.writeHead( 400, { "content-type": "application/json" } ); |
| resp.end( "{ \"code\": 40, \"message\": \"Bad Request\" }" ); |
| } else { |
| resp.writeHead( 400 ); |
| resp.end( "plain text message" ); |
| } |
| }, |
| headers: function( req, resp ) { |
| resp.writeHead( 200, { |
| "Sample-Header": "Hello World", |
| "Empty-Header": "", |
| "Sample-Header2": "Hello World 2", |
| "List-Header": "Item 1", |
| "list-header": "Item 2", |
| "constructor": "prototype collision (constructor)" |
| } ); |
| req.query.keys.split( "|" ).forEach( function( key ) { |
| if ( req.headers[ key.toLowerCase() ] ) { |
| resp.write( key + ": " + req.headers[ key.toLowerCase() ] + "\n" ); |
| } |
| } ); |
| resp.end(); |
| }, |
| echoData: function( req, resp, next ) { |
| getBody( req ).then( function( body ) { |
| resp.end( body ); |
| }, next ); |
| }, |
| echoQuery: function( req, resp ) { |
| resp.end( req.parsed.search.slice( 1 ) ); |
| }, |
| echoMethod: function( req, resp ) { |
| resp.end( req.method ); |
| }, |
| echoHtml: function( req, resp, next ) { |
| resp.writeHead( 200, { "Content-Type": "text/html" } ); |
| resp.write( "<div id='method'>" + req.method + "</div>" ); |
| resp.write( "<div id='query'>" + req.parsed.search.slice( 1 ) + "</div>" ); |
| getBody( req ).then( function( body ) { |
| resp.write( "<div id='data'>" + body + "</div>" ); |
| resp.end( body ); |
| }, next ); |
| }, |
| etag: function( req, resp ) { |
| var hash = Number( req.query.ts ).toString( 36 ); |
| var etag = "W/\"" + hash + "\""; |
| if ( req.headers[ "if-none-match" ] === etag ) { |
| resp.writeHead( 304 ); |
| resp.end(); |
| return; |
| } |
| resp.writeHead( 200, { |
| "Etag": etag |
| } ); |
| resp.end(); |
| }, |
| ims: function( req, resp, next ) { |
| var ts = req.query.ts; |
| if ( req.headers[ "if-modified-since" ] === ts ) { |
| resp.writeHead( 304 ); |
| resp.end(); |
| return; |
| } |
| resp.writeHead( 200, { |
| "Last-Modified": ts |
| } ); |
| resp.end(); |
| }, |
| status: function( req, resp, next ) { |
| resp.writeHead( Number( req.query.code ) ); |
| resp.end(); |
| }, |
| testHTML: function( req, resp ) { |
| resp.writeHead( 200, { "Content-Type": "text/html" } ); |
| var body = fs.readFileSync( __dirname + "/data/test.include.html" ).toString(); |
| body = body.replace( /{{baseURL}}/g, req.query.baseURL ); |
| resp.end( body ); |
| }, |
| cspFrame: function( req, resp ) { |
| resp.writeHead( 200, { |
| "Content-Type": "text/html", |
| "Content-Security-Policy": "default-src 'self'; report-uri /base/test/data/mock.php?action=cspLog" |
| } ); |
| var body = fs.readFileSync( __dirname + "/data/csp.include.html" ).toString(); |
| resp.end( body ); |
| }, |
| cspNonce: function( req, resp ) { |
| var testParam = req.query.test ? "-" + req.query.test : ""; |
| resp.writeHead( 200, { |
| "Content-Type": "text/html", |
| "Content-Security-Policy": "script-src 'nonce-jquery+hardcoded+nonce'; report-uri /base/test/data/mock.php?action=cspLog" |
| } ); |
| var body = fs.readFileSync( |
| __dirname + "/data/csp-nonce" + testParam + ".html" ).toString(); |
| resp.end( body ); |
| }, |
| cspLog: function( req, resp ) { |
| cspLog = "error"; |
| resp.writeHead( 200 ); |
| resp.end(); |
| }, |
| cspClean: function( req, resp ) { |
| cspLog = ""; |
| resp.writeHead( 200 ); |
| resp.end(); |
| }, |
| errorWithScript: function( req, resp ) { |
| if ( req.query.withScriptContentType ) { |
| resp.writeHead( 404, { "Content-Type": "application/javascript" } ); |
| } else { |
| resp.writeHead( 404, { "Content-Type": "text/html; charset=UTF-8" } ); |
| } |
| if ( req.query.callback ) { |
| resp.end( cleanCallback( req.query.callback ) + |
| "( {\"status\": 404, \"msg\": \"Not Found\"} )" ); |
| } else { |
| resp.end( "QUnit.assert.ok( false, \"Mock return erroneously executed\" );" ); |
| } |
| } |
| }; |
| var handlers = { |
| "test/data/mock.php": function( req, resp, next ) { |
| if ( !mocks[ req.query.action ] ) { |
| resp.writeHead( 400 ); |
| resp.end( "Invalid action query.\n" ); |
| console.log( "Invalid action query:", req.method, req.url ); |
| return; |
| } |
| mocks[ req.query.action ]( req, resp, next ); |
| }, |
| "test/data/support/csp.log": function( req, resp ) { |
| resp.writeHead( 200 ); |
| resp.end( cspLog ); |
| }, |
| "test/data/404.txt": function( req, resp ) { |
| resp.writeHead( 404 ); |
| resp.end( "" ); |
| } |
| }; |
| |
| /** |
| * Connect-compatible middleware factory for mocking server responses. |
| * Used by Ajax unit tests when run via Karma. |
| * |
| * Despite Karma using Express, it uses Connect to deal with custom middleware, |
| * which passes the raw Node Request and Response objects instead of the |
| * Express versions of these (e.g. no req.path, req.query, resp.set). |
| */ |
| function MockserverMiddlewareFactory() { |
| /** |
| * @param {http.IncomingMessage} req |
| * @param {http.ServerResponse} resp |
| * @param {Function} next Continue request handling |
| */ |
| return function( req, resp, next ) { |
| var parsed = url.parse( req.url, /* parseQuery */ true ), |
| path = parsed.pathname.replace( /^\/base\//, "" ), |
| query = parsed.query, |
| subReq = Object.assign( Object.create( req ), { |
| query: query, |
| parsed: parsed |
| } ); |
| |
| if ( /^test\/data\/mock.php\//.test( path ) ) { |
| // Support REST-like Apache PathInfo |
| path = "test\/data\/mock.php"; |
| } |
| |
| if ( !handlers[ path ] ) { |
| next(); |
| return; |
| } |
| |
| handlers[ path ]( subReq, resp, next ); |
| }; |
| } |
| |
| function getBody( req ) { |
| return req.method !== "POST" ? |
| Promise.resolve( "" ) : |
| getRawBody( req, { |
| encoding: true |
| } ); |
| } |
| |
| module.exports = MockserverMiddlewareFactory; |