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:
morluto committed 2026-10-12 18:44:02 +08:00
1 parent abc8c6dadf
commit fe4b94a661
3 files changed
+48 -31

No files matched your search

+4 -4
View File
@@ -20,7 +20,7 @@ const execute = promisify(execFile);
const verifyMcpToolsAndPrompts = async (client, mcpOptions) => {
await verifyCompleteToolCatalog(client, mcpOptions);
await prompts.verifyPromptCatalog(client, mcpOptions, prompts.names);
await prompts.verifyPromptCompletion(client, mcpOptions, false);
await prompts.verifyPromptCompletion(client, mcpOptions, []);
};
const verifyMcpTargetFree = async (client, mcpOptions) => {
@@ -122,7 +122,7 @@ const verifyMcpNonLinuxCurrentDocument = async (
const verifyMcpBinaryLifecycle = async (client, mcpOptions) => {
await verifyMcpOpenAndBind(client, mcpOptions);
// 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") {
await verifyMcpLinuxToolAvailability(client, mcpOptions);
} else {
@@ -130,8 +130,8 @@ const verifyMcpBinaryLifecycle = async (client, mcpOptions) => {
{ name: "current_document", arguments: {} },
mcpOptions,
);
await prompts.verifyPromptCompletion(client, mcpOptions, true);
await verifyMcpNonLinuxCurrentDocument(client, mcpOptions, current);
await prompts.verifyPromptCompletion(client, mcpOptions, ["fixture"]);
}
const closed = await client.callTool(
{ name: "close_binary", arguments: {} },
@@ -139,7 +139,7 @@ const verifyMcpBinaryLifecycle = async (client, mcpOptions) => {
);
if (closed.isError === true)
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) => {
+5 -4
View File
@@ -36,9 +36,8 @@ export const verifyPromptCatalog = async (client, options, expectedNames) => {
throw new Error("packaged MCP guided prompt rendering failed");
};
/** Verify live completion through each installed stdio target state. */
export const verifyPromptCompletion = async (client, options, targetOpen) => {
const expected = targetOpen ? ["fixture"] : [];
/** Verify completion candidates for the installed provider readiness state. */
export const verifyPromptCompletion = async (client, options, expected) => {
const requests = [
{
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.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)}`,
);
}
};
+39 -23
View File
@@ -5,7 +5,11 @@ import { afterEach, describe, expect, it } from "vitest";
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 { ARTIFACT_COMPARISON_EXAMPLE } from "../../../src/contracts/artifactComparisonExample.js";
import { PROCESS_CAPTURE_REFERENCE } from "../../../src/contracts/investigationExamples.js";
@@ -29,30 +33,34 @@ afterEach(async () => {
describe("guided prompt completion from live analysis", () => {
it("reads live documents and complete procedures without ambiguous names", async () => {
const requests: Array<Readonly<Record<string, unknown>>> = [];
const session = createTestBinarySession(() => client(requests), {
resolveAnalysisProfile: () =>
Promise.resolve(
resultOk({
profile: createAnalysisProfile(
{
id: "fixture",
name: "Fixture analysis provider",
version: "1",
},
{ fixture: true },
),
}),
),
});
const session = createTestBinarySession(
(_target, profile) => client(requests, profile),
{
resolveAnalysisProfile: () =>
Promise.resolve(
resultOk({
profile: createAnalysisProfile(
{
id: "fixture",
name: "Fixture analysis provider",
version: "1",
},
{ fixture: true },
),
}),
),
},
);
directory = await createTestTempDirectory("rea-prompt-completion-");
const target = join(directory, "fixture.hop");
await writeFile(target, "fixture");
expect((await session.open(target)).ok).toBe(true);
const completion = createPromptCompletionSource(session, session);
// Completion never starts a provider; an ordinary operation readies it.
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([
"App",
"AppTests",
@@ -272,14 +280,23 @@ const fixtureEnvironment = {
const client = (
requests: Array<Readonly<Record<string, unknown>>>,
analysisProfile?: AnalysisProfileCommitment,
): AnalysisClient => {
const observe = (value: unknown) =>
resultOk(
createAnalysisExecution(
value,
analysisProfile?.provider ?? fixtureProvider,
analysisProfile === undefined ? {} : { analysisProfile },
),
);
const execute: AnalysisClient["execute"] = (operation, parameters) => {
if (operation === "list_documents")
return Promise.resolve(observed(["AppTests", "App", "App"]));
return Promise.resolve(observe(["AppTests", "App", "App"]));
if (operation === "list_procedures") {
requests.push(parameters);
return Promise.resolve(
observed([
observe([
{ address: "0x1000", value: "duplicate" },
{ address: "0x2000", value: "unique" },
{ 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 {
execute,
executeIfReady: execute,