fix(qa): preserve image generation plugin allowlist

This commit is contained in:
Peter Steinberger
2026-04-23 02:54:16 +01:00
parent e3e2626583
commit 74dfeaae0d
4 changed files with 64 additions and 0 deletions

View File

@@ -14,6 +14,23 @@ describe("QA provider image generation config", () => {
expect(patch.models?.providers["mock-openai"]?.baseUrl).toBe("http://127.0.0.1:44080/v1");
});
it("preserves already-allowed plugins when configuring image generation", () => {
const patch = buildQaImageGenerationConfigPatch({
providerMode: "mock-openai",
providerBaseUrl: "http://127.0.0.1:44080/v1",
requiredPluginIds: ["qa-channel"],
existingPluginIds: ["openai", "anthropic", "qa-channel"],
});
expect(patch.plugins.allow).toEqual([
"acpx",
"memory-core",
"openai",
"anthropic",
"qa-channel",
]);
});
it("uses the selected mock provider for AIMock image generation", () => {
const patch = buildQaImageGenerationConfigPatch({
providerMode: "aimock",

View File

@@ -6,6 +6,7 @@ type QaImageGenerationPatchInput = {
providerMode: QaProviderMode;
providerBaseUrl?: string;
requiredPluginIds: readonly string[];
existingPluginIds?: readonly string[];
};
function splitModelProviderId(modelRef: string) {
@@ -48,6 +49,7 @@ export function buildQaImageGenerationConfigPatch(input: QaImageGenerationPatchI
plugins: {
allow: uniqueNonEmpty([
...QA_BASE_RUNTIME_PLUGIN_IDS,
...(input.existingPluginIds ?? []),
...enabledPluginIds,
...input.requiredPluginIds,
]),

View File

@@ -4,12 +4,16 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const fetchJsonMock = vi.hoisted(() => vi.fn());
const patchConfigMock = vi.hoisted(() => vi.fn(async () => undefined));
const readConfigSnapshotMock = vi.hoisted(() =>
vi.fn(async () => ({ hash: "hash", config: { plugins: { allow: [] as string[] } } })),
);
const waitForGatewayHealthyMock = vi.hoisted(() => vi.fn(async () => undefined));
const waitForTransportReadyMock = vi.hoisted(() => vi.fn(async () => undefined));
vi.mock("./suite-runtime-gateway.js", () => ({
fetchJson: fetchJsonMock,
patchConfig: patchConfigMock,
readConfigSnapshot: readConfigSnapshotMock,
waitForGatewayHealthy: waitForGatewayHealthyMock,
waitForTransportReady: waitForTransportReadyMock,
}));
@@ -29,6 +33,8 @@ describe("qa suite runtime agent media helpers", () => {
beforeEach(() => {
fetchJsonMock.mockReset();
patchConfigMock.mockClear();
readConfigSnapshotMock.mockReset();
readConfigSnapshotMock.mockResolvedValue({ hash: "hash", config: { plugins: { allow: [] } } });
waitForGatewayHealthyMock.mockClear();
waitForTransportReadyMock.mockClear();
});
@@ -102,4 +108,27 @@ describe("qa suite runtime agent media helpers", () => {
expect(waitForGatewayHealthyMock).toHaveBeenCalled();
expect(waitForTransportReadyMock).toHaveBeenCalledWith(expect.anything(), 60_000);
});
it("preserves plugins already allowed by the gateway when configuring media", async () => {
readConfigSnapshotMock.mockResolvedValue({
hash: "hash",
config: { plugins: { allow: ["openai", "anthropic", "qa-channel"] } },
});
await ensureImageGenerationConfigured({
providerMode: "mock-openai",
mock: { baseUrl: "http://127.0.0.1:9999" },
transport: { requiredPluginIds: ["qa-channel"] },
} as never);
expect(patchConfigMock).toHaveBeenCalledWith(
expect.objectContaining({
patch: expect.objectContaining({
plugins: expect.objectContaining({
allow: ["acpx", "memory-core", "openai", "anthropic", "qa-channel"],
}),
}),
}),
);
});
});

View File

@@ -4,6 +4,7 @@ import { buildQaImageGenerationConfigPatch } from "./providers/image-generation.
import {
fetchJson,
patchConfig,
readConfigSnapshot,
waitForGatewayHealthy,
waitForTransportReady,
} from "./suite-runtime-gateway.js";
@@ -13,6 +14,19 @@ function extractMediaPathFromText(text: string | undefined): string | undefined
return /MEDIA:([^\n]+)/.exec(text ?? "")?.[1]?.trim();
}
function readPluginAllow(config: Record<string, unknown>) {
const plugins = config.plugins;
if (typeof plugins !== "object" || plugins === null || Array.isArray(plugins)) {
return [];
}
const allow = (plugins as { allow?: unknown }).allow;
return Array.isArray(allow)
? allow.filter(
(pluginId): pluginId is string => typeof pluginId === "string" && pluginId.length > 0,
)
: [];
}
async function resolveGeneratedImagePath(params: {
env: Pick<QaSuiteRuntimeEnv, "mock" | "gateway">;
promptSnippet: string;
@@ -71,12 +85,14 @@ async function resolveGeneratedImagePath(params: {
}
async function ensureImageGenerationConfigured(env: QaSuiteRuntimeEnv) {
const snapshot = await readConfigSnapshot(env);
await patchConfig({
env,
patch: buildQaImageGenerationConfigPatch({
providerMode: env.providerMode,
providerBaseUrl: env.mock ? `${env.mock.baseUrl}/v1` : undefined,
requiredPluginIds: env.transport.requiredPluginIds,
existingPluginIds: readPluginAllow(snapshot.config),
}),
});
await waitForGatewayHealthy(env);