Skip to content

Commit f6b9901

Browse files
fix(msteams): bound team group lookup cache (#102814)
Co-authored-by: sunlit-deng <[email protected]>
1 parent 474d660 commit f6b9901

2 files changed

Lines changed: 36 additions & 1 deletion

File tree

extensions/msteams/src/graph-thread.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,37 @@ describe("resolveTeamGroupId", () => {
123123
const result = await resolveTeamGroupId("tok", "team-fallback");
124124
expect(result).toBe("team-fallback");
125125
});
126+
127+
it("caps cache at 500 entries — evicts oldest on overflow", async () => {
128+
vi.mocked(fetchGraphJson).mockResolvedValue({ id: "group-guid" } as never);
129+
130+
const token = "test-token";
131+
for (let i = 0; i < 500; i++) {
132+
await resolveTeamGroupId(token, `team-${i}`);
133+
}
134+
expect(_teamGroupIdCacheForTest.size).toBe(500);
135+
expect(_teamGroupIdCacheForTest.has("team-0")).toBe(true);
136+
expect(_teamGroupIdCacheForTest.has("team-499")).toBe(true);
137+
138+
vi.mocked(fetchGraphJson).mockClear();
139+
await resolveTeamGroupId(token, "team-500");
140+
expect(fetchGraphJson).toHaveBeenCalledTimes(1);
141+
expect(_teamGroupIdCacheForTest.size).toBe(500);
142+
expect(_teamGroupIdCacheForTest.has("team-0")).toBe(false);
143+
expect(_teamGroupIdCacheForTest.has("team-500")).toBe(true);
144+
145+
vi.mocked(fetchGraphJson).mockClear();
146+
await resolveTeamGroupId(token, "team-0");
147+
expect(fetchGraphJson).toHaveBeenCalledTimes(1);
148+
expect(_teamGroupIdCacheForTest.size).toBe(500);
149+
expect(_teamGroupIdCacheForTest.has("team-1")).toBe(false);
150+
expect(_teamGroupIdCacheForTest.has("team-500")).toBe(true);
151+
152+
// team-500 remains cached after team-0 is reinserted at the insertion-order tail.
153+
vi.mocked(fetchGraphJson).mockClear();
154+
await resolveTeamGroupId(token, "team-500");
155+
expect(fetchGraphJson).toHaveBeenCalledTimes(0);
156+
});
126157
});
127158

128159
describe("fetchChannelMessage", () => {

extensions/msteams/src/graph-thread.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Msteams plugin module implements graph thread behavior.
2+
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
23
import {
34
asDateTimestampMs,
45
resolveExpiresAtMsFromDurationMs,
@@ -15,9 +16,11 @@ export type GraphThreadMessage = {
1516
createdDateTime?: string;
1617
};
1718

18-
// TTL cache for team ID -> group GUID mapping.
19+
// Successful lookups use a 10-minute TTL and a 500-entry insertion-order cap.
20+
// Pruning after insert evicts the oldest team IDs before this process cache grows unbounded.
1921
const teamGroupIdCache = new Map<string, { groupId: string; expiresAt: number }>();
2022
const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
23+
const TEAM_GROUP_ID_CACHE_MAX_ENTRIES = 500;
2124

2225
function resolveTeamGroupIdCacheExpiresAt(nowRaw = Date.now()): number | undefined {
2326
const now = asDateTimestampMs(nowRaw);
@@ -84,6 +87,7 @@ export async function resolveTeamGroupId(
8487
groupId,
8588
expiresAt,
8689
});
90+
pruneMapToMaxSize(teamGroupIdCache, TEAM_GROUP_ID_CACHE_MAX_ENTRIES);
8791
}
8892

8993
return groupId;

0 commit comments

Comments
 (0)