-
richard.petersen authoredrichard.petersen authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
file_caching_test.js 9.97 KiB
import request from 'supertest'
import { generateSimpleViteManifest, mockApp, mockConfig, mockFetch, mockRedis } from './util.js'
import fs from 'fs'
import { expect } from 'chai'
import * as td from 'testdouble'
import { Response } from 'node-fetch'
import RedisMock from 'ioredis-mock'
import sinon from 'sinon'
const image = fs.readFileSync('./spec/media/image.png')
const imageStat = fs.statSync('./spec/media/image.png')
const sandbox = sinon.createSandbox()
describe('File caching service', function () {
let app
let redis
beforeEach(async function () {
let count = 0
mockConfig({ urls: ['http://ui-server/'] })
redis = mockRedis()
mockFetch({
'http://ui-server': {
'/manifest.json': generateSimpleViteManifest({
'example.js': { imports: ['test.txt'] },
'test.txt': { },
'main.css': {},
'index.html': {
file: 'index.html.js',
isEntry: true,
imports: ['example.js'],
css: ['main.css']
},
'image.png': {}
}),
'/example.js': () => new Response('this is example', { headers: { 'content-type': 'application/javascript' } }),
'/test.txt': () => new Response('this is test', { headers: { 'content-type': 'text/plain' } }),
'/index.html.js': () => new Response('this is index.html.js', { headers: { 'content-type': 'application/javascript' } }),
'/index.html': () => new Response('<html><head></head><body>it\'s me</body></html>', { headers: { 'content-type': 'text/html' } }),
'/main.css': () => new Response('.foo { color: #000; }', { headers: { 'content-type': 'text/css' } }),
'/favicon.ico': 'not really a favicon, though',
'/test.svg': () => {
if (count > 0) {
return new Response(null, { status: 404 })
}
count++
return new Response('<svg></svg>', { headers: { 'content-type': 'image/svg' } })
},
'/image.png': () => {
return new Response(image, {
headers: {
'Content-Type': 'image/png',
'Content-Length': imageStat.size
}
})
}
}
})
app = await mockApp()
})
afterEach(async function () {
await new RedisMock().flushdb()
td.reset()
})
it('serves files defined in manifest.json file', async function () {
const response = await request(app).get('/example.js')
expect(response.statusCode).to.equal(200)
expect(response.headers['content-type']).to.equal('application/javascript; charset=utf-8')
expect(response.text).to.equal('this is example')
// expect(response.headers['content-security-policy']).to.contain('sha256-NzZhMTE2Njc2YTgyNTZmZTdlZGVjZDU3YTNmYzRjNmM1OWZkMTI2NjRkYzZmMWM3YTkwMGU3ZTdhNDlhZmVlMwo=')
const response2 = await request(app).get('/test.txt')
expect(response2.statusCode).to.equal(200)
expect(response2.headers['content-type']).to.equal('text/plain; charset=utf-8')
expect(response2.text).to.equal('this is test')
})
it('serves css files', async function () {
const response = await request(app).get('/main.css')
expect(response.statusCode).to.equal(200)
expect(response.headers['content-type']).to.equal('text/css; charset=utf-8')
// expect(response.headers['content-security-policy']).to.contain('sha256-YjRiYWRlYTVhYmM5ZTZkNjE2ZGM4YjcwZWRlNzUxMmU0YjgxY2UxMWExOTI2ZjM1NzM1M2Y2MWJjNmUwMmZjMwo=')
})
it('serves / as index.html', async function () {
const response = await request(app).get('/')
expect(response.statusCode).to.equal(200)
expect(response.headers['content-type']).to.equal('text/html; charset=utf-8')
expect(response.text).to.equal('<html><head></head><body>it\'s me</body></html>')
})
it('directly fetches files not referenced in manifest.json files from the upstream servers', async function () {
const response = await request(app).get('/favicon.ico')
expect(response.statusCode).to.equal(200)
expect(response.text).to.equal('not really a favicon, though')
})
it('returns 404 if file can not be resolved', async function () {
const response = await request(app).get('/unknown-file.txt')
expect(response.statusCode).to.equal(404)
})
it('serves binary files', async function () {
const response = await request(app).get('/image.png')
expect(response.statusCode).to.equal(200)
expect(response.body.length === imageStat.size)
expect(response.body).to.deep.equal(image)
})
it('only fetches files once', async function () {
let spy
mockFetch({
'http://ui-server': {
'/manifest.json': generateSimpleViteManifest({}),
'/example.js': spy = sandbox.spy(() => {
return new Response('this is example', { headers: { 'content-type': 'application/javascript' } })
})
}
})
app = await mockApp()
expect(spy.callCount).to.equal(0)
let response = await request(app).get('/example.js')
expect(response.statusCode).to.equal(200)
expect(spy.callCount).to.equal(1)
response = await request(app).get('/example.js')
expect(response.statusCode).to.equal(200)
expect(spy.callCount).to.equal(1)
})
it('only fetches files once, but deliver from local cache', async function () {
let spy
mockFetch({
'http://ui-server': {
'/manifest.json': generateSimpleViteManifest({}),
'/example.js': spy = sandbox.spy(() => {
return new Response('this is example', { headers: { 'content-type': 'application/javascript' } })
})
}
})
app = await mockApp()
expect(spy.callCount).to.equal(0)
let response = await request(app).get('/example.js')
expect(response.statusCode).to.equal(200)
expect(spy.callCount).to.equal(1)
// delete file from redis
await redis.client.del(`${response.headers.version}:/example.js:body`)
await redis.client.del(`${response.headers.version}:/example.js:meta`)
// and fetch once more
response = await request(app).get('/example.js')
expect(response.statusCode).to.equal(200)
expect(spy.callCount).to.equal(1)
})
it('delivers binary files from cache', async function () {
let spy
mockFetch({
'http://ui-server': {
'/manifest.json': generateSimpleViteManifest({}),
'/image.png': spy = sandbox.spy(() => {
return new Response(image, {
headers: {
'Content-Type': 'image/png',
'Content-Length': imageStat.size
}
})
})
}
})
app = await mockApp()
expect(spy.callCount).to.equal(0)
let response = await request(app).get('/image.png')
expect(response.statusCode).to.equal(200)
expect(response.body).to.deep.equal(image)
expect(spy.callCount).to.equal(1)
response = await request(app).get('/image.png')
expect(response.statusCode).to.equal(200)
expect(response.body).to.deep.equal(image)
expect(spy.callCount).to.equal(1)
})
it('a file is not cached again, if loaded from cache', async function () {
const spy = sandbox.spy(redis.client, 'set')
let response = await request(app).get('/example.js')
expect(response.statusCode).to.equal(200)
// called 4 times.
// once for manifests
// once for dependencies
// two times for for example.js (meta and body)
expect(spy.callCount).to.equal(4)
response = await request(app).get('/example.js')
expect(response.statusCode).to.equal(200)
// should still be called 4 times, because everything is in cache
expect(spy.callCount).to.equal(4)
})
it('requests known file only from one server', async function () {
let spy1, spy2
mockConfig({ urls: ['http://ui-server1/', 'http://ui-server2/'] })
// we have example.js in both files. the first one will be overwritten and therefore not be called
mockFetch({
'http://ui-server1': {
'/manifest.json': generateSimpleViteManifest({
'example.js': { }
}),
'/example.js': spy1 = sandbox.spy(() => {
return new Response('example', { headers: { 'content-type': 'text/plain' } })
})
},
'http://ui-server2': {
'/manifest.json': generateSimpleViteManifest({
'example.js': { }
}),
'/example.js': spy2 = sandbox.spy(() => {
return new Response('example', { headers: { 'content-type': 'text/plain' } })
})
}
})
app = await mockApp()
const response = await request(app).get('/example.js')
expect(response.statusCode).to.equal(200)
expect(spy1.callCount).to.equal(0)
expect(spy2.callCount).to.equal(1)
})
it('serves cached files with version', async function () {
// we have example.js in both files. the first one will be overwritten and therefore not be called
mockFetch({
'http://ui-server': {
'/manifest.json': generateSimpleViteManifest({
'example.js': { }
}),
'/example.js': td.when(td.func()(td.matchers.anything())).thenReturn(
new Response('first', { headers: { 'content-type': 'text/plain' } }),
new Response('second', { headers: { 'content-type': 'text/plain' } })
)
}
})
app = await mockApp()
const response1 = await request(app).get('/example.js').set('version', 1234)
expect(response1.statusCode).to.equal(200)
expect(response1.text).to.equal('first')
const response2 = await request(app).get('/example.js')
expect(response2.statusCode).to.equal(200)
expect(response2.text).to.equal('second')
const latestVersion = response2.headers['latest-version']
const response3 = await request(app).get('/example.js').set('version', 1234)
expect(response3.statusCode).to.equal(200)
expect(response3.text).to.equal('first')
const response4 = await request(app).get('/example.js')
expect(response4.statusCode).to.equal(200)
expect(response4.text).to.equal('second')
const response5 = await request(app).get('/example.js').set('version', latestVersion)
expect(response5.statusCode).to.equal(200)
expect(response5.text).to.equal('second')
})
})