Skip to content

Commit

Permalink
fix: bubble up errors in rewrites (#11136)
Browse files Browse the repository at this point in the history
* fix: bubble up errors in rewrites

* docs

* remove commented code

* changesets

* fix string interpolation
ematipico authored Jun 5, 2024
1 parent 6bcbd15 commit 35ef53c
Showing 24 changed files with 360 additions and 167 deletions.
5 changes: 5 additions & 0 deletions .changeset/dull-lizards-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"astro": patch
---

Errors that are emitted during a rewrite are now bubbled up and shown to the user. A 404 response is not returned anymore.
7 changes: 7 additions & 0 deletions .changeset/nervous-pens-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"astro": patch
---

It's not possible anymore to use `Astro.rewrite("/404")` inside static pages. This isn't counterproductive because Astro will end-up emitting a page that contains the HTML of 404 error page.

It's still possible to use `Astro.rewrite("/404")` inside on-demand pages, or pages that opt-out from prerendering.
11 changes: 10 additions & 1 deletion packages/astro/src/container/pipeline.ts
Original file line number Diff line number Diff line change
@@ -7,12 +7,13 @@ import type {
} from '../@types/astro.js';
import { type HeadElements, Pipeline } from '../core/base-pipeline.js';
import type { SinglePageBuiltModule } from '../core/build/types.js';
import { RouteNotFound } from '../core/errors/errors-data.js';
import { InvalidRewrite404, RouteNotFound } from '../core/errors/errors-data.js';
import { AstroError } from '../core/errors/index.js';
import {
createModuleScriptElement,
createStylesheetElementSet,
} from '../core/render/ssr-element.js';
import { default404Page } from '../core/routing/astro-designed-error-pages.js';

export class ContainerPipeline extends Pipeline {
/**
@@ -111,4 +112,12 @@ export class ContainerPipeline extends Pipeline {
// At the moment it's not used by the container via any public API
// @ts-expect-error It needs to be implemented.
async getComponentByRoute(_routeData: RouteData): Promise<ComponentInstance> {}

rewriteKnownRoute(pathname: string, _sourceRoute: RouteData): ComponentInstance {
if (pathname === '/404') {
return { default: default404Page } as any as ComponentInstance;
}

throw new AstroError(InvalidRewrite404);
}
}
56 changes: 38 additions & 18 deletions packages/astro/src/core/app/pipeline.ts
Original file line number Diff line number Diff line change
@@ -9,8 +9,11 @@ import type {
import { Pipeline } from '../base-pipeline.js';
import type { SinglePageBuiltModule } from '../build/types.js';
import { DEFAULT_404_COMPONENT } from '../constants.js';
import { RewriteEncounteredAnError } from '../errors/errors-data.js';
import { AstroError } from '../errors/index.js';
import { RedirectSinglePageBuiltModule } from '../redirects/component.js';
import { createModuleScriptElement, createStylesheetElementSet } from '../render/ssr-element.js';
import { DEFAULT_404_ROUTE } from '../routing/astro-designed-error-pages.js';

export class AppPipeline extends Pipeline {
#manifestData: ManifestData | undefined;
@@ -82,36 +85,44 @@ export class AppPipeline extends Pipeline {

async tryRewrite(
payload: RewritePayload,
request: Request
request: Request,
sourceRoute: RouteData
): Promise<[RouteData, ComponentInstance]> {
let foundRoute;

for (const route of this.#manifestData!.routes) {
let finalUrl: URL | undefined = undefined;

if (payload instanceof URL) {
if (route.pattern.test(payload.pathname)) {
foundRoute = route;
break;
}
finalUrl = payload;
} else if (payload instanceof Request) {
const url = new URL(payload.url);
if (route.pattern.test(url.pathname)) {
foundRoute = route;
break;
}
finalUrl = new URL(payload.url);
} else {
const newUrl = new URL(payload, new URL(request.url).origin);
if (route.pattern.test(decodeURI(newUrl.pathname))) {
foundRoute = route;
break;
}
finalUrl = new URL(payload, new URL(request.url).origin);
}

if (route.pattern.test(decodeURI(finalUrl.pathname))) {
foundRoute = route;
break;
} else if (finalUrl.pathname === '/404') {
foundRoute = DEFAULT_404_ROUTE;
break;
}
}

if (foundRoute) {
const componentInstance = await this.getComponentByRoute(foundRoute);
return [foundRoute, componentInstance];
if (foundRoute.pathname === '/404') {
const componentInstance = this.rewriteKnownRoute(foundRoute.pathname, sourceRoute);
return [foundRoute, componentInstance];
} else {
const componentInstance = await this.getComponentByRoute(foundRoute);
return [foundRoute, componentInstance];
}
}
throw new Error('Route not found');
throw new AstroError({
...RewriteEncounteredAnError,
message: RewriteEncounteredAnError.message(payload.toString()),
});
}

async getModuleForRoute(route: RouteData): Promise<SinglePageBuiltModule> {
@@ -141,4 +152,13 @@ export class AppPipeline extends Pipeline {
);
}
}

// We don't need to check the source route, we already are in SSR
rewriteKnownRoute(pathname: string, _sourceRoute: RouteData): ComponentInstance {
if (pathname === '/404') {
return { default: () => new Response(null, { status: 404 }) } as ComponentInstance;
} else {
return { default: () => new Response(null, { status: 500 }) } as ComponentInstance;
}
}
}
17 changes: 15 additions & 2 deletions packages/astro/src/core/base-pipeline.ts
Original file line number Diff line number Diff line change
@@ -80,18 +80,31 @@ export abstract class Pipeline {
*
* - if not `RouteData` is found
*
* @param {RewritePayload} rewritePayload
* @param {RewritePayload} rewritePayload The payload provided by the user
* @param {Request} request The original request
* @param {RouteData} sourceRoute The original `RouteData`
*/
abstract tryRewrite(
rewritePayload: RewritePayload,
request: Request
request: Request,
sourceRoute: RouteData
): Promise<[RouteData, ComponentInstance]>;

/**
* Tells the pipeline how to retrieve a component give a `RouteData`
* @param routeData
*/
abstract getComponentByRoute(routeData: RouteData): Promise<ComponentInstance>;

/**
* Attempts to execute a rewrite of a known Astro route:
* - /404 -> src/pages/404.astro -> template
* - /500 -> src/pages/500.astro
*
* @param pathname The pathname where the user wants to rewrite e.g. "/404"
* @param sourceRoute The original route where the first request started. This is needed in case a pipeline wants to check if the current route is pre-rendered or not
*/
abstract rewriteKnownRoute(pathname: string, sourceRoute: RouteData): ComponentInstance;
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
2 changes: 0 additions & 2 deletions packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
@@ -44,8 +44,6 @@ import { getOutputFilename, isServerLikeOutput } from '../util.js';
import { getOutDirWithinCwd, getOutFile, getOutFolder } from './common.js';
import { cssOrder, mergeInlineCss } from './internal.js';
import { BuildPipeline } from './pipeline.js';
import { ASTRO_PAGE_MODULE_ID } from './plugins/plugin-pages.js';
import { getVirtualModulePageName } from './plugins/util.js';
import type {
PageBuildData,
SinglePageBuiltModule,
54 changes: 35 additions & 19 deletions packages/astro/src/core/build/pipeline.ts
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ import type {
import { getOutputDirectory } from '../../prerender/utils.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import type { SSRManifest } from '../app/types.js';
import { RouteNotFound } from '../errors/errors-data.js';
import { InvalidRewrite404, RewriteEncounteredAnError } from '../errors/errors-data.js';
import { AstroError } from '../errors/index.js';
import { routeIsFallback, routeIsRedirect } from '../redirects/helpers.js';
import { RedirectSinglePageBuiltModule } from '../redirects/index.js';
@@ -18,6 +18,7 @@ import {
createModuleScriptsSet,
createStylesheetElementSet,
} from '../render/ssr-element.js';
import { DEFAULT_404_ROUTE } from '../routing/astro-designed-error-pages.js';
import { isServerLikeOutput } from '../util.js';
import { getOutDirWithinCwd } from './common.js';
import { type BuildInternals, cssOrder, getPageData, mergeInlineCss } from './internal.js';
@@ -278,35 +279,43 @@ export class BuildPipeline extends Pipeline {

async tryRewrite(
payload: RewritePayload,
request: Request
request: Request,
sourceRoute: RouteData
): Promise<[RouteData, ComponentInstance]> {
let foundRoute: RouteData | undefined;
// options.manifest is the actual type that contains the information
for (const route of this.options.manifest.routes) {
let finalUrl: URL | undefined = undefined;

if (payload instanceof URL) {
if (route.pattern.test(payload.pathname)) {
foundRoute = route;
break;
}
finalUrl = payload;
} else if (payload instanceof Request) {
const url = new URL(payload.url);
if (route.pattern.test(url.pathname)) {
foundRoute = route;
break;
}
finalUrl = new URL(payload.url);
} else {
const newUrl = new URL(payload, new URL(request.url).origin);
if (route.pattern.test(decodeURI(newUrl.pathname))) {
foundRoute = route;
break;
}
finalUrl = new URL(payload, new URL(request.url).origin);
}

if (route.pattern.test(decodeURI(finalUrl.pathname))) {
foundRoute = route;
break;
} else if (finalUrl.pathname === '/404') {
foundRoute = DEFAULT_404_ROUTE;
break;
}
}
if (foundRoute) {
const componentInstance = await this.getComponentByRoute(foundRoute);
return [foundRoute, componentInstance];
if (foundRoute.pathname === '/404') {
const componentInstance = await this.rewriteKnownRoute(foundRoute.pathname, sourceRoute);
return [foundRoute, componentInstance];
} else {
const componentInstance = await this.getComponentByRoute(foundRoute);
return [foundRoute, componentInstance];
}
} else {
throw new AstroError(RouteNotFound);
throw new AstroError({
...RewriteEncounteredAnError,
message: RewriteEncounteredAnError.message(payload.toString()),
});
}
}

@@ -367,6 +376,13 @@ export class BuildPipeline extends Pipeline {

return RedirectSinglePageBuiltModule;
}

rewriteKnownRoute(_pathname: string, sourceRoute: RouteData): ComponentInstance {
if (!isServerLikeOutput(this.config) || sourceRoute.prerender) {
throw new AstroError(InvalidRewrite404);
}
throw new Error(`Unreachable, in SSG this route shouldn't be generated`);
}
}

function createEntryURL(filePath: string, outFolder: URL) {
2 changes: 1 addition & 1 deletion packages/astro/src/core/constants.ts
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@ export const ROUTE_TYPE_HEADER = 'X-Astro-Route-Type';
/**
* The value of the `component` field of the default 404 page, which is used when there is no user-provided 404.astro page.
*/
export const DEFAULT_404_COMPONENT = 'astro-default-404';
export const DEFAULT_404_COMPONENT = 'astro-default-404.astro';

/**
* A response with one of these status codes will be rewritten
23 changes: 23 additions & 0 deletions packages/astro/src/core/errors/errors-data.ts
Original file line number Diff line number Diff line change
@@ -1117,6 +1117,29 @@ export const MissingMiddlewareForInternationalization = {
"Your configuration setting `i18n.routing: 'manual'` requires you to provide your own i18n `middleware` file.",
} satisfies ErrorData;

/**
* @docs
* @description
* The user tried to rewrite using a route that doesn't exist, or it emitted a runtime error during its rendering phase.
*/
export const RewriteEncounteredAnError = {
name: 'RewriteEncounteredAnError',
title: "Astro couldn't find the route to rewrite, or if was found but it emitted an error during the rendering phase.",
message: (route: string, stack?: string) => `The route ${route} that you tried to render doesn't exist, or it emitted an error during the rendering phase. ${stack ? stack : ""}.`
} satisfies ErrorData;

/**
* @docs
* @description
*
* The user tried to rewrite a 404 page inside a static page.
*/
export const InvalidRewrite404 = {
name: 'InvalidRewrite404',
title: "You attempted to rewrite a 404 inside a static page, and this isn't allowed.",
message: 'Rewriting a 404 is only allowed inside on-demand pages.',
} satisfies ErrorData;

/**
* @docs
* @description
112 changes: 54 additions & 58 deletions packages/astro/src/core/render-context.ts
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ import { renderEndpoint } from '../runtime/server/endpoint.js';
import { renderPage } from '../runtime/server/index.js';
import {
ASTRO_VERSION,
DEFAULT_404_COMPONENT,
REROUTE_DIRECTIVE_HEADER,
ROUTE_TYPE_HEADER,
clientAddressSymbol,
@@ -38,6 +39,9 @@ import { type Pipeline, Slots, getParams, getProps } from './render/index.js';
* It contains data unique to each request. It is responsible for executing middleware, calling endpoints, and rendering the page by gathering necessary data from a `Pipeline`.
*/
export class RenderContext {
// The first route that this instance of the context attempts to render
originalRoute: RouteData;

private constructor(
readonly pipeline: Pipeline,
public locals: App.Locals,
@@ -50,7 +54,9 @@ export class RenderContext {
public params = getParams(routeData, pathname),
protected url = new URL(request.url),
public props: Props = {}
) {}
) {
this.originalRoute = routeData;
}

/**
* A flag that tells the render content if the rewriting was triggered
@@ -124,18 +130,15 @@ export class RenderContext {
const lastNext = async (ctx: APIContext, payload?: RewritePayload) => {
if (payload) {
if (this.pipeline.manifest.rewritingEnabled) {
try {
const [routeData, component] = await pipeline.tryRewrite(payload, this.request);
this.routeData = routeData;
componentInstance = component;
} catch (e) {
return new Response('Not found', {
status: 404,
statusText: 'Not found',
});
} finally {
this.isRewriting = true;
}
// we intentionally let the error bubble up
const [routeData, component] = await pipeline.tryRewrite(
payload,
this.request,
this.originalRoute
);
this.routeData = routeData;
componentInstance = component;
this.isRewriting = true;
} else {
this.pipeline.logger.warn(
'router',
@@ -166,6 +169,7 @@ export class RenderContext {
result.cancelled = true;
throw e;
}

// Signal to the i18n middleware to maybe act on this response
response.headers.set(ROUTE_TYPE_HEADER, 'page');
// Signal to the error-page-rerouting infra to let this response pass through to avoid loops
@@ -218,29 +222,25 @@ export class RenderContext {

const rewrite = async (reroutePayload: RewritePayload) => {
pipeline.logger.debug('router', 'Called rewriting to:', reroutePayload);
try {
const [routeData, component] = await pipeline.tryRewrite(reroutePayload, this.request);
this.routeData = routeData;
if (reroutePayload instanceof Request) {
this.request = reroutePayload;
} else {
this.request = this.#copyRequest(
new URL(routeData.pathname ?? routeData.route, this.url.origin),
this.request
);
}
this.url = new URL(this.request.url);
this.cookies = new AstroCookies(this.request);
this.params = getParams(routeData, url.toString());
this.isRewriting = true;
return await this.render(component);
} catch (e) {
pipeline.logger.debug('router', 'Rewrite failed.', e);
return new Response('Not found', {
status: 404,
statusText: 'Not found',
});
const [routeData, component] = await pipeline.tryRewrite(
reroutePayload,
this.request,
this.originalRoute
);
this.routeData = routeData;
if (reroutePayload instanceof Request) {
this.request = reroutePayload;
} else {
this.request = this.#copyRequest(
new URL(routeData.pathname ?? routeData.route, this.url.origin),
this.request
);
}
this.url = new URL(this.request.url);
this.cookies = new AstroCookies(this.request);
this.params = getParams(routeData, url.toString());
this.isRewriting = true;
return await this.render(component);
};

return {
@@ -409,30 +409,26 @@ export class RenderContext {
};

const rewrite = async (reroutePayload: RewritePayload) => {
try {
pipeline.logger.debug('router', 'Calling rewrite: ', reroutePayload);
const [routeData, component] = await pipeline.tryRewrite(reroutePayload, this.request);
this.routeData = routeData;
if (reroutePayload instanceof Request) {
this.request = reroutePayload;
} else {
this.request = this.#copyRequest(
new URL(routeData.pathname ?? routeData.route, this.url.origin),
this.request
);
}
this.url = new URL(this.request.url);
this.cookies = new AstroCookies(this.request);
this.params = getParams(routeData, url.toString());
this.isRewriting = true;
return await this.render(component);
} catch (e) {
pipeline.logger.debug('router', 'Rerouting failed, returning a 404.', e);
return new Response('Not found', {
status: 404,
statusText: 'Not found',
});
pipeline.logger.debug('router', 'Calling rewrite: ', reroutePayload);
const [routeData, component] = await pipeline.tryRewrite(
reroutePayload,
this.request,
this.originalRoute
);
this.routeData = routeData;
if (reroutePayload instanceof Request) {
this.request = reroutePayload;
} else {
this.request = this.#copyRequest(
new URL(routeData.pathname ?? routeData.route, this.url.origin),
this.request
);
}
this.url = new URL(this.request.url);
this.cookies = new AstroCookies(this.request);
this.params = getParams(routeData, url.toString());
this.isRewriting = true;
return await this.render(component);
};

return {
44 changes: 31 additions & 13 deletions packages/astro/src/core/routing/astro-designed-error-pages.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
import type { ManifestData } from '../../@types/astro.js';
import type { ManifestData, RouteData } from '../../@types/astro.js';
import notFoundTemplate from '../../template/4xx.js';
import { DEFAULT_404_COMPONENT } from '../constants.js';

export const DEFAULT_404_ROUTE: RouteData = {
component: DEFAULT_404_COMPONENT,
generate: () => '',
params: [],
pattern: /\/404/,
prerender: false,
pathname: '/404',
segments: [[{ content: '404', dynamic: false, spread: false }]],
type: 'page',
route: '/404',
fallbackRoutes: [],
isIndex: false,
};

export function ensure404Route(manifest: ManifestData) {
if (!manifest.routes.some((route) => route.route === '/404')) {
manifest.routes.push({
component: DEFAULT_404_COMPONENT,
generate: () => '',
params: [],
pattern: /\/404/,
prerender: false,
segments: [[{ content: '404', dynamic: false, spread: false }]],
type: 'page',
route: '/404',
fallbackRoutes: [],
isIndex: false,
});
manifest.routes.push(DEFAULT_404_ROUTE);
}
return manifest;
}

export async function default404Page({ pathname }: { pathname: string }) {
return new Response(
notFoundTemplate({
statusCode: 404,
title: 'Not found',
tabTitle: '404: Not Found',
pathname,
}),
{ status: 404, headers: { 'Content-Type': 'text/html; charset=utf-8' } }
);
}
// mark the function as an AstroComponentFactory for the rendering internals
default404Page.isAstroComponentFactory = true;
60 changes: 39 additions & 21 deletions packages/astro/src/vite-plugin-astro-server/pipeline.ts
Original file line number Diff line number Diff line change
@@ -11,20 +11,20 @@ import type {
SSRManifest,
} from '../@types/astro.js';
import { getInfoOutput } from '../cli/info/index.js';
import type { HeadElements } from '../core/base-pipeline.js';
import { type HeadElements } from '../core/base-pipeline.js';
import { ASTRO_VERSION, DEFAULT_404_COMPONENT } from '../core/constants.js';
import { enhanceViteSSRError } from '../core/errors/dev/index.js';
import { RouteNotFound } from '../core/errors/errors-data.js';
import { InvalidRewrite404, RewriteEncounteredAnError } from '../core/errors/errors-data.js';
import { AggregateError, AstroError, CSSError, MarkdownError } from '../core/errors/index.js';
import type { Logger } from '../core/logger/core.js';
import type { ModuleLoader } from '../core/module-loader/index.js';
import { Pipeline, loadRenderer } from '../core/render/index.js';
import { DEFAULT_404_ROUTE, default404Page } from '../core/routing/astro-designed-error-pages.js';
import { isPage, isServerLikeOutput, resolveIdToUrl, viteID } from '../core/util.js';
import { PAGE_SCRIPT_ID } from '../vite-plugin-scripts/index.js';
import { getStylesForURL } from './css.js';
import { getComponentMetadata } from './metadata.js';
import { createResolve } from './resolve.js';
import { default404Page } from './response.js';
import { getScriptsForURL } from './scripts.js';

export class DevPipeline extends Pipeline {
@@ -193,43 +193,61 @@ export class DevPipeline extends Pipeline {

async tryRewrite(
payload: RewritePayload,
request: Request
request: Request,
sourceRoute: RouteData
): Promise<[RouteData, ComponentInstance]> {
let foundRoute;
if (!this.manifestData) {
throw new Error('Missing manifest data. This is an internal error, please file an issue.');
}

for (const route of this.manifestData.routes) {
let finalUrl: URL | undefined = undefined;

if (payload instanceof URL) {
if (route.pattern.test(payload.pathname)) {
foundRoute = route;
break;
}
finalUrl = payload;
} else if (payload instanceof Request) {
const url = new URL(payload.url);
if (route.pattern.test(url.pathname)) {
foundRoute = route;
break;
}
finalUrl = new URL(payload.url);
} else {
const newUrl = new URL(payload, new URL(request.url).origin);
if (route.pattern.test(decodeURI(newUrl.pathname))) {
foundRoute = route;
break;
}
finalUrl = new URL(payload, new URL(request.url).origin);
}

if (route.pattern.test(decodeURI(finalUrl.pathname))) {
foundRoute = route;
break;
} else if (finalUrl.pathname === '/404') {
foundRoute = DEFAULT_404_ROUTE;
break;
}
}

if (foundRoute) {
const componentInstance = await this.getComponentByRoute(foundRoute);
return [foundRoute, componentInstance];
if (foundRoute.pathname === '/404') {
const componentInstance = this.rewriteKnownRoute(foundRoute.pathname, sourceRoute);
return [foundRoute, componentInstance];
} else {
const componentInstance = await this.getComponentByRoute(foundRoute);
return [foundRoute, componentInstance];
}
} else {
throw new AstroError(RouteNotFound);
throw new AstroError({
...RewriteEncounteredAnError,
message: RewriteEncounteredAnError.message(payload.toString()),
});
}
}

setManifestData(manifestData: ManifestData) {
this.manifestData = manifestData;
}

rewriteKnownRoute(route: string, sourceRoute: RouteData): ComponentInstance {
if (isServerLikeOutput(this.config) && sourceRoute.prerender) {
if (route === '/404') {
return { default: default404Page } as any as ComponentInstance;
}
}

throw new AstroError(InvalidRewrite404);
}
}
14 changes: 0 additions & 14 deletions packages/astro/src/vite-plugin-astro-server/response.ts
Original file line number Diff line number Diff line change
@@ -23,20 +23,6 @@ export async function handle404Response(
writeHtmlResponse(res, 404, html);
}

export async function default404Page({ pathname }: { pathname: string }) {
return new Response(
notFoundTemplate({
statusCode: 404,
title: 'Not found',
tabTitle: '404: Not Found',
pathname,
}),
{ status: 404, headers: { 'Content-Type': 'text/html; charset=utf-8' } }
);
}
// mark the function as an AstroComponentFactory for the rendering internals
default404Page.isAstroComponentFactory = true;

export async function handle500Response(
loader: ModuleLoader,
res: http.ServerResponse,
3 changes: 2 additions & 1 deletion packages/astro/src/vite-plugin-astro-server/route.ts
Original file line number Diff line number Diff line change
@@ -11,11 +11,12 @@ import { loadMiddleware } from '../core/middleware/loadMiddleware.js';
import { RenderContext } from '../core/render-context.js';
import { type SSROptions, getProps } from '../core/render/index.js';
import { createRequest } from '../core/request.js';
import { default404Page } from '../core/routing/astro-designed-error-pages.js';
import { matchAllRoutes } from '../core/routing/index.js';
import { normalizeTheLocale } from '../i18n/index.js';
import { getSortedPreloadedMatches } from '../prerender/routing.js';
import type { DevPipeline } from './pipeline.js';
import { default404Page, handle404Response, writeSSRResult, writeWebResponse } from './response.js';
import { handle404Response, writeSSRResult, writeWebResponse } from './response.js';

type AsyncReturnType<T extends (...args: any) => Promise<any>> = T extends (
...args: any
11 changes: 0 additions & 11 deletions packages/astro/test/fixtures/reroute/src/pages/blog/oops.astro

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from 'astro/config';

// https://astro.build/config
export default defineConfig({
experimental: {
rewriting: true
},
site: "https://example.com"
});
8 changes: 8 additions & 0 deletions packages/astro/test/fixtures/rewrite-404-invalid/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@test/rewrite-404-invalid",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
return Astro.rewrite("/404")
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from 'astro/config';

// https://astro.build/config
export default defineConfig({
experimental: {
rewriting: true
},
site: "https://example.com"
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@test/rewrite-runtime-errror",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
return Astro.rewrite('/errors/to');
---
<div></div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
throw new Error('Custom error')
---
<div></div>
49 changes: 43 additions & 6 deletions packages/astro/test/rewrite.test.js
Original file line number Diff line number Diff line change
@@ -55,11 +55,10 @@ describe('Dev reroute', () => {
assert.equal($('h1').text(), 'Index');
});

it('should render the 404 built-in page', async () => {
const html = await fixture.fetch('/blog/oops').then((res) => res.text());
const $ = cheerioLoad(html);
it('should return a 404', async () => {
const response = await fixture.fetch('/blog/oops');

assert.equal($('h1').text(), '404: Not found');
assert.equal(response.status, 404);
});
});

@@ -120,6 +119,21 @@ describe('Build reroute', () => {
});
});

describe('SSR route', () => {
it("should not build if a user tries to use rewrite('/404') in static pages", async () => {
try {
const fixture = await loadFixture({
root: './fixtures/rewrite-404-invalid/',
});
await fixture.build();
assert.fail('It should fail.');
} catch {
// it passes
assert.equal(true, true);
}
});
});

describe('SSR reroute', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
@@ -184,8 +198,7 @@ describe('SSR reroute', () => {
it('should render the 404 built-in page', async () => {
const request = new Request('http://example.com/blog/oops');
const response = await app.render(request);
const html = await response.text();
assert.equal(html, 'Not found');
assert.equal(response.status, 404);
});

it('should pass the POST data from one page to another', async () => {
@@ -246,3 +259,27 @@ describe('Middleware', () => {
assert.match($('h1').text(), /Index/);
});
});

describe('Runtime error', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
let devServer;

before(async () => {
fixture = await loadFixture({
root: './fixtures/rewrite-runtime-error/',
});
devServer = await fixture.startDevServer();
});

after(async () => {
await devServer.stop();
});

it('should render the index page when navigating /reroute ', async () => {
const html = await fixture.fetch('/errors/from').then((res) => res.text());
const $ = cheerioLoad(html);

assert.equal($('title').text(), 'Error');
});
});
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

0 comments on commit 35ef53c

Please sign in to comment.