test: Add RegExp tests for Next.config.js rewrites (#9681)
* Add RegExp tests * Update apps/web/test/lib/next-config.test.ts * Dont avoid rewrite for /booked event slug * Add /embed/embed.js test * Add dynamic group booking link testpull/9526/head
parent
6bdb2b94eb
commit
820b2da16d
|
@ -1,10 +1,10 @@
|
||||||
require("dotenv").config({ path: "../../.env" });
|
require("dotenv").config({ path: "../../.env" });
|
||||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
const os = require("os");
|
const os = require("os");
|
||||||
const glob = require("glob");
|
|
||||||
const englishTranslation = require("./public/static/locales/en/common.json");
|
const englishTranslation = require("./public/static/locales/en/common.json");
|
||||||
const { withAxiom } = require("next-axiom");
|
const { withAxiom } = require("next-axiom");
|
||||||
const { i18n } = require("./next-i18next.config");
|
const { i18n } = require("./next-i18next.config");
|
||||||
|
const { pages } = require("./pages");
|
||||||
|
|
||||||
if (!process.env.NEXTAUTH_SECRET) throw new Error("Please set NEXTAUTH_SECRET");
|
if (!process.env.NEXTAUTH_SECRET) throw new Error("Please set NEXTAUTH_SECRET");
|
||||||
if (!process.env.CALENDSO_ENCRYPTION_KEY) throw new Error("Please set CALENDSO_ENCRYPTION_KEY");
|
if (!process.env.CALENDSO_ENCRYPTION_KEY) throw new Error("Please set CALENDSO_ENCRYPTION_KEY");
|
||||||
|
@ -82,16 +82,19 @@ if (process.env.ANALYZE === "true") {
|
||||||
|
|
||||||
plugins.push(withAxiom);
|
plugins.push(withAxiom);
|
||||||
|
|
||||||
/** Needed to rewrite public booking page, gets all static pages but [user] */
|
// .* matches / as well(Note: *(i.e wildcard) doesn't match / but .*(i.e. RegExp) does)
|
||||||
const pages = glob
|
// It would match /free/30min but not /bookings/upcoming because 'bookings' is an item in pages
|
||||||
.sync("pages/**/[^_]*.{tsx,js,ts}", { cwd: __dirname })
|
// It would also not match /free/30min/embed because we are ensuring just two slashes
|
||||||
.map((filename) =>
|
// ?!book ensures it doesn't match /free/book page which doesn't have a corresponding new-booker page.
|
||||||
filename
|
// [^/]+ makes the RegExp match the full path, it seems like a partial match doesn't work.
|
||||||
.substr(6)
|
// book$ ensures that only /book is excluded from rewrite(which is at the end always) and not /booked
|
||||||
.replace(/(\.tsx|\.js|\.ts)/, "")
|
|
||||||
.replace(/\/.*/, "")
|
// Important Note: When modifying these RegExps update apps/web/test/lib/next-config.test.ts as well
|
||||||
)
|
const userTypeRouteRegExp = `/:user((?!${pages.join("/|")})[^/]*)/:type((?!book$)[^/]+)`;
|
||||||
.filter((v, i, self) => self.indexOf(v) === i && !v.startsWith("[user]"));
|
const teamTypeRouteRegExp = "/team/:slug/:type((?!book$)[^/]+)";
|
||||||
|
const privateLinkRouteRegExp = "/d/:link/:slug((?!book$)[^/]+)";
|
||||||
|
const embedUserTypeRouteRegExp = `/:user((?!${pages.join("|")}).*)/:type/embed`;
|
||||||
|
const embedTeamTypeRouteRegExp = "/team/:slug/:type/embed";
|
||||||
|
|
||||||
/** @type {import("next").NextConfig} */
|
/** @type {import("next").NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
|
@ -183,14 +186,6 @@ const nextConfig = {
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
// .* matches / as well(Note: *(i.e wildcard) doesn't match / but .*(i.e. RegExp) does)
|
|
||||||
// It would match /free/30min but not /bookings/upcoming because 'bookings' is an item in pages
|
|
||||||
// It would also not match /free/30min/embed because we are ensuring just two slashes
|
|
||||||
// ?!book ensures it doesn't match /free/book page which doesn't have a corresponding new-booker page.
|
|
||||||
// [^/]+ makes the RegExp match the full path, it seems like a partial match doesn't work.
|
|
||||||
const userTypeRouteRegExp = `/:user((?!${pages.join("/|")})[^/]*)/:type((?!book)[^/]+)`;
|
|
||||||
const teamTypeRouteRegExp = "/team/:slug/:type((?!book)[^/]+)";
|
|
||||||
const privateLinkRouteRegExp = "/d/:link/:slug((?!book)[^/]+)";
|
|
||||||
let rewrites = [
|
let rewrites = [
|
||||||
{
|
{
|
||||||
source: "/org/:slug",
|
source: "/org/:slug",
|
||||||
|
@ -248,12 +243,12 @@ const nextConfig = {
|
||||||
// Keep cookie based booker enabled to test new-booker embed in production
|
// Keep cookie based booker enabled to test new-booker embed in production
|
||||||
...[
|
...[
|
||||||
{
|
{
|
||||||
source: `/:user((?!${pages.join("|")}).*)/:type/embed`,
|
source: embedUserTypeRouteRegExp,
|
||||||
destination: "/new-booker/:user/:type/embed",
|
destination: "/new-booker/:user/:type/embed",
|
||||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: "/team/:slug/:type/embed",
|
source: embedTeamTypeRouteRegExp,
|
||||||
destination: "/new-booker/team/:slug/:type/embed",
|
destination: "/new-booker/team/:slug/:type/embed",
|
||||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
const glob = require("glob");
|
||||||
|
|
||||||
|
/** Needed to rewrite public booking page, gets all static pages but [user] */
|
||||||
|
const pages = glob
|
||||||
|
.sync("pages/**/[^_]*.{tsx,js,ts}", { cwd: __dirname })
|
||||||
|
.map((filename) =>
|
||||||
|
filename
|
||||||
|
.substr(6)
|
||||||
|
.replace(/(\.tsx|\.js|\.ts)/, "")
|
||||||
|
.replace(/\/.*/, "")
|
||||||
|
)
|
||||||
|
.filter((v, i, self) => self.indexOf(v) === i && !v.startsWith("[user]"));
|
||||||
|
|
||||||
|
exports.pages = pages;
|
|
@ -0,0 +1,157 @@
|
||||||
|
|
||||||
|
import { it, expect, describe, beforeAll, afterAll } from "vitest";
|
||||||
|
let userTypeRouteRegExp: RegExp;
|
||||||
|
let teamTypeRouteRegExp:RegExp;
|
||||||
|
let privateLinkRouteRegExp:RegExp
|
||||||
|
let embedUserTypeRouteRegExp:RegExp
|
||||||
|
let embedTeamTypeRouteRegExp:RegExp
|
||||||
|
|
||||||
|
|
||||||
|
const getRegExpFromNextJsRewriteRegExp = (nextJsRegExp:string) => {
|
||||||
|
// const parts = nextJsRegExp.split(':');
|
||||||
|
|
||||||
|
// const validNamedGroupRegExp = parts.map((part, index)=>{
|
||||||
|
// if (index === 0) {
|
||||||
|
// return part;
|
||||||
|
// }
|
||||||
|
// if (part.match(/^[a-zA-Z0-9]+$/)) {
|
||||||
|
// return `(?<${part}>[^/]+)`
|
||||||
|
// }
|
||||||
|
// part = part.replace(new RegExp('([^(]+)(.*)'), '(?<$1>$2)');
|
||||||
|
// return part
|
||||||
|
// }).join('');
|
||||||
|
|
||||||
|
// TODO: If we can easily convert the exported rewrite regexes from next.config.js to a valid named capturing group regex, it would be best
|
||||||
|
// Next.js does an exact match as per my testing.
|
||||||
|
return new RegExp(`^${nextJsRegExp}$`)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('next.config.js - RegExp', ()=>{
|
||||||
|
beforeAll(async()=>{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
// process.env.NEXTAUTH_SECRET = process.env.NEXTAUTH_URL = process.env.CALENDSO_ENCRYPTION_KEY = 1
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const pages = require("../../pages").pages
|
||||||
|
|
||||||
|
// How to convert a Next.js rewrite RegExp/wildcard to a valid JS named capturing Group RegExp?
|
||||||
|
// - /:user/ -> (?<user>[^/]+)
|
||||||
|
// - /:user(?!404)[^/]+/ -> (?<user>((?!404)[^/]+))
|
||||||
|
|
||||||
|
// userTypeRouteRegExp = `/:user((?!${pages.join("/|")})[^/]*)/:type((?!book$)[^/]+)`;
|
||||||
|
userTypeRouteRegExp = getRegExpFromNextJsRewriteRegExp(`/(?<user>((?!${pages.join("/|")})[^/]*))/(?<type>((?!book$)[^/]+))`);
|
||||||
|
|
||||||
|
// teamTypeRouteRegExp = "/team/:slug/:type((?!book$)[^/]+)";
|
||||||
|
teamTypeRouteRegExp = getRegExpFromNextJsRewriteRegExp("/team/(?<slug>[^/]+)/(?<type>((?!book$)[^/]+))");
|
||||||
|
|
||||||
|
// privateLinkRouteRegExp = "/d/:link/:slug((?!book$)[^/]+)";
|
||||||
|
privateLinkRouteRegExp = getRegExpFromNextJsRewriteRegExp("/d/(?<link>[^/]+)/(?<slug>((?!book$)[^/]+))");
|
||||||
|
|
||||||
|
// embedUserTypeRouteRegExp = `/:user((?!${pages.join("|")}).*)/:type/embed`;
|
||||||
|
embedUserTypeRouteRegExp = getRegExpFromNextJsRewriteRegExp(`/(?<user>((?!${pages.join("|")}).*))/(?<type>[^/]+)/embed`);
|
||||||
|
|
||||||
|
// embedTeamTypeRouteRegExp = "/team/:slug/:type/embed";
|
||||||
|
embedTeamTypeRouteRegExp = getRegExpFromNextJsRewriteRegExp("/team/(?<slug>[^/]+)/(?<type>[^/]+)/embed");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(()=>{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
//@ts-ignore
|
||||||
|
process.env.NEXTAUTH_SECRET = process.env.NEXTAUTH_URL = process.env.CALENDSO_ENCRYPTION_KEY = undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Booking Urls", async () => {
|
||||||
|
expect(userTypeRouteRegExp.exec('/free/30')?.groups).toContain({
|
||||||
|
user: 'free',
|
||||||
|
type: '30'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Edgecase of username starting with team also works
|
||||||
|
expect(userTypeRouteRegExp.exec('/teampro/30')?.groups).toContain({
|
||||||
|
user: 'teampro',
|
||||||
|
type: '30'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/teampro+pro/30')?.groups).toContain({
|
||||||
|
user: 'teampro+pro',
|
||||||
|
type: '30'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/teampro+pro/book')).toEqual(null)
|
||||||
|
|
||||||
|
// Because /book doesn't have a corresponding new-booker route.
|
||||||
|
expect(userTypeRouteRegExp.exec('/free/book')).toEqual(null)
|
||||||
|
|
||||||
|
// Because /booked is a normal event name
|
||||||
|
expect(userTypeRouteRegExp.exec('/free/booked')?.groups).toEqual({
|
||||||
|
user: 'free',
|
||||||
|
type: 'booked'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
expect(embedUserTypeRouteRegExp.exec('/free/30/embed')?.groups).toContain({
|
||||||
|
user: 'free',
|
||||||
|
type:'30'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(teamTypeRouteRegExp.exec('/team/seeded/30')?.groups).toContain({
|
||||||
|
slug: 'seeded',
|
||||||
|
type: '30'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Because /book doesn't have a corresponding new-booker route.
|
||||||
|
expect(teamTypeRouteRegExp.exec('/team/seeded/book')).toEqual(null)
|
||||||
|
|
||||||
|
expect(teamTypeRouteRegExp.exec('/team/seeded/30/embed')).toEqual(null)
|
||||||
|
|
||||||
|
expect(embedTeamTypeRouteRegExp.exec('/team/seeded/30/embed')?.groups).toContain({
|
||||||
|
slug: 'seeded',
|
||||||
|
type:'30'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(privateLinkRouteRegExp.exec('/d/3v4s321CXRJZx5TFxkpPvd/30min')?.groups).toContain({
|
||||||
|
link: '3v4s321CXRJZx5TFxkpPvd',
|
||||||
|
slug: '30min'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(privateLinkRouteRegExp.exec('/d/3v4s321CXRJZx5TFxkpPvd/30min')?.groups).toContain({
|
||||||
|
link: '3v4s321CXRJZx5TFxkpPvd',
|
||||||
|
slug: '30min'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Because /book doesn't have a corresponding new-booker route.
|
||||||
|
expect(privateLinkRouteRegExp.exec('/d/3v4s321CXRJZx5TFxkpPvd/book')).toEqual(null)
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Non booking Urls', ()=>{
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/404')).toEqual(null)
|
||||||
|
expect(teamTypeRouteRegExp.exec('/404')).toEqual(null)
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/404/30')).toEqual(null)
|
||||||
|
expect(teamTypeRouteRegExp.exec('/404/30')).toEqual(null)
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/api')).toEqual(null)
|
||||||
|
expect(teamTypeRouteRegExp.exec('/api')).toEqual(null)
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/api/30')).toEqual(null)
|
||||||
|
expect(teamTypeRouteRegExp.exec('/api/30')).toEqual(null)
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/workflows/30')).toEqual(null)
|
||||||
|
expect(teamTypeRouteRegExp.exec('/workflows/30')).toEqual(null)
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/event-types/30')).toEqual(null)
|
||||||
|
expect(teamTypeRouteRegExp.exec('/event-types/30')).toEqual(null)
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/teams/1')).toEqual(null)
|
||||||
|
expect(teamTypeRouteRegExp.exec('/teams/1')).toEqual(null)
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/teams')).toEqual(null)
|
||||||
|
expect(teamTypeRouteRegExp.exec('/teams')).toEqual(null)
|
||||||
|
|
||||||
|
// Note that even though it matches /embed/embed.js, but it's served from /public and the regexes are in afterEach, it won't hit the flow.
|
||||||
|
// expect(userTypeRouteRegExp.exec('/embed/embed.js')).toEqual(null)
|
||||||
|
// expect(teamTypeRouteRegExp.exec('/embed/embed.js')).toEqual(null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
Loading…
Reference in New Issue