61 lines
2.5 KiB
TypeScript
61 lines
2.5 KiB
TypeScript
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
import { NextRequest } from 'next/server';
|
|
import { POST } from '@/app/api/runtime/v1/bindings/validate/route';
|
|
|
|
describe('Makelore Learning Runtime binding', () => {
|
|
let root = '';
|
|
const previousStore = process.env.MAKELORE_COURSE_STORE_DIR;
|
|
const previousToken = process.env.MAKELORE_RUNTIME_TOKEN;
|
|
|
|
beforeEach(async () => {
|
|
root = await mkdtemp(join(tmpdir(), 'makelore-runtime-'));
|
|
process.env.MAKELORE_COURSE_STORE_DIR = root;
|
|
process.env.MAKELORE_RUNTIME_TOKEN = 'runtime-secret';
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await rm(root, { recursive: true, force: true });
|
|
if (previousStore === undefined) delete process.env.MAKELORE_COURSE_STORE_DIR;
|
|
else process.env.MAKELORE_COURSE_STORE_DIR = previousStore;
|
|
if (previousToken === undefined) delete process.env.MAKELORE_RUNTIME_TOKEN;
|
|
else process.env.MAKELORE_RUNTIME_TOKEN = previousToken;
|
|
});
|
|
|
|
it('accepts only a retained course with the exact immutable hash', async () => {
|
|
const hash = 'a'.repeat(64);
|
|
await mkdir(root, { recursive: true });
|
|
await writeFile(join(root, 'course-1.json'), JSON.stringify({
|
|
schemaVersion: 1,
|
|
courseId: 'course-1',
|
|
contentHash: hash,
|
|
title: 'Python 基础入门',
|
|
classroom: { stage: { id: 'stage-1', name: 'Python 基础入门' }, scenes: [] },
|
|
}));
|
|
const request = new NextRequest('http://localhost/api/runtime/v1/bindings/validate', {
|
|
method: 'POST',
|
|
headers: { Authorization: 'Bearer runtime-secret', 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
session_id: 'session-1', owner_user_id: 'user-1', runtime: 'learning', runtime_version: 'v1',
|
|
binding: { kind: 'learning-course', key: `course-1:${hash}` },
|
|
}),
|
|
});
|
|
|
|
const response = await POST(request);
|
|
expect(response.status).toBe(200);
|
|
await expect(response.json()).resolves.toMatchObject({
|
|
type: 'learning.course.snapshot',
|
|
payload: { courseId: 'course-1', contentHash: hash, sceneCount: 0 },
|
|
});
|
|
});
|
|
|
|
it('rejects calls without the private runtime token', async () => {
|
|
const response = await POST(new NextRequest('http://localhost/api/runtime/v1/bindings/validate', {
|
|
method: 'POST', body: '{}', headers: { 'Content-Type': 'application/json' },
|
|
}));
|
|
expect(response.status).toBe(401);
|
|
});
|
|
});
|