Files
openclaw/extensions/msteams/src/graph.timeout.test.ts
wangmiao0668000666 dc0285366e fix(msteams): bound probe token acquisition to request deadline (#106386)
* fix(msteams): bound probe token acquisition to request deadline

probeMSTeams() at extensions/msteams/src/probe.ts:75 and :89 awaited
tokenProvider.getAccessToken(...) for the Bot Framework and Microsoft
Graph token endpoints with no surrounding deadline. The Microsoft
Teams SDK does not carry an inherent timeout on these calls, so a
stalled Azure AD token endpoint pinned the probe indefinitely.

Wrap both awaits with withMSTeamsRequestDeadline (default
MSTEAMS_REQUEST_TIMEOUT_MS = 30_000), matching the pattern already
used by six other MS Teams call sites: attachments/bot-framework.ts:252,
attachments/graph.ts:258, monitor-handler/message-handler.ts:594/654/685/692,
attachments/download.ts:167, team-identity.ts:37.

The probe was the one missing site. No new helper, no SDK change.
The existing outer catch at probe.ts:138 and inner catch at probe.ts:110
convert the timeout into a ProbeMSTeamsResult with ok: false and a
structured error field.

Added probe.timeout.test.ts: real probeMSTeams() with vi.mock
injected never-resolving getBotToken/getGraphToken; asserts the call
returns within the 30s bound instead of hanging to the proof budget.

* test(msteams): drive probe timeout test with vi.useFakeTimers

The original probe.timeout.test.ts waited 90 seconds of wall-clock per
focused run (3 stalled cases racing against a real setTimeout budget).
Per ClawSweeper P2 (automation), this material deterministic CI cost can
slow or time out test shards.

Drive the withTimeout race (from @openclaw/fs-safe/dist/timing.js, uses
setTimeout + clearTimeout) via vi.useFakeTimers() so each stalled case
resolves in milliseconds. Add one new case that spies on withTimeout's
timeoutMs argument to assert the production default deadline is exactly
MSTEAMS_REQUEST_TIMEOUT_MS = 30_000, so the production contract is not
silently weakened by the fake-timer change.

Per-case wall-clock: 25ms / 3ms / 2ms / 1ms / 2ms (was: 30s / 30s / 30s /
2ms / n/a).

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(msteams): bound remaining token acquisition

* test(msteams): keep credential fixture unchanged

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-18 01:21:49 +01:00

65 lines
1.7 KiB
TypeScript

// Msteams tests cover Graph token request deadlines.
import { afterEach, describe, expect, it, vi } from "vitest";
const sdkMock = vi.hoisted(() => ({
acquire: vi.fn<(scope: string) => Promise<string>>(),
}));
vi.mock("./sdk.js", () => ({
createMSTeamsTokenProvider() {
return {
async getAccessToken(scope: string) {
return await sdkMock.acquire(scope);
},
};
},
async loadMSTeamsSdkWithAuth() {
return { app: {} };
},
}));
vi.mock("./token-response.js", () => ({
readAccessToken(value: unknown) {
return typeof value === "string" ? value : null;
},
}));
vi.mock("./token.js", () => ({
async resolveDelegatedAccessToken() {
return undefined;
},
resolveMSTeamsCredentials() {
return {
type: "secret",
appId: "app-id",
appPassword: "test-app-password",
tenantId: "tenant-id",
};
},
}));
import { resolveGraphToken } from "./graph.js";
import { MSTEAMS_REQUEST_TIMEOUT_MS } from "./request-timeout.js";
describe("resolveGraphToken request deadline", () => {
afterEach(() => {
vi.clearAllMocks();
vi.useRealTimers();
});
it("bounds stalled SDK token acquisition", async () => {
sdkMock.acquire.mockImplementation(() => new Promise<string>(() => {}));
vi.useFakeTimers();
const result = resolveGraphToken({ channels: { msteams: {} } });
const rejection = expect(result).rejects.toThrow(
`MS Teams Graph token timed out after ${MSTEAMS_REQUEST_TIMEOUT_MS}ms`,
);
await vi.advanceTimersByTimeAsync(0);
await vi.advanceTimersByTimeAsync(MSTEAMS_REQUEST_TIMEOUT_MS);
await rejection;
expect(sdkMock.acquire).toHaveBeenCalledWith("https://graph.microsoft.com");
});
});