Files
openmaic/OpenMAIC/tests/api/generate-image-provider-access.test.ts
2026-08-16 14:58:47 +08:00

106 lines
3.4 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { NextRequest } from 'next/server';
const mocks = vi.hoisted(() => ({
generateImage: vi.fn(),
managed: vi.fn((_section: string, _providerId: string) => false),
resolveApiKey: vi.fn(),
resolveBaseUrl: vi.fn(),
recordUsage: vi.fn(),
}));
vi.mock('@/lib/media/image-providers', () => ({
IMAGE_PROVIDERS: { seedream: { requiresApiKey: true } },
aspectRatioToDimensions: () => ({ width: 1024, height: 576 }),
generateImage: (...args: unknown[]) => mocks.generateImage(...args),
}));
vi.mock('@/lib/server/provider-config', () => ({
isServerConfiguredProvider: (section: string, providerId: string) =>
mocks.managed(section, providerId),
resolveImageApiKey: (...args: unknown[]) => mocks.resolveApiKey(...args),
resolveImageBaseUrl: (...args: unknown[]) => mocks.resolveBaseUrl(...args),
}));
vi.mock('@/lib/server/usage-storage', () => ({
recordGenerationUsage: (...args: unknown[]) => mocks.recordUsage(...args),
}));
vi.mock('@/lib/server/ssrf-guard', () => ({
validateUrlForSSRF: vi.fn(async () => null),
}));
vi.mock('@/lib/logger', () => ({
createLogger: () => ({
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
}),
}));
async function postImage(headers: Record<string, string> = {}) {
const { POST } = await import('@/app/api/generate/image/route');
const request = new Request('http://localhost/api/generate/image', {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...headers },
body: JSON.stringify({ prompt: 'A classroom diagram' }),
});
return POST(request as unknown as NextRequest);
}
describe('POST /api/generate/image provider access response', () => {
beforeEach(() => {
vi.resetModules();
vi.unstubAllEnvs();
mocks.generateImage.mockReset();
mocks.managed.mockReset().mockReturnValue(false);
mocks.resolveApiKey.mockReset();
mocks.resolveBaseUrl.mockReset();
mocks.recordUsage.mockReset();
});
it('returns 403/FORBIDDEN and never calls the provider for a central denial', async () => {
const { ServerManagedProviderRequiredError } =
await import('@/lib/server/provider-access-policy');
mocks.resolveApiKey.mockImplementation(() => {
throw new ServerManagedProviderRequiredError();
});
const response = await postImage({
'x-image-provider': 'seedream',
'x-api-key': 'caller-key',
'x-base-url': 'https://caller.invalid/v1',
});
expect(response.status).toBe(403);
await expect(response.json()).resolves.toMatchObject({
success: false,
errorCode: 'FORBIDDEN',
});
expect(mocks.generateImage).not.toHaveBeenCalled();
});
it('keeps the existing successful resolution path unchanged', async () => {
mocks.resolveApiKey.mockReturnValue('client-key');
mocks.resolveBaseUrl.mockReturnValue('https://client.example/v1');
mocks.generateImage.mockResolvedValue({ url: 'https://cdn.example/image.png' });
const response = await postImage({
'x-image-provider': 'seedream',
'x-api-key': 'client-key',
'x-base-url': 'https://client.example/v1',
});
expect(response.status).toBe(200);
expect(mocks.generateImage).toHaveBeenCalledWith(
expect.objectContaining({
providerId: 'seedream',
apiKey: 'client-key',
baseUrl: 'https://client.example/v1',
}),
expect.objectContaining({ prompt: 'A classroom diagram' }),
);
});
});