From 9e3eeb61b63c9217d4b51335a6a7fa674d6747ae Mon Sep 17 00:00:00 2001 From: Raees Iqbal Date: Tue, 12 May 2020 10:27:30 -0700 Subject: [PATCH] More redirect and rewrite fixes (#885) * serveRedirect: Fix a condition check * Add test for 30x redirects * Improve CRA tests * Fix file shadowing * Add tests for shadowing and 404 * Fix 404 redirects behavior * Add tests for 404 redirect rules * Update rules-proxy.test.js * Remove a redudndant test * Make redirect test more robust * Formatting * Remove unused imports * File ending fixes for Windows * Use request.get for headers * Functions: Make sure body is undefined when not provided * Syntax formatting * Add tests for echo functions * Add echo function * Tests: Add CRA function tests with rewrites * Test Utils: Improve random port * Dev: Fallback to random port for functions server --- package.json | 1 + src/commands/dev/index.js | 13 ++- src/utils/detect-server.js | 2 +- src/utils/rules-proxy.test.js | 62 ++++++++++- src/utils/serve-functions.js | 19 ++-- tests/cra.test.js | 93 +++++++++++++---- tests/dev.test.js | 144 ++++++++++++++++++++++++-- tests/dummy-site/404.html | 10 ++ tests/dummy-site/build/index.html | 14 --- tests/dummy-site/foo.html | 1 + tests/dummy-site/foo/bar.html | 1 + tests/dummy-site/functions/echo.js | 6 ++ tests/dummy-site/netlify.toml | 28 ++++- tests/dummy-site/not-foo/index.html | 1 + tests/dummy-site/test-404b.html | 1 + tests/dummy-site/test-404c.html | 1 + tests/rules-proxy.test.js | 12 --- tests/site-cra/_redirects | 6 +- tests/site-cra/functions/echo.js | 6 ++ tests/site-cra/netlify.toml | 2 + tests/site-cra/public/otherthing.html | 1 + tests/utils/index.js | 4 +- 22 files changed, 346 insertions(+), 82 deletions(-) create mode 100644 tests/dummy-site/404.html delete mode 100644 tests/dummy-site/build/index.html create mode 100644 tests/dummy-site/foo.html create mode 100644 tests/dummy-site/foo/bar.html create mode 100644 tests/dummy-site/functions/echo.js create mode 100644 tests/dummy-site/not-foo/index.html create mode 100644 tests/dummy-site/test-404b.html create mode 100644 tests/dummy-site/test-404c.html create mode 100644 tests/site-cra/functions/echo.js create mode 100644 tests/site-cra/public/otherthing.html diff --git a/package.json b/package.json index 2b14c960b88..0c6a6def32e 100644 --- a/package.json +++ b/package.json @@ -161,6 +161,7 @@ "eslint-plugin-import": "^2.17.3", "eslint-plugin-node": "^8.0.1", "eslint-plugin-prettier": "^3.1.0", + "form-data": "^3.0.0", "from2-string": "^1.1.0", "gh-release": "^3.5.0", "globby": "^10.0.1", diff --git a/src/commands/dev/index.js b/src/commands/dev/index.js index ec0b2e9f808..2734fa63cb1 100644 --- a/src/commands/dev/index.js +++ b/src/commands/dev/index.js @@ -45,14 +45,14 @@ function addonUrl(addonUrls, req) { return addonUrl ? `${addonUrl}${m[2]}` : null } -async function isStatic(pathname, publicFolder) { +async function getStatic(pathname, publicFolder) { const alternatives = alternativePathsFor(pathname).map(p => path.resolve(publicFolder, p.substr(1))) for (const i in alternatives) { const p = alternatives[i] try { const pathStats = await stat(p) - if (pathStats.isFile()) return true + if (pathStats.isFile()) return '/'+path.relative(publicFolder, p) } catch (err) { // Ignore } @@ -273,6 +273,8 @@ async function serveRedirect(req, res, proxy, match, options) { } } + const staticFile = await getStatic(req.url, options.publicFolder) + if (staticFile) req.url = staticFile const reqUrl = new url.URL( req.url, `${req.protocol || (req.headers.scheme && req.headers.scheme + ':') || 'http:'}//${req.headers['host'] || @@ -283,8 +285,9 @@ async function serveRedirect(req, res, proxy, match, options) { return render404(options.publicFolder) } - if (match.force || (!(await isStatic(reqUrl.pathname, options.publicFolder) || options.framework) && match.status !== 404)) { + if (match.force || (!(staticFile && options.framework))) { const dest = new url.URL(match.to, `${reqUrl.protocol}//${reqUrl.host}`) + const destStaticFile = await getStatic(dest.pathname, options.publicFolder) if (isRedirect(match)) { res.writeHead(match.status, { Location: match.to, @@ -309,8 +312,8 @@ async function serveRedirect(req, res, proxy, match, options) { const destURL = dest.pathname + (urlParams.toString() && '?' + urlParams.toString()) let status - if (match.force || isInternal(destURL) || !options.framework) { - req.url = destURL + if (match.force || isInternal(destURL) || (!staticFile && !options.framework && destStaticFile)) { + req.url = destStaticFile ? destStaticFile : destURL status = match.status console.log(`${NETLIFYDEVLOG} Rewrote URL to `, req.url) } diff --git a/src/utils/detect-server.js b/src/utils/detect-server.js index e15294ff8ec..ec6a03773d4 100644 --- a/src/utils/detect-server.js +++ b/src/utils/detect-server.js @@ -126,7 +126,7 @@ module.exports.serverSettings = async (devConfig, flags, projectDir, log) => { settings.port = port settings.jwtRolePath = devConfig.jwtRolePath || 'app_metadata.authorization.roles' - settings.functionsPort = await getPort({ port: settings.functionsPort || 34567 }) + settings.functionsPort = await getPort({ port: settings.functionsPort || 0 }) settings.functions = devConfig.functions || settings.functions return settings diff --git a/src/utils/rules-proxy.test.js b/src/utils/rules-proxy.test.js index 78bb8fbc258..282b9e15ffc 100644 --- a/src/utils/rules-proxy.test.js +++ b/src/utils/rules-proxy.test.js @@ -19,11 +19,38 @@ test('parseFile: netlify.toml', async t => { status: 200, }, { - path: '/*', - to: '/index.html', + path: '/foo', + to: '/not-foo', status: 200, force: false, }, + { + path: '/foo.html', + to: '/not-foo', + status: 200, + }, + { + path: '/not-foo', + to: '/foo', + status: 200, + force: true, + }, + { + path: '/test-404a', + to: '/foo', + status: 404, + }, + { + path: '/test-404b', + to: '/foo', + status: 404, + }, + { + path: '/test-404c', + to: '/foo', + status: 404, + force: true, + }, ] t.deepEqual(rules, expected) @@ -58,11 +85,38 @@ test('parseRules', async t => { status: 200, }, { - path: '/*', - to: '/index.html', + path: '/foo', + to: '/not-foo', status: 200, force: false, }, + { + path: '/foo.html', + to: '/not-foo', + status: 200, + }, + { + path: '/not-foo', + to: '/foo', + status: 200, + force: true, + }, + { + path: '/test-404a', + to: '/foo', + status: 404, + }, + { + path: '/test-404b', + to: '/foo', + status: 404, + }, + { + path: '/test-404c', + to: '/foo', + status: 404, + force: true, + }, ] t.deepEqual(rules, expected) diff --git a/src/utils/serve-functions.js b/src/utils/serve-functions.js index faf5b12cb40..9c231578071 100644 --- a/src/utils/serve-functions.js +++ b/src/utils/serve-functions.js @@ -121,19 +121,16 @@ function createHandler(dir) { } const { functionPath } = functions[func] - const body = request.body.toString() - var isBase64Encoded = Buffer.from(body, 'base64').toString('base64') === body + const body = request.get('content-length') ? request.body.toString() : undefined + let isBase64Encoded = false + if (body) isBase64Encoded = Buffer.from(body, 'base64').toString('base64') === body - let remoteAddress = - request.headers['x-forwarded-for'] || request.headers['X-Forwarded-for'] || request.connection.remoteAddress || '' - remoteAddress = remoteAddress - .split(remoteAddress.includes('.') ? ':' : ',') - .pop() - .trim() + let remoteAddress = request.get('x-forwarded-for') || request.connection.remoteAddress || '' + remoteAddress = remoteAddress.split(remoteAddress.includes('.') ? ':' : ',').pop().trim() let requestPath = request.path - if (request.headers['x-netlify-original-pathname']) { - requestPath = request.headers['x-netlify-original-pathname'] + if (request.get('x-netlify-original-pathname')) { + requestPath = request.get('x-netlify-original-pathname') delete request.headers['x-netlify-original-pathname'] } @@ -141,7 +138,7 @@ function createHandler(dir) { path: requestPath, httpMethod: request.method, queryStringParameters: queryString.parse(request.url.split(/\?(.+)/)[1]), - headers: Object.assign({}, request.headers, { 'client-ip': remoteAddress }), + headers: { ...request.headers, 'client-ip': remoteAddress }, body: body, isBase64Encoded: isBase64Encoded } diff --git a/tests/cra.test.js b/tests/cra.test.js index f7d9a059989..9b2ea7fafd4 100644 --- a/tests/cra.test.js +++ b/tests/cra.test.js @@ -1,16 +1,12 @@ -const fs = require('fs') const path = require('path') -const util = require('util') const { spawn } = require('child_process') +const url = require('url') const test = require('ava') const fetch = require('node-fetch') -const mkdirp = require('mkdirp') const cliPath = require('./utils/cliPath') const { randomPort } = require('./utils/') const sitePath = path.join(__dirname, 'site-cra') -const fileWrite = util.promisify(fs.writeFile) - let ps, host, port test.before(async t => { @@ -39,13 +35,13 @@ test.before(async t => { }) }) -test('netlify dev cra: homepage', async t => { +test('homepage', async t => { const response = await fetch(`http://${host}:${port}/`).then(r => r.text()) t.regex(response, /Web site created using create-react-app/) }) -test('netlify dev cra: static/js/bundle.js', async t => { +test('static/js/bundle.js', async t => { const response = await fetch(`http://${host}:${port}/static/js/bundle.js`) const body = await response.text() @@ -55,29 +51,29 @@ test('netlify dev cra: static/js/bundle.js', async t => { t.regex(body, /webpackBootstrap/) }) -test('netlify dev cra: static file under build/', async t => { - const publicPath = path.join(sitePath, 'public') - await mkdirp(publicPath) - - const expectedContent = '

Test content' - - await fileWrite(path.join(publicPath, 'test.html'), expectedContent) - +test('static file under public/', async t => { const response = await fetch(`http://${host}:${port}/test.html`) const body = await response.text() t.is(response.status, 200) t.truthy(response.headers.get('content-type').startsWith('text/html')) - t.is(body, expectedContent) + t.is(body, '

Test content') }) -test('netlify dev cra: force rewrite', async t => { - const publicPath = path.join(sitePath, 'public') - await mkdirp(publicPath) +test('redirect test', async t => { + const requestURL = new url.URL(`http://${host}:${port}/something`) + const response = await fetch(requestURL, { redirect: 'manual' }) - await fileWrite(path.join(publicPath, 'force.html'), '

This should never show') + const expectedUrl = new url.URL(requestURL.toString()) + expectedUrl.pathname = '/otherthing.html' - const response = await fetch(`http://${host}:${port}/force.html`) + t.is(response.status, 301) + t.is(response.headers.get('location'), expectedUrl.toString()) + t.is(await response.text(), 'Redirecting to /otherthing.html') +}) + +test('normal rewrite', async t => { + const response = await fetch(`http://${host}:${port}/doesnt-exist`) const body = await response.text() t.is(response.status, 200) @@ -85,7 +81,16 @@ test('netlify dev cra: force rewrite', async t => { t.regex(body, /Web site created using create-react-app/) }) -test('netlify dev cra: robots.txt', async t => { +test('force rewrite', async t => { + const response = await fetch(`http://${host}:${port}/force.html`) + const body = await response.text() + + t.is(response.status, 200) + t.truthy(response.headers.get('content-type').startsWith('text/html')) + t.is(body, '

Test content') +}) + +test('robots.txt', async t => { const response = await fetch(`http://${host}:${port}/robots.txt`) const body = await response.text() @@ -95,6 +100,50 @@ test('netlify dev cra: robots.txt', async t => { t.regex(body, /# https:\/\/www.robotstxt.org\/robotstxt.html/) }) + +test('functions rewrite echo without body', async t => { + const response = await fetch(`http://${host}:${port}/api/echo?ding=dong`).then(r => r.json()) + + t.is(response.body, undefined) + t.deepEqual(response.headers, { + accept: '*/*', + 'accept-encoding': 'gzip,deflate', + 'client-ip': '127.0.0.1', + connection: 'close', + host: `${host}:${port}`, + 'user-agent': 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)', + 'x-forwarded-for': '::ffff:127.0.0.1', + }) + t.is(response.httpMethod, 'GET') + t.is(response.isBase64Encoded, false) + t.is(response.path, '/api/echo') + t.deepEqual(response.queryStringParameters, { ding: 'dong' }) +}) + +test('functions rewrite echo with body', async t => { + const response = await fetch(`http://${host}:${port}/api/echo?ding=dong`, { + method: 'POST', + body: 'some=thing', + }).then(r => r.json()) + + t.is(response.body, 'some=thing') + t.deepEqual(response.headers, { + 'accept': '*/*', + 'accept-encoding': 'gzip,deflate', + 'client-ip': '127.0.0.1', + 'connection': 'close', + 'host': `${host}:${port}`, + 'content-type': 'text/plain;charset=UTF-8', + 'content-length': '10', + 'user-agent': 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)', + 'x-forwarded-for': '::ffff:127.0.0.1', + }) + t.is(response.httpMethod, 'POST') + t.is(response.isBase64Encoded, false) + t.is(response.path, '/api/echo') + t.deepEqual(response.queryStringParameters, { ding: 'dong' }) +}) + test.after.always('cleanup', async t => { if (ps && ps.pid) ps.kill(process.platform !== 'win32' ? 'SIGHUP' : undefined) }) diff --git a/tests/dev.test.js b/tests/dev.test.js index 5dead717ef6..67603ebc261 100644 --- a/tests/dev.test.js +++ b/tests/dev.test.js @@ -3,6 +3,7 @@ const util = require('util') const { spawn, exec } = require('child_process') const test = require('ava') const fetch = require('node-fetch') +const FormData = require('form-data') const cliPath = require('./utils/cliPath') const { randomPort } = require('./utils/') const sitePath = path.join(__dirname, 'dummy-site') @@ -36,19 +37,19 @@ test.before(async t => { }) }) -test('netlify dev: /', async t => { +test('/', async t => { const response = await fetch(`http://${host}:${port}/`).then(r => r.text()) t.regex(response, /⊂◉‿◉つ/) }) -test('netlify dev: functions timeout', async t => { +test('functions timeout', async t => { const response = await fetch(`http://${host}:${port}/.netlify/functions/timeout`).then(r => r.text()) t.is(response, '"ping"') }) -test('netlify functions:invoke', async t => { +test('functions:invoke', async t => { const { stdout } = await execProcess([cliPath, 'functions:invoke', 'timeout', '--identity', '--port='+port].join(' '), { cwd: sitePath, env: process.env, @@ -57,24 +58,155 @@ test('netlify functions:invoke', async t => { t.is(stdout, '"ping"\n') }) -test('netlify dev: functions env file', async t => { +test('functions env file', async t => { const response = await fetch(`http://${host}:${port}/.netlify/functions/env`).then(r => r.text()) t.is(response, 'true') }) -test('netlify dev: functions env file overriding prod var', async t => { +test('functions rewrite echo without body', async t => { + const response = await fetch(`http://${host}:${port}/api/echo?ding=dong`).then(r => r.json()) + + t.is(response.body, undefined) + t.deepEqual(response.headers, { + accept: '*/*', + 'accept-encoding': 'gzip,deflate', + 'client-ip': '127.0.0.1', + connection: 'close', + host: `${host}:${port}`, + 'user-agent': 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)', + 'x-forwarded-for': '::ffff:127.0.0.1', + }) + t.is(response.httpMethod, 'GET') + t.is(response.isBase64Encoded, false) + t.is(response.path, '/api/echo') + t.deepEqual(response.queryStringParameters, { ding: 'dong' }) +}) + +test('functions rewrite echo with body', async t => { + const response = await fetch(`http://${host}:${port}/api/echo?ding=dong`, { + method: 'POST', + body: 'some=thing', + }).then(r => r.json()) + + t.is(response.body, 'some=thing') + t.deepEqual(response.headers, { + 'accept': '*/*', + 'accept-encoding': 'gzip,deflate', + 'client-ip': '127.0.0.1', + 'connection': 'close', + 'host': `${host}:${port}`, + 'content-type': 'text/plain;charset=UTF-8', + 'content-length': '10', + 'user-agent': 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)', + 'x-forwarded-for': '::ffff:127.0.0.1', + }) + t.is(response.httpMethod, 'POST') + t.is(response.isBase64Encoded, false) + t.is(response.path, '/api/echo') + t.deepEqual(response.queryStringParameters, { ding: 'dong' }) +}) + +test('functions rewrite echo with Form body', async t => { + const form = new FormData() + form.append('some', 'thing') + const response = await fetch(`http://${host}:${port}/api/echo?ding=dong`, { + method: 'POST', + body: form.getBuffer(), + headers: form.getHeaders(), + }).then(r => r.json()) + + const formBoundary = form.getBoundary() + + t.deepEqual(response.headers, { + 'accept': '*/*', + 'accept-encoding': 'gzip,deflate', + 'client-ip': '127.0.0.1', + 'connection': 'close', + 'host': `${host}:${port}`, + 'content-length': form.getLengthSync().toString(), + 'content-type': `multipart/form-data; boundary=${formBoundary}`, + 'user-agent': 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)', + 'x-forwarded-for': '::ffff:127.0.0.1', + }) + t.is(response.httpMethod, 'POST') + t.is(response.isBase64Encoded, false) + t.is(response.path, '/api/echo') + t.deepEqual(response.queryStringParameters, { ding: 'dong' }) + t.regex(response.body, new RegExp(formBoundary)) +}) + +test('functions env file overriding prod var', async t => { const response = await fetch(`http://${host}:${port}/.netlify/functions/override-process-env`).then(r => r.text()) t.is(response, 'false') }) -test('netlify dev: api rewrite', async t => { +test('api rewrite', async t => { const response = await fetch(`http://${host}:${port}/api/timeout`).then(r => r.text()) t.is(response, '"ping"') }) +test('shadowing: foo', async t => { + const response = await fetch(`http://${host}:${port}/foo`).then(r => r.text()) + + t.is(response, '

foo') +}) + +test('shadowing: foo.html', async t => { + const response = await fetch(`http://${host}:${port}/foo.html`).then(r => r.text()) + + t.is(response, '

foo') +}) + + +test('shadowing: not-foo', async t => { + const response = await fetch(`http://${host}:${port}/not-foo`).then(r => r.text()) + + t.is(response, '

foo') +}) + +test('shadowing: not-foo/', async t => { + const response = await fetch(`http://${host}:${port}/not-foo/`).then(r => r.text()) + + t.is(response, '

foo') +}) + + +test('shadowing: not-foo/index.html', async t => { + const response = await fetch(`http://${host}:${port}/not-foo/index.html`).then(r => r.text()) + + t.is(response, '

not-foo') +}) + +test('404.html', async t => { + const response = await fetch(`http://${host}:${port}/non-existent`).then(r => r.text()) + + t.regex(response, /

404 - Page not found<\/h1>/) +}) + +test('test 404 shadow - no static file', async t => { + const response = await fetch(`http://${host}:${port}/test-404a`) + + t.is(response.status, 404) + t.is(await response.text(), '

foo') +}) + +test('test 404 shadow - with static file', async t => { + const response = await fetch(`http://${host}:${port}/test-404b`) + + t.is(response.status, 200) + t.is(await response.text(), '

This page actually exists') +}) + +test('test 404 shadow - with static file but force', async t => { + const response = await fetch(`http://${host}:${port}/test-404c`) + + t.is(response.status, 404) + t.is(await response.text(), '

foo') +}) + test.after.always('cleanup', async t => { if (ps && ps.pid) ps.kill(process.platform !== 'win32' ? 'SIGHUP' : undefined) }) diff --git a/tests/dummy-site/404.html b/tests/dummy-site/404.html new file mode 100644 index 00000000000..7c904be1196 --- /dev/null +++ b/tests/dummy-site/404.html @@ -0,0 +1,10 @@ + + + + + 404 not found + + +

404 - Page not found

+ + diff --git a/tests/dummy-site/build/index.html b/tests/dummy-site/build/index.html deleted file mode 100644 index d98cf6e1cdb..00000000000 --- a/tests/dummy-site/build/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - -

⊂◉‿◉つ

- - diff --git a/tests/dummy-site/foo.html b/tests/dummy-site/foo.html new file mode 100644 index 00000000000..a9b26b09132 --- /dev/null +++ b/tests/dummy-site/foo.html @@ -0,0 +1 @@ +

foo \ No newline at end of file diff --git a/tests/dummy-site/foo/bar.html b/tests/dummy-site/foo/bar.html new file mode 100644 index 00000000000..95c09244dc5 --- /dev/null +++ b/tests/dummy-site/foo/bar.html @@ -0,0 +1 @@ +

bar \ No newline at end of file diff --git a/tests/dummy-site/functions/echo.js b/tests/dummy-site/functions/echo.js new file mode 100644 index 00000000000..f2ca9e72805 --- /dev/null +++ b/tests/dummy-site/functions/echo.js @@ -0,0 +1,6 @@ +exports.handler = async (event, context) => { + return { + statusCode: 200, + body: JSON.stringify(event), + } +} diff --git a/tests/dummy-site/netlify.toml b/tests/dummy-site/netlify.toml index 95bedfac966..34e67281be7 100644 --- a/tests/dummy-site/netlify.toml +++ b/tests/dummy-site/netlify.toml @@ -1,15 +1,35 @@ [dev] autoLaunch = false [build] - command = "npm run build" - publish = "build" functions = "functions" [[redirects]] from = "/api/*" to = "/.netlify/functions/:splat" status = 200 [[redirects]] - from = "/*" - to = "/index.html" + from = "/foo" + to = "/not-foo" status = 200 force = false +[[redirects]] + from = "/foo.html" + to = "/not-foo" + status = 200 +[[redirects]] + from = "/not-foo" + to = "/foo" + status = 200 + force = true +[[redirects]] + from = "/test-404a" + to = "/foo" + status = 404 +[[redirects]] + from = "/test-404b" + to = "/foo" + status = 404 +[[redirects]] + from = "/test-404c" + to = "/foo" + status = 404 + force = true diff --git a/tests/dummy-site/not-foo/index.html b/tests/dummy-site/not-foo/index.html new file mode 100644 index 00000000000..e3552ad1fbd --- /dev/null +++ b/tests/dummy-site/not-foo/index.html @@ -0,0 +1 @@ +

not-foo \ No newline at end of file diff --git a/tests/dummy-site/test-404b.html b/tests/dummy-site/test-404b.html new file mode 100644 index 00000000000..64d272fec74 --- /dev/null +++ b/tests/dummy-site/test-404b.html @@ -0,0 +1 @@ +

This page actually exists \ No newline at end of file diff --git a/tests/dummy-site/test-404c.html b/tests/dummy-site/test-404c.html new file mode 100644 index 00000000000..68e3dd628cc --- /dev/null +++ b/tests/dummy-site/test-404c.html @@ -0,0 +1 @@ +

This page actually exists as well diff --git a/tests/rules-proxy.test.js b/tests/rules-proxy.test.js index 59764b5f03e..fab2777cb6e 100644 --- a/tests/rules-proxy.test.js +++ b/tests/rules-proxy.test.js @@ -25,18 +25,6 @@ test.before(async t => { return server.listen(port) }) -test('homepage rule', async t => { - const response = await fetch(`http://localhost:${t.context.port}/`).then(r => r.json()) - - t.is(response.from, '/*') - t.is(response.to, '/index.html') - t.is(response.force, false) - t.is(response.host, '') - t.is(response.negative, false) - t.is(response.scheme, '') - t.is(response.status, 200) -}) - test('/something rule', async t => { const response = await fetch(`http://localhost:${t.context.port}/something`).then(r => r.json()) diff --git a/tests/site-cra/_redirects b/tests/site-cra/_redirects index d6787d0c2d7..dca7588d888 100644 --- a/tests/site-cra/_redirects +++ b/tests/site-cra/_redirects @@ -1,2 +1,4 @@ -/force.html /index.html 200! -/* /index.html 200 +/force.html /test.html 200! +/something /otherthing.html +/api/* /.netlify/functions/:splat 200 +/* /index.html 200 diff --git a/tests/site-cra/functions/echo.js b/tests/site-cra/functions/echo.js new file mode 100644 index 00000000000..85849a49119 --- /dev/null +++ b/tests/site-cra/functions/echo.js @@ -0,0 +1,6 @@ +exports.handler = async (event, context) => { + return { + statusCode: 200, + body: JSON.stringify(event), + } +} diff --git a/tests/site-cra/netlify.toml b/tests/site-cra/netlify.toml index c47e219009a..2db35f561ad 100644 --- a/tests/site-cra/netlify.toml +++ b/tests/site-cra/netlify.toml @@ -1,2 +1,4 @@ [dev] framework = "create-react-app" +[build] + functions = "functions" diff --git a/tests/site-cra/public/otherthing.html b/tests/site-cra/public/otherthing.html new file mode 100644 index 00000000000..5cab1ac3b86 --- /dev/null +++ b/tests/site-cra/public/otherthing.html @@ -0,0 +1 @@ +

other thing \ No newline at end of file diff --git a/tests/utils/index.js b/tests/utils/index.js index c38964e0f09..59741246d38 100644 --- a/tests/utils/index.js +++ b/tests/utils/index.js @@ -1,6 +1,8 @@ function randomPort(){ - return '2'+Math.random().toString().substr(2, 4) + const min = Math.ceil(2000); + const max = Math.floor(7999); + return (Math.floor(Math.random() * (max - min + 1)) + min).toString(); } module.exports = {