fix(feishu): bound probe cache expiry clocks

This commit is contained in:
Peter Steinberger
2026-05-30 11:31:16 -04:00
parent f91ddefbfb
commit 3da34a4673
2 changed files with 43 additions and 3 deletions
+26
View File
@@ -187,6 +187,32 @@ describe("probeFeishu", () => {
expect(requestFn).toHaveBeenCalledTimes(1);
});
it("does not cache probe results when the expiry would exceed a valid Date", async () => {
await withFakeTimers(async () => {
vi.setSystemTime(new Date(8_640_000_000_000_000));
const requestFn = setupSuccessClient();
const { first, second } = await readSequentialDefaultProbePair();
expect(first).toEqual(second);
expect(requestFn).toHaveBeenCalledTimes(2);
});
});
it("evicts cached probe results when the current clock is invalid", async () => {
const requestFn = setupSuccessClient();
await probeFeishu(DEFAULT_CREDS);
const dateNow = vi.spyOn(Date, "now").mockReturnValue(Number.NaN);
try {
await probeFeishu(DEFAULT_CREDS);
} finally {
dateNow.mockRestore();
}
expect(requestFn).toHaveBeenCalledTimes(2);
});
it("makes a fresh API call after cache expires", async () => {
await withFakeTimers(async () => {
const requestFn = setupSuccessClient();
+17 -3
View File
@@ -1,4 +1,8 @@
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import {
asDateTimestampMs,
resolveExpiresAtMsFromDurationMs,
} from "openclaw/plugin-sdk/number-runtime";
import { raceWithTimeoutAndAbort } from "./async.js";
import { createFeishuClient, type FeishuClientCredentials } from "./client.js";
import type { FeishuProbeResult } from "./types.js";
@@ -38,7 +42,12 @@ function setCachedProbeResult(
result: FeishuProbeResult,
ttlMs: number,
): FeishuProbeResult {
probeCache.set(cacheKey, { result, expiresAt: Date.now() + ttlMs });
const expiresAt = resolveExpiresAtMsFromDurationMs(ttlMs);
if (expiresAt === undefined) {
probeCache.delete(cacheKey);
return result;
}
probeCache.set(cacheKey, { result, expiresAt });
if (probeCache.size > MAX_PROBE_CACHE_SIZE) {
const oldest = probeCache.keys().next().value;
if (oldest !== undefined) {
@@ -74,8 +83,13 @@ export async function probeFeishu(
// pollute each other's cache entry.
const cacheKey = creds.accountId ?? `${creds.appId}:${creds.appSecret.slice(0, 8)}`;
const cached = probeCache.get(cacheKey);
if (cached && cached.expiresAt > Date.now()) {
return cached.result;
if (cached) {
const now = asDateTimestampMs(Date.now());
const expiresAt = asDateTimestampMs(cached.expiresAt);
if (now !== undefined && expiresAt !== undefined && expiresAt > now) {
return cached.result;
}
probeCache.delete(cacheKey);
}
try {