144 lines
5.5 KiB
TypeScript
144 lines
5.5 KiB
TypeScript
import { afterEach, describe, expect, test, vi } from 'vitest';
|
|
import { NextRequest } from 'next/server';
|
|
import { middleware } from '@/middleware';
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllEnvs();
|
|
});
|
|
|
|
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('allows a single-course generation POST while hiding the 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.headers.get('x-middleware-next')).toBe('1');
|
|
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 preempt a server publish request authenticated by Bearer at the route', 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(200);
|
|
expect(response.headers.get('x-middleware-next')).toBe('1');
|
|
});
|
|
|
|
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('preserves operations workbench routing', 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.headers.get('x-middleware-next')).toBe('1');
|
|
});
|
|
});
|