mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 01:13:39 +00:00
test: finish independent session cleanup and completion gates
This commit is contained in:
1 parent
fe1a5b1498
commit
724211b468
7 files changed
+269
-190
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { closeVerifierResources } from "../../../scripts/lib/verifier-cleanup.mjs";
|
||||
import { Cli, z } from "incur";
|
||||
import pino from "pino";
|
||||
import { readFile, readdir, writeFile } from "node:fs/promises";
|
||||
@@ -76,10 +77,9 @@ const fixture = async (kind: (typeof kinds)[number]) => {
|
||||
createManager,
|
||||
() => service,
|
||||
);
|
||||
onTestFinished(async () => {
|
||||
await service.close();
|
||||
await manager.closeAll();
|
||||
});
|
||||
onTestFinished(() =>
|
||||
closeVerifierResources([() => service.close(), () => manager.closeAll()]),
|
||||
);
|
||||
const commandName =
|
||||
kind === "gdb"
|
||||
? CLI_COMMANDS.debugWithGdb
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { withVerifierCleanup } from "../../../scripts/lib/verifier-cleanup.mjs";
|
||||
import { parseEvidenceBundle } from "../../../src/domain/evidenceBundle.js";
|
||||
import { EvidenceIntegrityError } from "../../../src/domain/evidenceErrors.js";
|
||||
import { ProviderCleanupError } from "../../../src/domain/providerCleanupError.js";
|
||||
@@ -1670,132 +1671,141 @@ const expectProfilelessWorkflowSnapshotRejections = async (
|
||||
snapshot: AnalysisSnapshot,
|
||||
): Promise<void> => {
|
||||
const session = createTestBinarySession(provider);
|
||||
const opened = await session.open(path);
|
||||
if (!opened.ok) throw opened.error;
|
||||
const readerIdentity = APPLE_DISPATCH_METADATA_ANALYSIS_PROFILE.provider;
|
||||
const wrongReader = structuredClone(snapshot);
|
||||
// Reader v2 could discard owners when their adopted-protocol list was unreadable.
|
||||
const otherReaderProfile = createAnalysisProfile(
|
||||
{ ...readerIdentity, version: "apple-metadata-reader-2" },
|
||||
{
|
||||
operation: "inspect_native_dispatch_metadata",
|
||||
reader: "apple-dispatch-metadata",
|
||||
revision: 2,
|
||||
},
|
||||
);
|
||||
wrongReader.binding = {
|
||||
provider: otherReaderProfile.provider,
|
||||
analysis_profile: otherReaderProfile,
|
||||
};
|
||||
const rejectedReader = session.importAnalysisSnapshot(wrongReader);
|
||||
expect(rejectedReader.ok).toBe(false);
|
||||
if (!rejectedReader.ok) {
|
||||
expectReaderBindingMismatch(
|
||||
rejectedReader.error,
|
||||
readerIdentity.version,
|
||||
otherReaderProfile.digest,
|
||||
);
|
||||
}
|
||||
|
||||
expectProfilelessOperationMismatch(session, snapshot);
|
||||
|
||||
const mixedOperation = "inspect_native_api";
|
||||
const mixedProfile = workflowAnalysisProfile(
|
||||
APPLE_DISPATCH_METADATA_ANALYSIS_PROFILE,
|
||||
mixedOperation,
|
||||
);
|
||||
const mixedSnapshot = {
|
||||
...snapshot,
|
||||
workflow_entries: [
|
||||
...snapshot.workflow_entries,
|
||||
createAnalysisSnapshotWorkflowEntry({
|
||||
target: snapshot.target,
|
||||
binding: snapshot.binding,
|
||||
operation: mixedOperation,
|
||||
parameters: {},
|
||||
execution: {
|
||||
result: null,
|
||||
rawResult: null,
|
||||
provider: mixedProfile.provider,
|
||||
analysisProfile: mixedProfile,
|
||||
limitations: [],
|
||||
locations: [],
|
||||
subject: null,
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
const rejectedMixed = session.importAnalysisSnapshot(mixedSnapshot);
|
||||
expect(rejectedMixed.ok).toBe(false);
|
||||
if (!rejectedMixed.ok)
|
||||
expect(projectAnalysisError(rejectedMixed.error).details).toMatchObject({
|
||||
binding_mismatch: { workflow_scope: "incompatible" },
|
||||
});
|
||||
|
||||
const deepProfile = createAnalysisProfile(
|
||||
{ id: "ghidra", name: "Ghidra", version: "fixture-version" },
|
||||
{ fixture: "deep-provider-only" },
|
||||
);
|
||||
const deepWorkflowProfile = workflowAnalysisProfile(
|
||||
deepProfile,
|
||||
"inspect_native_dispatch_metadata",
|
||||
);
|
||||
const originalWorkflow = snapshot.workflow_entries[0]!;
|
||||
const deepSnapshot = {
|
||||
...snapshot,
|
||||
binding: {
|
||||
provider: deepProfile.provider,
|
||||
analysis_profile: deepProfile,
|
||||
},
|
||||
workflow_entries: [
|
||||
createAnalysisSnapshotWorkflowEntry({
|
||||
target: snapshot.target,
|
||||
binding: {
|
||||
provider: deepProfile.provider,
|
||||
analysis_profile: deepProfile,
|
||||
},
|
||||
await withVerifierCleanup(async () => {
|
||||
const opened = await session.open(path);
|
||||
if (!opened.ok) throw opened.error;
|
||||
const readerIdentity = APPLE_DISPATCH_METADATA_ANALYSIS_PROFILE.provider;
|
||||
const wrongReader = structuredClone(snapshot);
|
||||
// Reader v2 could discard owners when their adopted-protocol list was unreadable.
|
||||
const otherReaderProfile = createAnalysisProfile(
|
||||
{ ...readerIdentity, version: "apple-metadata-reader-2" },
|
||||
{
|
||||
operation: "inspect_native_dispatch_metadata",
|
||||
parameters: originalWorkflow.parameters,
|
||||
execution: {
|
||||
result: originalWorkflow.execution.result,
|
||||
rawResult: originalWorkflow.execution.raw_result,
|
||||
provider: deepWorkflowProfile.provider,
|
||||
analysisProfile: deepWorkflowProfile,
|
||||
limitations: originalWorkflow.execution.limitations,
|
||||
...(originalWorkflow.execution.relationship_coverage === null
|
||||
? {}
|
||||
: {
|
||||
relationshipCoverage:
|
||||
originalWorkflow.execution.relationship_coverage,
|
||||
}),
|
||||
locations: originalWorkflow.execution.locations,
|
||||
subject:
|
||||
originalWorkflow.execution.subject === null
|
||||
? null
|
||||
: {
|
||||
path: originalWorkflow.execution.subject.path,
|
||||
sha256: originalWorkflow.execution.subject.sha256,
|
||||
format: originalWorkflow.execution.subject.format,
|
||||
...(originalWorkflow.execution.subject.architecture === null
|
||||
? {}
|
||||
: {
|
||||
architecture:
|
||||
originalWorkflow.execution.subject.architecture,
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
} as AnalysisSnapshot;
|
||||
const rejectedDeepOnly = session.importAnalysisSnapshot(deepSnapshot);
|
||||
expect(rejectedDeepOnly.ok).toBe(false);
|
||||
if (!rejectedDeepOnly.ok)
|
||||
expect(projectAnalysisError(rejectedDeepOnly.error).details).toMatchObject({
|
||||
binding_mismatch: {
|
||||
provider: { snapshot: { id: "ghidra" } },
|
||||
profile_digest: { snapshot: deepProfile.digest },
|
||||
reader: "apple-dispatch-metadata",
|
||||
revision: 2,
|
||||
},
|
||||
});
|
||||
);
|
||||
wrongReader.binding = {
|
||||
provider: otherReaderProfile.provider,
|
||||
analysis_profile: otherReaderProfile,
|
||||
};
|
||||
const rejectedReader = session.importAnalysisSnapshot(wrongReader);
|
||||
expect(rejectedReader.ok).toBe(false);
|
||||
if (!rejectedReader.ok) {
|
||||
expectReaderBindingMismatch(
|
||||
rejectedReader.error,
|
||||
readerIdentity.version,
|
||||
otherReaderProfile.digest,
|
||||
);
|
||||
}
|
||||
|
||||
expectProfilelessOperationMismatch(session, snapshot);
|
||||
|
||||
const mixedOperation = "inspect_native_api";
|
||||
const mixedProfile = workflowAnalysisProfile(
|
||||
APPLE_DISPATCH_METADATA_ANALYSIS_PROFILE,
|
||||
mixedOperation,
|
||||
);
|
||||
const mixedSnapshot = {
|
||||
...snapshot,
|
||||
workflow_entries: [
|
||||
...snapshot.workflow_entries,
|
||||
createAnalysisSnapshotWorkflowEntry({
|
||||
target: snapshot.target,
|
||||
binding: snapshot.binding,
|
||||
operation: mixedOperation,
|
||||
parameters: {},
|
||||
execution: {
|
||||
result: null,
|
||||
rawResult: null,
|
||||
provider: mixedProfile.provider,
|
||||
analysisProfile: mixedProfile,
|
||||
limitations: [],
|
||||
locations: [],
|
||||
subject: null,
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
const rejectedMixed = session.importAnalysisSnapshot(mixedSnapshot);
|
||||
expect(rejectedMixed.ok).toBe(false);
|
||||
if (!rejectedMixed.ok)
|
||||
expect(projectAnalysisError(rejectedMixed.error).details).toMatchObject({
|
||||
binding_mismatch: { workflow_scope: "incompatible" },
|
||||
});
|
||||
|
||||
const deepProfile = createAnalysisProfile(
|
||||
{ id: "ghidra", name: "Ghidra", version: "fixture-version" },
|
||||
{ fixture: "deep-provider-only" },
|
||||
);
|
||||
const deepWorkflowProfile = workflowAnalysisProfile(
|
||||
deepProfile,
|
||||
"inspect_native_dispatch_metadata",
|
||||
);
|
||||
const originalWorkflow = snapshot.workflow_entries[0]!;
|
||||
const deepSnapshot = {
|
||||
...snapshot,
|
||||
binding: {
|
||||
provider: deepProfile.provider,
|
||||
analysis_profile: deepProfile,
|
||||
},
|
||||
workflow_entries: [
|
||||
createAnalysisSnapshotWorkflowEntry({
|
||||
target: snapshot.target,
|
||||
binding: {
|
||||
provider: deepProfile.provider,
|
||||
analysis_profile: deepProfile,
|
||||
},
|
||||
operation: "inspect_native_dispatch_metadata",
|
||||
parameters: originalWorkflow.parameters,
|
||||
execution: {
|
||||
result: originalWorkflow.execution.result,
|
||||
rawResult: originalWorkflow.execution.raw_result,
|
||||
provider: deepWorkflowProfile.provider,
|
||||
analysisProfile: deepWorkflowProfile,
|
||||
limitations: originalWorkflow.execution.limitations,
|
||||
...(originalWorkflow.execution.relationship_coverage === null
|
||||
? {}
|
||||
: {
|
||||
relationshipCoverage:
|
||||
originalWorkflow.execution.relationship_coverage,
|
||||
}),
|
||||
locations: originalWorkflow.execution.locations,
|
||||
subject:
|
||||
originalWorkflow.execution.subject === null
|
||||
? null
|
||||
: {
|
||||
path: originalWorkflow.execution.subject.path,
|
||||
sha256: originalWorkflow.execution.subject.sha256,
|
||||
format: originalWorkflow.execution.subject.format,
|
||||
...(originalWorkflow.execution.subject.architecture === null
|
||||
? {}
|
||||
: {
|
||||
architecture:
|
||||
originalWorkflow.execution.subject.architecture,
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
} as AnalysisSnapshot;
|
||||
const rejectedDeepOnly = session.importAnalysisSnapshot(deepSnapshot);
|
||||
expect(rejectedDeepOnly.ok).toBe(false);
|
||||
if (!rejectedDeepOnly.ok)
|
||||
expect(
|
||||
projectAnalysisError(rejectedDeepOnly.error).details,
|
||||
).toMatchObject({
|
||||
binding_mismatch: {
|
||||
provider: { snapshot: { id: "ghidra" } },
|
||||
profile_digest: { snapshot: deepProfile.digest },
|
||||
},
|
||||
});
|
||||
}, [
|
||||
async () => {
|
||||
const closed = await session.close();
|
||||
if (!closed.ok) throw closed.error;
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
it("retains a native dispatch workflow snapshot without a deep provider profile", async () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { withVerifierCleanup } from "../../../scripts/lib/verifier-cleanup.mjs";
|
||||
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
||||
import { expect, it } from "vitest";
|
||||
import type { JsonValue } from "../../../src/domain/jsonValue.js";
|
||||
@@ -63,7 +64,7 @@ it("locates malformed pcap parent fields beneath the analysis-view source", asyn
|
||||
});
|
||||
const server = createServer({ kind: "session", session });
|
||||
const client = new Client({ name: "pcap-parent-path-repro", version: "1" });
|
||||
try {
|
||||
await withVerifierCleanup(async () => {
|
||||
const [clientTransport, serverTransport] =
|
||||
InMemoryTransport.createLinkedPair();
|
||||
await server.connect(serverTransport);
|
||||
@@ -111,9 +112,12 @@ it("locates malformed pcap parent fields beneath the analysis-view source", asyn
|
||||
});
|
||||
}
|
||||
await client.ping();
|
||||
} finally {
|
||||
await client.close();
|
||||
await server.close();
|
||||
await session.close();
|
||||
}
|
||||
}, [
|
||||
() => client.close(),
|
||||
() => server.close(),
|
||||
async () => {
|
||||
const closed = await session.close();
|
||||
if (!closed.ok) throw closed.error;
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
import { closeVerifierResources } from "../../../scripts/lib/verifier-cleanup.mjs";
|
||||
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
||||
import { chmod, mkdir, open, readdir, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
@@ -46,23 +47,27 @@ const inspectWith = async (allocate: Allocate, path: string) => {
|
||||
REA_CAPINFOS_COMMAND: join(stubs, "capinfos-stub.sh"),
|
||||
},
|
||||
});
|
||||
const session = createTestBinarySession(() => {
|
||||
throw new Error("No native binary provider");
|
||||
});
|
||||
const server = createServer(
|
||||
{
|
||||
kind: "session",
|
||||
session: createTestBinarySession(() => {
|
||||
throw new Error("No native binary provider");
|
||||
}),
|
||||
},
|
||||
{ kind: "session", session },
|
||||
{ pcapAnalysis: provider },
|
||||
);
|
||||
const client = new Client({ name: "pcap-workspace-test", version: "1" });
|
||||
const [clientTransport, serverTransport] =
|
||||
InMemoryTransport.createLinkedPair();
|
||||
onTestFinished(async () => {
|
||||
await client.close();
|
||||
await server.close();
|
||||
await provider.close();
|
||||
});
|
||||
onTestFinished(() =>
|
||||
closeVerifierResources([
|
||||
() => client.close(),
|
||||
() => server.close(),
|
||||
() => provider.close(),
|
||||
async () => {
|
||||
const closed = await session.close();
|
||||
if (!closed.ok) throw closed.error;
|
||||
},
|
||||
]),
|
||||
);
|
||||
await server.connect(serverTransport);
|
||||
await client.connect(clientTransport);
|
||||
return parseMcpToolError(
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import {
|
||||
closeVerifierResources,
|
||||
withVerifierCleanup,
|
||||
} from "../../../scripts/lib/verifier-cleanup.mjs";
|
||||
import { access, mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
||||
import { expect, it, vi } from "vitest";
|
||||
import { expect, it, onTestFinished, vi } from "vitest";
|
||||
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
@@ -53,6 +57,7 @@ const connectSession = async () => {
|
||||
const client = new Client({ name: "process-cleanup-retry", version: "1" });
|
||||
const [clientTransport, serverTransport] =
|
||||
InMemoryTransport.createLinkedPair();
|
||||
onTestFinished(() => closeSession({ client, server, session }));
|
||||
await server.connect(serverTransport);
|
||||
await client.connect(clientTransport);
|
||||
return { client, server, session };
|
||||
@@ -61,10 +66,14 @@ const connectSession = async () => {
|
||||
const closeSession = async (
|
||||
resources: Awaited<ReturnType<typeof connectSession>>,
|
||||
): Promise<void> => {
|
||||
await resources.client.close();
|
||||
await resources.server.close();
|
||||
const closed = await resources.session.close();
|
||||
if (!closed.ok) throw closed.error;
|
||||
await closeVerifierResources([
|
||||
() => resources.client.close(),
|
||||
() => resources.server.close(),
|
||||
async () => {
|
||||
const closed = await resources.session.close();
|
||||
if (!closed.ok) throw closed.error;
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const captureCleanupRetry = (root: string, isDenied: () => boolean) => {
|
||||
@@ -116,7 +125,7 @@ it("projects the retained process root through close_binary and resolves its ret
|
||||
const callClose = () =>
|
||||
resources.client.callTool({ name: "close_binary", arguments: {} });
|
||||
|
||||
try {
|
||||
await withVerifierCleanup(async () => {
|
||||
const failed = parseMcpToolError(await callClose());
|
||||
const firstWire = JSON.stringify(failed);
|
||||
expect(failed.error).toMatchObject({
|
||||
@@ -143,12 +152,20 @@ it("projects the retained process root through close_binary and resolves its ret
|
||||
);
|
||||
expect(JSON.stringify(failed)).toBe(firstWire);
|
||||
await expect(access(root)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
} finally {
|
||||
denyRemoval = false;
|
||||
fault.retry.mockRestore();
|
||||
await closeSession(resources);
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
}, [
|
||||
async () => {
|
||||
denyRemoval = false;
|
||||
},
|
||||
async () => {
|
||||
fault.retry.mockRestore();
|
||||
},
|
||||
async () => {
|
||||
await closeSession(resources);
|
||||
},
|
||||
async () => {
|
||||
await rm(root, { recursive: true, force: true });
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it.each(["returned", "thrown", "synchronous"] as const)(
|
||||
@@ -176,7 +193,7 @@ it.each(["returned", "thrown", "synchronous"] as const)(
|
||||
resources.client.callTool({ name: "close_binary", arguments: {} });
|
||||
const delivery = vi.spyOn(ToolResultDelivery.prototype, "toCallToolResult");
|
||||
|
||||
try {
|
||||
await withVerifierCleanup(async () => {
|
||||
const failed = parseMcpToolError(await callClose());
|
||||
const firstWire = JSON.stringify(failed);
|
||||
const delivered = delivery.mock.calls.at(-1)?.[0];
|
||||
@@ -230,13 +247,25 @@ it.each(["returned", "thrown", "synchronous"] as const)(
|
||||
expect(JSON.stringify(failed)).toBe(firstWire);
|
||||
expect(primary.cleanupIncomplete).toBe(false);
|
||||
await expect(access(root)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
} finally {
|
||||
denyRemoval = false;
|
||||
closeSpy.mockRestore();
|
||||
delivery.mockRestore();
|
||||
fault.retry.mockRestore();
|
||||
await closeSession(resources);
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
}, [
|
||||
async () => {
|
||||
denyRemoval = false;
|
||||
},
|
||||
async () => {
|
||||
closeSpy.mockRestore();
|
||||
},
|
||||
async () => {
|
||||
delivery.mockRestore();
|
||||
},
|
||||
async () => {
|
||||
fault.retry.mockRestore();
|
||||
},
|
||||
async () => {
|
||||
await closeSession(resources);
|
||||
},
|
||||
async () => {
|
||||
await rm(root, { recursive: true, force: true });
|
||||
},
|
||||
]);
|
||||
},
|
||||
);
|
||||
@@ -1,3 +1,4 @@
|
||||
import { withVerifierCleanup } from "../../../scripts/lib/verifier-cleanup.mjs";
|
||||
import { ok as resultOk, err, ok } from "../../../src/domain/result.js";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { basename, join } from "node:path";
|
||||
@@ -561,16 +562,25 @@ it("guided prompt completion avoids cold startup and cancels warm lookup on clos
|
||||
|
||||
const startup = fixture.holdStartup();
|
||||
const warming = client.callTool({ name: "list_documents", arguments: {} });
|
||||
await startup.started;
|
||||
expect(await complete(client, "document", "")).toEqual([]);
|
||||
expect(await complete(client, "procedure", "")).toEqual([]);
|
||||
expect(fixture.childrenCreated()).toBe(1);
|
||||
startup.finish();
|
||||
const warmed = await warming;
|
||||
expect(warmed.isError, JSON.stringify(warmed)).not.toBe(true);
|
||||
expect(fixture.childrenCreated()).toBe(1);
|
||||
expect(await complete(client, "document", "fixture")).toEqual([
|
||||
"fixture-document",
|
||||
await withVerifierCleanup(async () => {
|
||||
await startup.started;
|
||||
expect(await complete(client, "document", "")).toEqual([]);
|
||||
expect(await complete(client, "procedure", "")).toEqual([]);
|
||||
expect(fixture.childrenCreated()).toBe(1);
|
||||
startup.finish();
|
||||
const warmed = await warming;
|
||||
expect(warmed.isError, JSON.stringify(warmed)).not.toBe(true);
|
||||
expect(fixture.childrenCreated()).toBe(1);
|
||||
expect(await complete(client, "document", "fixture")).toEqual([
|
||||
"fixture-document",
|
||||
]);
|
||||
}, [
|
||||
async () => {
|
||||
startup.finish();
|
||||
},
|
||||
async () => {
|
||||
await warming;
|
||||
},
|
||||
]);
|
||||
|
||||
const cancellation = fixture.holdCompletion();
|
||||
@@ -704,6 +714,10 @@ describe("guided prompt completion lifecycle", () => {
|
||||
const gate = new Promise<void>((resolve) => {
|
||||
release = resolve;
|
||||
});
|
||||
let producerDone!: () => void;
|
||||
const producerFinished = new Promise<void>((resolve) => {
|
||||
producerDone = resolve;
|
||||
});
|
||||
const delivery = createToolResultDelivery(STDIO_DEFAULT_MAX_BUFFER_SIZE);
|
||||
const server = new EvidenceMcpServer(
|
||||
{ name: "prompt-cancellation-test", version: "1" },
|
||||
@@ -716,8 +730,12 @@ describe("guided prompt completion lifecycle", () => {
|
||||
calls += 1;
|
||||
if (calls === 1) {
|
||||
markStarted();
|
||||
await gate;
|
||||
return observed(["late-document"]);
|
||||
try {
|
||||
await gate;
|
||||
return observed(["late-document"]);
|
||||
} finally {
|
||||
producerDone();
|
||||
}
|
||||
}
|
||||
return observed(["fresh-document"]);
|
||||
},
|
||||
@@ -732,16 +750,29 @@ describe("guided prompt completion lifecycle", () => {
|
||||
},
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
await started;
|
||||
controller.abort();
|
||||
await expect(pending).rejects.toThrow();
|
||||
release();
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
await withVerifierCleanup(async () => {
|
||||
await started;
|
||||
controller.abort();
|
||||
await expect(pending).rejects.toThrow();
|
||||
release();
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
expect(await complete(client, "document", "fresh")).toEqual([
|
||||
"fresh-document",
|
||||
expect(await complete(client, "document", "fresh")).toEqual([
|
||||
"fresh-document",
|
||||
]);
|
||||
expect(calls).toBe(2);
|
||||
}, [
|
||||
async () => {
|
||||
controller.abort();
|
||||
release();
|
||||
},
|
||||
async () => {
|
||||
await pending.catch(() => undefined);
|
||||
},
|
||||
async () => {
|
||||
await producerFinished;
|
||||
},
|
||||
]);
|
||||
expect(calls).toBe(2);
|
||||
});
|
||||
|
||||
it("refreshes wire completions across open, switch, concurrency, and close", async () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { closeVerifierResources } from "../../../scripts/lib/verifier-cleanup.mjs";
|
||||
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
||||
import { expect, it, onTestFinished } from "vitest";
|
||||
|
||||
@@ -271,10 +272,9 @@ it("refuses summary delivery before calling a fixed provider without retention",
|
||||
{ environment: {} },
|
||||
);
|
||||
const client = new Client({ name: "pseudocode-no-retention", version: "1" });
|
||||
onTestFinished(async () => {
|
||||
await client.close();
|
||||
await server.close();
|
||||
});
|
||||
onTestFinished(() =>
|
||||
closeVerifierResources([() => client.close(), () => server.close()]),
|
||||
);
|
||||
const [clientTransport, serverTransport] =
|
||||
InMemoryTransport.createLinkedPair();
|
||||
await server.connect(serverTransport);
|
||||
|
||||
Reference in new issue
Block a user