mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
test(mcp): align completion checks with provider readiness
Exercise cold and warm completion through ordinary provider operations. Bind fixture observations to the selected analysis profile so the warm-up call satisfies session validation, and make package completion candidates explicit with useful failure diagnostics.
This commit is contained in:
1 parent
abc8c6dadf
commit
fe4b94a661
3 files changed
+48
-31
No files matched your search
@@ -20,7 +20,7 @@ const execute = promisify(execFile);
|
|||||||
const verifyMcpToolsAndPrompts = async (client, mcpOptions) => {
|
const verifyMcpToolsAndPrompts = async (client, mcpOptions) => {
|
||||||
await verifyCompleteToolCatalog(client, mcpOptions);
|
await verifyCompleteToolCatalog(client, mcpOptions);
|
||||||
await prompts.verifyPromptCatalog(client, mcpOptions, prompts.names);
|
await prompts.verifyPromptCatalog(client, mcpOptions, prompts.names);
|
||||||
await prompts.verifyPromptCompletion(client, mcpOptions, false);
|
await prompts.verifyPromptCompletion(client, mcpOptions, []);
|
||||||
};
|
};
|
||||||
|
|
||||||
const verifyMcpTargetFree = async (client, mcpOptions) => {
|
const verifyMcpTargetFree = async (client, mcpOptions) => {
|
||||||
@@ -122,7 +122,7 @@ const verifyMcpNonLinuxCurrentDocument = async (
|
|||||||
const verifyMcpBinaryLifecycle = async (client, mcpOptions) => {
|
const verifyMcpBinaryLifecycle = async (client, mcpOptions) => {
|
||||||
await verifyMcpOpenAndBind(client, mcpOptions);
|
await verifyMcpOpenAndBind(client, mcpOptions);
|
||||||
// Completion never starts a provider; only an ordinary operation readies it.
|
// Completion never starts a provider; only an ordinary operation readies it.
|
||||||
await prompts.verifyPromptCompletion(client, mcpOptions, false);
|
await prompts.verifyPromptCompletion(client, mcpOptions, []);
|
||||||
if (process.platform === "linux") {
|
if (process.platform === "linux") {
|
||||||
await verifyMcpLinuxToolAvailability(client, mcpOptions);
|
await verifyMcpLinuxToolAvailability(client, mcpOptions);
|
||||||
} else {
|
} else {
|
||||||
@@ -130,8 +130,8 @@ const verifyMcpBinaryLifecycle = async (client, mcpOptions) => {
|
|||||||
{ name: "current_document", arguments: {} },
|
{ name: "current_document", arguments: {} },
|
||||||
mcpOptions,
|
mcpOptions,
|
||||||
);
|
);
|
||||||
await prompts.verifyPromptCompletion(client, mcpOptions, true);
|
|
||||||
await verifyMcpNonLinuxCurrentDocument(client, mcpOptions, current);
|
await verifyMcpNonLinuxCurrentDocument(client, mcpOptions, current);
|
||||||
|
await prompts.verifyPromptCompletion(client, mcpOptions, ["fixture"]);
|
||||||
}
|
}
|
||||||
const closed = await client.callTool(
|
const closed = await client.callTool(
|
||||||
{ name: "close_binary", arguments: {} },
|
{ name: "close_binary", arguments: {} },
|
||||||
@@ -139,7 +139,7 @@ const verifyMcpBinaryLifecycle = async (client, mcpOptions) => {
|
|||||||
);
|
);
|
||||||
if (closed.isError === true)
|
if (closed.isError === true)
|
||||||
throw new Error("packaged MCP could not close its binary");
|
throw new Error("packaged MCP could not close its binary");
|
||||||
await prompts.verifyPromptCompletion(client, mcpOptions, false);
|
await prompts.verifyPromptCompletion(client, mcpOptions, []);
|
||||||
};
|
};
|
||||||
|
|
||||||
const verifyMcpEvidenceBundle = async (client, mcpOptions, evidenceRoot) => {
|
const verifyMcpEvidenceBundle = async (client, mcpOptions, evidenceRoot) => {
|
||||||
|
|||||||
@@ -36,9 +36,8 @@ export const verifyPromptCatalog = async (client, options, expectedNames) => {
|
|||||||
throw new Error("packaged MCP guided prompt rendering failed");
|
throw new Error("packaged MCP guided prompt rendering failed");
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Verify live completion through each installed stdio target state. */
|
/** Verify completion candidates for the installed provider readiness state. */
|
||||||
export const verifyPromptCompletion = async (client, options, targetOpen) => {
|
export const verifyPromptCompletion = async (client, options, expected) => {
|
||||||
const expected = targetOpen ? ["fixture"] : [];
|
|
||||||
const requests = [
|
const requests = [
|
||||||
{
|
{
|
||||||
ref: { type: "ref/prompt", name: "investigate_feature" },
|
ref: { type: "ref/prompt", name: "investigate_feature" },
|
||||||
@@ -57,6 +56,8 @@ export const verifyPromptCompletion = async (client, options, targetOpen) => {
|
|||||||
result.completion.total !== expected.length ||
|
result.completion.total !== expected.length ||
|
||||||
result.completion.hasMore !== false
|
result.completion.hasMore !== false
|
||||||
)
|
)
|
||||||
throw new Error("packaged MCP prompt completion lifecycle failed");
|
throw new Error(
|
||||||
|
`packaged MCP ${request.argument.name} completion mismatch: expected ${JSON.stringify(expected)}, received ${JSON.stringify(result.completion)}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -5,7 +5,11 @@ import { afterEach, describe, expect, it } from "vitest";
|
|||||||
|
|
||||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||||
|
|
||||||
import type { AnalysisClient } from "../../../src/application/AnalysisProvider.js";
|
import {
|
||||||
|
createAnalysisExecution,
|
||||||
|
type AnalysisClient,
|
||||||
|
} from "../../../src/application/AnalysisProvider.js";
|
||||||
|
import type { AnalysisProfileCommitment } from "../../../src/domain/analysisProfile.js";
|
||||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||||
import { ARTIFACT_COMPARISON_EXAMPLE } from "../../../src/contracts/artifactComparisonExample.js";
|
import { ARTIFACT_COMPARISON_EXAMPLE } from "../../../src/contracts/artifactComparisonExample.js";
|
||||||
import { PROCESS_CAPTURE_REFERENCE } from "../../../src/contracts/investigationExamples.js";
|
import { PROCESS_CAPTURE_REFERENCE } from "../../../src/contracts/investigationExamples.js";
|
||||||
@@ -29,30 +33,34 @@ afterEach(async () => {
|
|||||||
describe("guided prompt completion from live analysis", () => {
|
describe("guided prompt completion from live analysis", () => {
|
||||||
it("reads live documents and complete procedures without ambiguous names", async () => {
|
it("reads live documents and complete procedures without ambiguous names", async () => {
|
||||||
const requests: Array<Readonly<Record<string, unknown>>> = [];
|
const requests: Array<Readonly<Record<string, unknown>>> = [];
|
||||||
const session = createTestBinarySession(() => client(requests), {
|
const session = createTestBinarySession(
|
||||||
resolveAnalysisProfile: () =>
|
(_target, profile) => client(requests, profile),
|
||||||
Promise.resolve(
|
{
|
||||||
resultOk({
|
resolveAnalysisProfile: () =>
|
||||||
profile: createAnalysisProfile(
|
Promise.resolve(
|
||||||
{
|
resultOk({
|
||||||
id: "fixture",
|
profile: createAnalysisProfile(
|
||||||
name: "Fixture analysis provider",
|
{
|
||||||
version: "1",
|
id: "fixture",
|
||||||
},
|
name: "Fixture analysis provider",
|
||||||
{ fixture: true },
|
version: "1",
|
||||||
),
|
},
|
||||||
}),
|
{ fixture: true },
|
||||||
),
|
),
|
||||||
});
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
directory = await createTestTempDirectory("rea-prompt-completion-");
|
directory = await createTestTempDirectory("rea-prompt-completion-");
|
||||||
const target = join(directory, "fixture.hop");
|
const target = join(directory, "fixture.hop");
|
||||||
await writeFile(target, "fixture");
|
await writeFile(target, "fixture");
|
||||||
expect((await session.open(target)).ok).toBe(true);
|
expect((await session.open(target)).ok).toBe(true);
|
||||||
const completion = createPromptCompletionSource(session, session);
|
const completion = createPromptCompletionSource(session, session);
|
||||||
|
|
||||||
// Completion never starts a provider; an ordinary operation readies it.
|
|
||||||
expect(await completion.complete("document", "app")).toEqual([]);
|
expect(await completion.complete("document", "app")).toEqual([]);
|
||||||
expect((await session.execute("list_documents", {})).ok).toBe(true);
|
expect(await completion.complete("procedure", "")).toEqual([]);
|
||||||
|
const warmed = await session.execute("list_documents", {});
|
||||||
|
if (!warmed.ok) throw warmed.error;
|
||||||
|
|
||||||
expect(await completion.complete("document", "app")).toEqual([
|
expect(await completion.complete("document", "app")).toEqual([
|
||||||
"App",
|
"App",
|
||||||
"AppTests",
|
"AppTests",
|
||||||
@@ -272,14 +280,23 @@ const fixtureEnvironment = {
|
|||||||
|
|
||||||
const client = (
|
const client = (
|
||||||
requests: Array<Readonly<Record<string, unknown>>>,
|
requests: Array<Readonly<Record<string, unknown>>>,
|
||||||
|
analysisProfile?: AnalysisProfileCommitment,
|
||||||
): AnalysisClient => {
|
): AnalysisClient => {
|
||||||
|
const observe = (value: unknown) =>
|
||||||
|
resultOk(
|
||||||
|
createAnalysisExecution(
|
||||||
|
value,
|
||||||
|
analysisProfile?.provider ?? fixtureProvider,
|
||||||
|
analysisProfile === undefined ? {} : { analysisProfile },
|
||||||
|
),
|
||||||
|
);
|
||||||
const execute: AnalysisClient["execute"] = (operation, parameters) => {
|
const execute: AnalysisClient["execute"] = (operation, parameters) => {
|
||||||
if (operation === "list_documents")
|
if (operation === "list_documents")
|
||||||
return Promise.resolve(observed(["AppTests", "App", "App"]));
|
return Promise.resolve(observe(["AppTests", "App", "App"]));
|
||||||
if (operation === "list_procedures") {
|
if (operation === "list_procedures") {
|
||||||
requests.push(parameters);
|
requests.push(parameters);
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
observed([
|
observe([
|
||||||
{ address: "0x1000", value: "duplicate" },
|
{ address: "0x1000", value: "duplicate" },
|
||||||
{ address: "0x2000", value: "unique" },
|
{ address: "0x2000", value: "unique" },
|
||||||
{ address: "0x3000", value: "duplicate" },
|
{ address: "0x3000", value: "duplicate" },
|
||||||
@@ -287,9 +304,8 @@ const client = (
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Promise.resolve(observed(null));
|
return Promise.resolve(observe(null));
|
||||||
};
|
};
|
||||||
// An in-process fixture client is ready as soon as the session opens.
|
|
||||||
return {
|
return {
|
||||||
execute,
|
execute,
|
||||||
executeIfReady: execute,
|
executeIfReady: execute,
|
||||||
|
|||||||
Reference in new issue
Block a user