Skip to content

Commit

Permalink
Fix (tests): Prevent crashing the manual test server when reading a n…
Browse files Browse the repository at this point in the history
…on-existing file due to an "ERR_HTTP_HEADERS_SENT" Node.js error.
  • Loading branch information
pomek committed Sep 24, 2024
1 parent 3b69ca6 commit 15a8f79
Show file tree
Hide file tree
Showing 2 changed files with 293 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,19 @@ export default function createManualTestServer( sourcePath, port = 8125, onCreat
}

function onRequest( sourcePath, request, response ) {
response.writeHead( 200, {
'Content-Type': getContentType( request.url.endsWith( '/' ) ? '.html' : path.extname( request.url ) )
} );
const contentType = getContentType( request.url.endsWith( '/' ) ? '.html' : path.extname( request.url ) );

// Ignore a 'favicon' request.
if ( request.url === '/favicon.ico' ) {
response.writeHead( 200, { 'Content-Type': contentType } );

return response.end( null, 'utf-8' );
}

// Generate index.html with list of the tests.
if ( request.url === '/' ) {
response.writeHead( 200, { 'Content-Type': contentType } );

return response.end( generateIndex( sourcePath ), 'utf-8' );
}

Expand All @@ -79,7 +81,9 @@ function onRequest( sourcePath, request, response ) {
const url = request.url.replace( /\?.+$/, '' );
const content = fs.readFileSync( path.join( sourcePath, url ) );

response.end( content, 'utf-8' );
response.writeHead( 200, { 'Content-Type': contentType } );

return response.end( content, 'utf-8' );
} catch ( error ) {
logger().error( `[Server] Cannot find file '${ request.url }'.` );

Expand All @@ -101,6 +105,7 @@ function getContentType( fileExtension ) {
return 'text/css';

case '.json':
case '.map':
return 'application/json';

case '.png':
Expand Down Expand Up @@ -152,6 +157,8 @@ function generateIndex( sourcePath ) {
testList += '</ul></li>';
}

testList += '</ul>';

const headerHtml = '<body class="manual-test-list-container"><h1>CKEditor 5 manual tests</h1></body>';

return combine( viewTemplate, headerHtml, testList );
Expand Down
283 changes: 282 additions & 1 deletion packages/ckeditor5-dev-tests/tests/utils/manual-tests/createserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@

import http from 'http';
import readline from 'readline';
import fs from 'fs';
import { globSync } from 'glob';
import combine from 'dom-combiner';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { logger } from '@ckeditor/ckeditor5-dev-utils';
import createManualTestServer from '../../../lib/utils/manual-tests/createserver.js';

vi.mock( '@ckeditor/ckeditor5-dev-utils' );
vi.mock( 'readline' );
vi.mock( 'fs' );
vi.mock( '@ckeditor/ckeditor5-dev-utils' );
vi.mock( 'glob' );
vi.mock( 'dom-combiner' );

describe( 'createManualTestServer()', () => {
let loggerStub, server;
Expand Down Expand Up @@ -86,4 +92,279 @@ describe( 'createManualTestServer()', () => {
expect( vi.mocked( readline ).createInterface ).toHaveBeenCalledOnce();
expect( readlineInterface.on ).toHaveBeenCalledExactlyOnceWith( 'SIGINT', expect.any( Function ) );
} );

describe( 'request handler', () => {
beforeEach( () => {
createManualTestServer( 'workspace/build/.manual-tests' );
} );

it( 'should handle a request for a favicon (`/favicon.ico`)', () => {
const [ firstCall ] = vi.mocked( http ).createServer.mock.calls;
const [ serverCallback ] = firstCall;

const request = {
url: '/favicon.ico'
};
const response = {
writeHead: vi.fn(),
end: vi.fn()
};

serverCallback( request, response );

expect( response.writeHead ).toHaveBeenCalledExactlyOnceWith( 200, expect.objectContaining( {
'Content-Type': 'image/x-icon'
} ) );

expect( response.end ).toHaveBeenCalledExactlyOnceWith( null, 'utf-8' );
} );

it( 'should handle a root request (`/`)', () => {
const [ firstCall ] = vi.mocked( http ).createServer.mock.calls;
const [ serverCallback ] = firstCall;

const request = {
url: '/'
};
const response = {
writeHead: vi.fn(),
end: vi.fn()
};

vi.mocked( fs ).readFileSync.mockReturnValue( '<template></template>' );
vi.mocked( globSync ).mockReturnValue( [
'/build/.manual-tests/ckeditor5-foo/tests/manual/test-1.html',
'/build/.manual-tests/ckeditor5-foo/tests/manual/test-2.html',
'/build/.manual-tests/ckeditor5-bar/tests/manual/test-3.html',
'/build/.manual-tests/ckeditor5-bar/tests/manual/test-4.html'
] );
vi.mocked( combine ).mockReturnValue( 'Generated index.' );

serverCallback( request, response );

const expectedTestList =
'<ul>' +
'<li>' +
'<strong>ckeditor5-bar</strong>' +
'<ul>' +
'<li><a href="/build/.manual-tests/ckeditor5-bar/tests/manual/test-3.html">test-3.html</a></li>' +
'<li><a href="/build/.manual-tests/ckeditor5-bar/tests/manual/test-4.html">test-4.html</a></li>' +
'</ul>' +
'</li>' +
'<li>' +
'<strong>ckeditor5-foo</strong>' +
'<ul>' +
'<li><a href="/build/.manual-tests/ckeditor5-foo/tests/manual/test-1.html">test-1.html</a></li>' +
'<li><a href="/build/.manual-tests/ckeditor5-foo/tests/manual/test-2.html">test-2.html</a></li>' +
'</ul>' +
'</li>' +
'</ul>';

expect( vi.mocked( combine ) ).toHaveBeenCalledExactlyOnceWith(
'<template></template>',
expect.stringContaining( 'CKEditor 5 manual tests' ),
expectedTestList
);

expect( response.writeHead ).toHaveBeenCalledExactlyOnceWith( 200, expect.objectContaining( {
'Content-Type': 'text/html'
} ) );

expect( response.end ).toHaveBeenCalledExactlyOnceWith( 'Generated index.', 'utf-8' );
} );

it( 'should handle a request for a static resource (`*.html`)', () => {
const [ firstCall ] = vi.mocked( http ).createServer.mock.calls;
const [ serverCallback ] = firstCall;

const request = {
url: '/file.html'
};
const response = {
writeHead: vi.fn(),
end: vi.fn()
};

vi.mocked( fs ).readFileSync.mockReturnValue( 'An example content.' );

serverCallback( request, response );

expect( response.writeHead ).toHaveBeenCalledExactlyOnceWith( 200, expect.objectContaining( {
'Content-Type': 'text/html'
} ) );

expect( response.end ).toHaveBeenCalledExactlyOnceWith( 'An example content.', 'utf-8' );
} );

it( 'should handle a request for a static resource (`*.js`)', () => {
const [ firstCall ] = vi.mocked( http ).createServer.mock.calls;
const [ serverCallback ] = firstCall;

const request = {
url: '/file.js'
};
const response = {
writeHead: vi.fn(),
end: vi.fn()
};

vi.mocked( fs ).readFileSync.mockReturnValue( 'An example content.' );

serverCallback( request, response );

expect( response.writeHead ).toHaveBeenCalledExactlyOnceWith( 200, expect.objectContaining( {
'Content-Type': 'text/javascript'
} ) );

expect( response.end ).toHaveBeenCalledExactlyOnceWith( 'An example content.', 'utf-8' );
} );

it( 'should handle a request for a static resource (`*.json`)', () => {
const [ firstCall ] = vi.mocked( http ).createServer.mock.calls;
const [ serverCallback ] = firstCall;

const request = {
url: '/file.json'
};
const response = {
writeHead: vi.fn(),
end: vi.fn()
};

vi.mocked( fs ).readFileSync.mockReturnValue( 'An example content.' );

serverCallback( request, response );

expect( response.writeHead ).toHaveBeenCalledExactlyOnceWith( 200, expect.objectContaining( {
'Content-Type': 'application/json'
} ) );

expect( response.end ).toHaveBeenCalledExactlyOnceWith( 'An example content.', 'utf-8' );
} );

it( 'should handle a request for a static resource (`*.js.map`)', () => {
const [ firstCall ] = vi.mocked( http ).createServer.mock.calls;
const [ serverCallback ] = firstCall;

const request = {
url: '/file.js.map'
};
const response = {
writeHead: vi.fn(),
end: vi.fn()
};

vi.mocked( fs ).readFileSync.mockReturnValue( 'An example content.' );

serverCallback( request, response );

expect( response.writeHead ).toHaveBeenCalledExactlyOnceWith( 200, expect.objectContaining( {
'Content-Type': 'application/json'
} ) );

expect( response.end ).toHaveBeenCalledExactlyOnceWith( 'An example content.', 'utf-8' );
} );

it( 'should handle a request for a static resource (`*.css`)', () => {
const [ firstCall ] = vi.mocked( http ).createServer.mock.calls;
const [ serverCallback ] = firstCall;

const request = {
url: '/file.css'
};
const response = {
writeHead: vi.fn(),
end: vi.fn()
};

vi.mocked( fs ).readFileSync.mockReturnValue( 'An example content.' );

serverCallback( request, response );

expect( response.writeHead ).toHaveBeenCalledExactlyOnceWith( 200, expect.objectContaining( {
'Content-Type': 'text/css'
} ) );

expect( response.end ).toHaveBeenCalledExactlyOnceWith( 'An example content.', 'utf-8' );
} );

it( 'should handle a request for a static resource (`*.png`)', () => {
const [ firstCall ] = vi.mocked( http ).createServer.mock.calls;
const [ serverCallback ] = firstCall;

const request = {
url: '/file.png'
};
const response = {
writeHead: vi.fn(),
end: vi.fn()
};

vi.mocked( fs ).readFileSync.mockReturnValue( 'An example content.' );

serverCallback( request, response );

expect( response.writeHead ).toHaveBeenCalledExactlyOnceWith( 200, expect.objectContaining( {
'Content-Type': 'image/png'
} ) );

expect( response.end ).toHaveBeenCalledExactlyOnceWith( 'An example content.', 'utf-8' );
} );

it( 'should handle a request for a static resource (`*.jpg`)', () => {
const [ firstCall ] = vi.mocked( http ).createServer.mock.calls;
const [ serverCallback ] = firstCall;

const request = {
url: '/file.jpg'
};
const response = {
writeHead: vi.fn(),
end: vi.fn()
};

vi.mocked( fs ).readFileSync.mockReturnValue( 'An example content.' );

serverCallback( request, response );

expect( response.writeHead ).toHaveBeenCalledExactlyOnceWith( 200, expect.objectContaining( {
'Content-Type': 'image/jpg'
} ) );

expect( response.end ).toHaveBeenCalledExactlyOnceWith( 'An example content.', 'utf-8' );
} );

it( 'should handle a request for a non-existing resource', () => {
const [ firstCall ] = vi.mocked( http ).createServer.mock.calls;
const [ serverCallback ] = firstCall;

const request = {
url: '/file.jpg'
};
const response = {
writeHead: vi.fn(),
end: vi.fn()
};

vi.mocked( logger ).mockReturnValue( {
error: vi.fn()
} );

vi.mocked( fs ).readFileSync.mockImplementation( () => {
const error = new Error( 'A resource does not exist' );
error.code = 'ENOENT';

throw error;
} );

serverCallback( request, response );

expect( response.writeHead ).toHaveBeenCalledExactlyOnceWith( 404 );

expect( response.end ).toHaveBeenCalledExactlyOnceWith(
expect.stringContaining( 'Sorry, check with the site admin for error: ENOENT' ),
'utf-8'
);
} );
} );
} );

0 comments on commit 15a8f79

Please sign in to comment.