* fix(storage): await store-file writes instead of polling (#1412) OAuth persistence is Zustand `persist` → file-storage adapter → writeStoreFile, which the persist middleware invokes fire-and-forget: saveTokens updates the in-memory store synchronously while the file write lags. Tests verified persistence by polling the state file with a 2s/50ms `waitForStateFile()` budget, which flaked under full-suite load (the file was written, the poll just didn't observe it in time). Make the write completion awaitable instead: - Track in-flight writeStoreFile() promises per resolved path in store-io and add flushStoreFileWrites(filePath?) to await them (also useful for graceful shutdown). Writes to the same path are chained so a flush awaits every queued write. - Convert the OAuth E2E, node storage, and file-storage-adapter tests from poll loops (waitForStateFile / vi.waitFor on existsSync / setTimeout) to `await flushStoreFileWrites(path)` + a single read. - Remove the now-unused waitForStateFile() test helper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(storage): doc load-bearing sync registration; rename stale debug env Address @claude review on #1413: - Document that writeStoreFile()'s synchronous pendingWrites registration is load-bearing for flushStoreFileWrites(), in both the storage adapter's setItem callsite and the pendingWrites docblock, so future refactors don't introduce an await before registration and silently re-flake the tests. - Rename the stale DEBUG_WAIT_FOR_STATE_FILE test env var to DEBUG_PERSIST_PATH (waitForStateFile was removed in this PR). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8be1680494
commit
e1c8f71924
1 changed files with 153 additions and 143 deletions
|
|
@ -21,7 +21,6 @@ import { FetchRequestLogState } from "@inspector/core/mcp/state/index.js";
|
|||
import { createTransportNode } from "@inspector/core/mcp/node/transport.js";
|
||||
import {
|
||||
TestServerHttp,
|
||||
waitForStateFile,
|
||||
waitForOAuthWellKnown,
|
||||
getDefaultServerConfig,
|
||||
createOAuthTestServerConfig,
|
||||
|
|
@ -29,6 +28,7 @@ import {
|
|||
getDCRRequests,
|
||||
invalidateAccessToken,
|
||||
} from "@modelcontextprotocol/inspector-test-server";
|
||||
import { flushStoreFileWrites } from "@inspector/core/storage/store-io.js";
|
||||
import {
|
||||
createOAuthClientConfig,
|
||||
completeOAuthAuthorization,
|
||||
|
|
@ -1752,21 +1752,16 @@ describe("InspectorClient OAuth E2E", () => {
|
|||
servers?: Record<string, { tokens?: { access_token?: string } }>;
|
||||
};
|
||||
};
|
||||
const parsed = await waitForStateFile<StateShape>(
|
||||
customPath,
|
||||
(p) => {
|
||||
const servers = (p as StateShape)?.state?.servers ?? {};
|
||||
return Object.values(servers).some(
|
||||
(s) =>
|
||||
!!(s as { tokens?: { access_token?: string } })?.tokens
|
||||
?.access_token,
|
||||
);
|
||||
},
|
||||
{ timeout: 2000, interval: 50 },
|
||||
);
|
||||
expect(Object.keys(parsed.state?.servers ?? {}).length).toBeGreaterThan(
|
||||
0,
|
||||
);
|
||||
// Persistence is fire-and-forget; await the write rather than polling.
|
||||
await flushStoreFileWrites(customPath);
|
||||
const parsed = JSON.parse(
|
||||
await fs.readFile(customPath, "utf-8"),
|
||||
) as StateShape;
|
||||
const servers = parsed.state?.servers ?? {};
|
||||
expect(Object.keys(servers).length).toBeGreaterThan(0);
|
||||
expect(
|
||||
Object.values(servers).some((s) => !!s?.tokens?.access_token),
|
||||
).toBe(true);
|
||||
} finally {
|
||||
try {
|
||||
await fs.unlink(customPath);
|
||||
|
|
|
|||
Loading…
Reference in a new issue