82 lines
2.8 KiB
TypeScript
82 lines
2.8 KiB
TypeScript
// @vitest-environment node
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { createLearningRuntimeBridge } from '@electron/services/learning-runtime-bridge';
|
|
import type {
|
|
LearningRuntimeBridgeEvent,
|
|
LearningRuntimeBridgeRequest,
|
|
} from '../../shared/learning';
|
|
|
|
describe('Learning runtime bridge', () => {
|
|
const fetchImpl = vi.fn<typeof fetch>();
|
|
const getAccessToken = vi.fn();
|
|
|
|
beforeEach(() => {
|
|
fetchImpl.mockReset();
|
|
getAccessToken.mockReset();
|
|
getAccessToken.mockResolvedValue('works-token');
|
|
fetchImpl.mockResolvedValue(new Response(JSON.stringify({ score: 1 }), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}));
|
|
});
|
|
|
|
it('pins aggregate identity in the path/header and forwards only the module and scene anchor', async () => {
|
|
const bridge = createLearningRuntimeBridge({
|
|
fetchImpl,
|
|
getAccessToken,
|
|
apiBaseUrl: 'https://square.example',
|
|
});
|
|
const events: LearningRuntimeBridgeEvent[] = [];
|
|
const request = {
|
|
requestId: 'runtime-1',
|
|
courseId: 'course-1',
|
|
contentHash: 'a'.repeat(64),
|
|
capability: 'pbl/v2/evaluate',
|
|
method: 'POST',
|
|
context: {
|
|
moduleId: 'module-1',
|
|
moduleContentHash: 'b'.repeat(64),
|
|
anchor: {
|
|
sceneId: 'scene-2',
|
|
sceneOrder: 2,
|
|
courseId: 'iframe-course',
|
|
contentHash: 'c'.repeat(64),
|
|
moduleId: 'iframe-module',
|
|
actionIndex: 9,
|
|
},
|
|
},
|
|
body: { answer: 'B' },
|
|
} as unknown as LearningRuntimeBridgeRequest;
|
|
|
|
await bridge.request(request, (event) => events.push(event));
|
|
|
|
expect(fetchImpl).toHaveBeenCalledWith(
|
|
'https://square.example/api/learning/courses/course-1/runtime/pbl/v2/evaluate',
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
headers: expect.objectContaining({
|
|
Authorization: 'Bearer works-token',
|
|
'X-Course-Content-Hash': 'a'.repeat(64),
|
|
'X-Content-Hash': 'a'.repeat(64),
|
|
}),
|
|
}),
|
|
);
|
|
const init = fetchImpl.mock.calls[0][1]!;
|
|
expect(JSON.parse(String(init.body))).toEqual({
|
|
context: {
|
|
moduleId: 'module-1',
|
|
moduleContentHash: 'b'.repeat(64),
|
|
anchor: { sceneId: 'scene-2', sceneOrder: 2 },
|
|
},
|
|
body: { answer: 'B' },
|
|
});
|
|
expect(events[0]).toMatchObject({ type: 'start', status: 200, contentType: 'application/json' });
|
|
expect(events.at(-1)).toEqual({ requestId: 'runtime-1', type: 'end' });
|
|
const responseText = events
|
|
.filter((event): event is Extract<LearningRuntimeBridgeEvent, { type: 'chunk' }> => event.type === 'chunk')
|
|
.map((event) => new TextDecoder().decode(event.chunk))
|
|
.join('');
|
|
expect(JSON.parse(responseText)).toEqual({ score: 1 });
|
|
});
|
|
});
|