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 test
pull/9526/head
Hariom Balhara 2023-06-21 20:36:44 +05:30 committed by GitHub
parent 6bdb2b94eb
commit 820b2da16d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 187 additions and 21 deletions

View File

@ -1,10 +1,10 @@
require("dotenv").config({ path: "../../.env" });
const CopyWebpackPlugin = require("copy-webpack-plugin");
const os = require("os");
const glob = require("glob");
const englishTranslation = require("./public/static/locales/en/common.json");
const { withAxiom } = require("next-axiom");
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.CALENDSO_ENCRYPTION_KEY) throw new Error("Please set CALENDSO_ENCRYPTION_KEY");
@ -82,16 +82,19 @@ if (process.env.ANALYZE === "true") {
plugins.push(withAxiom);
/** 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]"));
// .* 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.
// book$ ensures that only /book is excluded from rewrite(which is at the end always) and not /booked
// Important Note: When modifying these RegExps update apps/web/test/lib/next-config.test.ts as well
const userTypeRouteRegExp = `/:user((?!${pages.join("/|")})[^/]*)/:type((?!book$)[^/]+)`;
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} */
const nextConfig = {
@ -183,14 +186,6 @@ const nextConfig = {
return config;
},
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 = [
{
source: "/org/:slug",
@ -248,12 +243,12 @@ const nextConfig = {
// 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",
has: [{ type: "cookie", key: "new-booker-enabled" }],
},
{
source: "/team/:slug/:type/embed",
source: embedTeamTypeRouteRegExp,
destination: "/new-booker/team/:slug/:type/embed",
has: [{ type: "cookie", key: "new-booker-enabled" }],
},

14
apps/web/pages.js Normal file
View File

@ -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;

View File

@ -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)
})
})