import { parse } from '@babel/parser' import { FileType, ProjectPluginStructure, UIDLPluginSettingValue, } from '@teleporthq/teleport-types' import { NextAccessRulesProjectPlugin } from '../src/access-rules/project-plugin' import { resolveAccessRulesSettings } from '../src/access-rules/settings' const APP_CONTENT = `import { GlobalProvider } from '../global-context' const MyApp = ({ Component, pageProps }) => { return ( ) } export default MyApp ` const AUTH_MIDDLEWARE = `import { NextResponse } from 'next/server'; async function middleware(request) { const pathname = request.nextUrl.pathname; return NextResponse.next(); } export default middleware; export const config = { matcher: ['/', '/((?!api/|_next/static|_next/image|favicon\\\\.ico).*)'], }; ` function makeStructure( settings: Record | null, withAuthMiddleware = false, record: { data?: Record; pages?: Record } = {} ): ProjectPluginStructure { const files = new Map() files.set('_app', { path: ['pages'], files: [{ name: '_app', fileType: FileType.JS, content: APP_CONTENT }], }) if (withAuthMiddleware) { files.set('auth-middleware', { path: [], files: [{ name: 'middleware', fileType: FileType.JS, content: AUTH_MIDDLEWARE }], }) } return { uidl: { name: 'test-project', globals: { settings: { title: 'Test', language: 'en' }, assets: [] }, root: {} as never, ...(settings ? { plugins: { accessRules: { settings, ...record } } } : {}), }, files, dependencies: {}, devDependencies: {}, template: { files: [], subFolders: [] }, } as unknown as ProjectPluginStructure } const appSource = (structure: ProjectPluginStructure): string => structure.files.get('_app').files[0].content as string const parses = (source: string) => parse(source, { sourceType: 'module', plugins: ['jsx'] }) describe('NextAccessRulesProjectPlugin', () => { it('leaves a project without the plugin exactly as it was', async () => { const structure = makeStructure(null) await new NextAccessRulesProjectPlugin().runAfter(structure) expect(structure.files.get('access-rules-middleware')).toBeUndefined() expect(appSource(structure)).toBe(APP_CONTENT) }) it('writes the country rule as a middleware of its own, with a plain notice for a project without the Unavailable page', async () => { const structure = makeStructure({ ageGateEnabled: false, geoMode: 'block', geoCountries: ['ru', 'BY', 'xyz'], geoUnknownCountry: 'block', }) await new NextAccessRulesProjectPlugin().runAfter(structure) expect(appSource(structure)).toBe(APP_CONTENT) const middleware = structure.files.get('access-rules-middleware') expect(middleware.path).toEqual([]) expect(middleware.files[0].name).toBe('middleware') const source = middleware.files[0].content as string expect(source).toContain('"countries":["RU","BY"]') expect(source).toContain('"unknown":"block"') expect(source).toContain('status: 451') expect(source).toContain('This site is not available in your country.') expect(source).not.toContain('redirect') expect(source).toContain( "matcher: ['/', '/((?!api/|_next/static|_next/image|favicon\\\\.ico).*)']" ) expect(() => parses(source)).not.toThrow() }) it('wraps the authentication middleware instead of writing a second one', async () => { const structure = makeStructure( { ageGateEnabled: false, geoMode: 'allow', geoCountries: ['US'] }, true ) await new NextAccessRulesProjectPlugin().runAfter(structure) expect(structure.files.get('access-rules-middleware')).toBeUndefined() const source = structure.files.get('auth-middleware').files[0].content as string expect(source).toContain('"mode":"allow"') expect(source).toContain('export default __tqWithGeo(middleware);') expect(source).not.toContain('export default middleware;') expect(source).toContain('return __tqGeoResponse(request) || next(request);') // Nothing the auth file had is lost. expect(source).toContain('const pathname = request.nextUrl.pathname;') expect(source).toContain( "matcher: ['/', '/((?!api/|_next/static|_next/image|favicon\\\\.ico).*)']" ) expect(() => parses(source)).not.toThrow() // Idempotent: a second run changes nothing. await new NextAccessRulesProjectPlugin().runAfter(structure) expect(structure.files.get('auth-middleware').files[0].content).toBe(source) }) it('writes no middleware for an empty country list, and no gate for a gate scoped to no page', async () => { const empty = makeStructure( { ageGateEnabled: true, ageGateScope: 'pages', geoMode: 'block', geoCountries: [] }, false, { data: { gate: 'component', gatedPaths: [] } } ) const plugin = new NextAccessRulesProjectPlugin() await plugin.runBefore(empty) await plugin.runAfter(empty) // A gate scoped to no page shows nowhere, and a rule with no country does nothing. expect(empty.files.get('access-rules-middleware')).toBeUndefined() expect((empty.uidl.globals as { customCode?: { head?: string } }).customCode).toBeUndefined() }) }) describe('NextAccessRulesProjectPlugin with the editor’s Age Gate component', () => { it('puts the gate’s head script first in the head and writes no gate of its own', async () => { const structure = makeStructure( { ageGateEnabled: true, minimumAge: 21, ageGateScope: 'pages', allowCrawlers: false, rememberDays: 7, }, false, { data: { gate: 'component', gatedPaths: ['/Shop/', '/fr/shop', 'nonsense'], unavailablePaths: ['/unavailable', '/fr/unavailable'], }, } ) ;(structure.uidl.globals as { customCode?: { head?: string } }).customCode = { head: '', } const plugin = new NextAccessRulesProjectPlugin() await plugin.runBefore(structure) await plugin.runAfter(structure) const head = (structure.uidl.globals as { customCode?: { head?: string } }).customCode?.head ?? '' expect( head.startsWith( '