cdnV2

git clone https://github.com/notamitgamer/cdnV2.git
· rebuilt hourlyOpen on GitHub
root / tests / prefetch.test.js

prefetch.test.js (6855B)


      1 // Run with: node --test tests/*.test.js
      2 const test = require('node:test');
      3 const assert = require('node:assert/strict');
      4 const { DEFAULTS, pathAllowed, candidateUrl, createPrefetcher } = require('../app/static/prefetch.js');
      5 
      6 const BASE = new URL('https://cdn.example.com/photos/');
      7 const link = (href, extra = {}) => ({ href, ...extra });
      8 const pick = (href, extra) => candidateUrl(link(href, extra), BASE);
      9 
     10 test('allows folders, files, and top-level pages', () => {
     11   assert.equal(pick('/uploads/'), 'https://cdn.example.com/uploads/');
     12   assert.equal(pick('/uploads/report.pdf'), 'https://cdn.example.com/uploads/report.pdf');
     13   assert.equal(pick('/find'), 'https://cdn.example.com/find');
     14   assert.equal(pick('/documentation'), 'https://cdn.example.com/documentation');
     15   assert.equal(pick('sub/dir'), 'https://cdn.example.com/photos/sub/dir'); // relative
     16 });
     17 
     18 test('never prefetches the app’s own endpoints, downloads or redirects', () => {
     19   for (const p of ['/api/get/uploads/big.zip', '/api/pack/uploads', '/admin', '/admin/x',
     20                    '/mask/abc123', '/pack/batch1', '/static/sw.js', '/raw/uploads/a.png',
     21                    '/favicon.ico']) {
     22     assert.equal(pick(p), null, p);
     23   }
     24 });
     25 
     26 test('reserved names only match whole path segments', () => {
     27   assert.equal(pathAllowed('/administrator/'), true);
     28   assert.equal(pathAllowed('/apikeys/'), true);
     29   assert.equal(pathAllowed('/api'), false);
     30   assert.equal(pathAllowed('/'), true);
     31 });
     32 
     33 test('skips other origins and non-http schemes', () => {
     34   assert.equal(pick('https://raw.cdn.example.com/uploads/a.png'), null);
     35   assert.equal(pick('https://example.org/'), null);
     36   assert.equal(pick('mailto:a@b.c'), null);
     37   assert.equal(pick('javascript:void(0)'), null);
     38 });
     39 
     40 test('respects opt-outs on the link', () => {
     41   assert.equal(pick('/uploads/', { download: true }), null);
     42   assert.equal(pick('/uploads/', { target: '_blank' }), null);
     43   assert.equal(pick('/uploads/', { target: '_self' }), 'https://cdn.example.com/uploads/');
     44   assert.equal(pick('/uploads/', { turboOff: true }), null);
     45   assert.equal(pick('/uploads/', { noPrefetch: true }), null);
     46 });
     47 
     48 test('skips the current page, strips hashes', () => {
     49   assert.equal(pick('/photos/'), null);
     50   assert.equal(pick('/photos/#top'), null);
     51   assert.equal(pick('/uploads/#x'), 'https://cdn.example.com/uploads/');
     52   assert.equal(pick('/photos/?q=1'), 'https://cdn.example.com/photos/?q=1');
     53 });
     54 
     55 // --- queue ---------------------------------------------------------------------------------------
     56 
     57 // A fetch whose responses the test finishes by hand, so we can observe concurrency.
     58 function manualFetch() {
     59   const calls = [];
     60   const fn = (url, signal) =>
     61     new Promise((resolve, reject) => {
     62       const call = { url, signal, resolve, reject, aborted: false };
     63       signal.addEventListener('abort', () => { call.aborted = true; reject(new Error('aborted')); });
     64       calls.push(call);
     65     });
     66   return { fn, calls };
     67 }
     68 const tick = () => new Promise((r) => setImmediate(r));
     69 const urls = (n) => Array.from({ length: n }, (_, i) => `https://cdn.example.com/f${i}/`);
     70 
     71 test('runs at most `concurrency` fetches at once and drains the queue', async () => {
     72   const { fn, calls } = manualFetch();
     73   const pf = createPrefetcher({ fetchFn: fn, config: { concurrency: 2, maxLinks: 10 } });
     74   pf.enqueue(urls(5));
     75   await tick();
     76   assert.equal(calls.length, 2);
     77   calls[0].resolve();
     78   await tick();
     79   assert.equal(calls.length, 3);
     80   calls.forEach((c) => c.resolve());
     81   await tick(); await tick();
     82   assert.equal(calls.length, 5);
     83 });
     84 
     85 test('stops accepting links after maxLinks per page view, and newPage resets the budget', async () => {
     86   const { fn } = manualFetch();
     87   const pf = createPrefetcher({ fetchFn: fn, config: { maxLinks: 3 } });
     88   assert.equal(pf.enqueue(urls(10)), 3);
     89   assert.equal(pf.remaining(), 0);
     90   assert.equal(pf.enqueue(urls(10)), 0);
     91   pf.newPage();
     92   assert.equal(pf.remaining(), 3);
     93 });
     94 
     95 test('does not refetch inside the TTL, does after it', async () => {
     96   let t = 1000;
     97   const { fn, calls } = manualFetch();
     98   const pf = createPrefetcher({ fetchFn: fn, now: () => t, config: { ttlMs: 50000, maxLinks: 10 } });
     99   pf.enqueue(urls(1));
    100   await tick();
    101   calls[0].resolve();
    102   await tick();
    103   pf.newPage();
    104   assert.equal(pf.enqueue(urls(1)), 0);   // still fresh
    105   t += 60000;
    106   assert.equal(pf.enqueue(urls(1)), 1);   // expired
    107 });
    108 
    109 test('does not queue a URL that is already in flight', async () => {
    110   const { fn } = manualFetch();
    111   const pf = createPrefetcher({ fetchFn: fn, config: { maxLinks: 10 } });
    112   pf.enqueue(urls(1));
    113   await tick();
    114   pf.newPage();
    115   assert.equal(pf.enqueue(urls(1)), 0);
    116 });
    117 
    118 test('abort cancels in-flight requests and clears the queue', async () => {
    119   const { fn, calls } = manualFetch();
    120   const pf = createPrefetcher({ fetchFn: fn, config: { concurrency: 1, maxLinks: 10 } });
    121   pf.enqueue(urls(4));
    122   await tick();
    123   pf.abort();
    124   await tick();
    125   assert.equal(calls[0].aborted, true);
    126   assert.equal(calls.length, 1);          // nothing else started
    127   assert.equal(pf.stats().queued, 0);
    128   // an aborted fetch is not remembered as done, so it can be tried again later
    129   pf.newPage();
    130   assert.equal(pf.enqueue(urls(1)), 1);
    131 });
    132 
    133 test('a failing fetch does not stall the queue and is not retried in a loop', async () => {
    134   const { fn, calls } = manualFetch();
    135   const pf = createPrefetcher({ fetchFn: fn, config: { concurrency: 1, maxLinks: 10 } });
    136   pf.enqueue(urls(2));
    137   await tick();
    138   calls[0].reject(new Error('network'));
    139   await tick(); await tick();
    140   assert.equal(calls.length, 2);          // moved on to the next one
    141   pf.newPage();
    142   assert.equal(pf.enqueue(urls(1)), 0);   // failed URL remembered for the TTL
    143 });
    144 
    145 test('paused queue holds work until resumed', async () => {
    146   const { fn, calls } = manualFetch();
    147   const pf = createPrefetcher({ fetchFn: fn, config: { concurrency: 2, maxLinks: 10 } });
    148   pf.setPaused(true);
    149   pf.enqueue(urls(3));
    150   await tick();
    151   assert.equal(calls.length, 0);
    152   pf.setPaused(false);
    153   await tick();
    154   assert.equal(calls.length, 2);
    155 });
    156 
    157 test('abort(keepUrl) leaves the page being opened running and cancels the rest', async () => {
    158   const { fn, calls } = manualFetch();
    159   const pf = createPrefetcher({ fetchFn: fn, config: { concurrency: 3, maxLinks: 10 } });
    160   const [a, b, c] = urls(3);
    161   pf.enqueue([a, b, c, ...urls(6).slice(3)]);
    162   await tick();
    163   pf.abort(b);
    164   await tick();
    165   assert.deepEqual(calls.map((x) => x.aborted), [true, false, true]);
    166   assert.equal(pf.stats().queued, 0);
    167   calls[1].resolve();           // the kept request finishes normally and is remembered
    168   await tick(); await tick();
    169   pf.newPage();
    170   assert.equal(pf.enqueue([b]), 0);
    171 });
    172 
    173 test('prefetch TTL is shorter than the pages\' browser-cache lifetime (30 s)', () => {
    174   assert.ok(DEFAULTS.ttlMs < 30000);
    175 });