|
| 1 | +/*--------------------------------------------------------------------------------------------- |
| 2 | + * web/lib/pages.mjs — run: node web/test/unit/pages.test.js |
| 3 | + * |
| 4 | + * How Cloudflare Pages answers for a release, as web/serve.mjs --pages answers: what wrangler uploads, the _redirects |
| 5 | + * rules, the .html redirects and the page for anything else. And the release's own answer to the one that broke the |
| 6 | + * terminal: every file of the client's node_modules, which Pages is never sent, is served from the copy it is sent. |
| 7 | + *--------------------------------------------------------------------------------------------*/ |
| 8 | +// @ts-check |
| 9 | +'use strict'; |
| 10 | + |
| 11 | +const assert = require('assert'); |
| 12 | +const path = require('path'); |
| 13 | +const { pathToFileURL } = require('url'); |
| 14 | + |
| 15 | +let n = 0; |
| 16 | +async function test(name, fn) { await fn(); n++; console.log(' ok - ' + name); } |
| 17 | + |
| 18 | +(async () => { |
| 19 | + const { uploaded, parseRedirects, matchRedirect, resolvePage, toRedirectsFile, NODE_MODULES_COPY } = |
| 20 | + await import(pathToFileURL(path.join(__dirname, '..', '..', 'lib', 'pages.mjs')).href); |
| 21 | + |
| 22 | + await test('what wrangler uploads: nothing in a folder named node_modules or .git, anywhere, and not its own files at the root', async () => { |
| 23 | + for (const rel of ['_/abc/static/node_modules/@xterm/xterm/lib/xterm.js', 'node_modules/x.js', '_/abc/extensions/x/node_modules/y.js', |
| 24 | + '_/abc/.git/HEAD', '_/abc/static/.DS_Store', '_headers', '_redirects', '_worker.js', '_routes.json', 'functions/api.js']) { |
| 25 | + assert.strictEqual(uploaded(rel), false, rel); |
| 26 | + } |
| 27 | + for (const rel of ['index.html', 'callback.html', '_/abc/static/node-modules/@xterm/xterm/lib/xterm.js', '_/abc/static/out/nls.messages.js', |
| 28 | + '_/abc/extensions/levelcode-ai/package.json', '_/abc/functions/x.js', '_/abc/static/node_modules_x/y.js']) { |
| 29 | + assert.strictEqual(uploaded(rel), true, rel); |
| 30 | + } |
| 31 | + }); |
| 32 | + |
| 33 | + await test('_redirects: rules in order, comments and blank lines skipped, 302 when no status is given', async () => { |
| 34 | + const rules = parseRedirects('# a comment\n\n/old /new 301\n/a/:x/* /b/:x/:splat 200\n/plain /there\n'); |
| 35 | + assert.deepStrictEqual(rules, [{ from: '/old', to: '/new', status: 301 }, { from: '/a/:x/*', to: '/b/:x/:splat', status: 200 }, { from: '/plain', to: '/there', status: 302 }]); |
| 36 | + assert.deepStrictEqual(matchRedirect(rules, '/a/one/two/three.js'), { to: '/b/one/two/three.js', status: 200 }); |
| 37 | + assert.strictEqual(matchRedirect(rules, '/a/one'), null, 'a splat needs the slash before it'); |
| 38 | + assert.deepStrictEqual(matchRedirect(parseRedirects('/x /first 200\n/x /second 200\n'), '/x'), { to: '/first', status: 200 }, 'the first match wins'); |
| 39 | + }); |
| 40 | + |
| 41 | + await test('the release\'s rule serves every node_modules file from the copy that was uploaded, in this build and in a kept one', async () => { |
| 42 | + const rules = parseRedirects(toRedirectsFile()); |
| 43 | + for (const id of ['5bfb4df6caa1', 'ae34c91f6d37']) { |
| 44 | + for (const file of ['@xterm/xterm/lib/xterm.js', 'vscode-textmate/release/main.js', 'vscode-oniguruma/release/onig.wasm', '@vscode/codicons/dist/codicon.ttf']) { |
| 45 | + const asked = `/_/${id}/static/node_modules/${file}`; |
| 46 | + const r = matchRedirect(rules, asked); |
| 47 | + assert.ok(r && r.status === 200, asked + ' is rewritten, not redirected'); |
| 48 | + assert.strictEqual(r.to, `/_/${id}/static/${NODE_MODULES_COPY}/${file}`); |
| 49 | + assert.ok(uploaded(r.to.slice(1)), r.to + ' is a path Pages is sent'); |
| 50 | + } |
| 51 | + } |
| 52 | + assert.strictEqual(matchRedirect(rules, '/_/abc/static/out/vs/workbench/workbench.web.main.internal.js'), null, 'nothing else is rewritten'); |
| 53 | + assert.strictEqual(matchRedirect(rules, '/_/abc/def/static/node_modules/x.js'), null, 'a build id is one segment'); |
| 54 | + }); |
| 55 | + |
| 56 | + await test('.html addresses: redirected without it; the address without it serves the page', async () => { |
| 57 | + const files = new Set(['index.html', 'callback.html', 'callback.js', '_/abc/static/out/x/frame.html', '_/abc/static/out/x/index.html']); |
| 58 | + const isFile = (rel) => files.has(rel); |
| 59 | + assert.deepStrictEqual(resolvePage('/', isFile), { file: 'index.html' }); |
| 60 | + assert.deepStrictEqual(resolvePage('/index.html', isFile), { redirect: '/' }); |
| 61 | + assert.deepStrictEqual(resolvePage('/callback.html', isFile), { redirect: '/callback' }); |
| 62 | + assert.deepStrictEqual(resolvePage('/callback', isFile), { file: 'callback.html' }); |
| 63 | + assert.deepStrictEqual(resolvePage('/callback.js', isFile), { file: 'callback.js' }); |
| 64 | + assert.deepStrictEqual(resolvePage('/_/abc/static/out/x/frame.html', isFile), { redirect: '/_/abc/static/out/x/frame' }); |
| 65 | + assert.deepStrictEqual(resolvePage('/_/abc/static/out/x/frame', isFile), { file: '_/abc/static/out/x/frame.html' }); |
| 66 | + assert.deepStrictEqual(resolvePage('/_/abc/static/out/x/index.html', isFile), { redirect: '/_/abc/static/out/x/' }); |
| 67 | + assert.deepStrictEqual(resolvePage('/_/abc/static/out/x/', isFile), { file: '_/abc/static/out/x/index.html' }); |
| 68 | + assert.strictEqual(resolvePage('/missing.js', isFile), null, 'no such file: the caller answers with the page'); |
| 69 | + }); |
| 70 | + |
| 71 | + console.log(`\n${n} tests passed`); |
| 72 | +})().catch((e) => { console.error(e); process.exit(1); }); |
0 commit comments