// @vitest-environment jsdom import { act, createElement } from 'react'; import { createRoot, type Root } from 'react-dom/client'; import { afterEach, describe, expect, it, vi } from 'vitest'; const mocks = vi.hoisted(() => ({ opsEnabled: true, push: vi.fn(), })); vi.mock('next/navigation', () => ({ useRouter: () => ({ push: mocks.push }), })); vi.mock('@/lib/config/deployment-role', () => ({ isOpsWorkbenchEnabled: () => mocks.opsEnabled, })); vi.mock('@/lib/hooks/use-i18n', () => ({ useI18n: () => ({ t: (key: string) => key, }), })); import { GenerationJobsPanel } from '@/components/classroom/generation-jobs-panel'; ( globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } ).IS_REACT_ACT_ENVIRONMENT = true; let root: Root | null = null; let container: HTMLDivElement | null = null; async function renderPanel() { container = document.createElement('div'); document.body.appendChild(container); root = createRoot(container); await act(async () => { root?.render(createElement(GenerationJobsPanel)); await new Promise((resolve) => setTimeout(resolve, 0)); }); } function jsonResponse(body: unknown, status = 200): Response { return new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' }, }); } afterEach(async () => { if (root) { await act(async () => root?.unmount()); } container?.remove(); root = null; container = null; mocks.opsEnabled = true; mocks.push.mockReset(); vi.unstubAllGlobals(); }); describe('GenerationJobsPanel deployment boundary', () => { it('does not request the operations course API on a learner deployment', async () => { mocks.opsEnabled = false; const fetchMock = vi.fn(async () => jsonResponse({ items: [ { jobId: 'job-1', status: 'queued', step: 'queued', progress: 0, message: 'Waiting', createdAt: '2026-08-15T00:00:00.000Z', requirement: 'Learner single course', scenesGenerated: 0, done: false, }, ], }), ); vi.stubGlobal('fetch', fetchMock); await renderPanel(); expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledWith('/api/generate-classroom?limit=10'); expect(container?.textContent).toContain('Learner single course'); }); it('keeps single-courseware jobs visible when the operations course API returns 403', async () => { const fetchMock = vi.fn(async (input: RequestInfo | URL) => { const url = String(input); if (url.startsWith('/api/generate-classroom')) { return jsonResponse({ items: [ { jobId: 'job-2', status: 'succeeded', step: 'done', progress: 100, message: 'Done', createdAt: '2026-08-15T00:00:00.000Z', requirement: 'Completed single course', scenesGenerated: 3, totalScenes: 3, result: { classroomId: 'classroom-2', url: '/classroom/classroom-2', scenesCount: 3 }, done: true, }, ], }); } return jsonResponse({ success: false, errorCode: 'FORBIDDEN' }, 403); }); vi.stubGlobal('fetch', fetchMock); await renderPanel(); expect(fetchMock).toHaveBeenCalledWith('/api/courses?limit=5'); expect(container?.textContent).toContain('Completed single course'); expect(container?.textContent).not.toContain('jobs.loadFailed'); }); });