UNPKG

fastify-cli

Version:

Run a fastify route with one command!

1,202 lines (928 loc) 32.3 kB
/* global GLOBAL_MODULE_1, GLOBAL_MODULE_2, GLOBAL_MODULE_3, GLOBAL_MODULE_4 */ 'use strict' const util = require('node:util') const { once } = require('node:events') const fs = require('node:fs') const path = require('node:path') const crypto = require('node:crypto') const semver = require('semver') const os = require('node:os') const baseFilename = path.join(__dirname, 'fixtures', `test_${crypto.randomBytes(16).toString('hex')}`) const { fork } = require('node:child_process') const { test: nodeTest } = require('node:test') const assert = require('node:assert/strict') const moduleSupport = semver.satisfies(process.version, '>= 14 || >= 12.17.0 < 13.0.0') const sinon = require('sinon') const proxyquire = require('proxyquire').noPreserveCache() const start = require('../start') const writeFile = util.promisify(fs.writeFile) const readFile = util.promisify(fs.readFile) function createTestDir (fixtures) { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'fastify-cli-')) function writeFixtures (baseDir, entries) { for (const [name, value] of Object.entries(entries)) { const filename = path.join(baseDir, name) if (value && typeof value === 'object' && !Buffer.isBuffer(value)) { fs.mkdirSync(filename, { recursive: true }) writeFixtures(filename, value) } else { fs.mkdirSync(path.dirname(filename), { recursive: true }) fs.writeFileSync(filename, value) } } } writeFixtures(dir, fixtures) return dir } function tapCompatTest (name, options, fn) { if (typeof options === 'function') { fn = options options = undefined } return nodeTest(name, options, async (t) => { let planned = null let assertions = 0 const count = (method) => (...args) => { assertions++ return method(...args) } t.plan = (total) => { planned = total } t.equal = count(assert.strictEqual) t.same = count(assert.deepStrictEqual) t.ok = count(assert.ok) t.pass = count(() => assert.ok(true)) t.fail = count((message) => assert.fail(message)) t.end = () => {} t.teardown = (hook) => t.after(async () => hook()) t.testdir = createTestDir await fn(t) if (planned !== null) { assert.strictEqual(assertions, planned, `plan expected ${planned} assertions but received ${assertions}`) } }) } const test = tapCompatTest function requireUncached (module) { delete require.cache[require.resolve(module)] return require(module) } let _port = 3001 function getPort () { return '' + _port++ } // FIXME // paths are relative to the root of the project // this can be run only from there test('should start the server', async t => { t.plan(4) const argv = ['-p', getPort(), './examples/plugin.js'] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${fastify.server.address().port}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { hello: 'world' }) await fastify.close() t.pass('server closed') }) test('should start the server with a typescript compiled module', async t => { t.plan(4) const argv = ['-p', getPort(), './examples/ts-plugin.js'] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${fastify.server.address().port}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { hello: 'world' }) await fastify.close() t.pass('server closed') }) test('should start the server with pretty output', async t => { t.plan(4) const argv = ['-p', getPort(), '-P', './examples/plugin.js'] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${fastify.server.address().port}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { hello: 'world' }) await fastify.close() t.pass('server closed') }) test('should start fastify with custom options', async t => { t.plan(1) // here the test should fail because of the wrong certificate // or because the server is booted without the custom options try { const argv = ['-p', getPort(), '-o', 'true', './examples/plugin-with-options.js'] const fastify = await start.start(argv) await fastify.close() t.pass('server closed') } catch (e) { t.pass('Custom options') } }) test('should start fastify with custom plugin options', async t => { t.plan(4) const argv = [ '-p', getPort(), './examples/plugin-with-custom-options.js', '--', '-abc', '--hello', 'world' ] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${fastify.server.address().port}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { a: true, b: true, c: true, hello: 'world' }) await fastify.close() t.pass('server closed') t.end() }) test('should start fastify with default custom plugin options', async t => { t.plan(4) const argv = [ '-o', '-p', getPort(), './examples/plugin-with-custom-options.js' ] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${fastify.server.address().port}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { hello: 'test' }) await fastify.close() t.pass('server closed') t.end() }) test('should start fastify with custom options with a typescript compiled plugin', async t => { t.plan(1) // here the test should fail because of the wrong certificate // or because the server is booted without the custom options try { const argv = ['-p', getPort(), '-o', 'true', './examples/ts-plugin-with-options.js'] await start.start(argv) t.fail('Custom options') } catch (e) { t.pass('Custom options') } }) test('should start fastify with custom plugin options with a typescript compiled plugin', async t => { t.plan(4) const argv = [ '-p', getPort(), './examples/ts-plugin-with-custom-options.js', '--', '-abc', '--hello', 'world' ] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${fastify.server.address().port}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { a: true, b: true, c: true, hello: 'world' }) await fastify.close() t.pass('server closed') }) test('should start fastify with custom plugin default exported options with a typescript compiled plugin', async t => { t.plan(4) const argv = [ '-o', '-p', getPort(), './examples/ts-plugin-with-custom-options.js' ] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${fastify.server.address().port}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { hello: 'test' }) await fastify.close() t.pass('server closed') t.end() }) test('should start the server at the given prefix', async t => { t.plan(4) const argv = ['-p', getPort(), '-x', '/api/hello', './examples/plugin.js'] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${fastify.server.address().port}/api/hello`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { hello: 'world' }) await fastify.close() t.pass('server closed') }) test('should start fastify at given socket path', { skip: process.platform === 'win32' }, async t => { t.plan(1) const sockFile = path.resolve('test.sock') t.teardown(() => { try { fs.unlinkSync(sockFile) } catch (e) { } }) const argv = ['-s', sockFile, '-o', 'true', './examples/plugin.js'] try { fs.unlinkSync(sockFile) } catch (e) { } const fastify = await start.start(argv) await new Promise((resolve, reject) => { const request = require('node:http').request({ method: 'GET', path: '/', socketPath: sockFile }, function (response) { t.same(response.statusCode, 200) return resolve() }) request.end() }) t.teardown(fastify.close.bind(fastify)) }) test('should error with a good timeout value', async t => { t.plan(1) const start = proxyquire('../start', { assert: { ifError (err) { t.equal(err.code, 'AVV_ERR_PLUGIN_EXEC_TIMEOUT') } } }) const port = getPort() try { const argv = ['-p', port, '-T', '100', './test/data/timeout-plugin.js'] await start.start(argv) } catch (err) { t.equal(err.code, 'AVV_ERR_PLUGIN_EXEC_TIMEOUT') } }) test('should warn on file not found', t => { t.plan(1) const oldStop = start.stop t.teardown(() => { start.stop = oldStop }) start.stop = function (message) { t.ok(/not-found.js doesn't exist within/.test(message), message) } const argv = ['-p', getPort(), './data/not-found.js'] start.start(argv) }) test('should throw on package not found', async t => { t.plan(1) const oldStop = start.stop t.teardown(() => { start.stop = oldStop }) await new Promise((resolve) => { start.stop = function (err) { t.ok(/Cannot find module 'unknown-package'/.test(err.message), err.message) resolve() } const argv = ['-p', getPort(), './test/data/package-not-found.js'] start.start(argv) }) }) test('should throw on parsing error', async t => { t.plan(1) const oldStop = start.stop t.teardown(() => { start.stop = oldStop }) await new Promise((resolve) => { start.stop = function (err) { t.equal(err.constructor, SyntaxError) resolve() } const argv = ['-p', getPort(), './test/data/parsing-error.js'] start.start(argv) }) }) test('should start the server with an async/await plugin', async t => { if (Number(process.versions.node[0]) < 7) { t.pass('Skip because Node version < 7') return t.end() } t.plan(4) const argv = ['-p', getPort(), './examples/async-await-plugin.js'] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${fastify.server.address().port}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { hello: 'world' }) await fastify.close() t.pass('server closed') }) test('should exit without error on help', t => { const exit = process.exit process.exit = sinon.spy() t.teardown(() => { process.exit = exit }) const argv = ['-p', getPort(), '-h', 'true'] start.start(argv) t.ok(process.exit.called) t.equal(process.exit.lastCall.args[0], undefined) t.end() }) test('should throw the right error on require file', async t => { t.plan(1) const oldStop = start.stop t.teardown(() => { start.stop = oldStop }) await new Promise((resolve) => { start.stop = function (err) { t.ok(/undefinedVariable is not defined/.test(err.message), err.message) resolve() } const argv = ['-p', getPort(), './test/data/undefinedVariable.js'] start.start(argv) }) }) test('should respond 413 - Payload too large', async t => { t.plan(3) const bodyTooLarge = '{1: 11}' const bodySmaller = '{1: 1}' const bodyLimitValue = '' + (bodyTooLarge.length - 1) const argv = ['-p', getPort(), '--body-limit', bodyLimitValue, './examples/plugin.js'] const fastify = await start.start(argv) const responseFail = await fetch(`http://localhost:${fastify.server.address().port}`, { method: 'POST', body: bodyTooLarge, }) t.equal(responseFail.status, 413) const responseOk = await fetch(`http://localhost:${fastify.server.address().port}`, { method: 'POST', body: bodySmaller, }) t.equal(responseOk.status, 200) await fastify.close() t.pass('server closed') }) test('should start the server (using env var)', async t => { t.plan(4) process.env.FASTIFY_PORT = getPort() const argv = ['./examples/plugin.js'] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${process.env.FASTIFY_PORT}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { hello: 'world' }) delete process.env.FASTIFY_PORT await fastify.close() t.pass('server closed') }) test('should start the server (using PORT-env var)', async t => { t.plan(4) process.env.PORT = getPort() const argv = ['./examples/plugin.js'] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${process.env.PORT}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { hello: 'world' }) delete process.env.PORT await fastify.close() t.pass('server closed') }) test('should start the server (using FASTIFY_PORT-env preceding PORT-env var)', async t => { t.plan(4) process.env.FASTIFY_PORT = getPort() process.env.PORT = getPort() const argv = ['./examples/plugin.js'] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${process.env.FASTIFY_PORT}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { hello: 'world' }) delete process.env.FASTIFY_PORT delete process.env.PORT await fastify.close() t.pass('server closed') }) test('should start the server (using -p preceding FASTIFY_PORT-env var)', async t => { t.plan(4) const port = getPort() process.env.FASTIFY_PORT = getPort() const argv = ['-p', port, './examples/plugin.js'] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${port}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { hello: 'world' }) delete process.env.FASTIFY_PORT await fastify.close() t.pass('server closed') }) test('should start the server at the given prefix (using env var)', async t => { t.plan(4) process.env.FASTIFY_PORT = getPort() process.env.FASTIFY_PREFIX = '/api/hello' const argv = ['./examples/plugin.js'] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${process.env.FASTIFY_PORT}/api/hello`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { hello: 'world' }) delete process.env.FASTIFY_PORT delete process.env.FASTIFY_PREFIX await fastify.close() t.pass('server closed') }) test('should start the server at the given prefix (using env var read from dotenv)', async t => { t.plan(4) const stub = sinon.stub(process, 'loadEnvFile').callsFake(() => { t.pass('config called') process.env.FASTIFY_PORT = 8080 }) const argv = ['./examples/plugin.js'] const fastify = await start.start(argv) t.equal(fastify.server.address().port, 8080) delete process.env.FASTIFY_PORT stub.restore() await fastify.close() t.pass('server closed') }) test('should start the server listening on 0.0.0.0 when running in docker', async t => { t.plan(2) const isDocker = sinon.stub() isDocker.returns(true) const start = proxyquire('../start', { 'is-docker': isDocker }) const argv = ['-p', getPort(), './examples/plugin.js'] const fastify = await start.start(argv) t.equal(fastify.server.address().address, '0.0.0.0') await fastify.close() t.pass('server closed') }) test('should start the server listening on 0.0.0.0 when running in kubernetes', async t => { t.plan(2) const isKubernetes = sinon.stub() isKubernetes.returns(true) const start = proxyquire('../start', { './util': { ...require('../util'), isKubernetes } }) const argv = ['-p', getPort(), './examples/plugin.js'] const fastify = await start.start(argv) t.equal(fastify.server.address().address, '0.0.0.0') await fastify.close() t.pass('server closed') }) test('should start the server with watch options that the child process restart when directory changed', { skip: ['win32', 'darwin'].includes(process.platform) }, async (t) => { t.plan(3) const tmpjs = path.resolve(baseFilename + '.js') const port = getPort() await writeFile(tmpjs, 'hello world') const argv = ['-p', port, '-w', './examples/plugin.js'] const fastifyEmitter = await start.start(argv) t.teardown(async () => { if (fs.existsSync(tmpjs)) { fs.unlinkSync(tmpjs) } await fastifyEmitter.stop() }) await once(fastifyEmitter, 'ready') t.pass('should receive ready event') const restartPromise = once(fastifyEmitter, 'restart') await writeFile(tmpjs, 'hello fastify', { flag: 'a+' }) // chokidar watch can't catch change event in CI, but local test is all ok. you can remove annotation in local environment. t.pass('change tmpjs') // this might happen more than once but does not matter in this context await restartPromise t.pass('should receive restart event') }) test('should start the server with watch and verbose-watch options that the child process restart when directory changed with console message about changes ', { skip: ['win32', 'darwin'].includes(process.platform) }, async (t) => { t.plan(4) const spy = sinon.spy() const watch = proxyquire('../lib/watch', { './utils': { logWatchVerbose: spy } }) const start = proxyquire('../start', { './lib/watch': watch }) const tmpjs = path.resolve(baseFilename + '.js') const port = getPort() await writeFile(tmpjs, 'hello world') const argv = ['-p', port, '-w', '--verbose-watch', './examples/plugin.js'] const fastifyEmitter = await start.start(argv) t.teardown(async () => { if (fs.existsSync(tmpjs)) { fs.unlinkSync(tmpjs) } await fastifyEmitter.stop() }) await once(fastifyEmitter, 'ready') t.pass('should receive ready event') const restartPromise = once(fastifyEmitter, 'restart') await writeFile(tmpjs, 'hello fastify', { flag: 'a+' }) // chokidar watch can't catch change event in CI, but local test is all ok. you can remove annotation in local environment. t.pass('change tmpjs') // this might happen more than once but does not matter in this context await restartPromise t.pass('should receive restart event') t.ok(spy.args.length > 0, 'should print a console message on file update') }) test('should reload the env on restart when watching', { skip: process.platform === 'win32' }, async (t) => { const testdir = t.testdir({ '.env': 'GREETING=world', 'plugin.js': await readFile(path.join(__dirname, '../examples/plugin-with-env.js')) }) const cwd = process.cwd() process.chdir(testdir) const port = getPort() const argv = ['-p', port, '-w', path.join(testdir, 'plugin.js')] const fastifyEmitter = await requireUncached('../start').start(argv) t.teardown(() => { process.chdir(cwd) }) await once(fastifyEmitter, 'ready') const r1 = await fetch(`http://localhost:${port}`) t.equal(r1.status, 200) const body1 = await r1.text() t.same(JSON.parse(body1), { hello: 'world' }) await writeFile(path.join(testdir, '.env'), 'GREETING=planet') await once(fastifyEmitter, 'restart') const r2 = await fetch(`http://localhost:${port}`) t.equal(r2.status, 200) const body2 = await r2.text() t.same(JSON.parse(body2), { hello: 'world' }) /* world because when making a restart the server still passes the arguments that change the environment variable */ await fastifyEmitter.stop() }) test('should read env variables from .env file', async (t) => { const port = getPort() const testdir = t.testdir({ '.env': `FASTIFY_PORT=${port}`, 'plugin.js': await readFile(path.join(__dirname, '../examples/plugin.js')) }) const cwd = process.cwd() process.chdir(testdir) t.teardown(() => { process.chdir(cwd) }) const fastify = await requireUncached('../start').start([path.join(testdir, 'plugin.js')]) t.equal(fastify.server.address().port, +port) const res = await fetch(`http://localhost:${port}`) t.equal(res.status, 200) const body = await res.text() t.same(JSON.parse(body), { hello: 'world' }) await fastify.close() }) test('crash on unhandled rejection', async t => { t.plan(1) const argv = ['-p', getPort(), './test/data/rejection.js'] const child = fork(path.join(__dirname, '..', 'start.js'), argv, { silent: true }) const [code] = await once(child, 'close') t.equal(code, 1) }) test('should start the server with inspect options and the defalut port is 9320', async t => { t.plan(3) const start = proxyquire('../start', { 'node:inspector': { open (p) { t.equal(p, 9320) t.pass('inspect open called') } } }) const argv = ['--d', './examples/plugin.js'] const fastify = await start.start(argv) await fastify.close() t.pass('server closed') }) test('should start the server with inspect options and use the exactly port', async t => { t.plan(3) const port = getPort() const start = proxyquire('../start', { 'node:inspector': { open (p) { t.equal(p, Number(port)) t.pass('inspect open called') } } }) const argv = ['--d', '--debug-port', port, './examples/plugin.js'] const fastify = await start.start(argv) await fastify.close() t.pass('server closed') }) test('boolean env are not overridden if no arguments are passed', async t => { t.plan(1) process.env.FASTIFY_OPTIONS = 'true' // here the test should fail because of the wrong certificate // or because the server is booted without the custom options try { const argv = ['./examples/plugin-with-options.js'] await start.start(argv) t.fail('Custom options') } catch (e) { t.pass('Custom options') } }) test('should support preloading custom module', async t => { t.plan(2) const argv = ['-r', './test/data/custom-require.js', './examples/plugin.js'] const fastify = await start.start(argv) t.ok(GLOBAL_MODULE_1) await fastify.close() t.pass('server closed') }) test('should support preloading custom ES module', async t => { t.plan(2) const argv = ['-i', './test/data/custom-import.mjs', './examples/plugin.js'] const fastify = await start.start(argv) t.ok(globalThis.GLOBAL_MODULE_3) await fastify.close() t.pass('server closed') }) test('should support preloading multiple custom modules', async t => { t.plan(3) const argv = ['-r', './test/data/custom-require.js', '-r', './test/data/custom-require2.js', './examples/plugin.js'] const fastify = await start.start(argv) t.ok(GLOBAL_MODULE_1) t.ok(GLOBAL_MODULE_2) await fastify.close() t.pass('server closed') }) test('should support preloading multiple custom ES modules', async t => { t.plan(3) const argv = ['-i', './test/data/custom-import.mjs', '-i', './test/data/custom-import2.mjs', './examples/plugin.js'] const fastify = await start.start(argv) t.ok(GLOBAL_MODULE_3) t.ok(GLOBAL_MODULE_4) await fastify.close() t.pass('server closed') }) test('preloading custom module with empty and trailing require flags should not throw', async t => { t.plan(2) const argv = ['-r', './test/data/custom-require.js', '-r', '', './examples/plugin.js', '-r'] const fastify = await start.start(argv) t.ok(GLOBAL_MODULE_1) await fastify.close() t.pass('server closed') }) test('preloading custom ES module with empty and trailing import flags should not throw', async t => { t.plan(2) const argv = ['-i', './test/data/custom-import.mjs', '-i', '', './examples/plugin.js', '-i'] const fastify = await start.start(argv) t.ok(GLOBAL_MODULE_3) await fastify.close() t.pass('server closed') }) test('preloading custom module that is not found should throw', async t => { t.plan(2) const oldStop = start.stop t.teardown(() => { start.stop = oldStop }) start.stop = function (err) { t.ok(/Cannot find module/.test(err.message), err.message) } const argv = ['-r', './test/data/require-missing.js', './examples/plugin.js'] const fastify = await start.start(argv) await fastify.close() t.pass('server closed') }) test('preloading custom ES module that is not found should throw', async t => { t.plan(2) const oldStop = start.stop t.teardown(() => { start.stop = oldStop }) start.stop = function (err) { t.ok(/Cannot find module/.test(err.message), err.message) } const argv = ['-i', './test/data/import-missing.mjs', './examples/plugin.js'] const fastify = await start.start(argv) await fastify.close() t.pass('server closed') }) test('preloading custom module should be done before starting server', async t => { t.plan(4) const argv = ['./examples/plugin-with-preloaded.js'] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${fastify.server.address().port}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { hasPreloaded: true }) await fastify.close() t.pass('server closed') }) test('should support custom logger configuration', async t => { t.plan(2) const argv = ['-L', './test/data/custom-logger.js', './examples/plugin.js'] const fastify = await start.start(argv) t.ok(fastify.log.test) await fastify.close() t.pass('server closed') }) test('should support custom logger configuration in ESM', async t => { t.plan(2) const argv = ['-L', './test/data/custom-logger.mjs', './examples/plugin.js'] const fastify = await start.start(argv) t.ok(fastify.log.test) await fastify.close() t.pass('server closed') }) test('preloading a built-in module works', async t => { t.plan(1) const argv = ['-r', 'path', './examples/plugin.js'] const fastify = await start.start(argv) await fastify.close() t.pass('server closed') }) test('preloading a module in node_modules works', async t => { t.plan(1) const argv = ['-r', 'semver', './examples/plugin.js'] const fastify = await start.start(argv) await fastify.close() t.pass('server closed') }) test('should throw on logger configuration module not found', async t => { t.plan(2) const oldStop = start.stop t.teardown(() => { start.stop = oldStop }) start.stop = function (err) { t.ok(/Cannot find module/.test(err.message), err.message) } const argv = ['-L', './test/data/missing.js', './examples/plugin.js'] const fastify = await start.start(argv) await fastify.close() t.pass('server closed') }) test('should throw on async plugin with one argument', async t => { t.plan(1) const oldStop = start.stop t.teardown(() => { start.stop = oldStop }) start.stop = function (err) { t.ok(/Async\/Await plugin function should contain 2 arguments./.test(err.message), err.message) } const argv = ['./test/data/async-plugin-with-one-argument.js'] await start.start(argv) }) test('should start fastify with custom plugin options with a ESM typescript compiled plugin', { skip: !moduleSupport }, async t => { t.plan(4) const argv = [ '-p', getPort(), './examples/ts-plugin-with-custom-options.mjs', '--', '-abc', '--hello', 'world' ] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${fastify.server.address().port}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { a: true, b: true, c: true, hello: 'world' }) await fastify.close() t.pass('server closed') t.end() }) test('should start fastify with custom plugin default options with a ESM typescript compiled plugin', { skip: !moduleSupport }, async t => { t.plan(4) const argv = [ '-o', '-p', getPort(), './examples/ts-plugin-with-custom-options.mjs' ] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${fastify.server.address().port}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { hello: 'test' }) await fastify.close() t.pass('server closed') t.end() }) test('should throw an error when loading ESM typescript compiled plugin and ESM is not supported', { skip: moduleSupport }, async t => { t.plan(1) const oldStop = start.stop t.teardown(() => { start.stop = oldStop }) start.stop = function (err) { t.ok(/Your version of node does not support ES modules./.test(err.message), err.message) } const argv = ['./examples/ts-plugin-with-custom-options.mjs'] await start.start(argv) t.end() }) test('should start fastify with custom plugin options with a ESM plugin with package.json "type":"module"', { skip: !moduleSupport }, async t => { t.plan(4) const argv = [ '-p', getPort(), './examples/package-type-module/ESM-plugin-with-custom-options.js', '--', '-abc', '--hello', 'world' ] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${fastify.server.address().port}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { a: true, b: true, c: true, hello: 'world' }) await fastify.close() t.pass('server closed') t.end() }) test('should start fastify with custom server options (ignoreTrailingSlash) with a ESM plugin with package.json "type":"module"', { skip: !moduleSupport }, async t => { t.plan(5) const argv = [ '-p', getPort(), './examples/package-type-module/ESM-plugin-with-custom-server-options.js', '--options' ] const fastify = await start.start(argv) const result1 = await fetch(`http://localhost:${fastify.server.address().port}/foo`) t.equal(result1.status, 200) const body1 = await result1.text() t.equal(result1.headers.get('content-length'), '' + body1.length) const result2 = await fetch(`http://localhost:${fastify.server.address().port}/foo/`) t.equal(result2.status, 200) const body2 = await result2.text() t.equal(result2.headers.get('content-length'), '' + body2.length) await fastify.close() t.pass('server closed') t.end() }) test('should start fastify with custom plugin options with a CJS plugin with package.json "type":"module"', { skip: !moduleSupport }, async t => { t.plan(4) const argv = [ '-p', getPort(), './examples/package-type-module/CJS-plugin-with-custom-options.cjs', '--', '-abc', '--hello', 'world' ] const fastify = await start.start(argv) const result = await fetch(`http://localhost:${fastify.server.address().port}`) t.equal(result.status, 200) const body = await result.text() t.equal(result.headers.get('content-length'), '' + body.length) t.same(JSON.parse(body), { a: true, b: true, c: true, hello: 'world' }) await fastify.close() t.pass('server closed') t.end() }) test('should throw error for invalid fastify plugin (object)', async t => { t.plan(1) try { const port = getPort() const argv = ['-p', port, '-T', '100', './test/data/object.js'] await start.start(argv) t.fail('should not start') } catch (err) { t.equal(err.code, 'AVV_ERR_PLUGIN_NOT_VALID') } })