diff --git a/packages/cli/src/commands/dev/dev.ts b/packages/cli/src/commands/dev/dev.ts index d864cc04d..665d95960 100644 --- a/packages/cli/src/commands/dev/dev.ts +++ b/packages/cli/src/commands/dev/dev.ts @@ -98,6 +98,12 @@ export default async function dev( ]); } + // This is just for tests - can be removed once project settings + // are respected locally in `.vercel/project.json` + if (process.env.VERCEL_DEV_COMMAND) { + devCommand = process.env.VERCEL_DEV_COMMAND; + } + const devServer = new DevServer(cwd, { output, devCommand, diff --git a/packages/cli/test/fixtures/unit/now-dev-api-with-public/api/[endpoint].js b/packages/cli/test/dev/fixtures/api-with-public/api/[endpoint].js similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-api-with-public/api/[endpoint].js rename to packages/cli/test/dev/fixtures/api-with-public/api/[endpoint].js diff --git a/packages/cli/test/fixtures/unit/now-dev-api-with-public/public/index.html b/packages/cli/test/dev/fixtures/api-with-public/public/index.html similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-api-with-public/public/index.html rename to packages/cli/test/dev/fixtures/api-with-public/public/index.html diff --git a/packages/cli/test/fixtures/unit/now-dev-api-with-static/api/[endpoint].js b/packages/cli/test/dev/fixtures/api-with-static/api/[endpoint].js similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-api-with-static/api/[endpoint].js rename to packages/cli/test/dev/fixtures/api-with-static/api/[endpoint].js diff --git a/packages/cli/test/fixtures/unit/now-dev-api-with-static/index.html b/packages/cli/test/dev/fixtures/api-with-static/index.html similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-api-with-static/index.html rename to packages/cli/test/dev/fixtures/api-with-static/index.html diff --git a/packages/cli/test/fixtures/unit/now-dev-custom-404/error.html b/packages/cli/test/dev/fixtures/custom-404/error.html similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-custom-404/error.html rename to packages/cli/test/dev/fixtures/custom-404/error.html diff --git a/packages/cli/test/fixtures/unit/now-dev-custom-404/error.js b/packages/cli/test/dev/fixtures/custom-404/error.js similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-custom-404/error.js rename to packages/cli/test/dev/fixtures/custom-404/error.js diff --git a/packages/cli/test/fixtures/unit/now-dev-custom-404/vercel.json b/packages/cli/test/dev/fixtures/custom-404/vercel.json similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-custom-404/vercel.json rename to packages/cli/test/dev/fixtures/custom-404/vercel.json diff --git a/packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/.gitignore b/packages/cli/test/dev/fixtures/dev-server-and-routes/.gitignore similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/.gitignore rename to packages/cli/test/dev/fixtures/dev-server-and-routes/.gitignore diff --git a/packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/api/[endpoint].js b/packages/cli/test/dev/fixtures/dev-server-and-routes/api/[endpoint].js similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/api/[endpoint].js rename to packages/cli/test/dev/fixtures/dev-server-and-routes/api/[endpoint].js diff --git a/packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/api/[endpoint]/[id].js b/packages/cli/test/dev/fixtures/dev-server-and-routes/api/[endpoint]/[id].js similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/api/[endpoint]/[id].js rename to packages/cli/test/dev/fixtures/dev-server-and-routes/api/[endpoint]/[id].js diff --git a/packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/api/welcome.js b/packages/cli/test/dev/fixtures/dev-server-and-routes/api/welcome.js similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/api/welcome.js rename to packages/cli/test/dev/fixtures/dev-server-and-routes/api/welcome.js diff --git a/packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/package.json b/packages/cli/test/dev/fixtures/dev-server-and-routes/package.json similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/package.json rename to packages/cli/test/dev/fixtures/dev-server-and-routes/package.json diff --git a/packages/cli/test/dev/fixtures/dev-server-and-routes/pages/index.js b/packages/cli/test/dev/fixtures/dev-server-and-routes/pages/index.js new file mode 100644 index 000000000..40d0c682a --- /dev/null +++ b/packages/cli/test/dev/fixtures/dev-server-and-routes/pages/index.js @@ -0,0 +1 @@ +export default () =>
hello, this is the frontend
; diff --git a/packages/cli/test/fixtures/unit/now-dev-next/.gitignore b/packages/cli/test/dev/fixtures/dev-server-query/.gitignore similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-next/.gitignore rename to packages/cli/test/dev/fixtures/dev-server-query/.gitignore diff --git a/packages/cli/test/fixtures/unit/now-dev-next/package.json b/packages/cli/test/dev/fixtures/dev-server-query/package.json similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-next/package.json rename to packages/cli/test/dev/fixtures/dev-server-query/package.json diff --git a/packages/cli/test/fixtures/unit/now-dev-next/pages/index.js b/packages/cli/test/dev/fixtures/dev-server-query/pages/index.js similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-next/pages/index.js rename to packages/cli/test/dev/fixtures/dev-server-query/pages/index.js diff --git a/packages/cli/test/fixtures/unit/now-dev-next/vercel.json b/packages/cli/test/dev/fixtures/dev-server-query/vercel.json similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-next/vercel.json rename to packages/cli/test/dev/fixtures/dev-server-query/vercel.json diff --git a/packages/cli/test/fixtures/unit/now-dev-directory-listing/file.txt b/packages/cli/test/dev/fixtures/directory-listing/file.txt similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-directory-listing/file.txt rename to packages/cli/test/dev/fixtures/directory-listing/file.txt diff --git a/packages/cli/test/fixtures/unit/now-dev-directory-listing/lambda.js b/packages/cli/test/dev/fixtures/directory-listing/lambda.js similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-directory-listing/lambda.js rename to packages/cli/test/dev/fixtures/directory-listing/lambda.js diff --git a/packages/cli/test/fixtures/unit/now-dev-directory-listing/vercel.json b/packages/cli/test/dev/fixtures/directory-listing/vercel.json similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-directory-listing/vercel.json rename to packages/cli/test/dev/fixtures/directory-listing/vercel.json diff --git a/packages/cli/test/fixtures/unit/now-dev-headers/foo.txt b/packages/cli/test/dev/fixtures/headers/foo.txt similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-headers/foo.txt rename to packages/cli/test/dev/fixtures/headers/foo.txt diff --git a/packages/cli/test/fixtures/unit/now-dev-headers/index.js b/packages/cli/test/dev/fixtures/headers/index.js similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-headers/index.js rename to packages/cli/test/dev/fixtures/headers/index.js diff --git a/packages/cli/test/fixtures/unit/now-dev-headers/vercel.json b/packages/cli/test/dev/fixtures/headers/vercel.json similarity index 82% rename from packages/cli/test/fixtures/unit/now-dev-headers/vercel.json rename to packages/cli/test/dev/fixtures/headers/vercel.json index 56a133e8c..cd498d652 100644 --- a/packages/cli/test/fixtures/unit/now-dev-headers/vercel.json +++ b/packages/cli/test/dev/fixtures/headers/vercel.json @@ -1,6 +1,5 @@ { "version": 2, - "name": "now-dev-headers", "builds": [ { "src": "index.js", "use": "@vercel/node" }, { "src": "foo.txt", "use": "@vercel/static" } diff --git a/packages/cli/test/fixtures/unit/now-dev-query-invoke/index.js b/packages/cli/test/dev/fixtures/node-query-invoke/index.js similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-query-invoke/index.js rename to packages/cli/test/dev/fixtures/node-query-invoke/index.js diff --git a/packages/cli/test/fixtures/unit/now-dev-query-invoke/vercel.json b/packages/cli/test/dev/fixtures/node-query-invoke/vercel.json similarity index 84% rename from packages/cli/test/fixtures/unit/now-dev-query-invoke/vercel.json rename to packages/cli/test/dev/fixtures/node-query-invoke/vercel.json index 2b02c6fad..5374612e4 100644 --- a/packages/cli/test/fixtures/unit/now-dev-query-invoke/vercel.json +++ b/packages/cli/test/dev/fixtures/node-query-invoke/vercel.json @@ -1,6 +1,5 @@ { "version": 2, - "name": "now-dev-query-test", "builds": [{ "src": "index.js", "use": "@vercel/node" }], "routes": [ { diff --git a/packages/cli/test/fixtures/unit/now-dev-request-body/api/data-events.js b/packages/cli/test/dev/fixtures/node-request-body/api/data-events.js similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-request-body/api/data-events.js rename to packages/cli/test/dev/fixtures/node-request-body/api/data-events.js diff --git a/packages/cli/test/fixtures/unit/now-dev-request-body/api/req-body.js b/packages/cli/test/dev/fixtures/node-request-body/api/req-body.js similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-request-body/api/req-body.js rename to packages/cli/test/dev/fixtures/node-request-body/api/req-body.js diff --git a/packages/cli/test/fixtures/unit/now-dev-query-proxy/index.js b/packages/cli/test/dev/fixtures/query-proxy/index.js similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-query-proxy/index.js rename to packages/cli/test/dev/fixtures/query-proxy/index.js diff --git a/packages/cli/test/fixtures/unit/now-dev-query-proxy/vercel.json b/packages/cli/test/dev/fixtures/query-proxy/vercel.json similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-query-proxy/vercel.json rename to packages/cli/test/dev/fixtures/query-proxy/vercel.json diff --git a/packages/cli/test/fixtures/unit/now-dev-static-build-routing/.gitignore b/packages/cli/test/dev/fixtures/static-build-routing/.gitignore similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-static-build-routing/.gitignore rename to packages/cli/test/dev/fixtures/static-build-routing/.gitignore diff --git a/packages/cli/test/fixtures/unit/now-dev-static-build-routing/api/date.js b/packages/cli/test/dev/fixtures/static-build-routing/api/date.js similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-static-build-routing/api/date.js rename to packages/cli/test/dev/fixtures/static-build-routing/api/date.js diff --git a/packages/cli/test/fixtures/unit/now-dev-static-build-routing/package.json b/packages/cli/test/dev/fixtures/static-build-routing/package.json similarity index 83% rename from packages/cli/test/fixtures/unit/now-dev-static-build-routing/package.json rename to packages/cli/test/dev/fixtures/static-build-routing/package.json index ceaf2bbff..cb1eb86bb 100644 --- a/packages/cli/test/fixtures/unit/now-dev-static-build-routing/package.json +++ b/packages/cli/test/dev/fixtures/static-build-routing/package.json @@ -1,5 +1,5 @@ { - "name": "now-dev-static-build-routing", + "name": "vc-dev-static-build-routing", "private": true, "scripts": { "now-dev": "serve -l $PORT src", diff --git a/packages/cli/test/fixtures/unit/now-dev-static-build-routing/src/index.html b/packages/cli/test/dev/fixtures/static-build-routing/src/index.html similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-static-build-routing/src/index.html rename to packages/cli/test/dev/fixtures/static-build-routing/src/index.html diff --git a/packages/cli/test/fixtures/unit/now-dev-static-build-routing/vercel.json b/packages/cli/test/dev/fixtures/static-build-routing/vercel.json similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-static-build-routing/vercel.json rename to packages/cli/test/dev/fixtures/static-build-routing/vercel.json diff --git a/packages/cli/test/fixtures/unit/now-dev-static-routes/vercel.json b/packages/cli/test/dev/fixtures/static-routes/vercel.json similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-static-routes/vercel.json rename to packages/cli/test/dev/fixtures/static-routes/vercel.json diff --git a/packages/cli/test/fixtures/unit/now-dev-static-routes/www/index.html b/packages/cli/test/dev/fixtures/static-routes/www/index.html similarity index 100% rename from packages/cli/test/fixtures/unit/now-dev-static-routes/www/index.html rename to packages/cli/test/dev/fixtures/static-routes/www/index.html diff --git a/packages/cli/test/dev/integration-1.test.ts b/packages/cli/test/dev/integration-1.test.ts index 0e185623f..c2209714a 100644 --- a/packages/cli/test/dev/integration-1.test.ts +++ b/packages/cli/test/dev/integration-1.test.ts @@ -1,6 +1,9 @@ import os from 'os'; +import url from 'url'; import fs from 'fs-extra'; import { join } from 'path'; +import listen from 'async-listen'; +import { createServer } from 'http'; const { exec, @@ -8,8 +11,374 @@ const { fixture, testFixture, testFixtureStdio, + validateResponseHeaders, } = require('./utils.js'); +test('[vercel dev] should support request body', async () => { + const dir = fixture('node-request-body'); + const { dev, port, readyResolver } = await testFixture(dir); + + try { + await readyResolver; + + const body = { hello: 'world' }; + + // Test that `req.body` works in dev + let res = await fetch(`http://localhost:${port}/api/req-body`, { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify(body), + }); + validateResponseHeaders(res); + expect(await res.json()).toMatchObject(body); + + // Test that `req` "data" events work in dev + res = await fetch(`http://localhost:${port}/api/data-events`, { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify(body), + }); + } finally { + dev.kill('SIGTERM'); + } +}); + +test('[vercel dev] should maintain query when invoking serverless function', async () => { + const dir = fixture('node-query-invoke'); + const { dev, port, readyResolver } = await testFixture(dir); + + try { + await readyResolver; + + const res = await fetch(`http://localhost:${port}/something?url-param=a`); + validateResponseHeaders(res); + + const text = await res.text(); + const parsed = url.parse(text, true); + expect(parsed.pathname).toEqual('/something'); + expect(parsed.query['url-param']).toEqual('a'); + expect(parsed.query['route-param']).toEqual('b'); + } finally { + dev.kill('SIGTERM'); + } +}); + +test('[vercel dev] should maintain query when proxy passing', async () => { + const dir = fixture('query-proxy'); + const { dev, port, readyResolver } = await testFixture(dir); + const dest = createServer((req, res) => { + res.end(req.url); + }); + + try { + await Promise.all([readyResolver, listen(dest, 0)]); + + const destAddr = dest.address(); + if (!destAddr || typeof destAddr === 'string') { + throw new Error('Unexpected HTTP address'); + } + + const res = await fetch( + `http://localhost:${port}/${destAddr.port}?url-param=a` + ); + validateResponseHeaders(res); + + const text = await res.text(); + const parsed = url.parse(text, true); + expect(parsed.pathname).toEqual('/something'); + expect(parsed.query['url-param']).toEqual('a'); + expect(parsed.query['route-param']).toEqual('b'); + } finally { + dest.close(); + dev.kill('SIGTERM'); + } +}); + +test('[vercel dev] should maintain query when dev server defines routes', async () => { + const dir = fixture('dev-server-query'); + const { dev, port, readyResolver } = await testFixture(dir, { + env: { + VERCEL_DEV_COMMAND: 'next dev --port $PORT', + }, + }); + + try { + await readyResolver; + + const res = await fetch(`http://localhost:${port}/test?url-param=a`); + validateResponseHeaders(res); + + const text = await res.text(); + + // Hacky way of getting the page payload from the response + // HTML since we don't have a HTML parser handy. + const json = text + .match(/
(.*)<\/pre>/)![1]
+      .replace('
', '') + .replace('', '') + .replace(/&/g, '&') + .replace(/"/g, '"'); + const parsed = JSON.parse(json); + const query = url.parse(parsed.url, true).query; + + expect(query['url-param']).toEqual('a'); + expect(query['route-param']).toEqual('b'); + } finally { + dev.kill('SIGTERM'); + } +}); + +test('[vercel dev] should allow `cache-control` to be overwritten', async () => { + const dir = fixture('headers'); + const { dev, port, readyResolver } = await testFixture(dir); + + try { + await readyResolver; + + const res = await fetch( + `http://localhost:${port}/?name=cache-control&value=immutable` + ); + expect(res.headers.get('cache-control')).toEqual('immutable'); + } finally { + dev.kill('SIGTERM'); + } +}); + +test('[vercel dev] should send `etag` header for static files', async () => { + const dir = fixture('headers'); + const { dev, port, readyResolver } = await testFixture(dir); + + try { + await readyResolver; + + const res = await fetch(`http://localhost:${port}/foo.txt`); + const expected = 'd263af8ab880c0b97eb6c5c125b5d44f9e5addd9'; + expect(res.headers.get('etag')).toEqual(`"${expected}"`); + const body = await res.text(); + expect(body.trim()).toEqual('hi'); + } finally { + dev.kill('SIGTERM'); + } +}); + +test('[vercel dev] should frontend dev server and routes', async () => { + const dir = fixture('dev-server-and-routes'); + const { dev, port, readyResolver } = await testFixture(dir, { + env: { + VERCEL_DEV_COMMAND: 'next dev --port $PORT', + }, + }); + + try { + await readyResolver; + + let podId: string; + + let res = await fetch(`http://localhost:${port}/`); + validateResponseHeaders(res); + podId = res.headers.get('x-vercel-id')!.match(/:(\w+)-/)![1]; + let body = await res.text(); + expect(body.includes('hello, this is the frontend')).toBeTruthy(); + + res = await fetch(`http://localhost:${port}/api/users`); + validateResponseHeaders(res, podId); + body = await res.text(); + expect(body).toEqual('users'); + + res = await fetch(`http://localhost:${port}/api/users/1`); + validateResponseHeaders(res, podId); + body = await res.text(); + expect(body).toEqual('users/1'); + + res = await fetch(`http://localhost:${port}/api/welcome`); + validateResponseHeaders(res, podId); + body = await res.text(); + expect(body).toEqual('hello and welcome'); + } finally { + dev.kill('SIGTERM'); + } +}); + +test('[vercel dev] should support `@vercel/static` routing', async () => { + const dir = fixture('static-routes'); + const { dev, port, readyResolver } = await testFixture(dir); + + try { + await readyResolver; + + const res = await fetch(`http://localhost:${port}/`); + expect(res.status).toEqual(200); + const body = await res.text(); + expect(body.trim()).toEqual('Hello!'); + } finally { + dev.kill('SIGTERM'); + } +}); + +test('[vercel dev] should support `@vercel/static-build` routing', async () => { + const dir = fixture('static-build-routing'); + const { dev, port, readyResolver } = await testFixture(dir); + + try { + await readyResolver; + + const res = await fetch(`http://localhost:${port}/api/date`); + expect(res.status).toEqual(200); + const body = await res.text(); + expect(body.startsWith('The current date:')).toBeTruthy(); + } finally { + dev.kill('SIGTERM'); + } +}); + +test('[vercel dev] should support directory listing', async () => { + const dir = fixture('directory-listing'); + const { dev, port, readyResolver } = await testFixture(dir); + + try { + await readyResolver; + + // Get directory listing + let res = await fetch(`http://localhost:${port}/`); + let body = await res.text(); + expect(res.status).toEqual(200); + expect(body.includes('Index of')).toBeTruthy(); + + // Get a file + res = await fetch(`http://localhost:${port}/file.txt`); + body = await res.text(); + expect(res.status).toEqual(200); + expect(body.trim()).toEqual('Hello from file!'); + + // Invoke a lambda + res = await fetch(`http://localhost:${port}/lambda.js`); + body = await res.text(); + expect(res.status).toEqual(200); + expect(body).toEqual('Hello from Lambda!'); + + // Trigger a 404 + res = await fetch(`http://localhost:${port}/does-not-exist`); + expect(res.status).toEqual(404); + } finally { + dev.kill('SIGTERM'); + } +}); + +test('[vercel dev] should respond with 404 listing with Accept header support', async () => { + const dir = fixture('directory-listing'); + const { dev, port, readyResolver } = await testFixture(dir); + + try { + await readyResolver; + + // HTML response + let res = await fetch(`http://localhost:${port}/does-not-exist`, { + headers: { + Accept: 'text/html', + }, + }); + expect(res.status).toEqual(404); + expect(res.headers.get('content-type')).toEqual('text/html; charset=utf-8'); + let body = await res.text(); + expect(body.startsWith('')).toBeTruthy(); + + // JSON response + res = await fetch(`http://localhost:${port}/does-not-exist`, { + headers: { + Accept: 'application/json', + }, + }); + expect(res.status).toEqual(404); + expect(res.headers.get('content-type')).toEqual('application/json'); + body = await res.text(); + expect(body).toEqual( + '{"error":{"code":404,"message":"The page could not be found."}}\n' + ); + + // Plain text response + res = await fetch(`http://localhost:${port}/does-not-exist`); + expect(res.status).toEqual(404); + body = await res.text(); + expect(res.headers.get('content-type')).toEqual( + 'text/plain; charset=utf-8' + ); + expect(body).toEqual('The page could not be found.\n\nNOT_FOUND\n'); + } finally { + dev.kill('SIGTERM'); + } +}); + +test('[vercel dev] should support `public` directory with zero config', async () => { + const dir = fixture('api-with-public'); + const { dev, port, readyResolver } = await testFixture(dir); + + try { + await readyResolver; + + let res = await fetch(`http://localhost:${port}/api/user`); + let body = await res.text(); + expect(body).toEqual('hello:user'); + + res = await fetch(`http://localhost:${port}/`); + body = await res.text(); + expect(body.startsWith('

hello world

')).toBeTruthy(); + } finally { + dev.kill('SIGTERM'); + } +}); + +test('[vercel dev] should support static files with zero config', async () => { + const dir = fixture('api-with-static'); + const { dev, port, readyResolver } = await testFixture(dir); + + try { + await readyResolver; + + let res = await fetch(`http://localhost:${port}/api/user`); + let body = await res.text(); + expect(body).toEqual('bye:user'); + + res = await fetch(`http://localhost:${port}/`); + body = await res.text(); + expect(body.startsWith('

goodbye world

')).toBeTruthy(); + } finally { + dev.kill('SIGTERM'); + } +}); + +test('[vercel dev] should support custom 404 routes', async () => { + const dir = fixture('custom-404'); + const { dev, port, readyResolver } = await testFixture(dir); + + try { + await readyResolver; + + // Test custom 404 with static dest + let res = await fetch(`http://localhost:${port}/error.html`); + expect(res.status).toEqual(404); + let body = await res.text(); + expect(body.trim()).toEqual('
Custom 404 page
'); + + // Test custom 404 with lambda dest + res = await fetch(`http://localhost:${port}/error.js`); + expect(res.status).toEqual(404); + body = await res.text(); + expect(body).toEqual('Custom 404 Lambda\n'); + + // Test regular 404 still works + res = await fetch(`http://localhost:${port}/does-not-exist`); + expect(res.status).toEqual(404); + body = await res.text(); + expect(body).toEqual('The page could not be found.\n\nNOT_FOUND\n'); + } finally { + dev.kill('SIGTERM'); + } +}); + test('[vercel dev] prints `npm install` errors', async () => { const dir = fixture('runtime-not-installed'); const result = await exec(dir); diff --git a/packages/cli/test/dev/utils.js b/packages/cli/test/dev/utils.js index 3a2cf2c9d..5db93f9db 100644 --- a/packages/cli/test/dev/utils.js +++ b/packages/cli/test/dev/utils.js @@ -96,10 +96,16 @@ function shouldSkip(name, versions) { return false; } -function validateResponseHeaders(res) { +function validateResponseHeaders(res, podId) { if (res.status < 500) { + expect(res.headers.get('server')).toEqual('Vercel'); + expect(res.headers.get('cache-control').length > 0).toBeTruthy(); expect(res.headers.get('x-vercel-id')).toBeTruthy(); - expect(res.headers.get('cache-control').length > 0).toBeTruthy; + if (podId) { + expect( + res.headers.get('x-vercel-id').includes(`::${podId}-`) + ).toBeTruthy(); + } } } diff --git a/packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/pages/index.js b/packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/pages/index.js deleted file mode 100644 index a6ae7f7d6..000000000 --- a/packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/pages/index.js +++ /dev/null @@ -1 +0,0 @@ -export default () =>
hello, this is the frontend
diff --git a/packages/cli/test/unit/util/build/import-builders.test.ts b/packages/cli/test/unit/util/build/import-builders.test.ts index 71d1fe346..41a575b58 100644 --- a/packages/cli/test/unit/util/build/import-builders.test.ts +++ b/packages/cli/test/unit/util/build/import-builders.test.ts @@ -1,3 +1,4 @@ +import ms from 'ms'; import { join } from 'path'; import { remove } from 'fs-extra'; import { getWriteableDirectory } from '@vercel/build-utils'; @@ -9,6 +10,8 @@ import { import vercelNextPkg from '@vercel/next/package.json'; import vercelNodePkg from '@vercel/node/package.json'; +jest.setTimeout(ms('20 seconds')); + describe('importBuilders()', () => { it('should import built-in Builders', async () => { const specs = new Set(['@vercel/node', '@vercel/next']); diff --git a/packages/cli/test/unit/util/dev/server.test.ts b/packages/cli/test/unit/util/dev/server.test.ts deleted file mode 100644 index a9bd6578f..000000000 --- a/packages/cli/test/unit/util/dev/server.test.ts +++ /dev/null @@ -1,438 +0,0 @@ -import ms from 'ms'; -import url from 'url'; -import path from 'path'; -import execa from 'execa'; -import fs from 'fs-extra'; -import fetch, { Response } from 'node-fetch'; -import listen from 'async-listen'; -import { createServer } from 'http'; -import { client } from '../../../mocks/client'; -import DevServer from '../../../../src/util/dev/server'; -import { DevServerOptions } from '../../../../src/util/dev/types'; - -const IS_WINDOWS = process.platform === 'win32'; - -async function runNpmInstall(fixturePath: string) { - if (await fs.pathExists(path.join(fixturePath, 'package.json'))) { - return execa('yarn', ['install', '--network-timeout', '1000000'], { - cwd: fixturePath, - shell: true, - }); - } -} - -interface TestFixtureOptions extends Omit { - skip?: boolean; -} - -const testFixture = - ( - name: string, - fn: (server: DevServer) => Promise, - options?: TestFixtureOptions - ) => - async () => { - if (options?.skip) { - console.log('Skipping this test for this platform.'); - return; - } - - let server: DevServer | undefined; - const fixturePath = path.join(__dirname, '../../fixtures/unit', name); - await runNpmInstall(fixturePath); - try { - server = new DevServer(fixturePath, { - ...options, - output: client.output, - }); - await server.start(0); - await fn(server); - } finally { - await server?.stop(); - } - }; - -function validateResponseHeaders(res: Response, podId?: string) { - expect(res.headers.get('server')).toEqual('Vercel'); - expect(res.headers.get('cache-control')!.length > 0).toBeTruthy(); - expect( - /^dev1::(dev1::)?[0-9a-z]{5}-[1-9][0-9]+-[a-f0-9]{12}$/.test( - res.headers.get('x-vercel-id')! - ) - ).toBeTruthy(); - if (podId) { - expect( - res.headers.get('x-vercel-id')!.startsWith(`dev1::${podId}`) || - res.headers.get('x-vercel-id')!.startsWith(`dev1::dev1::${podId}`) - ).toBeTruthy(); - } -} - -describe('DevServer', () => { - jest.setTimeout(ms('2m')); - - it( - 'should support request body', - testFixture('now-dev-request-body', async server => { - const body = { hello: 'world' }; - - // Test that `req.body` works in dev - let res = await fetch(`${server.address}/api/req-body`, { - method: 'POST', - headers: { - 'content-type': 'application/json', - }, - body: JSON.stringify(body), - }); - validateResponseHeaders(res); - expect(await res.json()).toMatchObject(body); - - // Test that `req` "data" events work in dev - res = await fetch(`${server.address}/api/data-events`, { - method: 'POST', - headers: { - 'content-type': 'application/json', - }, - body: JSON.stringify(body), - }); - expect(await res.json()).toMatchObject(body); - }) - ); - - it( - 'should maintain query when invoking serverless function', - testFixture('now-dev-query-invoke', async server => { - const res = await fetch(`${server.address}/something?url-param=a`); - validateResponseHeaders(res); - - const text = await res.text(); - const parsed = url.parse(text, true); - expect(parsed.pathname).toEqual('/something'); - expect(parsed.query['url-param']).toEqual('a'); - expect(parsed.query['route-param']).toEqual('b'); - }) - ); - - it( - 'should maintain query when proxy passing', - testFixture('now-dev-query-proxy', async server => { - const dest = createServer((req, res) => { - res.end(req.url); - }); - await listen(dest, 0); - const addr = dest.address(); - if (!addr || typeof addr === 'string') { - throw new Error('Unexpected HTTP address'); - } - const { port } = addr; - - try { - const res = await fetch(`${server.address}/${port}?url-param=a`); - validateResponseHeaders(res); - - const text = await res.text(); - const parsed = url.parse(text, true); - expect(parsed.pathname).toEqual('/something'); - expect(parsed.query['url-param']).toEqual('a'); - expect(parsed.query['route-param']).toEqual('b'); - } finally { - dest.close(); - } - }) - ); - - it( - 'should maintain query when builder defines routes', - testFixture( - 'now-dev-next', - async server => { - const res = await fetch(`${server.address}/test?url-param=a`); - validateResponseHeaders(res); - - const text = await res.text(); - - // Hacky way of getting the page payload from the response - // HTML since we don't have a HTML parser handy. - const json = text - .match(/
(.*)<\/pre>/)![1]
-          .replace('
', '') - .replace('', '') - .replace(/&/g, '&') - .replace(/"/g, '"'); - const parsed = JSON.parse(json); - const query = url.parse(parsed.url, true).query; - - expect(query['url-param']).toEqual('a'); - expect(query['route-param']).toEqual('b'); - }, - { - devCommand: 'next dev --port $PORT', - } - ) - ); - - it( - 'should allow `cache-control` to be overwritten', - testFixture('now-dev-headers', async server => { - const res = await fetch( - `${server.address}/?name=cache-control&value=immutable` - ); - expect(res.headers.get('cache-control')).toEqual('immutable'); - }) - ); - - it( - 'should send `etag` header for static files', - testFixture('now-dev-headers', async server => { - const res = await fetch(`${server.address}/foo.txt`); - const expected = IS_WINDOWS - ? '9dc423ab77c2e0446cd355256efff2ea1be27cbf' - : 'd263af8ab880c0b97eb6c5c125b5d44f9e5addd9'; - expect(res.headers.get('etag')).toEqual(`"${expected}"`); - const body = await res.text(); - expect(body.trim()).toEqual('hi'); - }) - ); - - it( - 'should support default builds and routes', - testFixture( - 'now-dev-default-builds-and-routes', - async server => { - let podId: string; - - let res = await fetch(`${server.address}/`); - validateResponseHeaders(res); - podId = res.headers.get('x-vercel-id')!.match(/:(\w+)-/)![1]; - let body = await res.text(); - expect(body.includes('hello, this is the frontend')).toBeTruthy(); - - res = await fetch(`${server.address}/api/users`); - validateResponseHeaders(res, podId); - body = await res.text(); - expect(body).toEqual('users'); - - res = await fetch(`${server.address}/api/users/1`); - validateResponseHeaders(res, podId); - body = await res.text(); - expect(body).toEqual('users/1'); - - res = await fetch(`${server.address}/api/welcome`); - validateResponseHeaders(res, podId); - body = await res.text(); - expect(body).toEqual('hello and welcome'); - }, - { - devCommand: 'next dev --port $PORT', - } - ) - ); - - it( - 'should support `@vercel/static` routing', - testFixture('now-dev-static-routes', async server => { - const res = await fetch(`${server.address}/`); - expect(res.status).toEqual(200); - const body = await res.text(); - expect(body.trim()).toEqual('Hello!'); - }) - ); - - it( - 'should support `@vercel/static-build` routing', - testFixture( - 'now-dev-static-build-routing', - async server => { - const res = await fetch(`${server.address}/api/date`); - expect(res.status).toEqual(200); - const body = await res.text(); - expect(body.startsWith('The current date:')).toBeTruthy(); - }, - { - // This test is currently failing on Windows, so skip for now: - // > Creating initial build - // $ serve -l $PORT src - // 'serve' is not recognized as an internal or external command, - // https://github.com/vercel/vercel/pull/6638/checks?check_run_id=3449662836 - skip: IS_WINDOWS, - } - ) - ); - - it( - 'should support directory listing', - testFixture('now-dev-directory-listing', async server => { - // Get directory listing - let res = await fetch(`${server.address}/`); - let body = await res.text(); - expect(res.status).toEqual(200); - expect(body.includes('Index of')).toBeTruthy(); - - // Get a file - res = await fetch(`${server.address}/file.txt`); - body = await res.text(); - expect(res.status).toEqual(200); - expect(body.trim()).toEqual('Hello from file!'); - - // Invoke a lambda - res = await fetch(`${server.address}/lambda.js`); - body = await res.text(); - expect(res.status).toEqual(200); - expect(body).toEqual('Hello from Lambda!'); - - // Trigger a 404 - res = await fetch(`${server.address}/does-not-exist`); - expect(res.status).toEqual(404); - }) - ); - - it( - 'should support `public` directory with zero config', - testFixture('now-dev-api-with-public', async server => { - let res = await fetch(`${server.address}/api/user`); - let body = await res.text(); - expect(body).toEqual('hello:user'); - - res = await fetch(`${server.address}/`); - body = await res.text(); - expect(body.startsWith('

hello world

')).toBeTruthy(); - }) - ); - - it( - 'should support static files with zero config', - testFixture('now-dev-api-with-static', async server => { - let res = await fetch(`${server.address}/api/user`); - let body = await res.text(); - expect(body).toEqual('bye:user'); - - res = await fetch(`${server.address}/`); - body = await res.text(); - expect(body.startsWith('

goodbye world

')).toBeTruthy(); - }) - ); - - it( - 'should respond with 404 listing with Accept header support', - testFixture('now-dev-directory-listing', async server => { - // HTML response - let res = await fetch(`${server.address}/does-not-exist`, { - headers: { - Accept: 'text/html', - }, - }); - expect(res.status).toEqual(404); - expect(res.headers.get('content-type')).toEqual( - 'text/html; charset=utf-8' - ); - let body = await res.text(); - expect(body.startsWith('')).toBeTruthy(); - - // JSON response - res = await fetch(`${server.address}/does-not-exist`, { - headers: { - Accept: 'application/json', - }, - }); - expect(res.status).toEqual(404); - expect(res.headers.get('content-type')).toEqual('application/json'); - body = await res.text(); - expect(body).toEqual( - '{"error":{"code":404,"message":"The page could not be found."}}\n' - ); - - // Plain text response - res = await fetch(`${server.address}/does-not-exist`); - expect(res.status).toEqual(404); - body = await res.text(); - expect(res.headers.get('content-type')).toEqual( - 'text/plain; charset=utf-8' - ); - expect(body).toEqual('The page could not be found.\n\nNOT_FOUND\n'); - }) - ); - - it( - 'should support custom 404 routes', - testFixture('now-dev-custom-404', async server => { - // Test custom 404 with static dest - let res = await fetch(`${server.address}/error.html`); - expect(res.status).toEqual(404); - let body = await res.text(); - expect(body.trim()).toEqual('
Custom 404 page
'); - - // Test custom 404 with lambda dest - res = await fetch(`${server.address}/error.js`); - expect(res.status).toEqual(404); - body = await res.text(); - expect(body).toEqual('Custom 404 Lambda\n'); - - // Test regular 404 still works - res = await fetch(`${server.address}/does-not-exist`); - expect(res.status).toEqual(404); - body = await res.text(); - expect(body).toEqual('The page could not be found.\n\nNOT_FOUND\n'); - }) - ); - - /* - it( - 'should support edge middleware', - testFixture('edge-middleware', async server => { - const response = await fetch(`${server.address}/index.html`); - const body = await response.json(); - expect(body).toEqual( - JSON.parse( - fs.readFileSync( - path.join( - __dirname, - '../../fixtures/unit/edge-middleware/response.json' - ), - 'utf8' - ) - ) - ); - }) - ); - - it( - 'should work with middleware written in typescript', - testFixture('edge-middleware-ts', async server => { - const response = await fetch(`${server.address}/index.html`); - const body = await response.text(); - expect(body).toStrictEqual('response'); - }) - ); - - it( - 'should render an error page when the middleware throws', - testFixture('edge-middleware-error', async server => { - const response = await fetch(`${server.address}/index.html`); - const body = await response.text(); - expect(body).toStrictEqual( - 'A server error has occurred\n\nEDGE_FUNCTION_INVOCATION_FAILED\n' - ); - }) - ); - - it( - 'should render an error page when the middleware returns not a Response', - testFixture('edge-middleware-invalid-response', async server => { - const response = await fetch(`${server.address}/index.html`); - const body = await response.text(); - expect(body).toStrictEqual( - 'A server error has occurred\n\nEDGE_FUNCTION_INVOCATION_FAILED\n' - ); - }) - ); - - it( - 'should run middleware in strict mode', - testFixture('edge-middleware-strict', async server => { - const response = await fetch(`${server.address}/index.html`); - const body = await response.text(); - expect(body).toStrictEqual('is strict mode? yes'); - }) - ); - */ -});