import { afterEach, describe, expect, test, vi } from 'vitest'; import { NextRequest } from 'next/server'; import { middleware } from '@/middleware'; afterEach(() => { vi.unstubAllEnvs(); vi.unstubAllGlobals(); }); describe('ACCESS_CODE middleware deployment boundary', () => { test.each([ ['/', 'text/plain; charset=utf-8'], ['/learn', 'text/plain; charset=utf-8'], ['/classroom/classroom-1', 'text/plain; charset=utf-8'], ['/api/courses', 'application/json'], ['/api/courses/course-1/publish', 'application/json'], ['/api/ops', 'application/json'], ['/api/ops/jobs', 'application/json'], ['/api/access-code/status', 'application/json'], ['/api/access-code/verify', 'application/json'], ['/api/proxy-media', 'application/json'], ['/api/usage', 'application/json'], ['/api/provider/probe-models', 'application/json'], ['/api/azure-voices', 'application/json'], ['/api/verify-model', 'application/json'], ['/api/export-video/render', 'application/json'], ['/api/persistence/documents', 'application/json'], ['/api/generate-classroom', 'application/json'], ])('returns a direct 404 for a server-owned hidden path: %s', async (pathname, contentType) => { vi.stubEnv('NODE_ENV', 'production'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'server'); vi.stubEnv('ACCESS_CODE', 'shared-compose-access-code'); const response = await middleware(new NextRequest(`https://server.example${pathname}`)); expect(response.status).toBe(404); expect(response.headers.get('location')).toBeNull(); expect(response.headers.get('x-middleware-next')).toBeNull(); expect(response.headers.get('content-type')).toContain(contentType); }); test('allows the internal batch publish route through to Bearer authentication', async () => { vi.stubEnv('NODE_ENV', 'production'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'server'); vi.stubEnv('ACCESS_CODE', 'shared-compose-access-code'); const response = await middleware( new NextRequest('https://server.example/api/internal/course-publish', { method: 'POST', headers: { authorization: 'Bearer server-publish-token' }, }), ); expect(response.status).toBe(200); expect(response.headers.get('x-middleware-next')).toBe('1'); }); test('hides both single-course generation POST and its ownerless global job list', async () => { vi.stubEnv('NODE_ENV', 'production'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'server'); const createResponse = await middleware( new NextRequest('https://server.example/api/generate-classroom', { method: 'POST' }), ); const listResponse = await middleware( new NextRequest('https://server.example/api/generate-classroom'), ); expect(createResponse.status).toBe(404); expect(listResponse.status).toBe(404); }); test.each(['/api/generate-classroom', '/api/generate-classroom/'])( 'hides the ownerless global job list from HEAD requests: %s', async (pathname) => { vi.stubEnv('NODE_ENV', 'production'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'server'); const response = await middleware( new NextRequest(`https://server.example${pathname}`, { method: 'HEAD' }), ); expect(response.status).toBe(404); expect(response.headers.get('x-middleware-next')).toBeNull(); }, ); test('does not let a trailing slash bypass the internal publish method boundary', async () => { vi.stubEnv('NODE_ENV', 'production'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'server'); const response = await middleware( new NextRequest('https://server.example/api/internal/course-publish/', { method: 'GET', }), ); expect(response.status).toBe(404); expect(response.headers.get('x-middleware-next')).toBeNull(); }); test('does not expose legacy publish APIs even when a caller supplies a Bearer', async () => { vi.stubEnv('NODE_ENV', 'production'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'server'); vi.stubEnv('ACCESS_CODE', 'shared-compose-access-code'); const response = await middleware( new NextRequest('https://server.example/api/coursewares', { method: 'POST', headers: { authorization: 'Bearer server-publish-token' }, }), ); expect(response.status).toBe(404); expect(response.headers.get('x-middleware-next')).toBeNull(); }); test('preserves learner handling for ordinary and operations paths', async () => { vi.stubEnv('NODE_ENV', 'production'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'learner'); vi.stubEnv('ACCESS_CODE', ''); const ordinaryPage = await middleware(new NextRequest('https://learner.example/learn')); const opsPage = await middleware(new NextRequest('https://learner.example/courses')); const opsApi = await middleware(new NextRequest('https://learner.example/api/courses')); expect(ordinaryPage.headers.get('x-middleware-next')).toBe('1'); expect(opsPage.status).toBe(307); expect(opsPage.headers.get('location')).toBe('https://learner.example/'); expect(opsApi.status).toBe(403); }); test('serves the operations shell but fails closed for APIs without Works identity config', async () => { vi.stubEnv('NODE_ENV', 'test'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'ops'); vi.stubEnv('ACCESS_CODE', ''); const page = await middleware(new NextRequest('https://ops.example/courses')); const api = await middleware(new NextRequest('https://ops.example/api/courses')); expect(page.headers.get('x-middleware-next')).toBe('1'); expect(api.status).toBe(503); }); test.each([ '/api/generate-classroom', '/api/quiz-grade', '/api/pbl/v2/instructor', ])('requires an introspected Works admin for every ops API: %s', async (pathname) => { vi.stubEnv('NODE_ENV', 'production'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'ops'); vi.stubEnv('WORKS_SQUARE_API_BASE_URL', 'https://works.example'); vi.stubEnv('OPS_PUBLIC_ORIGIN', 'https://ops.example'); const unauthorized = await middleware(new NextRequest(`https://ops.example${pathname}`, { method: 'POST', headers: { origin: 'https://ops.example' }, })); expect(unauthorized.status).toBe(401); const introspection = vi.fn(async () => Response.json({ username: 'jiaoyuop', site_role: 'admin', })); vi.stubGlobal('fetch', introspection); const authorized = await middleware(new NextRequest(`https://ops.example${pathname}`, { method: 'POST', headers: { origin: 'https://ops.example', authorization: 'Bearer works-session', }, })); expect(authorized.headers.get('x-middleware-next')).toBe('1'); expect(introspection).toHaveBeenCalledWith( 'https://works.example/api/auth/me', expect.objectContaining({ headers: expect.objectContaining({ Authorization: 'Bearer works-session' }), }), ); }); });