libresilient/__tests__/service-worker/service-worker.test.js

3240 wiersze
127 KiB
JavaScript

import {
describe,
it,
beforeEach,
beforeAll,
afterEach
} from "https://deno.land/std@0.183.0/testing/bdd.ts";
import {
assert,
assertThrows,
assertRejects,
assertEquals,
assertNotEquals
} from "https://deno.land/std@0.183.0/testing/asserts.ts";
import {
assertSpyCall,
assertSpyCalls,
spy,
} from "https://deno.land/std@0.183.0/testing/mock.ts";
/*
* mocking the FetchEvent class
* https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent
*/
class FetchEvent extends Event {
request = null
response = null
constructor(request, init=null) {
super('fetch')
if (typeof request == "string") {
if (request.indexOf('http') != 0) {
request = window.location.origin + request
}
if (init === null) {
request = new Request(request)
} else {
request = new Request(request, init)
// for some reason setting the mode in the init object just doesn't work
// we need to go manual
if ('mode' in init) {
request.mode = init.mode
}
}
}
this.request = request
}
clientId = 'libresilient-tests'
respondWith(a) {
this.response = a
}
waitForResponse() {
return new Promise(async (resolve, reject)=>{
while (this.response === null) {
await new Promise(resolve => setTimeout(resolve, 1))
}
resolve(this.response)
})
}
}
beforeAll(async ()=>{
// default mocked response data
let responseMockedData = {
data: JSON.stringify({test: "success"}),
type: "application/json",
status: 200,
statusText: "OK",
headers: {
'Last-Modified': 'TestingLastModifiedHeader',
'ETag': 'TestingETagHeader'
}
}
// get a Promise resolving to a mocked Response object built based on supplied data
window.getMockedResponse = (url, init, response_data={}) => {
let rdata = {
...responseMockedData,
...response_data
}
let response = new Response(
new Blob(
[rdata.data],
{type: rdata.type}
),
{
status: rdata.status,
statusText: rdata.statusText,
headers: rdata.headers
});
// Response.url is read-only, so we have
Object.defineProperty(
response,
"url",
{ value: url }
);
return Promise.resolve(response);
}
// get a mocked fetch()-like function that returns a Promise resolving to the above
window.getMockedFetch = (response_data={}) => {
return (url, init)=>{
return window.getMockedResponse(url, init, response_data)
}
}
// prepare a mocked transport plugin, returning a spied-on fetch()-like function
window.prepareMockedTransportConfigJSON = (plugin_name, config_data, stale=false) => {
// by default not stale
let date = new Date().toUTCString()
// very stale if need be
if (stale === true) {
date = new Date(0).toUTCString()
}
// prepare the function
let mockedFetch = spy(window.getMockedFetch({
data: JSON.stringify(config_data),
headers: {
Date: date
}
}))
// create the plugin
window.LibResilientPluginConstructors.set(plugin_name, ()=>{
return {
name: plugin_name,
description: 'Resolve with config data (pretending to be fetch).',
version: '0.0.1',
fetch: mockedFetch
}
})
return mockedFetch
}
/**
* caching a mocked config.json
*/
window.cacheMockedConfigJSON = async (cache_name, data, stale=false) => {
// prepare the data
let config_data = {
data: JSON.stringify(data)
}
// do we want a stale cache?
if (stale === true) {
config_data.headers = {
// very stale date
'Date': new Date(0).toUTCString()
}
} else {
config_data.headers = {
// current date
'Date': new Date().toUTCString()
}
}
// cache it in the relevant cache
return await caches
.open(cache_name)
.then(async (cache)=>{
await cache
.put(
self.config_url,
await window
.getMockedResponse(config_url, {}, config_data)
)
})
}
/*
* prototype of the plugin init object
*/
window.initPrototype = {
name: 'cache'
}
/*
* mocking caches.match()
* https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage/match#browser_compatibility
*/
caches.match = async (url, options={}) => {
let cn = 'v1'
if ('cacheName' in options) {
cn = options.cacheName
}
let cache = await caches.open(cn)
return cache.match(url)
}
/*
* mocking Event.waitUntil()
* https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil#browser_compatibility
*/
Event.prototype.waitUntil = async (promise) => {
await promise
}
/*
* mocking importScriptsPrototype
*/
window.importScriptsPrototype = (script) => {
let plugin = null
try {
plugin = script.split('/')[2]
} catch (e) {}
if (plugin === null) {
// ignoring errors here — these happen when we're not actually loading a plugin
return false
}
window
.LibResilientPluginConstructors
.set(
plugin,
window.LibResilientPluginConstructorsPrototype.get(plugin)
)
}
window.LRLogPrototype = (component, ...items)=>{
console.debug(component + ' :: ', ...items)
}
/*
* mocking window.clients
*/
window.clients = {
get: async (id) => {
// always return the same client, we care only about postMessage() working
// and getting the messages
return {
// that's the only thing we need
// this allows us to spy on client.postMessage() calls issued by the service worker
postMessage: window.clients.prototypePostMessage
}
},
claim: spy(async () => {
return undefined
}),
// the actual spy function must be possible to reference
// but we want spy data per test, so we set it properly in beforeEach()
prototypePostMessage: null
}
window.skipWaiting = async () => {
return undefined;
}
// we need to be able to reliably wait for SW installation
// which is triggered by an "install" Event
window.sw_install_ran = false
// override addEventListener in order to override the callback
// and to keep track of event listeners that we need to remove in afterEach()
window.event_listeners = new Array()
window.addEventListenerOrig = window.addEventListener
window.addEventListener = async (evtype, func) => {
// normally we want the handler to be what it says on the packaging
let handler = func
// but for "install" type event… we actually want to wrap it such that
// we can then await for it
if (evtype == 'install') {
handler = async (ev) => {
let result = await func(ev);
window.sw_install_ran = true;
return result;
}
}
// adding to the list of installed event listeners
window.event_listeners.push([evtype, handler])
// we're done
return await window.addEventListenerOrig(evtype, handler)
}
// wait for SW installation
window.waitForSWInstall = () => {
return new Promise(async (resolve, reject)=>{
while (!window.sw_install_ran) {
await new Promise(resolve => setTimeout(resolve, 1))
}
resolve(true)
})
}
// wait for caching of a URL, looped up to `tries` times
window.waitForCacheAction = (url, cache_name, action="add", tries=100) => {
if (action != "add" && action != "remove" && action != "update") {
throw new Error('waitForCacheAction()\'s action parameter can only be "add", "remove", or "update".')
}
console.log(`*** WAITING FOR CACHE (${cache_name}) ACTION:`, action, '\n - url:', url)
return new Promise(async (resolve, reject)=>{
// get the cache object
let cache = await caches.open(cache_name)
let last_result = false
// try to match until we succeed, or run out of tries
for (let i=0; i<tries; i++) {
// search the URL
let cache_result = await cache.match(url)
// waiting for content to be added to cache?
if (action === "add") {
if (cache_result != undefined) {
// we have to "use" the Response, otherwise we get an error:
//
// > A "CacheResponseResource" resource (rid 7) was created during
// > the test, but not cleaned up during the test. Close the resource
// > before the end of the test.
return resolve(await cache_result.text())
}
// waiting for cache content to be updated?
} else if (action === 'update') {
// we expect something to have already been in cache
if (cache_result === undefined) {
return reject('Nothing was in cache already, which is unexpected')
}
// save the "old" data
if (last_result === false) {
last_result = await cache_result.text()
// we are waiting for an update of the content
} else {
let cur = await cache_result.text()
if (last_result !== cur) {
return resolve(cur)
}
}
// waiting for content to be removed from cache?
} else if (action === "remove") {
if (cache_result === undefined) {
return resolve(undefined)
}
// as above, we need to "use" the resource
await cache_result.text()
}
await new Promise(resolve => setTimeout(resolve, 1))
}
return reject("Ran out of tries");
})
}
/*
* importScripts mock relies on all plugins being loaded here
* TODO: automagically load the list from the plugins directory
*/
let plugins = [
"alt-fetch",
"any-of",
"basic-integrity",
"cache",
"dnslink-fetch",
"dnslink-ipfs",
"fetch",
"gun-ipfs",
"integrity-check",
"ipns-ipfs",
"redirect",
"signed-integrity",
]
await Promise.all(
plugins.map(async (plugin)=>{
await import(`../../plugins/${plugin}/index.js`)
})
)
window.LibResilientPluginConstructorsPrototype = window.LibResilientPluginConstructors
window.LibResilientPluginConstructors = new Map()
})
/**
* we need to do all of this before each test in order to reset the fetch() use counter
* and make sure window.init is clean and not modified by previous tests
*/
beforeEach(async ()=>{
window.config_url = window.location.origin + 'config.json'
window.fetch = spy(window.getMockedFetch())
/*
* mocking our own ServiceWorker API, sigh!
* https://github.com/denoland/deno/issues/5957#issuecomment-985494969
*/
window.registration = {
scope: "https://test.resilient.is/",
unregister: spy(()=>{})
}
window.init = {
...window.initPrototype
}
// clear the caches
await caches
.has('v1')
.then(async (hasit) => {
if (hasit) {
await caches.delete('v1')
}
})
await caches
.has('v1:verified')
.then(async (hasit) => {
if (hasit) {
await caches.delete('v1:verified')
}
})
// make sure we're starting with a clean slate in LibResilientPluginConstructors
window.LibResilientPluginConstructors = new Map()
// keeping track of whether the SW got installed
window.sw_install_ran = false
// cleanup
self.LibResilientConfig = null
self.LibResilientPlugins = null
// postMessage spy
window.clients.prototypePostMessage = spy((msg)=>{console.log('*** got message', msg)})
// importScripts spy
window.importScripts = spy(window.importScriptsPrototype)
// LR.log spy
window.LR.log = spy(window.LRLogPrototype)
})
/**
* after each test we need to do a bit of cleanup
*
* specifically, since we need to load the service-worker.js module anew
* we want to clean up any side-effects of having loaded it for the previous test
* and any side-effects of the previous test itself
*/
afterEach(async ()=>{
while (window.event_listeners.length) {
await window.removeEventListener(...window.event_listeners.pop());
}
window.test_id += 1;
})
describe('service-worker', async () => {
// mocking window.location
// https://developer.mozilla.org/en-US/docs/Web/API/Window/location
window.location = {
origin: "https://test.resilient.is/"
}
window.LibResilientPluginConstructors = new Map()
window.LR = {
log: null
}
window.fetch = null
window.importScripts = null
window.test_id = 0
it("should call clients.claim() when activated", async () => {
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
await self.dispatchEvent(new Event('activate'))
assertSpyCalls(window.clients.claim, 1)
})
it("should use default LibResilientConfig values when config.json is missing", async () => {
let mock_response_data = {
data: JSON.stringify({text: "success"}),
status: 404,
statusText: "Not Found"
}
window.fetch = spy(window.getMockedFetch(mock_response_data))
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, "object")
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 10000)
assertEquals(self.LibResilientConfig.stillLoadingTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "fetch"},{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'fetch', 'cache'])
assertEquals(self.LibResilientConfig.normalizeQueryParams, true)
assertEquals(self.LibResilientConfig.useMimeSniffingLibrary, false)
assertSpyCalls(self.fetch, 1)
})
it("should throw a meaningful exception when an unhandled exception occurs during initialization", async () => {
// this will generate an exception on line where a new Map()
// is created from self.LibResilientPluginConstructors
self.LibResilientPluginConstructors = true
await import("../../service-worker.js?" + window.test_id);
// the a failed install event handler in the service worker code
// will run self.registration.unregister() to not leave the site in a broken state
await self.dispatchEvent(new Event('install'))
assertSpyCalls(self.registration.unregister, 1)
})
it("should use default LibResilientConfig values when fetching config.json throws an exception", async () => {
window.fetch = spy(() => {
throw new Error('Testing exception')
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, "object")
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 10000)
assertEquals(self.LibResilientConfig.stillLoadingTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "fetch"},{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'fetch', 'cache'])
assertEquals(self.LibResilientConfig.normalizeQueryParams, true)
assertEquals(self.LibResilientConfig.useMimeSniffingLibrary, false)
assertSpyCalls(self.fetch, 1)
})
it("should use default LibResilientConfig values when fetching config.json returns undefined", async () => {
window.fetch = spy(() => {
return undefined;
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, "object")
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 10000)
assertEquals(self.LibResilientConfig.stillLoadingTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "fetch"},{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'fetch', 'cache'])
assertEquals(self.LibResilientConfig.normalizeQueryParams, true)
assertEquals(self.LibResilientConfig.useMimeSniffingLibrary, false)
assertSpyCalls(self.fetch, 1)
})
it("should use default LibResilientConfig values when fetching config.json succeeds, but executing it throws an error", async () => {
let mock_response_data = {
data: JSON.stringify({loggedComponents: ['service-worker', 'no-such-plugin'], plugins: [{name: "no-such-plugin"}]})
}
window.fetch = spy(window.getMockedFetch(mock_response_data))
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, "object")
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 10000)
assertEquals(self.LibResilientConfig.stillLoadingTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "fetch"},{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'fetch', 'cache'])
assertEquals(self.LibResilientConfig.normalizeQueryParams, true)
assertEquals(self.LibResilientConfig.useMimeSniffingLibrary, false)
assertSpyCalls(self.fetch, 1)
})
it("should fail if default LibResilientConfig values are invalid and fetched config.json is not valid JSON", async () => {
let mock_response_data = {
data: "Not JSON"
}
window.fetch = spy(window.getMockedFetch(mock_response_data))
self.LibResilientConfig = {}
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertSpyCalls(self.fetch, 1)
assertSpyCalls(self.registration.unregister, 1)
})
it("should use default LibResilientConfig values when the fetched config.json is not valid JSON", async () => {
let mock_response_data = {
data: "Not JSON"
}
window.fetch = spy(window.getMockedFetch(mock_response_data))
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, "object")
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 10000)
assertEquals(self.LibResilientConfig.stillLoadingTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "fetch"},{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'fetch', 'cache'])
assertEquals(self.LibResilientConfig.normalizeQueryParams, true)
assertEquals(self.LibResilientConfig.useMimeSniffingLibrary, false)
assertSpyCalls(self.fetch, 1)
})
it("should use default LibResilientConfig values when no valid 'plugins' field in config.json", async () => {
let mock_response_data = {
data: JSON.stringify({loggedComponents: ['service-worker', 'fetch'], plugins: 'not a valid array'})
}
window.fetch = spy(window.getMockedFetch(mock_response_data))
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, "object")
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 10000)
assertEquals(self.LibResilientConfig.stillLoadingTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "fetch"},{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'fetch', 'cache'])
assertEquals(self.LibResilientConfig.normalizeQueryParams, true)
assertEquals(self.LibResilientConfig.useMimeSniffingLibrary, false)
assertSpyCalls(self.fetch, 1)
})
it("should use default LibResilientConfig values when no valid 'loggedComponents' field in config.json", async () => {
let mock_response_data = {
data: JSON.stringify({loggedComponents: 'not a valid array', plugins: [{name: "fetch"}]})
}
window.fetch = spy(window.getMockedFetch(mock_response_data))
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, "object")
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 10000)
assertEquals(self.LibResilientConfig.stillLoadingTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "fetch"},{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'fetch', 'cache'])
assertEquals(self.LibResilientConfig.normalizeQueryParams, true)
assertEquals(self.LibResilientConfig.useMimeSniffingLibrary, false)
assertSpyCalls(self.fetch, 1)
})
it("should use default LibResilientConfig values when 'defaultPluginTimeout' field in config.json contains an invalid value", async () => {
let mock_response_data = {
data: JSON.stringify({loggedComponents: ['service-worker', 'fetch'], plugins: [{name: "fetch"}], defaultPluginTimeout: 'not an integer'})
}
window.fetch = spy(window.getMockedFetch(mock_response_data))
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, "object")
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 10000)
assertEquals(self.LibResilientConfig.stillLoadingTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "fetch"},{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'fetch', 'cache'])
assertEquals(self.LibResilientConfig.normalizeQueryParams, true)
assertEquals(self.LibResilientConfig.useMimeSniffingLibrary, false)
assertSpyCalls(self.fetch, 1)
})
it("should use default LibResilientConfig values when 'stillLoadingTimeout' field in config.json contains an invalid value", async () => {
let mock_response_data = {
data: JSON.stringify({loggedComponents: ['service-worker', 'fetch'], plugins: [{name: "fetch"}], defaultPluginTimeout: 5000, stillLoadingTimeout: 'not an integer'})
}
window.fetch = spy(window.getMockedFetch(mock_response_data))
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, "object")
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 10000)
assertEquals(self.LibResilientConfig.stillLoadingTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "fetch"},{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'fetch', 'cache'])
assertEquals(self.LibResilientConfig.normalizeQueryParams, true)
assertEquals(self.LibResilientConfig.useMimeSniffingLibrary, false)
assertSpyCalls(self.fetch, 1)
})
it("should use default LibResilientConfig values when 'normalizeQueryParams' field in config.json contains an invalid value", async () => {
let mock_response_data = {
data: JSON.stringify({loggedComponents: ['service-worker', 'fetch'], plugins: [{name: "fetch"}], defaultPluginTimeout: 5000, normalizeQueryParams: "not a boolean"})
}
window.fetch = spy(window.getMockedFetch(mock_response_data))
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, "object")
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 10000)
assertEquals(self.LibResilientConfig.stillLoadingTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "fetch"},{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'fetch', 'cache'])
assertEquals(self.LibResilientConfig.normalizeQueryParams, true)
assertEquals(self.LibResilientConfig.useMimeSniffingLibrary, false)
assertSpyCalls(self.fetch, 1)
})
it("should use default LibResilientConfig values when 'useMimeSniffingLibrary' field in config.json contains an invalid value", async () => {
let mock_response_data = {
data: JSON.stringify({loggedComponents: ['service-worker', 'fetch'], plugins: [{name: "fetch"}], defaultPluginTimeout: 5000, normalizeQueryParams: false, useMimeSniffingLibrary: "not a boolean"})
}
window.fetch = spy(window.getMockedFetch(mock_response_data))
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, "object")
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 10000)
assertEquals(self.LibResilientConfig.stillLoadingTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "fetch"},{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'fetch', 'cache'])
assertEquals(self.LibResilientConfig.normalizeQueryParams, true)
assertEquals(self.LibResilientConfig.useMimeSniffingLibrary, false)
assertSpyCalls(self.fetch, 1)
})
it("should use config values from a valid fetched config.json file, caching it in both caches (v1, v1:verified)", async () => {
let mock_response_data = {
data: JSON.stringify({loggedComponents: ['service-worker', 'cache'], plugins: [{name: "cache"}], defaultPluginTimeout: 5000, stillLoadingTimeout: 1000, normalizeQueryParams: false, useMimeSniffingLibrary: true})
}
window.fetch = spy(window.getMockedFetch(mock_response_data))
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, "object")
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 5000)
assertEquals(self.LibResilientConfig.stillLoadingTimeout, 1000)
assertEquals(self.LibResilientConfig.plugins, [{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'cache'])
assertEquals(self.LibResilientConfig.normalizeQueryParams, false)
assertEquals(self.LibResilientConfig.useMimeSniffingLibrary, true)
assertSpyCalls(self.fetch, 1)
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1'),
mock_response_data.data
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
mock_response_data.data
);
})
it("should instantiate a complex tree of configured plugins", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'plugin-1',
uses: [{
name: 'plugin-2',
uses: [{
name: 'plugin-3'
}]
},{
name: 'plugin-3'
}]
},{
name: 'plugin-2',
uses: [{
name: 'plugin-3'
}]
},{
name: 'plugin-3',
uses: [{
name: 'plugin-1'
},{
name: 'plugin-2',
uses: [{
name: 'plugin-1',
uses: [{
name: 'plugin-4'
}]
}]
}]
},{
name: 'plugin-4'
}],
loggedComponents: ['service-worker']
}
window.LibResilientPluginConstructors.set('plugin-1', (LRPC, config)=>{
return {
name: 'plugin-1',
description: 'Plugin Type 1',
version: '0.0.1',
fetch: (url)=>{return true},
uses: config.uses || []
}
})
window.LibResilientPluginConstructors.set('plugin-2', (LRPC, config)=>{
return {
name: 'plugin-2',
description: 'Plugin Type 2',
version: '0.0.1',
fetch: (url)=>{return true},
uses: config.uses || []
}
})
window.LibResilientPluginConstructors.set('plugin-3', (LRPC, config)=>{
return {
name: 'plugin-3',
description: 'Plugin Type 3',
version: '0.0.1',
fetch: (url)=>{return true},
uses: config.uses || []
}
})
window.LibResilientPluginConstructors.set('plugin-4', (LRPC, config)=>{
return {
name: 'plugin-4',
description: 'Plugin Type 4',
version: '0.0.1',
fetch: (url)=>{return true},
uses: config.uses || []
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, 'object')
// basic stuff
assertEquals(self.LibResilientPlugins.length, 4)
assertEquals(self.LibResilientPlugins[0].name, 'plugin-1')
assertEquals(self.LibResilientPlugins[1].name, 'plugin-2')
assertEquals(self.LibResilientPlugins[2].name, 'plugin-3')
assertEquals(self.LibResilientPlugins[3].name, 'plugin-4')
// first plugin dependencies
assertEquals(self.LibResilientPlugins[0].uses.length, 2)
assertEquals(self.LibResilientPlugins[0].uses[0].name, 'plugin-2')
assertEquals(self.LibResilientPlugins[0].uses[0].uses.length, 1)
assertEquals(self.LibResilientPlugins[0].uses[0].uses[0].name, 'plugin-3')
assertEquals(self.LibResilientPlugins[0].uses[0].uses[0].uses.length, 0)
assertEquals(self.LibResilientPlugins[0].uses[1].name, 'plugin-3')
assertEquals(self.LibResilientPlugins[0].uses[1].uses.length, 0)
// second plugin dependencies
assertEquals(self.LibResilientPlugins[1].uses.length, 1)
assertEquals(self.LibResilientPlugins[1].uses[0].name, 'plugin-3')
assertEquals(self.LibResilientPlugins[1].uses[0].uses.length, 0)
// third plugin dependencies
assertEquals(self.LibResilientPlugins[2].uses.length, 2)
assertEquals(self.LibResilientPlugins[2].uses[0].name, 'plugin-1')
assertEquals(self.LibResilientPlugins[2].uses[0].uses.length, 0)
assertEquals(self.LibResilientPlugins[2].uses[1].name, 'plugin-2')
assertEquals(self.LibResilientPlugins[2].uses[1].uses.length, 1)
assertEquals(self.LibResilientPlugins[2].uses[1].uses[0].name, 'plugin-1')
assertEquals(self.LibResilientPlugins[2].uses[1].uses[0].uses.length, 1)
assertEquals(self.LibResilientPlugins[2].uses[1].uses[0].uses[0].name, 'plugin-4')
assertEquals(self.LibResilientPlugins[2].uses[1].uses[0].uses[0].uses.length, 0)
// fourth plugin dependencies
assertEquals(self.LibResilientPlugins[3].uses.length, 0)
})
it("should instantiate configured plugins; explicitly disabled plugins should not be instantiated", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'plugin-enabled'
},{
name: 'plugin-disabled',
enabled: false
},{
name: 'plugin-enabled',
enabled: true
}]
}
window.LibResilientPluginConstructors.set('plugin-enabled', ()=>{
return {
name: 'plugin-enabled',
description: 'Enabled plugin',
version: '0.0.1',
fetch: (url)=>{return true}
}
})
window.LibResilientPluginConstructors.set('plugin-disabled', ()=>{
return {
name: 'plugin-disabled',
description: 'Disabled plugin',
version: '0.0.1',
fetch: (url)=>{return true}
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientPlugins.length, 2)
assertEquals(self.LibResilientPlugins[0].name, 'plugin-enabled')
assertEquals(self.LibResilientPlugins[1].name, 'plugin-enabled')
})
it("should instantiate configured plugins; explicitly disabled dependencies should not be instantiated", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'plugin-disabled',
enabled: false,
uses: [{
name: 'dependency-enabled'
}]
},{
name: 'plugin-enabled',
uses: [{
name: 'dependency-disabled',
enabled: false
}]
},{
name: 'plugin-enabled',
uses: [{
name: 'dependency-enabled',
enabled: true
}]
}],
loggedComponents: ['service-worker']
}
window.LibResilientPluginConstructors.set('plugin-enabled', (LRPC, config)=>{
return {
name: 'plugin-enabled',
description: 'Enabled plugin',
version: '0.0.1',
fetch: (url)=>{return true},
uses: config.uses || []
}
})
window.LibResilientPluginConstructors.set('plugin-disabled', (LRPC, config)=>{
return {
name: 'plugin-disabled',
description: 'Disabled plugin',
version: '0.0.1',
fetch: (url)=>{return true},
uses: config.uses || []
}
})
window.LibResilientPluginConstructors.set('dependency-disabled', (LRPC, config)=>{
return {
name: 'dependency-disabled',
description: 'Disabled dependency plugin',
version: '0.0.1',
fetch: (url)=>{return true},
uses: config.uses || []
}
})
window.LibResilientPluginConstructors.set('dependency-enabled', (LRPC, config)=>{
return {
name: 'dependency-enabled',
description: 'Enabled dependency plugin',
version: '0.0.1',
fetch: (url)=>{return true},
uses: config.uses || []
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientPlugins.length, 2)
assertEquals(self.LibResilientPlugins[0].name, 'plugin-enabled')
assertEquals(self.LibResilientPlugins[0].uses.length, 0)
assertEquals(self.LibResilientPlugins[1].name, 'plugin-enabled')
assertEquals(self.LibResilientPlugins[1].uses.length, 1)
assertEquals(self.LibResilientPlugins[1].uses[0].name, 'dependency-enabled')
assertEquals(self.LibResilientPlugins[1].uses[0].uses.length, 0)
})
/*
* testing config.json fetching, verification, and caching
*/
it("should use a valid config.json from v1 cache, with no fetch happening, caching it in v1:verified cache", async () => {
// v1 cache data
let v1_data = {loggedComponents: ['service-worker', 'cache'], plugins: [{name: "cache"}], defaultPluginTimeout: 5000}
// cache it in v1 cache, not stale
await self.cacheMockedConfigJSON("v1", v1_data, false)
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// no fetch
assertSpyCalls(self.fetch, 0)
// config applied
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'cache'])
// in both caches we expect the contents of v1 cache
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1'),
JSON.stringify(v1_data),
"v1 cache has incorrect config.json data"
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
JSON.stringify(v1_data),
"v1:verified cache has incorrect config.json data"
);
})
it("should use a valid config.json from v1:verified cache when not available in v1, with no fetch happening, caching it in v1 cache", async () => {
// v1:verified cache data
let v1verified_data = {loggedComponents: ['service-worker', 'cache'], plugins: [{name: "cache"}], defaultPluginTimeout: 6000}
// cache it in v1 cache, not stale
await self.cacheMockedConfigJSON("v1:verified", v1verified_data, false)
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// no fetch
assertSpyCalls(self.fetch, 0)
// config applied
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 6000)
assertEquals(self.LibResilientConfig.plugins, [{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'cache'])
// in both caches we expect the contents of v1 cache
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1'),
JSON.stringify(v1verified_data),
"v1 cache has incorrect config.json data"
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
JSON.stringify(v1verified_data),
"v1:verified cache has incorrect config.json data"
);
})
it("should use a valid config.json from v1:verified cache when the one in v1 is invalid (structurally), with no fetch happening, caching it in v1 cache", async () => {
// v1 cached data invalid (invalid structure)
let v1_data = {loggedComponentsInvalid: ['service-worker', 'cache'], pluginsInvalid: [{name: "cache"}], defaultPluginTimeoutInvalid: 5000}
await self.cacheMockedConfigJSON("v1", v1_data, false)
// v1:verified cached data valid
let v1verified_data = {loggedComponents: ['service-worker', 'cache'], plugins: [{name: "cache"}], defaultPluginTimeout: 6000}
await self.cacheMockedConfigJSON("v1:verified", v1verified_data, false)
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// no fetch
assertSpyCalls(self.fetch, 0)
// config applied
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 6000)
assertEquals(self.LibResilientConfig.plugins, [{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'cache'])
// in both caches we expect the contents of v1 cache; in v1 the contents need to be updated
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1'),
JSON.stringify(v1verified_data),
"v1 cache has incorrect config.json data"
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
JSON.stringify(v1verified_data),
"v1:verified cache has incorrect config.json data"
);
})
it("should use a valid config.json from v1:verified cache when the one in v1 is invalid (syntactically), with no fetch happening, caching it in v1 cache", async () => {
// v1 cached data invalid (not JSON)
let v1_data = "NOT JSON"
await self.cacheMockedConfigJSON("v1", v1_data, false)
// v1:verified cached data valid
let v1verified_data = {loggedComponents: ['service-worker', 'cache'], plugins: [{name: "cache"}], defaultPluginTimeout: 6000}
await self.cacheMockedConfigJSON("v1:verified", v1verified_data, false)
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// no fetch
assertSpyCalls(self.fetch, 0)
// config applied
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 6000)
assertEquals(self.LibResilientConfig.plugins, [{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'cache'])
// in both caches we expect the contents of v1 cache; in v1 the contents need to be updated
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1'),
JSON.stringify(v1verified_data),
"v1 cache has incorrect config.json data"
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
JSON.stringify(v1verified_data),
"v1:verified cache has incorrect config.json data"
);
})
it("should use a valid config.json from v1:verified cache when the one in v1 is invalid (unavailable plugins), with no fetch happening, caching it in v1 cache", async () => {
// v1 cached data valid
let v1_data = {loggedComponents: ['service-worker', 'no-such-plugin'], plugins: [{name: "no-such-plugin"}], defaultPluginTimeout: 5000}
await self.cacheMockedConfigJSON("v1", v1_data, false)
// v1:verified cached data valid
let v1verified_data = {loggedComponents: ['service-worker', 'cache'], plugins: [{name: "cache"}], defaultPluginTimeout: 6000}
await self.cacheMockedConfigJSON("v1:verified", v1verified_data, false)
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// no fetch
assertSpyCalls(self.fetch, 0)
// config applied
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 6000)
assertEquals(self.LibResilientConfig.plugins, [{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'cache'])
// in both caches we expect the contents of v1 cache; in v1 the contents need to be updated
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1'),
JSON.stringify(v1verified_data),
"v1 cache has incorrect config.json data"
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
JSON.stringify(v1verified_data),
"v1:verified cache has incorrect config.json data"
);
})
it("should use a stale config.json from v1 cache, with no fetch happening, caching it in v1:verified cache", async () => {
// v1 cached data valid
let v1_data = {loggedComponents: ['service-worker', 'cache'], plugins: [{name: "cache"}], defaultPluginTimeout: 5000}
await self.cacheMockedConfigJSON("v1", v1_data, true) // stale!
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// no fetch
assertSpyCalls(self.fetch, 0)
// config applied
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'cache'])
// in both caches we expect the contents of v1 cache; in v1 the contents need to be updated
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1'),
JSON.stringify(v1_data),
"v1 cache has incorrect config.json data"
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
JSON.stringify(v1_data),
"v1:verified cache has incorrect config.json data"
);
})
it("should use a stale config.json from v1 cache, caching it in v1:verified cache; then try to fetch a fresh version, caching it if valid in v1 cache", async () => {
// v1 cached data valid
let v1_data = {loggedComponents: ['service-worker', 'mocked-fetch', 'cache'], plugins: [{name: "mocked-fetch"}, {name: 'cache'}], defaultPluginTimeout: 5000}
await self.cacheMockedConfigJSON("v1", v1_data, true) // stale!
let fetch_data = {loggedComponents: ['service-worker', 'mocked-fetch', 'cache'], plugins: [{name: "cache"}, {name: "mocked-fetch"}], defaultPluginTimeout: 7000}
let mockedFetch = self.prepareMockedTransportConfigJSON('mocked-fetch', fetch_data, false)
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// no fetch
assertSpyCalls(mockedFetch, 1)
// config applied
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "mocked-fetch"}, {name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'mocked-fetch', 'cache'])
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1', 'update'),
JSON.stringify(fetch_data),
"v1 cache has incorrect config.json data"
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
JSON.stringify(v1_data),
"v1:verified cache has incorrect config.json data"
);
})
it("should use a stale config.json from v1 cache, then try to fetch a fresh version, ignoring it if invalid (structurally) and caching the v1 version in v1:verified cache", async () => {
// v1 cached data valid
let v1_data = {loggedComponents: ['service-worker', 'fetch'], plugins: [{name: "fetch"}], defaultPluginTimeout: 5000}
await self.cacheMockedConfigJSON("v1", v1_data, true) // stale!
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// no fetch
assertSpyCalls(self.fetch, 1)
// config applied
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "fetch"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'fetch'])
// v1 cache should *not* be updated
try {
await window.waitForCacheAction(self.config_url, 'v1', 'update')
} catch (e) {
assertEquals(e, 'Ran out of tries')
}
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1'),
JSON.stringify(v1_data),
"v1 cache has incorrect config.json data"
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
JSON.stringify(v1_data),
"v1:verified cache has incorrect config.json data"
);
})
it("should use a stale config.json from v1 cache, then try to fetch a fresh version, ignoring it if invalid (unavailable plugins) and caching the v1 version in v1:verified cache", async () => {
// v1 cached data valid
let v1_data = {loggedComponents: ['service-worker', 'mocked-fetch'], plugins: [{name: "mocked-fetch"}], defaultPluginTimeout: 5000}
await self.cacheMockedConfigJSON("v1", v1_data, true) // stale!
let fetch_data = {loggedComponents: ['service-worker', 'mocked-fetch', 'no-such-plugin'], plugins: [{name: "mocked-fetch"}, {name: 'no-such-plugin'}], defaultPluginTimeout: 7000}
let mockedFetch = self.prepareMockedTransportConfigJSON('mocked-fetch', fetch_data, false)
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// no fetch
assertSpyCalls(mockedFetch, 1)
// config applied
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "mocked-fetch"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'mocked-fetch'])
// v1 cache should *not* be updated
try {
await window.waitForCacheAction(self.config_url, 'v1', 'update')
} catch (e) {
assertEquals(e, 'Ran out of tries')
}
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1'),
JSON.stringify(v1_data),
"v1 cache has incorrect config.json data"
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
JSON.stringify(v1_data),
"v1:verified cache has incorrect config.json data"
);
})
it("should use a stale config.json from v1:verified cache; then try to fetch a fresh version, caching it if valid in v1 cache", async () => {
// v1:verified cached data valid
let v1verified_data = {loggedComponents: ['service-worker', 'mocked-fetch', 'cache'], plugins: [{name: "mocked-fetch"}, {name: 'cache'}], defaultPluginTimeout: 6000}
await self.cacheMockedConfigJSON("v1:verified", v1verified_data, true) // stale!
let fetch_data = {loggedComponents: ['service-worker', 'mocked-fetch', 'cache'], plugins: [{name: "cache"}, {name: "mocked-fetch"}], defaultPluginTimeout: 7000}
let mockedFetch = self.prepareMockedTransportConfigJSON('mocked-fetch', fetch_data, false)
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// fetch
assertSpyCalls(mockedFetch, 1)
// config applied
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 6000)
assertEquals(self.LibResilientConfig.plugins, [{name: "mocked-fetch"}, {name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'mocked-fetch', 'cache'])
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1', 'update'),
JSON.stringify(fetch_data),
"v1 cache has incorrect config.json data"
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
JSON.stringify(v1verified_data),
"v1:verified cache has incorrect config.json data"
);
})
it("should use a stale config.json from v1:verified cache, then try to fetch a fresh version, ignoring it if invalid (structurally) and caching the v1:verified version in v1 cache", async () => {
// v1:verified cached data valid
let v1verified_data = {loggedComponents: ['service-worker', 'fetch'], plugins: [{name: "fetch"}], defaultPluginTimeout: 6000}
await self.cacheMockedConfigJSON("v1:verified", v1verified_data, true) // stale!
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// fetch
assertSpyCalls(self.fetch, 1)
// config applied
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 6000)
assertEquals(self.LibResilientConfig.plugins, [{name: "fetch"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'fetch'])
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1'),
JSON.stringify(v1verified_data),
"v1 cache has incorrect config.json data"
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
JSON.stringify(v1verified_data),
"v1:verified cache has incorrect config.json data"
);
})
it("should use a stale config.json from v1:verified cache, then try to fetch a fresh version, ignoring it if invalid (unavailable plugins) and caching the v1:verified version in v1 cache", async () => {
// v1:verified cached data valid
let v1verified_data = {loggedComponents: ['service-worker', 'mocked-fetch'], plugins: [{name: "mocked-fetch"}], defaultPluginTimeout: 6000}
await self.cacheMockedConfigJSON("v1:verified", v1verified_data, true) // stale!
let fetch_data = {loggedComponents: ['service-worker', 'mocked-fetch', 'no-such-plugin'], plugins: [{name: "mocked-fetch"}, {name: 'no-such-plugin'}], defaultPluginTimeout: 7000}
let mockedFetch = self.prepareMockedTransportConfigJSON('mocked-fetch', fetch_data, false)
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// mocked fetch
assertSpyCalls(mockedFetch, 1)
// config applied
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 6000)
assertEquals(self.LibResilientConfig.plugins, [{name: "mocked-fetch"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'mocked-fetch'])
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1'),
JSON.stringify(v1verified_data),
"v1 cache has incorrect config.json data"
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
JSON.stringify(v1verified_data),
"v1:verified cache has incorrect config.json data"
);
})
it("should ignore invalid (structurally) config from v1 cache and use a stale config.json from v1:verified cache; then try to fetch a fresh version, caching it if valid in v1 cache", async () => {
// v1 cached data invalid (invalid structure)
let v1_data = {loggedComponentsInvalid: ['service-worker', 'cache'], pluginsInvalid: [{name: "cache"}], defaultPluginTimeoutInvalid: 5000}
await self.cacheMockedConfigJSON("v1", v1_data, false)
// v1:verified cached data valid
let v1verified_data = {loggedComponents: ['service-worker', 'mocked-fetch', 'cache'], plugins: [{name: "mocked-fetch"}, {name: 'cache'}], defaultPluginTimeout: 6000}
await self.cacheMockedConfigJSON("v1:verified", v1verified_data, true) // stale!
let fetch_data = {loggedComponents: ['service-worker', 'mocked-fetch', 'cache'], plugins: [{name: "cache"}, {name: "mocked-fetch"}], defaultPluginTimeout: 7000}
let mockedFetch = self.prepareMockedTransportConfigJSON('mocked-fetch', fetch_data, false)
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// fetch
assertSpyCalls(mockedFetch, 1)
// config applied
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 6000)
assertEquals(self.LibResilientConfig.plugins, [{name: "mocked-fetch"}, {name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'mocked-fetch', 'cache'])
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1', 'update'),
JSON.stringify(fetch_data),
"v1 cache has incorrect config.json data"
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
JSON.stringify(v1verified_data),
"v1:verified cache has incorrect config.json data"
);
})
it("should ignore invalid (syntactically) config from v1 cache and use a stale config.json from v1:verified cache; then try to fetch a fresh version, caching it if valid in v1 cache", async () => {
// v1 cached data invalid (not JSON)
let v1_data = "NOT JSON"
await self.cacheMockedConfigJSON("v1", v1_data, false)
// v1:verified cached data valid
let v1verified_data = {loggedComponents: ['service-worker', 'mocked-fetch', 'cache'], plugins: [{name: "mocked-fetch"}, {name: 'cache'}], defaultPluginTimeout: 6000}
await self.cacheMockedConfigJSON("v1:verified", v1verified_data, true) // stale!
let fetch_data = {loggedComponents: ['service-worker', 'mocked-fetch', 'cache'], plugins: [{name: "cache"}, {name: "mocked-fetch"}], defaultPluginTimeout: 7000}
let mockedFetch = self.prepareMockedTransportConfigJSON('mocked-fetch', fetch_data, false)
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// fetch
assertSpyCalls(mockedFetch, 1)
// config applied
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 6000)
assertEquals(self.LibResilientConfig.plugins, [{name: "mocked-fetch"}, {name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'mocked-fetch', 'cache'])
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1', 'update'),
JSON.stringify(fetch_data),
"v1 cache has incorrect config.json data"
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
JSON.stringify(v1verified_data),
"v1:verified cache has incorrect config.json data"
);
})
it("should ignore invalid (unavailable plugins) config from v1 cache and use a stale config.json from v1:verified cache; then try to fetch a fresh version, caching it if valid in v1 cache", async () => {
// v1 cached data invalid (unavailable plugin)
let v1_data = {loggedComponents: ['service-worker', 'mocked-fetch', 'no-such-plugin'], plugins: [{name: "mocked-fetch"}, {name: 'no-such-plugin'}], defaultPluginTimeout: 5000}
await self.cacheMockedConfigJSON("v1", v1_data, false)
// v1:verified cached data valid
let v1verified_data = {loggedComponents: ['service-worker', 'mocked-fetch', 'cache'], plugins: [{name: "mocked-fetch"}, {name: 'cache'}], defaultPluginTimeout: 6000}
await self.cacheMockedConfigJSON("v1:verified", v1verified_data, true) // stale!
let fetch_data = {loggedComponents: ['service-worker', 'mocked-fetch', 'cache'], plugins: [{name: "cache"}, {name: "mocked-fetch"}], defaultPluginTimeout: 7000}
let mockedFetch = self.prepareMockedTransportConfigJSON('mocked-fetch', fetch_data, false)
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// fetch
assertSpyCalls(mockedFetch, 1)
// config applied
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 6000)
assertEquals(self.LibResilientConfig.plugins, [{name: "mocked-fetch"}, {name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'mocked-fetch', 'cache'])
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1', 'update'),
JSON.stringify(fetch_data),
"v1 cache has incorrect config.json data"
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
JSON.stringify(v1verified_data),
"v1:verified cache has incorrect config.json data"
);
})
it("should ignore invalid config from v1 cache and use a stale config.json from v1:verified cache; then try to fetch a fresh version, ignoring it if invalid", async () => {
// v1 cached data invalid (invalid structure)
let v1_data = {loggedComponentsInvalid: ['service-worker', 'cache'], pluginsInvalid: [{name: "cache"}], defaultPluginTimeoutInvalid: 5000}
await self.cacheMockedConfigJSON("v1", v1_data, false)
// v1:verified cached data valid
let v1verified_data = {loggedComponents: ['service-worker', 'mocked-fetch', 'cache'], plugins: [{name: "mocked-fetch"}, {name: 'cache'}], defaultPluginTimeout: 6000}
await self.cacheMockedConfigJSON("v1:verified", v1verified_data, true) // stale!
let fetch_data = "NOT JSON"
let mockedFetch = self.prepareMockedTransportConfigJSON('mocked-fetch', fetch_data, false)
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// fetch
assertSpyCalls(mockedFetch, 1)
// config applied
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 6000)
assertEquals(self.LibResilientConfig.plugins, [{name: "mocked-fetch"}, {name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'mocked-fetch', 'cache'])
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1'),
JSON.stringify(v1verified_data),
"v1 cache has incorrect config.json data"
);
assertEquals(
await window.waitForCacheAction(self.config_url, 'v1:verified'),
JSON.stringify(v1verified_data),
"v1:verified cache has incorrect config.json data"
);
})
/*
* end of testing config.json fetching, verification, and caching
*/
it("should ignore failed fetch by first configured plugin if followed by a successful fetch by a second one", async () => {
window.LibResilientConfig = {
plugins: [{
name: 'reject-all'
},{
name: 'resolve-all'
}],
loggedComponents: [
'service-worker'
]
}
let rejectingFetch = spy(
(request, init)=>{ return Promise.reject('reject-all rejecting a request for: ' + request); }
)
window.LibResilientPluginConstructors.set('reject-all', ()=>{
return {
name: 'reject-all',
description: 'Reject all requests.',
version: '0.0.1',
fetch: rejectingFetch
}
})
window.LibResilientPluginConstructors.set('resolve-all', ()=>{
return {
name: 'resolve-all',
description: 'Resolve all requests.',
version: '0.0.1',
fetch: fetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent('test.json')
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
assertSpyCalls(window.fetch, 2); // two, because the first one is for config.json
assertSpyCalls(rejectingFetch, 1);
assertSpyCall(window.fetch, 1, { args: [
"https://test.resilient.is/test.json",
{
cache: undefined,
integrity: undefined,
method: "GET",
redirect: "follow",
referrer: undefined,
}]
})
assertEquals(await response.json(), { test: "success" })
});
it("should use return a 4xx error directly from the last plugin, regardless of previous plugin errors or rejection", async () => {
window.LibResilientConfig = {
plugins: [{
name: 'reject-all'
},{
name: 'error-out'
},{
name: 'return-418'
}],
loggedComponents: [
'service-worker'
]
}
let rejectingFetch = spy(
(request, init)=>{ return Promise.reject('reject-all rejecting a request for: ' + request); }
)
window.LibResilientPluginConstructors.set('reject-all', ()=>{
return {
name: 'reject-all',
description: 'Reject all requests.',
version: '0.0.1',
fetch: rejectingFetch
}
})
let throwingFetch = spy(
(request, init)=>{ throw new Error('error-out throwing an Error for: ' + request); }
)
window.LibResilientPluginConstructors.set('error-out', ()=>{
return {
name: 'error-out',
description: 'Throws.',
version: '0.0.1',
fetch: throwingFetch
}
})
let mock_response_data = {
data: JSON.stringify({text: "success"}),
status: 418,
statusText: "Im A Teapot"
}
window.fetch = spy(window.getMockedFetch(mock_response_data))
window.LibResilientPluginConstructors.set('return-418', ()=>{
return {
name: 'return-418',
description: 'Return 418 HTTP Error.',
version: '0.0.1',
fetch: fetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent('test.json')
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
assertSpyCalls(window.fetch, 2); // two, because the first one is for config.json
assertSpyCalls(rejectingFetch, 1);
assertSpyCalls(throwingFetch, 1);
assertSpyCall(window.fetch, 1, { args: [
"https://test.resilient.is/test.json",
{
cache: undefined,
integrity: undefined,
method: "GET",
redirect: "follow",
referrer: undefined,
}]
})
assertEquals(response.status, 418)
assertEquals(response.statusText, 'Im A Teapot')
assertEquals(await response.json(), { text: "success" })
});
it("should use return a 4xx error directly from a plugin, regardless of any following plugins", async () => {
window.LibResilientConfig = {
plugins: [{
name: 'return-418'
},{
name: 'reject-all'
},{
name: 'error-out'
}],
loggedComponents: [
'service-worker'
]
}
let rejectingFetch = spy(
(request, init)=>{ return Promise.reject('reject-all rejecting a request for: ' + request); }
)
window.LibResilientPluginConstructors.set('reject-all', ()=>{
return {
name: 'reject-all',
description: 'Reject all requests.',
version: '0.0.1',
fetch: rejectingFetch
}
})
let throwingFetch = spy(
(request, init)=>{ throw new Error('error-out throwing an Error for: ' + request); }
)
window.LibResilientPluginConstructors.set('error-out', ()=>{
return {
name: 'error-out',
description: 'Throws.',
version: '0.0.1',
fetch: throwingFetch
}
})
let mock_response_data = {
data: JSON.stringify({text: "success"}),
status: 418,
statusText: "Im A Teapot"
}
window.fetch = spy(window.getMockedFetch(mock_response_data))
window.LibResilientPluginConstructors.set('return-418', ()=>{
return {
name: 'return-418',
description: 'Return 418 HTTP Error.',
version: '0.0.1',
fetch: fetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent('test.json')
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
assertSpyCalls(window.fetch, 2); // two, because the first one is for config.json
assertSpyCalls(rejectingFetch, 0);
assertSpyCalls(throwingFetch, 0);
assertSpyCall(window.fetch, 1, { args: [
"https://test.resilient.is/test.json",
{
cache: undefined,
integrity: undefined,
method: "GET",
redirect: "follow",
referrer: undefined,
}]
})
assertEquals(response.status, 418)
assertEquals(response.statusText, 'Im A Teapot')
assertEquals(await response.json(), { text: "success" })
});
it("should use treat a 5xx error from a plugin as internal error and try following plugins", async () => {
window.LibResilientConfig = {
plugins: [{
name: 'return-500'
},{
name: 'reject-all'
},{
name: 'error-out'
}],
loggedComponents: [
'service-worker'
]
}
let rejectingFetch = spy(
(request, init)=>{ return Promise.reject('reject-all rejecting a request for: ' + request); }
)
window.LibResilientPluginConstructors.set('reject-all', ()=>{
return {
name: 'reject-all',
description: 'Reject all requests.',
version: '0.0.1',
fetch: rejectingFetch
}
})
let throwingFetch = spy(
(request, init)=>{ throw new Error('error-out throwing an Error for: ' + request); }
)
window.LibResilientPluginConstructors.set('error-out', ()=>{
return {
name: 'error-out',
description: 'Throws.',
version: '0.0.1',
fetch: throwingFetch
}
})
let mock_response_data = {
data: JSON.stringify({text: "success"}),
status: 500,
statusText: "Internal Server Error"
}
window.fetch = spy(window.getMockedFetch(mock_response_data))
window.LibResilientPluginConstructors.set('return-500', ()=>{
return {
name: 'return-500',
description: 'Return 500 HTTP Error.',
version: '0.0.1',
fetch: fetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent('test.json')
window.dispatchEvent(fetch_event)
let response = fetch_event.waitForResponse()
assertRejects( async () => {
return await response
})
// wait for the response to resolve
await response.catch((e)=>{})
assertSpyCalls(window.fetch, 2);
assertSpyCalls(rejectingFetch, 1);
assertSpyCalls(throwingFetch, 1);
});
it("should normalize query params in requested URLs by default", async () => {
console.log(self.LibResilientConfig)
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent('test.json?b=bbb&a=aaa&d=ddd&c=ccc')
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
assertEquals(
fetch.calls[1].args[0],
"https://test.resilient.is/test.json?a=aaa&b=bbb&c=ccc&d=ddd"
)
})
it("should not normalize query params in requested URLs if 'normalizeQueryParams' is set to false", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'fetch'
}],
loggedComponents: [
'service-worker'
],
normalizeQueryParams: false
}
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent('test.json?b=bbb&a=aaa&d=ddd&c=ccc')
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
assertEquals(
fetch.calls[1].args[0],
"https://test.resilient.is/test.json?b=bbb&a=aaa&d=ddd&c=ccc"
)
})
it("should pass the Request() init data to plugins", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'reject-all'
},{
name: 'resolve-all'
}],
loggedComponents: [
'service-worker'
]
}
let rejectingFetch = spy(
(request, init)=>{ return Promise.reject('reject-all rejecting a request for: ' + request); }
)
window.LibResilientPluginConstructors.set('reject-all', ()=>{
return {
name: 'reject-all',
description: 'Reject all requests.',
version: '0.0.1',
fetch: rejectingFetch
}
})
window.LibResilientPluginConstructors.set('resolve-all', ()=>{
return {
name: 'resolve-all',
description: 'Resolve all requests.',
version: '0.0.1',
fetch: fetch
}
})
let initTest = {
method: "GET",
// TODO: ref. https://gitlab.com/rysiekpl/libresilient/-/issues/23
//headers: new Headers({"x-stub": "STUB"}),
//mode: "mode-stub",
//credentials: "credentials-stub",
//cache: "cache-stub",
//referrer: "referrer-stub",
redirect: "error",
//integrity: ""
}
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent('test.json', initTest)
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
assertSpyCalls(rejectingFetch, 1);
assertSpyCalls(window.fetch, 2); // two, because the first one is for config.json
assertEquals(await response.json(), { test: "success" })
assertSpyCall(rejectingFetch, 0, { args: [
"https://test.resilient.is/test.json",
{
cache: undefined,
integrity: undefined,
method: "GET",
redirect: "error",
referrer: undefined,
}]
})
assertSpyCall(window.fetch, 1, { args: [
"https://test.resilient.is/test.json",
{
cache: undefined,
integrity: undefined,
method: "GET",
redirect: "error",
referrer: undefined,
}]
})
});
it("should respect defaultPluginTimeout", async () => {
window.LibResilientConfig = {
defaultPluginTimeout: 100,
plugins: [{
name: 'resolve-with-timeout'
}],
loggedComponents: [
'service-worker',
]
}
let rwtCallback = spy()
let rwt_timeout_id = null
window.LibResilientPluginConstructors.set('resolve-with-timeout', ()=>{
return {
name: 'resolve-with-timeout',
description: 'Resolve all requests after a timeout.',
version: '0.0.1',
fetch: (request, init)=>{
return new Promise((resolve, reject)=>{
rwt_timeout_id = setTimeout(rwtCallback, 300)
})
}
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent('test.json')
window.dispatchEvent(fetch_event)
let err = null
try {
let response = await fetch_event.waitForResponse()
} catch(e) {
err = e
}
clearTimeout(rwt_timeout_id)
assertEquals(err.toString(), "Error: LibResilient request using resolve-with-timeout timed out after 100ms.")
});
it("external request should work and not go through the plugins", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'reject-all'
}],
loggedComponents: [
'service-worker'
]
}
window.LibResilientPluginConstructors.set('reject-all', ()=>{
return {
name: 'reject-all',
description: 'Reject all requests.',
version: '0.0.1',
fetch: (request, init)=>{ return Promise.reject(request); }
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent('https://example.com/test.json')
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
assertEquals(await response.json(), { test: "success" })
})
it("should make POST requests not go through the plugins", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'reject-all'
}],
loggedComponents: [
'service-worker'
]
}
window.LibResilientPluginConstructors.set('reject-all', ()=>{
return {
name: 'reject-all',
description: 'Reject all requests.',
version: '0.0.1',
fetch: (request, init)=>{ return Promise.reject(request); }
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent(window.location.origin + 'test.json', {method: "POST"})
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
assertEquals(await response.json(), { test: "success" })
})
it("should stash content after a successful fetch", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'fetch'
},{
name: 'cache'
}],
loggedComponents: [
'service-worker', 'fetch', 'cache'
]
}
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent(window.location.origin + 'test.json')
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
assertEquals(await response.json(), { test: "success" })
// stashing plugin's stash() is called asynchronously in the Service Worker,
// if we don't make sure that the caching has completed, we will get an error.
// so we wait until config.json is cached, and use that to verify it is in fact
// cached
assertEquals(
JSON.parse(
await window.waitForCacheAction(window.location.origin + 'test.json', 'v1')),
{ test: "success" }
);
});
it("should skip stashing should if content was retrieved from a stashing plugin", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'stashing-test'
},{
name: 'reject-all'
}],
loggedComponents: [
'service-worker'
]
}
// three little mocks
let resolvingFetch = spy(window.getMockedFetch())
let rejectingFetch = spy((request, init)=>{ return Promise.reject(request); })
let stashingStash = spy()
// two little plugins
window.LibResilientPluginConstructors.set('stashing-test', ()=>{
return {
name: 'stashing-test',
description: 'Mock stashing plugin.',
version: '0.0.1',
fetch: resolvingFetch,
stash: stashingStash
}
})
window.LibResilientPluginConstructors.set('reject-all', ()=>{
return {
name: 'reject-all',
description: 'Reject all requests.',
version: '0.0.1',
fetch: rejectingFetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent(window.location.origin + 'test.json')
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
assertEquals(await response.json(), { test: "success" })
assertSpyCalls(resolvingFetch, 1)
assertSpyCalls(stashingStash, 0)
assertSpyCalls(rejectingFetch, 1)
});
it("should stash content if it was retrieved from a job after retrieval from a stashing plugin, and it differs from the stashed version", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'stashing-test'
},{
name: 'resolve-all'
}],
loggedComponents: [
'service-worker'
]
}
// three little mocks
let resolvingFetch = spy(window.getMockedFetch())
let resolvingFetch2 = spy(window.getMockedFetch({
data: JSON.stringify({ test: "success2" }),
headers: { 'X-LibResilient-ETag': 'NewTestingETagHeader' }
}))
let stashingStash = spy(async (response, url)=>{
assertEquals(await response.json(), { test: "success2" })
assertEquals(response.headers.get('X-LibResilient-ETag'), 'NewTestingETagHeader')
})
window.LibResilientPluginConstructors.set('stashing-test', ()=>{
return {
name: 'stashing-test',
description: 'Mock stashing plugin.',
version: '0.0.1',
fetch: resolvingFetch,
stash: stashingStash
}
})
window.LibResilientPluginConstructors.set('resolve-all', ()=>{
return {
name: 'resolve-all',
description: 'Resolve all requests.',
version: '0.0.1',
fetch: resolvingFetch2
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent(window.location.origin + 'test.json')
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
assertEquals(await response.json(), { test: "success" })
assertSpyCalls(resolvingFetch, 1)
assertSpyCalls(stashingStash, 1)
assertSpyCalls(resolvingFetch2, 1)
assertSpyCall(
window.clients.prototypePostMessage,
8,
{ args: [{
url: "https://test.resilient.is/test.json",
fetchedDiffers: true
}]}
)
});
it("should stash content when explicitly asked to", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'cache'
}],
loggedComponents: [
'service-worker', 'cache'
]
}
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let stashEvent = new Event('message')
stashEvent.data = {
stash: [await window.getMockedResponse(window.location.origin + 'test.json')]
}
// stash it!
await self.dispatchEvent(stashEvent)
// let's see if it got added to cache
assertEquals(
JSON.parse(await window.waitForCacheAction(window.location.origin + 'test.json', 'v1')),
{ test: "success" }
);
});
it("should pass the Request() init data to a background plugin after a retrieval from a stashing plugin", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'stashing-test'
},{
name: 'resolve-all'
}],
loggedComponents: [
'service-worker'
]
}
let resolvingFetch = spy(window.getMockedFetch({
headers: {
'X-LibResilient-Method': 'resolve-all',
'X-LibResilient-ETag': 'TestingETagHeader'
}
}))
let resolvingFetch2 = spy(window.getMockedFetch({
data: JSON.stringify({ test: "success2" }),
headers: {
'ETag': 'NewTestingETagHeader'
}
}))
let stashingStash = spy(async (response, url)=>{
assertEquals(await response.json(), { test: "success2" })
assertEquals(response.headers.get('ETag'), 'NewTestingETagHeader')
})
window.LibResilientPluginConstructors.set('stashing-test', ()=>{
return {
name: 'stashing-test',
description: 'Mock stashing plugin.',
version: '0.0.1',
fetch: resolvingFetch,
stash: stashingStash
}
})
window.LibResilientPluginConstructors.set('resolve-all', ()=>{
return {
name: 'resolve-all',
description: 'Resolve all requests.',
version: '0.0.1',
fetch: resolvingFetch2
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let initTest = {
method: "GET",
// TODO: ref. https://gitlab.com/rysiekpl/libresilient/-/issues/23
//headers: new Headers({"x-stub": "STUB"}),
//mode: "mode-stub",
//credentials: "same-origin",
cache: undefined,
referrer: undefined,
redirect: "error", // this is the only signal we get here, really!
integrity: undefined
}
let fetch_event = new FetchEvent(window.location.origin + 'test.json', initTest)
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
assertSpyCalls(resolvingFetch, 1);
assertSpyCalls(resolvingFetch2, 1);
assertEquals(await response.json(), { test: "success" })
assertSpyCall(
resolvingFetch,
0,
{ args: [
window.location.origin + 'test.json',
initTest
]}
)
assertSpyCall(
resolvingFetch2,
0,
{ args: [
window.location.origin + 'test.json',
initTest
]}
)
});
it("should unstash content when explicitly asked to", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'cache'
}],
loggedComponents: [
'service-worker', 'cache'
]
}
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let stashEvent = new Event('message')
stashEvent.data = {
stash: [await window.getMockedResponse(window.location.origin + 'test.json')]
}
// stash it!
await self.dispatchEvent(stashEvent)
// let's see if it got added to cache
assertEquals(
JSON.parse(await window.waitForCacheAction(window.location.origin + 'test.json', 'v1')),
{ test: "success" }
);
let unstashEvent = new Event("message")
unstashEvent.data = {
unstash: [window.location.origin + 'test.json']
}
// unstash it!
await self.dispatchEvent(unstashEvent)
// let's see if it got removed from cache
assertEquals(
await window.waitForCacheAction(window.location.origin + 'test.json', 'v1', "remove"),
undefined
);
});
it("should handle publishing content explicitly", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'publish-test'
}],
loggedComponents: [
'service-worker'
]
}
let publishMock = spy()
window.LibResilientPluginConstructors.set('publish-test', ()=>{
return {
name: 'publish-test',
description: 'Publish plugin fixture.',
version: '0.0.1',
publish: publishMock
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let publishEvent = new Event('message')
publishEvent.data = {
publish: [await window.getMockedResponse(window.location.origin + 'test.json')]
}
// publish it!
await self.dispatchEvent(publishEvent)
assertSpyCall(publishMock, 0, {
args: [publishEvent.data.publish[0]]
})
})
it("should be able to use plugins with dependencies correctly", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'dependent-test',
uses: [{
name: 'dependency1-test'
},{
name: 'dependency2-test'
}]
}],
loggedComponents: [
'service-worker'
]
}
window.LibResilientPluginConstructors.set('dependent-test', ()=>{
return {
name: 'dependent-test',
description: 'Dependent plugin fixture.',
version: '0.0.1',
uses: [{
name: 'dependency1-test'
},{
name: 'dependency2-test'
}]
}
})
window.LibResilientPluginConstructors.set('dependency1-test', ()=>{
return {
name: 'dependency1-test',
description: 'First dependency plugin fixture.',
version: '0.0.1'
}
})
window.LibResilientPluginConstructors.set('dependency2-test', ()=>{
return {
name: 'dependency2-test',
description: 'Second dependency plugin fixture.',
version: '0.0.1'
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(self.LibResilientPlugins.map(p=>p.name), ['dependent-test'])
assertEquals(self.LibResilientPlugins[0].uses.map(p=>p.name), ['dependency1-test', 'dependency2-test'])
})
it("should be able to use multiple instances of the same plugin", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'plugin-test',
},{
name: 'plugin-test',
},{
name: 'plugin-test',
}],
loggedComponents: [
'service-worker'
]
}
var pver = 0
window.LibResilientPluginConstructors.set('plugin-test', ()=>{
pver += 1
return {
name: 'plugin-test',
description: 'Simple plugin stub.',
version: '0.0.' + pver
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(self.LibResilientPlugins.map(p=>p.name), ['plugin-test', 'plugin-test', 'plugin-test'])
assertEquals(self.LibResilientPlugins.map(p=>p.version), ['0.0.1', '0.0.2', '0.0.3'])
})
it("should error out if all plugins fail", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'reject-all'
}],
loggedComponents: [
'service-worker'
]
}
window.LibResilientPluginConstructors.set('reject-all', ()=>{
return {
name: 'reject-all',
description: 'Reject all requests.',
version: '0.0.1',
fetch: (request, init)=>{ return Promise.reject(request); }
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent('test.json')
window.dispatchEvent(fetch_event)
assertRejects(
()=>{
return fetch_event.waitForResponse()
},
fetch_event.request
)
})
it("should send clientId back if event.resultingClientId is set", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'resolve-all'
}],
loggedComponents: [
'service-worker'
]
}
window.LibResilientPluginConstructors.set('resolve-all', ()=>{
return {
name: 'resolve-all',
description: 'Resolve all requests.',
version: '0.0.1',
fetch: window.fetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// we need a FetchEvent with a resultingClientId field set
let fetch_event = new FetchEvent('test.json')
fetch_event.resultingClientId = 'resulting-client-id-test'
// do the fetch and wait for the result that we don't really care about
window.dispatchEvent(fetch_event)
await fetch_event.waitForResponse()
// assert that resulting-client-id-test shows up in messages
// posted from the service worker
assertSpyCall(window.clients.prototypePostMessage, 0, {
args: [{
clientId: "resulting-client-id-test",
plugins: [ "resolve-all" ],
serviceWorker: "COMMIT_UNKNOWN"
}]
})
})
it("guessMimeType() should correctly guess content type based on extension by default", async () => {
// set things up
await import("../../service-worker.js?" + window.test_id);
self.pluginName = "service-worker-test"
// extensions we support, with associated MIME types
let ext_to_mime = new Map([
['htm', 'text/html'],
['html', 'text/html'],
['css', 'text/css'],
['js', 'text/javascript'],
['json', 'application/json'],
['svg', 'image/svg+xml'],
['ico', 'image/x-icon'],
['gif', 'image/gif'],
['png', 'image/png'],
['jpg', 'image/jpeg'],
['jpeg', 'image/jpeg'],
['jpe', 'image/jpeg'],
['jfif', 'image/jpeg'],
['pjpeg', 'image/jpeg'],
['pjp', 'image/jpeg'],
['webp', 'image/webp'],
['avi', 'video/avi'],
['mp4', 'video/mp4'],
['mp2', 'video/mpeg'],
['mp3', 'audio/mpeg'],
['mpa', 'video/mpeg'],
['pdf', 'application/pdf'],
['txt', 'text/plain'],
['ics', 'text/calendar'],
['jsonld', 'application/ld+json'],
['mjs', 'text/javascript'],
['oga', 'audio/ogg'],
['ogv', 'video/ogg'],
['ogx', 'application/ogg'],
['opus', 'audio/opus'],
['otf', 'font/otf'],
['ts', 'video/mp2t'],
['ttf', 'font/ttf'],
['weba', 'audio/webm'],
['webm', 'video/webm'],
['webp', 'image/webp'],
['woff', 'font/woff'],
['woff2', 'font/woff2'],
['xhtml', 'application/xhtml+xml'],
['xml', 'application/xml']
])
// check'em all
for (let [ext, mime] of ext_to_mime.entries()) {
assertEquals(await self.guessMimeType(ext, null), mime)
}
})
it("should attempt to load the external MIME type sniffing library if 'useMimeSniffingLibrary' is set to true", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'resolve-all',
}],
useMimeSniffingLibrary: true,
loggedComponents: ['service-worker']
}
window.fileType = {
fileTypeFromBuffer: (ext, content)=>{
console.log(`fileTypeFromBuffer(${ext}, ${content.length})`)
}
}
window.LibResilientPluginConstructors.set('resolve-all', ()=>{
return {
name: 'resolve-all',
description: 'Resolve all requests.',
version: '0.0.1',
fetch: fetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertSpyCall(self.importScripts, 0, {args: ["./lib/file-type.js"]})
})
it("should default to extension-based MIME sniffing if 'useMimeSniffingLibrary' is set to true but loading the library failed", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'resolve-all',
}],
useMimeSniffingLibrary: true,
loggedComponents: ['service-worker']
}
window.fileType = undefined
window.LibResilientPluginConstructors.set('resolve-all', ()=>{
return {
name: 'resolve-all',
description: 'Resolve all requests.',
version: '0.0.1',
fetch: fetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(await self.guessMimeType("png", "test arg 2"), "image/png")
})
it("should call the external library function in 'guessMimeType()', passing the second argument, if 'useMimeSniffingLibrary' is set to true", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'resolve-all',
}],
useMimeSniffingLibrary: true,
loggedComponents: ['service-worker']
}
window.fileType = {
fileTypeFromBuffer: spy(async (ext, content)=>{
return undefined
})
}
window.LibResilientPluginConstructors.set('resolve-all', ()=>{
return {
name: 'resolve-all',
description: 'Resolve all requests.',
version: '0.0.1',
fetch: fetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
await self.guessMimeType("test arg 1", "test arg 2")
assertSpyCall(window.fileType.fileTypeFromBuffer, 0, {args: ["test arg 2"]})
})
it("should revert to guessing MIME type based on extension if the external library function in 'guessMimeType()' errors out", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'resolve-all',
}],
useMimeSniffingLibrary: true,
loggedComponents: ['service-worker']
}
window.fileType = {
fileTypeFromBuffer: spy(async (ext, content)=>{
throw new Error('test error')
})
}
window.LibResilientPluginConstructors.set('resolve-all', ()=>{
return {
name: 'resolve-all',
description: 'Resolve all requests.',
version: '0.0.1',
fetch: fetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(await self.guessMimeType("gif", "test arg 2"), "image/gif")
assertSpyCalls(window.fileType.fileTypeFromBuffer, 1)
})
it("should revert to guessing MIME type based on extension if the external library function in 'guessMimeType()' fails to guess the MIME type", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'resolve-all',
}],
useMimeSniffingLibrary: true,
loggedComponents: ['service-worker']
}
window.fileType = {
fileTypeFromBuffer: spy(async (ext, content)=>{
return undefined
})
}
window.LibResilientPluginConstructors.set('resolve-all', ()=>{
return {
name: 'resolve-all',
description: 'Resolve all requests.',
version: '0.0.1',
fetch: fetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(await self.guessMimeType("png", "test arg 2"), "image/png")
assertSpyCalls(window.fileType.fileTypeFromBuffer, 1)
})
it("should revert to guessing MIME type based on extension if the external library function in 'guessMimeType()' returns an unexpected value type", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'resolve-all',
}],
useMimeSniffingLibrary: true,
loggedComponents: ['service-worker']
}
window.fileType = {
fileTypeFromBuffer: spy(async (ext, content)=>{
return "this should not be a string"
})
}
window.LibResilientPluginConstructors.set('resolve-all', ()=>{
return {
name: 'resolve-all',
description: 'Resolve all requests.',
version: '0.0.1',
fetch: fetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(await self.guessMimeType("jpg", "test arg 2"), "image/jpeg")
assertSpyCalls(window.fileType.fileTypeFromBuffer, 1)
})
it("should revert to guessing MIME type based on extension if the external library function in 'guessMimeType()' returns an incorrectly formatted object", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'resolve-all',
}],
useMimeSniffingLibrary: true,
loggedComponents: ['service-worker']
}
window.fileType = {
fileTypeFromBuffer: spy(async (ext, content)=>{
return {bad: "data"}
})
}
window.LibResilientPluginConstructors.set('resolve-all', ()=>{
return {
name: 'resolve-all',
description: 'Resolve all requests.',
version: '0.0.1',
fetch: fetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(await self.guessMimeType("txt", "test arg 2"), "text/plain")
assertSpyCalls(window.fileType.fileTypeFromBuffer, 1)
})
it("should ignore extension if the external library function in 'guessMimeType()' returns valid data", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'resolve-all',
}],
useMimeSniffingLibrary: true,
loggedComponents: ['service-worker']
}
window.fileType = {
fileTypeFromBuffer: spy(async (ext, content)=>{
return {ext: "txt", mime: "text/plain"}
})
}
window.LibResilientPluginConstructors.set('resolve-all', ()=>{
return {
name: 'resolve-all',
description: 'Resolve all requests.',
version: '0.0.1',
fetch: fetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(await self.guessMimeType("png", "test arg 2"), "text/plain")
assertSpyCalls(window.fileType.fileTypeFromBuffer, 1)
})
it("should not guess a MIME type if both external library function and built-in extension-based heuristic fail", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'resolve-all',
}],
useMimeSniffingLibrary: true,
loggedComponents: ['service-worker']
}
window.fileType = {
fileTypeFromBuffer: spy(async (ext, content)=>{
return {bad: "data"}
})
}
window.LibResilientPluginConstructors.set('resolve-all', ()=>{
return {
name: 'resolve-all',
description: 'Resolve all requests.',
version: '0.0.1',
fetch: fetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
// if we're not sure, according to RFC 7231 we should not set Content-Type
// (or, set it to an empty string)
// https://www.rfc-editor.org/rfc/rfc7231#section-3.1.1.5
assertEquals(await self.guessMimeType("no-such-extension", "test arg 2"), "")
assertSpyCalls(window.fileType.fileTypeFromBuffer, 1)
})
// ========================================================================
// ========================================================================
// ========================================================================
it("should use the still-loading screen when handling a navigation request with a stashing plugin configured and enabled and stillLoadingTimeout set to a positive integer", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'delayed-resolve'
},{
name: "mock-stash"
}],
defaultPluginTimeout: 100,
stillLoadingTimeout: 1,
loggedComponents: ['service-worker']
}
window.LibResilientPluginConstructors.set('delayed-resolve', ()=>{
return {
name: 'delayed-resolve',
description: 'Resolve all requests with a delay.',
version: '0.0.1',
fetch: async (url, init) => {
await new Promise(resolve => setTimeout(resolve, (self.LibResilientConfig.stillLoadingTimeout + 50)))
return fetch(url, init)
}
}
})
window.LibResilientPluginConstructors.set('mock-stash', ()=>{
return {
name: 'mock-stash',
description: 'No-op mock stashing plugin.',
version: '0.0.1',
fetch: a=>Promise.resolve(a),
stash: a=>Promise.resolve(a)
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent(window.location.origin + 'test.json', {mode: "navigate"})
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
await new Promise(resolve => setTimeout(resolve, (self.LibResilientConfig.stillLoadingTimeout + 50)))
assertEquals((await response.text()).slice(0, 55), '<!DOCTYPE html><html><head><title>Still loading</title>')
})
it("should not use the still-loading screen when handling a regular request, even if a stashing plugin is configured and enabled and stillLoadingTimeout set to a positive integer", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'delayed-resolve'
},{
name: "mock-stash"
}],
defaultPluginTimeout: 100,
stillLoadingTimeout: 1,
loggedComponents: ['service-worker']
}
window.LibResilientPluginConstructors.set('delayed-resolve', ()=>{
return {
name: 'delayed-resolve',
description: 'Resolve all requests with a delay.',
version: '0.0.1',
fetch: async (url, init) => {
await new Promise(resolve => setTimeout(resolve, (self.LibResilientConfig.stillLoadingTimeout + 50)))
return fetch(url, init)
}
}
})
window.LibResilientPluginConstructors.set('mock-stash', ()=>{
return {
name: 'mock-stash',
description: 'No-op mock stashing plugin.',
version: '0.0.1',
fetch: a=>Promise.resolve(a),
stash: a=>Promise.resolve(a)
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent(window.location.origin + 'test.json')
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
await new Promise(resolve => setTimeout(resolve, (self.LibResilientConfig.stillLoadingTimeout + 50)))
assertEquals(await response.json(), { test: "success" })
})
it("should not use the still-loading screen when handling a navigation request, even if a stashing plugin is configured and enabled, but stillLoadingTimeout is not set to a positive integer", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'delayed-resolve'
},{
name: "mock-stash"
}],
defaultPluginTimeout: 100,
stillLoadingTimeout: 0,
loggedComponents: ['service-worker']
}
window.LibResilientPluginConstructors.set('delayed-resolve', ()=>{
return {
name: 'delayed-resolve',
description: 'Resolve all requests with a delay.',
version: '0.0.1',
fetch: async (url, init) => {
await new Promise(resolve => setTimeout(resolve, (self.LibResilientConfig.stillLoadingTimeout + 50)))
return fetch(url, init)
}
}
})
window.LibResilientPluginConstructors.set('mock-stash', ()=>{
return {
name: 'mock-stash',
description: 'No-op mock stashing plugin.',
version: '0.0.1',
fetch: a=>Promise.resolve(a),
stash: a=>Promise.resolve(a)
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent(window.location.origin + 'test.json', {mode: "navigate"})
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
await new Promise(resolve => setTimeout(resolve, (self.LibResilientConfig.stillLoadingTimeout + 50)))
assertEquals(await response.json(), { test: "success" })
})
it("should not use the still-loading screen when handling a navigation request when a stashing plugin is not configured, even though stillLoadingTimeout is set to a positive integer", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'delayed-resolve'
}],
defaultPluginTimeout: 100,
stillLoadingTimeout: 1,
loggedComponents: ['service-worker']
}
window.LibResilientPluginConstructors.set('delayed-resolve', ()=>{
return {
name: 'delayed-resolve',
description: 'Resolve all requests with a delay.',
version: '0.0.1',
fetch: async (url, init) => {
await new Promise(resolve => setTimeout(resolve, (self.LibResilientConfig.stillLoadingTimeout + 50)))
return fetch(url, init)
}
}
})
window.LibResilientPluginConstructors.set('mock-stash', ()=>{
return {
name: 'mock-stash',
description: 'No-op mock stashing plugin.',
version: '0.0.1',
fetch: a=>Promise.resolve(a),
stash: a=>Promise.resolve(a)
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent(window.location.origin + 'test.json', {mode: "navigate"})
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
await new Promise(resolve => setTimeout(resolve, (self.LibResilientConfig.stillLoadingTimeout + 50)))
assertEquals(await response.json(), { test: "success" })
})
it("should not use the still-loading screen when handling a navigation request when a stashing plugin is configured but not enabled, even though stillLoadingTimeout is set to a positive integer", async () => {
self.LibResilientConfig = {
plugins: [{
name: 'delayed-resolve'
},{
name: "mock-stash",
enabled: false
}],
defaultPluginTimeout: 100,
stillLoadingTimeout: 1,
loggedComponents: ['service-worker']
}
window.LibResilientPluginConstructors.set('delayed-resolve', ()=>{
return {
name: 'delayed-resolve',
description: 'Resolve all requests with a delay.',
version: '0.0.1',
fetch: async (url, init) => {
await new Promise(resolve => setTimeout(resolve, (self.LibResilientConfig.stillLoadingTimeout + 50)))
return fetch(url, init)
}
}
})
window.LibResilientPluginConstructors.set('mock-stash', ()=>{
return {
name: 'mock-stash',
description: 'No-op mock stashing plugin.',
version: '0.0.1',
fetch: a=>Promise.resolve(a),
stash: a=>Promise.resolve(a)
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent(window.location.origin + 'test.json', {mode: "navigate"})
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
await new Promise(resolve => setTimeout(resolve, (self.LibResilientConfig.stillLoadingTimeout + 50)))
assertEquals(await response.json(), { test: "success" })
})
it("should return a 404 Not Found HTTP response object with an error screen when handling a rejected navigation request", async () => {
window.LibResilientConfig = {
plugins: [{
name: 'reject-all'
}],
loggedComponents: [
'service-worker'
]
}
let rejectingFetch = spy(
(request, init)=>{ return Promise.reject('reject-all rejecting a request for: ' + request); }
)
window.LibResilientPluginConstructors.set('reject-all', ()=>{
return {
name: 'reject-all',
description: 'Reject all requests.',
version: '0.0.1',
fetch: rejectingFetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent(window.location.origin + 'test.json', {mode: "navigate"})
window.dispatchEvent(fetch_event)
let response = await fetch_event.waitForResponse()
assertEquals(response.status, 404)
assertEquals(response.statusText, 'Not Found')
assertEquals(response.headers.get('content-type'), 'text/html')
assertEquals((await response.text()).slice(0, 57), '<!DOCTYPE html><html><head><title>Loading failed.</title>')
})
it("should not return a 404 Not Found HTTP response object with an error screen when handling a rejected non-navigation request", async () => {
window.LibResilientConfig = {
plugins: [{
name: 'reject-all'
}],
loggedComponents: [
'service-worker'
]
}
let rejectingFetch = spy(
(request, init)=>{ return Promise.reject('reject-all rejecting a request for: ' + request); }
)
window.LibResilientPluginConstructors.set('reject-all', ()=>{
return {
name: 'reject-all',
description: 'Reject all requests.',
version: '0.0.1',
fetch: rejectingFetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent(window.location.origin + 'test.json')
window.dispatchEvent(fetch_event)
assertRejects(async ()=>{ await fetch_event.waitForResponse() })
})
it("should not return a 404 Not Found HTTP response object with an error screen when handling a non-navigation request that throws an error", async () => {
window.LibResilientConfig = {
plugins: [{
name: 'error-out'
}],
loggedComponents: [
'service-worker'
]
}
let throwingFetch = spy(
(request, init)=>{ throw new Error('error-out throwing an Error for: ' + request); }
)
window.LibResilientPluginConstructors.set('error-out', ()=>{
return {
name: 'error-out',
description: 'Throws.',
version: '0.0.1',
fetch: throwingFetch
}
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
let fetch_event = new FetchEvent(window.location.origin + 'test.json')
window.dispatchEvent(fetch_event)
assertRejects(async ()=>{
await fetch_event.waitForResponse()
},
Error,
'error-out throwing an Error for: https://test.resilient.is/test.json'
)
})
})