Allow custom 404 route to handle API route missing methods (#4594)
* Properly allow file uploads in the dev server * Allow custom 404 route to handle API route missing methods * Add a changeset * what was i thinking * Pass through the pathname * Move the try/catch out and into handleRequest * await the result of handleRoute
This commit is contained in:
parent
04daafbbdf
commit
005d5bacd9
7 changed files with 289 additions and 154 deletions
5
.changeset/twelve-days-build.md
Normal file
5
.changeset/twelve-days-build.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Allow custom 404 route to handle API route missing methods
|
|
@ -202,6 +202,13 @@ export class App {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.type === 'response') {
|
if (result.type === 'response') {
|
||||||
|
if(result.response.headers.get('X-Astro-Response') === 'Not-Found') {
|
||||||
|
const fourOhFourRequest = new Request(new URL('/404', request.url));
|
||||||
|
const fourOhFourRouteData = this.match(fourOhFourRequest);
|
||||||
|
if(fourOhFourRouteData) {
|
||||||
|
return this.render(fourOhFourRequest, fourOhFourRouteData);
|
||||||
|
}
|
||||||
|
}
|
||||||
return result.response;
|
return result.response;
|
||||||
} else {
|
} else {
|
||||||
const body = result.body;
|
const body = result.body;
|
||||||
|
|
|
@ -18,13 +18,24 @@ function getHandlerFromModule(mod: EndpointHandler, method: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Renders an endpoint request to completion, returning the body. */
|
/** Renders an endpoint request to completion, returning the body. */
|
||||||
export async function renderEndpoint(mod: EndpointHandler, request: Request, params: Params) {
|
export async function renderEndpoint(
|
||||||
|
mod: EndpointHandler,
|
||||||
|
request: Request,
|
||||||
|
params: Params
|
||||||
|
) {
|
||||||
const chosenMethod = request.method?.toLowerCase();
|
const chosenMethod = request.method?.toLowerCase();
|
||||||
const handler = getHandlerFromModule(mod, chosenMethod);
|
const handler = getHandlerFromModule(mod, chosenMethod);
|
||||||
if (!handler || typeof handler !== 'function') {
|
if (!handler || typeof handler !== 'function') {
|
||||||
throw new Error(
|
// No handler found, so this should be a 404. Using a custom header
|
||||||
`Endpoint handler not found! Expected an exported function for "${chosenMethod}"`
|
// to signal to the renderer that this is an internal 404 that should
|
||||||
);
|
// be handled by a custom 404 route if possible.
|
||||||
|
let response = new Response(null, {
|
||||||
|
status: 404,
|
||||||
|
headers: {
|
||||||
|
'X-Astro-Response': 'Not-Found'
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handler.length > 1) {
|
if (handler.length > 1) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type http from 'http';
|
import type http from 'http';
|
||||||
import mime from 'mime';
|
import mime from 'mime';
|
||||||
import type * as vite from 'vite';
|
import type * as vite from 'vite';
|
||||||
import type { AstroConfig, ManifestData } from '../@types/astro';
|
import type { AstroConfig, ManifestData, SSRManifest } from '../@types/astro';
|
||||||
import type { SSROptions } from '../core/render/dev/index';
|
import type { SSROptions } from '../core/render/dev/index';
|
||||||
|
|
||||||
import { Readable } from 'stream';
|
import { Readable } from 'stream';
|
||||||
|
@ -28,13 +28,8 @@ interface AstroPluginOptions {
|
||||||
logging: LogOptions;
|
logging: LogOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
function truncateString(str: string, n: number) {
|
type AsyncReturnType<T extends (...args: any) => Promise<any>> =
|
||||||
if (str.length > n) {
|
T extends (...args: any) => Promise<infer R> ? R : any
|
||||||
return str.substring(0, n) + '…';
|
|
||||||
} else {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeHtmlResponse(res: http.ServerResponse, statusCode: number, html: string) {
|
function writeHtmlResponse(res: http.ServerResponse, statusCode: number, html: string) {
|
||||||
res.writeHead(statusCode, {
|
res.writeHead(statusCode, {
|
||||||
|
@ -180,6 +175,68 @@ export function baseMiddleware(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function matchRoute(
|
||||||
|
pathname: string,
|
||||||
|
routeCache: RouteCache,
|
||||||
|
viteServer: vite.ViteDevServer,
|
||||||
|
logging: LogOptions,
|
||||||
|
manifest: ManifestData,
|
||||||
|
config: AstroConfig,
|
||||||
|
) {
|
||||||
|
const matches = matchAllRoutes(pathname, manifest);
|
||||||
|
|
||||||
|
for await (const maybeRoute of matches) {
|
||||||
|
const filePath = new URL(`./${maybeRoute.component}`, config.root);
|
||||||
|
const preloadedComponent = await preload({ astroConfig: config, filePath, viteServer });
|
||||||
|
const [, mod] = preloadedComponent;
|
||||||
|
// attempt to get static paths
|
||||||
|
// if this fails, we have a bad URL match!
|
||||||
|
const paramsAndPropsRes = await getParamsAndProps({
|
||||||
|
mod,
|
||||||
|
route: maybeRoute,
|
||||||
|
routeCache,
|
||||||
|
pathname: pathname,
|
||||||
|
logging,
|
||||||
|
ssr: config.output === 'server',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (paramsAndPropsRes !== GetParamsAndPropsError.NoMatchingStaticPath) {
|
||||||
|
return {
|
||||||
|
route: maybeRoute,
|
||||||
|
filePath,
|
||||||
|
preloadedComponent,
|
||||||
|
mod,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches.length) {
|
||||||
|
warn(
|
||||||
|
logging,
|
||||||
|
'getStaticPaths',
|
||||||
|
`Route pattern matched, but no matching static path found. (${pathname})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
log404(logging, pathname);
|
||||||
|
const custom404 = getCustom404Route(config, manifest);
|
||||||
|
|
||||||
|
if (custom404) {
|
||||||
|
const filePath = new URL(`./${custom404.component}`, config.root);
|
||||||
|
const preloadedComponent = await preload({ astroConfig: config, filePath, viteServer });
|
||||||
|
const [, mod] = preloadedComponent;
|
||||||
|
|
||||||
|
return {
|
||||||
|
route: custom404,
|
||||||
|
filePath,
|
||||||
|
preloadedComponent,
|
||||||
|
mod,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/** The main logic to route dev server requests to pages in Astro. */
|
/** The main logic to route dev server requests to pages in Astro. */
|
||||||
async function handleRequest(
|
async function handleRequest(
|
||||||
routeCache: RouteCache,
|
routeCache: RouteCache,
|
||||||
|
@ -190,7 +247,6 @@ async function handleRequest(
|
||||||
req: http.IncomingMessage,
|
req: http.IncomingMessage,
|
||||||
res: http.ServerResponse
|
res: http.ServerResponse
|
||||||
) {
|
) {
|
||||||
const reqStart = performance.now();
|
|
||||||
const origin = `${viteServer.config.server.https ? 'https' : 'http'}://${req.headers.host}`;
|
const origin = `${viteServer.config.server.https ? 'https' : 'http'}://${req.headers.host}`;
|
||||||
const buildingToSSR = config.output === 'server';
|
const buildingToSSR = config.output === 'server';
|
||||||
// Ignore `.html` extensions and `index.html` in request URLS to ensure that
|
// Ignore `.html` extensions and `index.html` in request URLS to ensure that
|
||||||
|
@ -217,7 +273,7 @@ async function handleRequest(
|
||||||
if (!(req.method === 'GET' || req.method === 'HEAD')) {
|
if (!(req.method === 'GET' || req.method === 'HEAD')) {
|
||||||
let bytes: Uint8Array[] = [];
|
let bytes: Uint8Array[] = [];
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
req.on('data', (part) => {
|
req.on('data', part => {
|
||||||
bytes.push(part);
|
bytes.push(part);
|
||||||
});
|
});
|
||||||
req.on('end', resolve);
|
req.on('end', resolve);
|
||||||
|
@ -225,6 +281,62 @@ async function handleRequest(
|
||||||
body = Buffer.concat(bytes);
|
body = Buffer.concat(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let filePath: URL | undefined;
|
||||||
|
try {
|
||||||
|
const matchedRoute = await matchRoute(
|
||||||
|
pathname,
|
||||||
|
routeCache,
|
||||||
|
viteServer,
|
||||||
|
logging,
|
||||||
|
manifest,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
filePath = matchedRoute?.filePath;
|
||||||
|
|
||||||
|
return await handleRoute(
|
||||||
|
matchedRoute,
|
||||||
|
url,
|
||||||
|
pathname,
|
||||||
|
body,
|
||||||
|
origin,
|
||||||
|
routeCache,
|
||||||
|
viteServer,
|
||||||
|
manifest,
|
||||||
|
logging,
|
||||||
|
config,
|
||||||
|
req,
|
||||||
|
res
|
||||||
|
);
|
||||||
|
} catch(_err) {
|
||||||
|
const err = fixViteErrorMessage(_err, viteServer, filePath);
|
||||||
|
const errorWithMetadata = collectErrorMetadata(err);
|
||||||
|
error(logging, null, msg.formatErrorMessage(errorWithMetadata));
|
||||||
|
handle500Response(viteServer, origin, req, res, errorWithMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRoute(
|
||||||
|
matchedRoute: AsyncReturnType<typeof matchRoute>,
|
||||||
|
url: URL,
|
||||||
|
pathname: string,
|
||||||
|
body: ArrayBuffer | undefined,
|
||||||
|
origin: string,
|
||||||
|
routeCache: RouteCache,
|
||||||
|
viteServer: vite.ViteDevServer,
|
||||||
|
manifest: ManifestData,
|
||||||
|
logging: LogOptions,
|
||||||
|
config: AstroConfig,
|
||||||
|
req: http.IncomingMessage,
|
||||||
|
res: http.ServerResponse
|
||||||
|
): Promise<void> {
|
||||||
|
if (!matchedRoute) {
|
||||||
|
return handle404Response(origin, config, req, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath: URL | undefined = matchedRoute.filePath;
|
||||||
|
const { route, preloadedComponent, mod } = matchedRoute;
|
||||||
|
const buildingToSSR = config.output === 'server';
|
||||||
|
|
||||||
// Headers are only available when using SSR.
|
// Headers are only available when using SSR.
|
||||||
const request = createRequest({
|
const request = createRequest({
|
||||||
url,
|
url,
|
||||||
|
@ -236,123 +348,75 @@ async function handleRequest(
|
||||||
clientAddress: buildingToSSR ? req.socket.remoteAddress : undefined,
|
clientAddress: buildingToSSR ? req.socket.remoteAddress : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
async function matchRoute() {
|
// attempt to get static paths
|
||||||
const matches = matchAllRoutes(pathname, manifest);
|
// if this fails, we have a bad URL match!
|
||||||
|
const paramsAndPropsRes = await getParamsAndProps({
|
||||||
|
mod,
|
||||||
|
route,
|
||||||
|
routeCache,
|
||||||
|
pathname: pathname,
|
||||||
|
logging,
|
||||||
|
ssr: config.output === 'server',
|
||||||
|
});
|
||||||
|
|
||||||
for await (const maybeRoute of matches) {
|
const options: SSROptions = {
|
||||||
const filePath = new URL(`./${maybeRoute.component}`, config.root);
|
astroConfig: config,
|
||||||
const preloadedComponent = await preload({ astroConfig: config, filePath, viteServer });
|
filePath,
|
||||||
const [, mod] = preloadedComponent;
|
logging,
|
||||||
// attempt to get static paths
|
mode: 'development',
|
||||||
// if this fails, we have a bad URL match!
|
origin,
|
||||||
const paramsAndPropsRes = await getParamsAndProps({
|
pathname: pathname,
|
||||||
mod,
|
route,
|
||||||
route: maybeRoute,
|
routeCache,
|
||||||
routeCache,
|
viteServer,
|
||||||
pathname: pathname,
|
request,
|
||||||
logging,
|
};
|
||||||
ssr: config.output === 'server',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (paramsAndPropsRes !== GetParamsAndPropsError.NoMatchingStaticPath) {
|
// Route successfully matched! Render it.
|
||||||
return {
|
if (route.type === 'endpoint') {
|
||||||
route: maybeRoute,
|
const result = await callEndpoint(options);
|
||||||
filePath,
|
if (result.type === 'response') {
|
||||||
preloadedComponent,
|
if(result.response.headers.get('X-Astro-Response') === 'Not-Found') {
|
||||||
mod,
|
const fourOhFourRoute = await matchRoute(
|
||||||
};
|
'/404',
|
||||||
}
|
routeCache,
|
||||||
}
|
viteServer,
|
||||||
|
logging,
|
||||||
if (matches.length) {
|
manifest,
|
||||||
warn(
|
config
|
||||||
logging,
|
);
|
||||||
'getStaticPaths',
|
return handleRoute(
|
||||||
`Route pattern matched, but no matching static path found. (${pathname})`
|
fourOhFourRoute,
|
||||||
);
|
new URL('/404', url),
|
||||||
}
|
'/404',
|
||||||
|
body,
|
||||||
log404(logging, pathname);
|
origin,
|
||||||
const custom404 = getCustom404Route(config, manifest);
|
routeCache,
|
||||||
|
viteServer,
|
||||||
if (custom404) {
|
manifest,
|
||||||
const filePath = new URL(`./${custom404.component}`, config.root);
|
logging,
|
||||||
const preloadedComponent = await preload({ astroConfig: config, filePath, viteServer });
|
config,
|
||||||
const [, mod] = preloadedComponent;
|
req,
|
||||||
|
res
|
||||||
return {
|
);
|
||||||
route: custom404,
|
|
||||||
filePath,
|
|
||||||
preloadedComponent,
|
|
||||||
mod,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
let filePath: URL | undefined;
|
|
||||||
try {
|
|
||||||
const matchedRoute = await matchRoute();
|
|
||||||
|
|
||||||
if (!matchedRoute) {
|
|
||||||
return handle404Response(origin, config, req, res);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { route, preloadedComponent, mod } = matchedRoute;
|
|
||||||
filePath = matchedRoute.filePath;
|
|
||||||
|
|
||||||
// attempt to get static paths
|
|
||||||
// if this fails, we have a bad URL match!
|
|
||||||
const paramsAndPropsRes = await getParamsAndProps({
|
|
||||||
mod,
|
|
||||||
route,
|
|
||||||
routeCache,
|
|
||||||
pathname: pathname,
|
|
||||||
logging,
|
|
||||||
ssr: config.output === 'server',
|
|
||||||
});
|
|
||||||
|
|
||||||
const options: SSROptions = {
|
|
||||||
astroConfig: config,
|
|
||||||
filePath,
|
|
||||||
logging,
|
|
||||||
mode: 'development',
|
|
||||||
origin,
|
|
||||||
pathname: pathname,
|
|
||||||
route,
|
|
||||||
routeCache,
|
|
||||||
viteServer,
|
|
||||||
request,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Route successfully matched! Render it.
|
|
||||||
if (route.type === 'endpoint') {
|
|
||||||
const result = await callEndpoint(options);
|
|
||||||
if (result.type === 'response') {
|
|
||||||
await writeWebResponse(res, result.response);
|
|
||||||
} else {
|
|
||||||
let contentType = 'text/plain';
|
|
||||||
// Dynamic routes don’t include `route.pathname`, so synthesise a path for these (e.g. 'src/pages/[slug].svg')
|
|
||||||
const filepath =
|
|
||||||
route.pathname ||
|
|
||||||
route.segments.map((segment) => segment.map((p) => p.content).join('')).join('/');
|
|
||||||
const computedMimeType = mime.getType(filepath);
|
|
||||||
if (computedMimeType) {
|
|
||||||
contentType = computedMimeType;
|
|
||||||
}
|
|
||||||
res.writeHead(200, { 'Content-Type': `${contentType};charset=utf-8` });
|
|
||||||
res.end(result.body);
|
|
||||||
}
|
}
|
||||||
|
await writeWebResponse(res, result.response);
|
||||||
} else {
|
} else {
|
||||||
const result = await ssr(preloadedComponent, options);
|
let contentType = 'text/plain';
|
||||||
return await writeSSRResult(result, res);
|
// Dynamic routes don’t include `route.pathname`, so synthesise a path for these (e.g. 'src/pages/[slug].svg')
|
||||||
|
const filepath =
|
||||||
|
route.pathname ||
|
||||||
|
route.segments.map((segment) => segment.map((p) => p.content).join('')).join('/');
|
||||||
|
const computedMimeType = mime.getType(filepath);
|
||||||
|
if (computedMimeType) {
|
||||||
|
contentType = computedMimeType;
|
||||||
|
}
|
||||||
|
res.writeHead(200, { 'Content-Type': `${contentType};charset=utf-8` });
|
||||||
|
res.end(result.body);
|
||||||
}
|
}
|
||||||
} catch (_err) {
|
} else {
|
||||||
const err = fixViteErrorMessage(_err, viteServer, filePath);
|
const result = await ssr(preloadedComponent, options);
|
||||||
const errorWithMetadata = collectErrorMetadata(err);
|
return await writeSSRResult(result, res);
|
||||||
error(logging, null, msg.formatErrorMessage(errorWithMetadata));
|
|
||||||
handle500Response(viteServer, origin, req, res, errorWithMetadata);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -285,6 +285,10 @@ describe('Development Routing', () => {
|
||||||
devServer = await fixture.startDevServer();
|
devServer = await fixture.startDevServer();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await devServer.stop();
|
||||||
|
});
|
||||||
|
|
||||||
it('200 when loading /index.html', async () => {
|
it('200 when loading /index.html', async () => {
|
||||||
const response = await fixture.fetch('/index.html');
|
const response = await fixture.fetch('/index.html');
|
||||||
expect(response.status).to.equal(200);
|
expect(response.status).to.equal(200);
|
||||||
|
|
6
packages/astro/test/fixtures/ssr-api-route-custom-404/src/pages/api/route.js
vendored
Normal file
6
packages/astro/test/fixtures/ssr-api-route-custom-404/src/pages/api/route.js
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
export function post() {
|
||||||
|
return {
|
||||||
|
body: JSON.stringify({ ok: true })
|
||||||
|
};
|
||||||
|
}
|
|
@ -13,37 +13,75 @@ describe('404 and 500 pages', () => {
|
||||||
output: 'server',
|
output: 'server',
|
||||||
adapter: testAdapter(),
|
adapter: testAdapter(),
|
||||||
});
|
});
|
||||||
await fixture.build({});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('404 page returned when a route does not match', async () => {
|
describe('Development', () => {
|
||||||
const app = await fixture.loadTestAdapterApp();
|
/** @type {import('./test-utils').DevServer} */
|
||||||
const request = new Request('http://example.com/some/fake/route');
|
let devServer;
|
||||||
const response = await app.render(request);
|
before(async () => {
|
||||||
expect(response.status).to.equal(404);
|
devServer = await fixture.startDevServer();
|
||||||
const html = await response.text();
|
});
|
||||||
const $ = cheerio.load(html);
|
|
||||||
expect($('h1').text()).to.equal('Something went horribly wrong!');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('404 page returned when a route does not match and passing routeData', async () => {
|
after(async () => {
|
||||||
const app = await fixture.loadTestAdapterApp();
|
await devServer.stop();
|
||||||
const request = new Request('http://example.com/some/fake/route');
|
});
|
||||||
const routeData = app.match(request, { matchNotFound: true });
|
|
||||||
const response = await app.render(request, routeData);
|
|
||||||
expect(response.status).to.equal(404);
|
|
||||||
const html = await response.text();
|
|
||||||
const $ = cheerio.load(html);
|
|
||||||
expect($('h1').text()).to.equal('Something went horribly wrong!');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('500 page returned when there is an error', async () => {
|
it('Returns 404 when hitting an API route with the wrong method', async () => {
|
||||||
const app = await fixture.loadTestAdapterApp();
|
let res = await fixture.fetch('/api/route', {
|
||||||
const request = new Request('http://example.com/causes-error');
|
method: 'PUT'
|
||||||
const response = await app.render(request);
|
});
|
||||||
expect(response.status).to.equal(500);
|
let html = await res.text();
|
||||||
const html = await response.text();
|
let $ = cheerio.load(html);
|
||||||
const $ = cheerio.load(html);
|
expect($('h1').text()).to.equal(`Something went horribly wrong!`);
|
||||||
expect($('h1').text()).to.equal('This is an error page');
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Production', () => {
|
||||||
|
before(async () => {
|
||||||
|
await fixture.build({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('404 page returned when a route does not match', async () => {
|
||||||
|
const app = await fixture.loadTestAdapterApp();
|
||||||
|
const request = new Request('http://example.com/some/fake/route');
|
||||||
|
const response = await app.render(request);
|
||||||
|
expect(response.status).to.equal(404);
|
||||||
|
const html = await response.text();
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('h1').text()).to.equal('Something went horribly wrong!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('404 page returned when a route does not match and passing routeData', async () => {
|
||||||
|
const app = await fixture.loadTestAdapterApp();
|
||||||
|
const request = new Request('http://example.com/some/fake/route');
|
||||||
|
const routeData = app.match(request, { matchNotFound: true });
|
||||||
|
const response = await app.render(request, routeData);
|
||||||
|
expect(response.status).to.equal(404);
|
||||||
|
const html = await response.text();
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('h1').text()).to.equal('Something went horribly wrong!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('500 page returned when there is an error', async () => {
|
||||||
|
const app = await fixture.loadTestAdapterApp();
|
||||||
|
const request = new Request('http://example.com/causes-error');
|
||||||
|
const response = await app.render(request);
|
||||||
|
expect(response.status).to.equal(500);
|
||||||
|
const html = await response.text();
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('h1').text()).to.equal('This is an error page');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Returns 404 when hitting an API route with the wrong method', async () => {
|
||||||
|
const app = await fixture.loadTestAdapterApp();
|
||||||
|
const request = new Request('http://example.com/api/route', {
|
||||||
|
method: 'PUT'
|
||||||
|
});
|
||||||
|
const response = await app.render(request);
|
||||||
|
expect(response.status).to.equal(404);
|
||||||
|
const html = await response.text();
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('h1').text()).to.equal(`Something went horribly wrong!`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue