96 lines
4.0 KiB
TypeScript
96 lines
4.0 KiB
TypeScript
// @vitest-environment node
|
|
import { createHash } from 'node:crypto';
|
|
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
import AdmZip from 'adm-zip';
|
|
import {
|
|
createLearningPlayerServer,
|
|
registerLearningCoursePackage,
|
|
} from '@electron/services/learning-player-server';
|
|
|
|
async function writePlayerArtifact(root: string): Promise<void> {
|
|
const html = '<!doctype html><main>player</main>';
|
|
await mkdir(join(root, '_next', 'static', 'chunks'), { recursive: true });
|
|
await mkdir(join(root, 'avatars'), { recursive: true });
|
|
await mkdir(join(root, 'public'), { recursive: true });
|
|
await writeFile(join(root, 'index.html'), html);
|
|
await writeFile(join(root, 'artifact.json'), JSON.stringify({
|
|
schemaVersion: 1,
|
|
entrypoint: 'index.html',
|
|
htmlSha256: createHash('sha256').update(html).digest('hex'),
|
|
}));
|
|
await writeFile(join(root, '_next', 'static', 'chunks', 'app.js'), 'window.player = true');
|
|
await writeFile(join(root, 'avatars', 'teacher.png'), 'avatar');
|
|
await writeFile(join(root, 'public', 'openmaic-mark.png'), 'mark');
|
|
}
|
|
|
|
describe('Learning player static server', () => {
|
|
let root: string | null = null;
|
|
let close: (() => Promise<void>) | null = null;
|
|
|
|
afterEach(async () => {
|
|
if (close) await close();
|
|
if (root) await rm(root, { recursive: true, force: true });
|
|
close = null;
|
|
root = null;
|
|
});
|
|
|
|
it('serves only the embedded OpenMAIC player and immutable static assets', async () => {
|
|
root = await mkdtemp(join(tmpdir(), 'makelore-player-server-'));
|
|
await writePlayerArtifact(root);
|
|
const server = await createLearningPlayerServer({ artifactRoot: root });
|
|
close = server.close;
|
|
|
|
const index = await fetch(server.url);
|
|
expect(index.status).toBe(200);
|
|
expect(await index.text()).toContain('player');
|
|
expect(index.headers.get('content-security-policy')).toContain("object-src 'none'");
|
|
|
|
const asset = await fetch(new URL('/_next/static/chunks/app.js', server.url));
|
|
expect(asset.headers.get('cache-control')).toContain('immutable');
|
|
expect(await asset.text()).toContain('window.player');
|
|
|
|
const avatar = await fetch(new URL('/avatars/teacher.png', server.url));
|
|
expect(avatar.status).toBe(200);
|
|
expect(avatar.headers.get('content-type')).toBe('image/png');
|
|
|
|
const publicAsset = await fetch(new URL('/openmaic-mark.png', server.url));
|
|
expect(publicAsset.status).toBe(200);
|
|
expect(publicAsset.headers.get('cache-control')).toContain('immutable');
|
|
expect(await publicAsset.text()).toBe('mark');
|
|
|
|
expect((await fetch(new URL('/package.json', server.url))).status).toBe(404);
|
|
expect((await fetch(new URL('/%2e%2e/artifact.json', server.url))).status).toBe(404);
|
|
});
|
|
|
|
it('serves only audio and media entries from a verified registered course package', async () => {
|
|
root = await mkdtemp(join(tmpdir(), 'makelore-player-server-'));
|
|
await writePlayerArtifact(root);
|
|
const archivePath = join(root, 'course.zip');
|
|
const archive = new AdmZip();
|
|
archive.addFile('modules/module-1/audio/line.mp3', Buffer.from('audio-bytes'));
|
|
archive.addFile('modules/module-1/media/diagram.png', Buffer.from('image-bytes'));
|
|
archive.addFile('modules/module-1/bundle.json', Buffer.from('{}'));
|
|
archive.writeZip(archivePath);
|
|
const contentHash = 'a'.repeat(64);
|
|
registerLearningCoursePackage('course-assets-test', contentHash, archivePath);
|
|
const server = await createLearningPlayerServer({ artifactRoot: root });
|
|
close = server.close;
|
|
|
|
const audio = await fetch(new URL(
|
|
`/course-assets/course-assets-test/${contentHash}/modules/module-1/audio/line.mp3`,
|
|
server.url,
|
|
));
|
|
expect(audio.status).toBe(200);
|
|
expect(audio.headers.get('content-type')).toBe('audio/mpeg');
|
|
expect(await audio.text()).toBe('audio-bytes');
|
|
|
|
expect((await fetch(new URL(
|
|
`/course-assets/course-assets-test/${contentHash}/modules/module-1/bundle.json`,
|
|
server.url,
|
|
))).status).toBe(404);
|
|
});
|
|
});
|