Tests: replace express with basic Node server

Closes gh-5531
This commit is contained in:
Timmy Willison 2024-08-11 11:17:34 -04:00
parent af599d0d63
commit b4ab47afd7
7 changed files with 1638 additions and 1049 deletions

View File

@ -205,7 +205,15 @@ module.exports = [
},
rules: {
...jqueryConfig.rules,
"no-implicit-globals": "error"
"no-implicit-globals": "error",
"no-unused-vars": [
"error",
{
args: "after-used",
argsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_"
}
]
}
},
@ -271,7 +279,7 @@ module.exports = [
"test/node_smoke_tests/**",
"test/bundler_smoke_tests/**/*",
"test/promises_aplus_adapters/**",
"test/middleware-mockserver.js"
"test/middleware-mockserver.cjs"
],
languageOptions: {
ecmaVersion: "latest",

2414
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -61,35 +61,33 @@
"license": "MIT",
"devDependencies": {
"@babel/cli": "7.24.8",
"@babel/core": "7.24.9",
"@babel/core": "7.25.2",
"@babel/plugin-transform-for-of": "7.24.7",
"@prantlf/jsonlint": "14.0.3",
"@types/selenium-webdriver": "4.1.24",
"body-parser": "1.20.2",
"archiver": "7.0.1",
"bootstrap": "5.3.3",
"browserstack-local": "1.5.5",
"chalk": "5.3.0",
"colors": "1.4.0",
"commitplease": "3.2.0",
"concurrently": "8.2.2",
"core-js-bundle": "3.37.1",
"core-js-bundle": "3.38.0",
"cross-env": "7.0.3",
"diff": "5.2.0",
"eslint": "9.4.0",
"eslint": "9.8.0",
"eslint-config-jquery": "3.0.2",
"exit-hook": "4.0.0",
"express": "4.19.2",
"express-body-parser-error-handler": "1.0.7",
"globals": "15.8.0",
"husky": "9.1.1",
"globals": "15.9.0",
"husky": "9.1.4",
"jsdom": "24.1.1",
"marked": "13.0.2",
"marked": "14.0.0",
"native-promise-only": "0.8.1",
"nodemon": "3.1.4",
"promises-aplus-tests": "2.1.2",
"q": "1.5.1",
"qunit": "2.21.1",
"raw-body": "2.5.2",
"raw-body": "3.0.0",
"release-it": "17.6.0",
"requirejs": "2.3.7",
"rimraf": "6.0.1",

View File

@ -1,7 +1,7 @@
<?php
/**
* Keep in sync with /test/middleware-mockserver.js
* Keep in sync with /test/middleware-mockserver.cjs
*/
function cleanCallback( $callback ) {
return preg_replace( '/[^a-z0-9_]/i', '', $callback );

View File

@ -165,16 +165,7 @@ const mocks = {
"constructor": "prototype collision (constructor)"
};
// Use resp.append in express to
// avoid overwriting List-Header
if ( resp.append ) {
for ( const key in headers ) {
resp.append( key, headers[ key ] );
}
} else {
resp.writeHead( 200, headers );
}
resp.writeHead( 200, headers );
req.query.keys.split( "|" ).forEach( function( key ) {
if ( key.toLowerCase() in req.headers ) {
resp.write( `${ key }: ${ req.headers[ key.toLowerCase() ] }\n` );
@ -305,11 +296,7 @@ const handlers = {
/**
* 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).
* Used by Ajax tests run in Node.
*/
function MockserverMiddlewareFactory() {

View File

@ -1,70 +1,192 @@
import bodyParser from "body-parser";
import express from "express";
import bodyParserErrorHandler from "express-body-parser-error-handler";
import { readFile } from "node:fs/promises";
import mockServer from "../middleware-mockserver.js";
import http from "node:http";
import { readFile, stat } from "node:fs/promises";
import { createReadStream } from "node:fs";
import mockServer from "../middleware-mockserver.cjs";
import getRawBody from "raw-body";
export async function createTestServer( report ) {
const nameHTML = await readFile( "./test/data/name.html", "utf8" );
export async function createTestServer( report, { quiet } = {} ) {
const indexHTML = await readFile( "./test/index.html", "utf8" );
const app = express();
// Support connect-style middleware
const middlewares = [];
function use( middleware ) {
middlewares.push( middleware );
}
function run( req, res ) {
let i = 0;
// Log responses unless quiet is set
if ( !quiet ) {
const originalEnd = res.end;
res.end = function( ...args ) {
console.log( `${ req.method } ${ req.url } ${ this.statusCode }` );
originalEnd.call( this, ...args );
};
}
// Add a parsed URL object to the request object
req.parsedUrl = new URL(
`http://${ process.env.HOST ?? "localhost" }${ req.url }`
);
// Add a simplified redirect helper to the response object
res.redirect = ( status, location ) => {
if ( !location ) {
location = status;
status = 303;
}
res.writeHead( status, { Location: location } );
res.end();
};
const next = () => {
const middleware = middlewares[ i++ ];
if ( middleware ) {
try {
middleware( req, res, next );
} catch ( error ) {
console.error( error );
res.writeHead( 500, { "Content-Type": "application/json" } );
res.end( "Internal Server Error" );
}
} else {
res.writeHead( 404 );
res.end();
}
};
next();
}
// Redirect home to test page
app.get( "/", ( _req, res ) => {
res.redirect( "/test/" );
use( ( req, res, next ) => {
if ( req.parsedUrl.pathname === "/" ) {
res.redirect( "/test/" );
} else {
next();
}
} );
// Redirect to trailing slash
app.use( ( req, res, next ) => {
if ( req.path === "/test" ) {
const query = req.url.slice( req.path.length );
res.redirect( 301, `${ req.path }/${ query }` );
use( ( req, res, next ) => {
if ( req.parsedUrl.pathname === "/test" ) {
res.redirect( 308, `${ req.parsedUrl.pathname }/${ req.parsedUrl.search }` );
} else {
next();
}
} );
// Add a script tag to the index.html to load the QUnit listeners
app.use( /\/test(?:\/index.html)?\//, ( _req, res ) => {
res.send(
indexHTML.replace(
"</head>",
"<script src=\"/test/runner/listeners.js\"></script></head>"
)
);
use( ( req, res, next ) => {
if (
( req.method === "GET" || req.method === "HEAD" ) &&
( req.parsedUrl.pathname === "/test/" ||
req.parsedUrl.pathname === "/test/index.html" )
) {
res.writeHead( 200, { "Content-Type": "text/html" } );
res.end(
indexHTML.replace(
"</head>",
"<script src=\"/test/runner/listeners.js\"></script></head>"
)
);
} else {
next();
}
} );
// Bind the reporter
app.post(
"/api/report",
bodyParser.json( { limit: "50mb" } ),
async( req, res ) => {
if ( report ) {
const response = await report( req.body );
if ( response ) {
res.json( response );
return;
}
}
res.sendStatus( 204 );
use( async( req, res, next ) => {
if ( req.url !== "/api/report" || req.method !== "POST" ) {
return next();
}
let body;
try {
body = JSON.parse( await getRawBody( req ) );
} catch ( error ) {
if ( error.code === "ECONNABORTED" ) {
return;
}
console.error( error );
res.writeHead( 400, { "Content-Type": "application/json" } );
res.end( JSON.stringify( { error: "Invalid JSON" } ) );
return;
}
const response = await report( body );
if ( response ) {
res.writeHead( 200, { "Content-Type": "application/json" } );
res.end( JSON.stringify( response ) );
} else {
res.writeHead( 204 );
res.end();
}
);
// Handle errors from the body parser
app.use( bodyParserErrorHandler() );
// Hook up mock server
app.use( mockServer() );
// Serve static files
app.post( "/test/data/name.html", ( _req, res ) => {
res.send( nameHTML );
} );
app.use( "/dist", express.static( "dist" ) );
app.use( "/src", express.static( "src" ) );
app.use( "/test", express.static( "test" ) );
app.use( "/external", express.static( "external" ) );
// Hook up mock server
use( mockServer() );
return app;
// Serve static files
const validMimeTypes = {
// No .mjs or .cjs files are used in tests
".js": "application/javascript",
".css": "text/css",
".html": "text/html",
".xml": "application/xml",
".xhtml": "application/xhtml+xml",
".jpg": "image/jpeg",
".png": "image/png",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".map": "application/json",
".txt": "text/plain",
".log": "text/plain"
};
use( async( req, res, next ) => {
if (
!req.url.startsWith( "/dist/" ) &&
!req.url.startsWith( "/src/" ) &&
!req.url.startsWith( "/test/" ) &&
!req.url.startsWith( "/external/" )
) {
return next();
}
const file = req.parsedUrl.pathname.slice( 1 );
const ext = file.slice( file.lastIndexOf( "." ) );
// Allow POST to .html files in tests
if (
req.method !== "GET" &&
req.method !== "HEAD" &&
( ext !== ".html" || req.method !== "POST" )
) {
return next();
}
const mimeType = validMimeTypes[ ext ];
if ( mimeType ) {
try {
await stat( file );
} catch ( _ ) {
res.writeHead( 404 );
res.end();
return;
}
res.writeHead( 200, { "Content-Type": mimeType } );
createReadStream( file )
.pipe( res )
.on( "error", ( error ) => {
console.error( error );
res.writeHead( 500 );
res.end();
} );
} else {
console.error( `Invalid file extension: ${ ext }` );
res.writeHead( 404 );
res.end();
}
} );
return http.createServer( run );
}

View File

@ -137,7 +137,9 @@ export async function run( {
default:
console.warn( "Received unknown message type:", message.type );
}
} );
// Hide test server request logs in CLI output
}, { quiet: true } );
// Start up local test server
let server;