<|start_filename|>test/test-utils/sw-installer.js<|end_filename|>
/**
 * Copyright 2019 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

export async function performCleanupAndWaitForSWActivation(
  driver,
  swFile,
  performRefresh = true,
) {
  performRefresh && (await driver.navigate().refresh());
  await driver.executeAsyncScript(async (swFile, cb) => {
    await window.__testCleanup();
    const registration = await navigator.serviceWorker.register(swFile);
    await window.__waitForSWState(registration, 'activated');
    cb();
  }, swFile);
  const swRegCount = await driver.executeAsyncScript(async cb => {
    const regs = await navigator.serviceWorker.getRegistrations();
    cb(regs.length);
  });
  expect(swRegCount).to.be.equal(1);
}


<|start_filename|>test/server.js<|end_filename|>
/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import * as http from 'http';
import handler from 'serve-handler';

const createServer = () =>
  http.createServer((request, response) => {
    request
      .addListener('end', async () => {
        //
        // Serve files!
        //
        await handler(request, response, {
          cleanUrls: false,
        });
      })
      .resume();
  });

const startServer = (server, port) =>
  new Promise(resolve => {
    server.listen(port, () => {
      console.log(`Test server running at http://localhost:${port}`);
      resolve();
    });
  });

export { createServer, startServer };


<|start_filename|>test/modules/service-worker-remover/service-worker-remover-e2e-test.js<|end_filename|>
/**
 * Copyright 2019 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { buildSW } from '../../builder/index';
import { promisify } from 'util';
import * as fs from 'fs';
import { join } from 'path';
import { UNVERSIONED_CACHE_NAME } from '../../../src/modules/amp-caching/constants';
import { AMP_ASSET_CACHE } from '../../../src/modules/asset-caching/constants';
import { AMP_PUBLISHER_CACHE } from '../../../src/modules/document-caching/constants';
import { AMP_PREFETCHED_LINKS } from '../../../src/modules/link-prefetch/constants';

const writeFile = promisify(fs.writeFile);
const wait = promisify(setTimeout);

describe('AMP Service worker remover', function() {
  const driver = global.__AMPSW.driver;
  const serviceWorkerPath = join('test', 'amp-caching-sw.js');
  const serviceWorkerRemoverPath = join('test', 'kill-switch-sw.js');

  before(async () => {
    const generatedSW = await buildSW({}, '/test/dist/amp-sw.js');
    const generatedSwRemover = await buildSW({}, '/test/dist/amp-sw.js', true);
    await writeFile(serviceWorkerPath, generatedSW);
    await writeFile(serviceWorkerRemoverPath, generatedSwRemover);
  });

  beforeEach(async () => {
    await driver.navigate().refresh();
    await driver.executeAsyncScript(async cb => {
      await window.__testCleanup();
      const registration = await navigator.serviceWorker.register(
        '/test/amp-caching-sw.js',
      );
      await window.__waitForSWState(registration, 'activated');
      cb();
    });
    const swRegCount = await driver.executeAsyncScript(async cb => {
      const regs = await navigator.serviceWorker.getRegistrations();
      cb(regs.length);
    });
    expect(swRegCount).to.be.equal(1);
  });

  afterEach(async () => {
    await driver.executeAsyncScript(async cb => {
      await window.__testCleanup();
      cb();
    });
  });

  it('should remove currently registered service worker', async () => {
    const installedServiceWorker = await driver.executeAsyncScript(async cb => {
      executeScript(
        async cb => cb(navigator.serviceWorker.controller.scriptURL),
        cb,
      );
    });
    expect(installedServiceWorker).to.be.equal(
      'http://localhost:6881/test/amp-caching-sw.js',
    );
    await installAndTestSwRemover(driver);
  });

  it('should clean all known caches', async () => {
    const cacheNames = {
      UNVERSIONED_CACHE_NAME,
      AMP_ASSET_CACHE,
      AMP_PREFETCHED_LINKS,
      AMP_PUBLISHER_CACHE,
    };

    const cacheKeysCount = await driver.executeAsyncScript(
      async (cacheNames, cb) => {
        executeScript(
          async (
            cb,
            {
              UNVERSIONED_CACHE_NAME,
              AMP_ASSET_CACHE,
              AMP_PREFETCHED_LINKS,
              AMP_PUBLISHER_CACHE,
            },
          ) => {
            const [
              unversionedCache,
              assetCache,
              prefetchLinkCache,
              publisherCache,
            ] = await Promise.all([
              caches.open(UNVERSIONED_CACHE_NAME),
              caches.open(AMP_ASSET_CACHE),
              caches.open(AMP_PREFETCHED_LINKS),
              caches.open(AMP_PUBLISHER_CACHE),
            ]);
            // create and put dummy entries in all four cache
            await Promise.all([
              unversionedCache.put(new Request('key'), new Response('value')),
              assetCache.put(new Request('key'), new Response('value')),
              prefetchLinkCache.put(new Request('key'), new Response('value')),
              publisherCache.put(new Request('key'), new Response('value')),
            ]);

            const cacheKeys = await caches.keys();
            cb(cacheKeys.length);
          },
          cb,
          cacheNames,
        );
      },
      cacheNames,
    );
    expect(cacheKeysCount).to.be.equal(4);
    await installAndTestSwRemover(driver);
    // wait for a second for the cleanup to happen
    await wait(1000);
    const postSwDeletionCacheKeysCount = await driver.executeAsyncScript(
      async cb => {
        executeScript(async cb => {
          const cacheKeys = await caches.keys();
          cb(cacheKeys.length);
        }, cb);
      },
    );
    expect(postSwDeletionCacheKeysCount).to.be.equal(0);
  });
});

async function installAndTestSwRemover(driver) {
  const newServiceWorker = await driver.executeAsyncScript(async cb => {
    executeScript(cb => {
      return new Promise(resolve => {
        navigator.serviceWorker.oncontrollerchange = function() {
          resolve(cb(navigator.serviceWorker.controller.scriptURL));
        };
        navigator.serviceWorker.register('/test/kill-switch-sw.js');
      });
    }, cb);
  });

  expect(newServiceWorker).to.be.equal(
    'http://localhost:6881/test/kill-switch-sw.js',
  );
}


<|start_filename|>test/modules/link-prefetch/link-prefetch-e2e-test.js<|end_filename|>
/**
 * Copyright 2019 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { buildSW } from '../../builder/index';
import { promisify } from 'util';
import * as fs from 'fs';
import { join } from 'path';
import { performCleanupAndWaitForSWActivation } from '../../test-utils/sw-installer';

const writeFile = promisify(fs.writeFile);
const unlink = promisify(fs.unlink);

describe('Link prefetch module', function() {
  const driver = global.__AMPSW.driver;
  const serviceWorkerPath = join('test', 'link-prefetch-sw.js');

  before(async () => {
    const generatedSW = await buildSW(
      {
        linkPrefetchOptions: {},
      },
      '/test/dist/amp-sw.js',
    );
    await writeFile(serviceWorkerPath, generatedSW);
  });

  beforeEach(async () => {
    await registerSW(driver);
  });

  after(async () => {
    await unlink(serviceWorkerPath);
  });

  afterEach(async () => {
    await unregisterSW(driver);
  });

  it('should listen to the postMessage and put the qualifying URLs in cache', async () => {
    const results = await driver.executeAsyncScript(async cb => {
      executeScript(async () => {
        const cacheName = window.__cacheName;
        navigator.serviceWorker.controller.postMessage(
          JSON.stringify({
            type: 'AMP__LINK-PREFETCH',
            payload: [
              'http://localhost:6881/test/accordian.amp.html',
              '/test/alternate.amp.html',
              'https://cdn.ampproject.org/rtv/011810152207300/v0.js',
            ],
          }),
        );
        await new Promise(resolve => setTimeout(resolve, 500));
        const cache = await caches.open(cacheName);
        cb((await cache.keys()).map(request => request.url));
      }, cb);
    });
    expect(results).to.deep.equal([
      'http://localhost:6881/test/accordian.amp.html',
      'http://localhost:6881/test/alternate.amp.html',
    ]);
  });

  it('should respond with CacheFirst for the prefetched request', async () => {
    await addDummyResponseToCache(driver);
    const response = await driver.executeAsyncScript(async cb => {
      executeScript(async cb => {
        const url = '/test/alternate.amp.html';
        const fetchedResponse = await fetch(url);
        cb(await fetchedResponse.text());
      }, cb);
    });
    // Expecting the dummy response added above.
    expect(response).to.be.equal('hello world');
  });

  it('should respond for the prefetched request only for 1 request', async () => {
    await addDummyResponseToCache(driver);
    const response = await driver.executeAsyncScript(async cb => {
      executeScript(async cb => {
        const url = '/test/alternate.amp.html';
        // first request
        await fetch(url);
        // second request
        const fetchedResponse = await fetch(url);
        cb(await fetchedResponse.text());
      }, cb);
    });
    // Expecting the dummy response added above.
    expect(response).to.not.be.equal('hello world');
  });

  it('should respond for the prefetched request only within configured time', async () => {
    await addDummyResponseToCache(driver);
    const response = await driver.executeAsyncScript(async cb => {
      executeScript(async cb => {
        const url = '/test/alternate.amp.html';
        const cache = await caches.open(cacheName);
        const timeDelta = 6 * 60 * 1000;
        // Adding a dummy response.
        await cache.put(
          new Request(url),
          new Response('hello world', {
            headers: {
              date: new Date(Date.now() - timeDelta),
            },
          }),
        );
        const fetchedResponse = await fetch(url);
        cb(await fetchedResponse.text());
      }, cb);
    });
    // Expecting the dummy response added above.
    expect(response).to.not.be.equal('hello world');
  });
});

async function registerSW(driver) {
  await performCleanupAndWaitForSWActivation(
    driver,
    '/test/link-prefetch-sw.js',
  );
  await driver.executeAsyncScript(async cb => {
    window.__cacheName = 'AMP-PREFETCHED-LINKS';
    cb();
  });
}

async function unregisterSW(driver) {
  await driver.get('http://localhost:6881/test/index.html');
  await driver.executeAsyncScript(async cb => {
    try {
      await window.__testCleanup();
      window.__cacheName = null;
      cb();
    } catch (e) {
      cb(); // NOOP
    }
  });
}

async function addDummyResponseToCache(driver) {
  return await driver.executeAsyncScript(async (url, cb) => {
    executeScript(
      async cb => {
        const cacheName = window.__cacheName;
        const url = '/test/alternate.amp.html';
        // This'll add an entry to denyList of navigation preload.
        navigator.serviceWorker.controller.postMessage(
          JSON.stringify({
            type: 'AMP__LINK-PREFETCH',
            payload: [url],
          }),
        );
        await new Promise(resolve => setTimeout(resolve, 500));
        const cache = await caches.open(cacheName);
        // Adding a dummy response.
        await cache.put(new Request(url), new Response('hello world'));
        cb(url);
      },
      url,
      cb,
    );
  });
}


<|start_filename|>test/conformance-test/version-test.js<|end_filename|>
import test from 'ava';
import * as fs from 'fs';
import { promisify } from 'util';
import { resolve, join } from 'path';
import webpack from 'webpack';
import webpackConfig from '../../webpack.config';

const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
const copyFile = promisify(fs.copyFile);
const deleteFile = promisify(fs.unlink);

const newPackageFile = resolve(join('.', '.test-package.json'));
const version = `${Date.now()}.0.0`;

test.before(async () => {
  await copyFile('./package.json', newPackageFile);
  const packageData = await readFile(newPackageFile, {
    encoding: 'utf-8',
  });
  const packageJSON = JSON.parse(packageData);
  packageJSON.version = version;
  await writeFile(newPackageFile, JSON.stringify(packageJSON));

  const compiler = webpack(webpackConfig({ packageFile: newPackageFile }));
  return new Promise((res, rej) => {
    compiler.run((err, stats) => {
      if (err || (stats && stats.hasErrors())) {
        rej();
      }
      res(stats);
    });
  });
});

test.after(async () => {
  await deleteFile(newPackageFile);
});

test('should have the package version for the bundle', async (t) => {
  const entryBundlePath = resolve(__dirname, '../../dist/amp-sw.js');
  const entryBundleContent = await readFile(entryBundlePath, 'utf-8');
  t.assert(entryBundleContent.startsWith(`/*! AMP_SW_v${version} */`));
});


<|start_filename|>test/modules/service-worker-remover/unit/service-worker-remover-test.js<|end_filename|>
import sinon from 'sinon';
import test from 'ava';
import { ServiceWorkerRemover } from '../../../../src/modules/service-worker-remover';
import {
  VERSIONED_CACHE_NAME,
  UNVERSIONED_CACHE_NAME,
} from '../../../../src/modules/amp-caching/constants';
import { AMP_ASSET_CACHE } from '../../../../src/modules/asset-caching/constants';
import { AMP_PUBLISHER_CACHE } from '../../../../src/modules/document-caching/constants';
import { AMP_PREFETCHED_LINKS } from '../../../../src/modules/link-prefetch/constants';

const swRemover = new ServiceWorkerRemover();

test.before(() => {
  global.caches = {
    delete: name => Promise.resolve(name),
  };
});

test('Service worker remover should delete all known caches', async t => {
  const deleteSpy = sinon.spy(global.caches, 'delete');
  const result = await swRemover.cleanCacheStorage();
  t.assert(deleteSpy.callCount === 5);
  t.deepEqual(result, [
    VERSIONED_CACHE_NAME,
    UNVERSIONED_CACHE_NAME,
    AMP_ASSET_CACHE,
    AMP_PUBLISHER_CACHE,
    AMP_PREFETCHED_LINKS,
  ]);
});

test('Service worker remover should delete caches even if one of the deletion fails', async t => {
  global.caches = {
    delete: name => {
      if (name === AMP_PUBLISHER_CACHE) {
        return Promise.reject();
      }
      return Promise.resolve(name);
    },
  };
  const deleteSpy = sinon.spy(global.caches, 'delete');
  try {
    await swRemover.cleanCacheStorage();
  } catch (e) {}

  t.assert(deleteSpy.callCount === 5);
});

test('Forced refresh should reset the URLs for all window clients', async t => {
  const url = 'URL';
  const windowClient = {
    url,
    navigate: sinon.stub(),
  };

  const clients = {
    claim: () => Promise.resolve(true),
    matchAll: sinon.stub().returns(
      new Promise(resolve => {
        resolve([windowClient]);
      }),
    ),
  };

  await swRemover.forceRefreshClients(clients);
  t.assert(clients.matchAll.calledOnceWith({ type: 'window' }));
  t.assert(windowClient.navigate.calledOnceWith(url));
});


<|start_filename|>test/test-utils/sw-test-cleanup.js<|end_filename|>
/**
 * Copyright 2019 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

window.__testCleanup = () => {
  const unregisterSW = () => {
    return navigator.serviceWorker.getRegistrations().then(registrations => {
      const unregisterPromise = registrations.map(registration => {
        return registration.unregister();
      });
      return Promise.all(unregisterPromise);
    });
  };

  const clearCaches = () => {
    return window.caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          return window.caches.delete(cacheName);
        }),
      );
    });
  };

  return Promise.all([unregisterSW(), clearCaches()]);
};


<|start_filename|>test/builder/serialize-test.js<|end_filename|>
/**
 * Copyright 2019 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { serializeObject } from '../../src/builder/serialize';

describe('serializeObject', () => {
  it('should serialize given object with regexp', () => {
    const obj = {
      regexp: [/http:\/\/google.com\//, /https:\/\/cdn.ampproject.org/],
    };
    const serializedValue = serializeObject(obj);
    expect(obj).to.deep.equal(
      wrapSerializedValueIntoFunction(serializedValue)(),
    );
  });
  it('should serialize given object with mixed data types', () => {
    const obj = {
      regexp: [1, '2', /a/g],
    };
    const serializedValue = serializeObject(obj);
    expect(obj).to.deep.equal(
      wrapSerializedValueIntoFunction(serializedValue)(),
    );
  });
  it('should serialize object within objects', () => {
    const obj = {
      child: {
        key: {
          subkey: 'value',
          indexes: [0, 4, 5],
        },
      },
    };
    const serializedValue = serializeObject(obj);
    expect(obj).to.deep.equal(
      wrapSerializedValueIntoFunction(serializedValue)(),
    );
  });
  it('should serialize arrays', () => {
    const obj = [
      {
        key: {
          subkey: 'value',
          indexes: [0, 4, 5],
        },
      },
    ];
    const serializedValue = serializeObject(obj);
    expect(obj).to.deep.equal(
      wrapSerializedValueIntoFunction(serializedValue)(),
    );
  });
});

function wrapSerializedValueIntoFunction(serializedValue) {
  return eval(`() => {return ${serializedValue}}`);
}


<|start_filename|>test/strategy-tests/strategy-tests.js<|end_filename|>
/**
 * Copyright 2019 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

export async function testStaleWhileRevalidate(driver, scriptURL, cacheName) {
  const DUMMY_RESPONSE = 'dummy response';
  const networkResponse = await driver.executeAsyncScript(
    async (scriptURL, cb) => {
      const response = await fetch(scriptURL);
      const text = await response.text();
      cb(text);
    },
    scriptURL,
  );
  // 1st response should be actual response from network
  expect(networkResponse).to.not.be.equal(DUMMY_RESPONSE);
  const cacheResponse = await driver.executeAsyncScript(
    async (cacheName, scriptURL, DUMMY_RESPONSE, cb) => {
      const cache = await caches.open(cacheName);
      await cache.put(new Request(scriptURL), new Response(DUMMY_RESPONSE));
      const response = await fetch(scriptURL);
      const text = await response.text();
      cb(text);
    },
    cacheName,
    scriptURL,
    DUMMY_RESPONSE,
  );
  // 2nd response should be the dummy response
  expect(cacheResponse).to.be.equal(DUMMY_RESPONSE);
  const storedCacheResponse = await driver.executeAsyncScript(
    async (cacheName, scriptURL, cb) => {
      const cache = await caches.open(cacheName);
      const response = await cache.match(scriptURL);
      const text = await response.text();
      cb(text);
    },
    cacheName,
    scriptURL,
  );
  // 3rd response will again be equal to network response because `stale-while-revalidate`
  expect(storedCacheResponse).to.not.be.equal(DUMMY_RESPONSE);
  expect(storedCacheResponse).to.be.equal(networkResponse);
}

export async function testCacheFirst(driver, scriptURL, cacheName) {
  const DUMMY_RESPONSE = 'dummy response';
  const networkResponse = await driver.executeAsyncScript(
    async (scriptURL, cb) => {
      const response = await fetch(scriptURL);
      const text = await response.text();
      cb(text);
    },
    scriptURL,
  );
  // 1st response should be actual response from network
  expect(networkResponse).to.not.be.equal(DUMMY_RESPONSE);
  const cacheResponse = await driver.executeAsyncScript(
    async (cacheName, scriptURL, DUMMY_RESPONSE, cb) => {
      const cache = await caches.open(cacheName);
      await cache.put(new Request(scriptURL), new Response(DUMMY_RESPONSE));
      const response = await fetch(scriptURL);
      const text = await response.text();
      cb(text);
    },
    cacheName,
    scriptURL,
    DUMMY_RESPONSE,
  );
  // 2nd response should be the dummy response
  expect(cacheResponse).to.be.equal(DUMMY_RESPONSE);
}

export async function testNetworkFirst(driver, scriptURL, cacheName) {
  const DUMMY_RESPONSE = 'dummy response';
  const networkResponse = await driver.executeAsyncScript(
    async (scriptURL, cb) => {
      const response = await fetch(scriptURL);
      const text = await response.text();
      cb(text);
    },
    scriptURL,
  );
  // 1st response should be actual response from network
  expect(networkResponse).to.not.be.equal(DUMMY_RESPONSE);
  const cacheResponse = await driver.executeAsyncScript(
    async (cacheName, scriptURL, DUMMY_RESPONSE, cb) => {
      const cache = await caches.open(cacheName);
      await cache.put(new Request(scriptURL), new Response(DUMMY_RESPONSE));
      const response = await fetch(scriptURL);
      const text = await response.text();
      cb(text);
    },
    cacheName,
    scriptURL,
    DUMMY_RESPONSE,
  );
  // 2nd response should be the dummy response
  expect(cacheResponse).to.not.be.equal(DUMMY_RESPONSE);
}
