mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-26 23:48:52 -07:00
Add hosted Studio template distribution (#139)
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import { env, SELF } from 'cloudflare:test'
|
||||
import { beforeEach, expect, it } from 'vitest'
|
||||
|
||||
import type { Env } from '../../context'
|
||||
|
||||
declare module 'cloudflare:test' {
|
||||
interface ProvidedEnv extends Env {}
|
||||
}
|
||||
|
||||
const origin = 'https://example.com/1/apps/template-fixture'
|
||||
beforeEach(async () => {
|
||||
env.STUDIO_DISTRIBUTION_MANIFEST = 'distribution/catalog.json'
|
||||
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode('template-fixture'))
|
||||
await env.CDN_ASSETS.put(
|
||||
env.STUDIO_DISTRIBUTION_MANIFEST,
|
||||
JSON.stringify({
|
||||
appSecretSha256: Array.from(new Uint8Array(digest), (b) =>
|
||||
b.toString(16).padStart(2, '0')
|
||||
).join(''),
|
||||
latest: 170,
|
||||
legacySummaries: { '169': { files: [{ path: 'original.tgz', hash: '87654321', size: 80 }] } },
|
||||
releases: [
|
||||
{
|
||||
version: 170,
|
||||
label: 'v170',
|
||||
partSize: 32,
|
||||
contentUrl: 'https://cdn.example.test/template.zip',
|
||||
summary: {
|
||||
hash_code: 'a'.repeat(64),
|
||||
compression_method: 'zip',
|
||||
size: 50,
|
||||
files: [{ path: 'patched.tgz', hash: '12345678', size: 80 }],
|
||||
chunks: { size: 32, hashes: ['12345678', '87654321'] },
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
it('serves the prepatched v170 catalog and complete multipart download contract', async () => {
|
||||
expect(await (await SELF.fetch(origin + '/changelog')).json()).toEqual({
|
||||
versions: [{ vid: 170, label: 'v170', changelog: 'v170' }],
|
||||
})
|
||||
expect(await (await SELF.fetch(origin + '/versions/latest/id')).json()).toEqual({ id: 170 })
|
||||
expect(await (await SELF.fetch(origin)).json()).toMatchObject({
|
||||
current_version: 170,
|
||||
use_keys: false,
|
||||
})
|
||||
expect(await (await SELF.fetch(origin + '/versions/170')).json()).toMatchObject({
|
||||
id: 170,
|
||||
label: 'v170',
|
||||
patch_links: { incoming: [], outgoing: [] },
|
||||
content_guid: 'a'.repeat(64),
|
||||
})
|
||||
expect(await (await SELF.fetch(origin + '/versions/170/content_urls')).json()).toEqual([
|
||||
{ url: 'https://cdn.example.test/template.zip', part_size: 32 },
|
||||
])
|
||||
expect(await (await SELF.fetch(origin + '/versions/170/content_summary')).json()).toMatchObject({
|
||||
compression_method: 'zip',
|
||||
chunks: { hashes: ['12345678', '87654321'] },
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects malformed or unsafe catalogs without caching a broken update response', async () => {
|
||||
const key = env.STUDIO_DISTRIBUTION_MANIFEST!
|
||||
const original = await (await env.CDN_ASSETS.get(key))!.text()
|
||||
for (const change of [
|
||||
(c: any) => {
|
||||
c.latest = 171
|
||||
},
|
||||
(c: any) => {
|
||||
c.releases.push(c.releases[0])
|
||||
},
|
||||
(c: any) => {
|
||||
c.legacySummaries['170'] = c.legacySummaries['169']
|
||||
},
|
||||
(c: any) => {
|
||||
c.releases[0].contentUrl = 'http://cdn.example.test/content.zip'
|
||||
},
|
||||
(c: any) => {
|
||||
c.releases[0].summary.files[0].path = '../outside'
|
||||
},
|
||||
(c: any) => {
|
||||
c.releases[0].summary.files = null
|
||||
},
|
||||
(c: any) => {
|
||||
delete c.releases[0].summary.hash_code
|
||||
},
|
||||
(c: any) => {
|
||||
c.releases[0].summary.chunks.hashes = []
|
||||
},
|
||||
]) {
|
||||
const catalog = JSON.parse(original)
|
||||
change(catalog)
|
||||
await env.CDN_ASSETS.put(key, JSON.stringify(catalog))
|
||||
const response = await SELF.fetch(origin + '/versions/170/diff_summary')
|
||||
expect(response.status).toBe(503)
|
||||
expect(response.headers.get('Cache-Control')).toBe('no-store')
|
||||
}
|
||||
await env.CDN_ASSETS.put(key, '{')
|
||||
expect((await SELF.fetch(origin)).status).toBe(503)
|
||||
await env.CDN_ASSETS.put(key, ' '.repeat(8 * 1024 * 1024 + 1))
|
||||
expect((await SELF.fetch(origin)).status).toBe(503)
|
||||
})
|
||||
it('does not route other applications, missing versions, or disabled catalogs', async () => {
|
||||
expect((await SELF.fetch('https://example.com/1/apps/unrelated/versions/latest/id')).status).toBe(
|
||||
404
|
||||
)
|
||||
expect(await (await SELF.fetch(origin + '/versions/169')).json()).toMatchObject({
|
||||
id: 169,
|
||||
label: 'v169',
|
||||
})
|
||||
expect((await SELF.fetch(origin + '/versions/168')).status).toBe(404)
|
||||
expect((await SELF.fetch(origin + '/versions/169/content_urls')).status).toBe(404)
|
||||
delete env.STUDIO_DISTRIBUTION_MANIFEST
|
||||
expect((await SELF.fetch(origin + '/versions/latest/id')).status).toBe(404)
|
||||
})
|
||||
|
||||
it('provides cached comparison and full-replacement update metadata without legacy downloads', async () => {
|
||||
expect(await (await SELF.fetch(origin + '/versions/169/content_summary')).json()).toMatchObject({
|
||||
files: [{ path: 'original.tgz', hash: '87654321' }],
|
||||
})
|
||||
expect(await (await SELF.fetch(origin + '/versions/170/diff_summary')).json()).toMatchObject({
|
||||
compression_method: 'zip',
|
||||
added_files: ['patched.tgz'],
|
||||
modified_files: [],
|
||||
unchanged_files: [],
|
||||
})
|
||||
expect(await (await SELF.fetch(origin + '/versions/170/diff_urls')).json()).toEqual([
|
||||
{ url: 'https://cdn.example.test/template.zip', part_size: 32 },
|
||||
])
|
||||
})
|
||||
Reference in New Issue
Block a user