mirror of https://github.com/openclaw/openclaw.git
test: dedupe feishu probe fixtures
This commit is contained in:
parent
5b51d92f3e
commit
b23bfef8cc
|
|
@ -8,6 +8,22 @@ vi.mock("./client.js", () => ({
|
||||||
|
|
||||||
import { FEISHU_PROBE_REQUEST_TIMEOUT_MS, probeFeishu, clearProbeCache } from "./probe.js";
|
import { FEISHU_PROBE_REQUEST_TIMEOUT_MS, probeFeishu, clearProbeCache } from "./probe.js";
|
||||||
|
|
||||||
|
const DEFAULT_CREDS = { appId: "cli_123", appSecret: "secret" } as const; // pragma: allowlist secret
|
||||||
|
const DEFAULT_SUCCESS_RESPONSE = {
|
||||||
|
code: 0,
|
||||||
|
bot: { bot_name: "TestBot", open_id: "ou_abc123" },
|
||||||
|
} as const;
|
||||||
|
const DEFAULT_SUCCESS_RESULT = {
|
||||||
|
ok: true,
|
||||||
|
appId: "cli_123",
|
||||||
|
botName: "TestBot",
|
||||||
|
botOpenId: "ou_abc123",
|
||||||
|
} as const;
|
||||||
|
const BOT1_RESPONSE = {
|
||||||
|
code: 0,
|
||||||
|
bot: { bot_name: "Bot1", open_id: "ou_1" },
|
||||||
|
} as const;
|
||||||
|
|
||||||
function makeRequestFn(response: Record<string, unknown>) {
|
function makeRequestFn(response: Record<string, unknown>) {
|
||||||
return vi.fn().mockResolvedValue(response);
|
return vi.fn().mockResolvedValue(response);
|
||||||
}
|
}
|
||||||
|
|
@ -18,6 +34,64 @@ function setupClient(response: Record<string, unknown>) {
|
||||||
return requestFn;
|
return requestFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupSuccessClient() {
|
||||||
|
return setupClient(DEFAULT_SUCCESS_RESPONSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function expectDefaultSuccessResult(
|
||||||
|
creds = DEFAULT_CREDS,
|
||||||
|
expected = DEFAULT_SUCCESS_RESULT,
|
||||||
|
) {
|
||||||
|
const result = await probeFeishu(creds);
|
||||||
|
expect(result).toEqual(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function withFakeTimers(run: () => Promise<void>) {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
try {
|
||||||
|
await run();
|
||||||
|
} finally {
|
||||||
|
vi.useRealTimers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function expectErrorResultCached(params: {
|
||||||
|
requestFn: ReturnType<typeof vi.fn>;
|
||||||
|
expectedError: string;
|
||||||
|
ttlMs: number;
|
||||||
|
}) {
|
||||||
|
createFeishuClientMock.mockReturnValue({ request: params.requestFn });
|
||||||
|
|
||||||
|
const first = await probeFeishu(DEFAULT_CREDS);
|
||||||
|
const second = await probeFeishu(DEFAULT_CREDS);
|
||||||
|
expect(first).toMatchObject({ ok: false, error: params.expectedError });
|
||||||
|
expect(second).toMatchObject({ ok: false, error: params.expectedError });
|
||||||
|
expect(params.requestFn).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(params.ttlMs + 1);
|
||||||
|
|
||||||
|
await probeFeishu(DEFAULT_CREDS);
|
||||||
|
expect(params.requestFn).toHaveBeenCalledTimes(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function expectFreshDefaultProbeAfter(
|
||||||
|
requestFn: ReturnType<typeof vi.fn>,
|
||||||
|
invalidate: () => void,
|
||||||
|
) {
|
||||||
|
await probeFeishu(DEFAULT_CREDS);
|
||||||
|
expect(requestFn).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
|
||||||
|
await probeFeishu(DEFAULT_CREDS);
|
||||||
|
expect(requestFn).toHaveBeenCalledTimes(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readSequentialDefaultProbePair() {
|
||||||
|
const first = await probeFeishu(DEFAULT_CREDS);
|
||||||
|
return { first, second: await probeFeishu(DEFAULT_CREDS) };
|
||||||
|
}
|
||||||
|
|
||||||
describe("probeFeishu", () => {
|
describe("probeFeishu", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
clearProbeCache();
|
clearProbeCache();
|
||||||
|
|
@ -44,28 +118,16 @@ describe("probeFeishu", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns bot info on successful probe", async () => {
|
it("returns bot info on successful probe", async () => {
|
||||||
const requestFn = setupClient({
|
const requestFn = setupSuccessClient();
|
||||||
code: 0,
|
|
||||||
bot: { bot_name: "TestBot", open_id: "ou_abc123" },
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await probeFeishu({ appId: "cli_123", appSecret: "secret" }); // pragma: allowlist secret
|
await expectDefaultSuccessResult();
|
||||||
expect(result).toEqual({
|
|
||||||
ok: true,
|
|
||||||
appId: "cli_123",
|
|
||||||
botName: "TestBot",
|
|
||||||
botOpenId: "ou_abc123",
|
|
||||||
});
|
|
||||||
expect(requestFn).toHaveBeenCalledTimes(1);
|
expect(requestFn).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes the probe timeout to the Feishu request", async () => {
|
it("passes the probe timeout to the Feishu request", async () => {
|
||||||
const requestFn = setupClient({
|
const requestFn = setupSuccessClient();
|
||||||
code: 0,
|
|
||||||
bot: { bot_name: "TestBot", open_id: "ou_abc123" },
|
|
||||||
});
|
|
||||||
|
|
||||||
await probeFeishu({ appId: "cli_123", appSecret: "secret" }); // pragma: allowlist secret
|
await probeFeishu(DEFAULT_CREDS);
|
||||||
|
|
||||||
expect(requestFn).toHaveBeenCalledWith(
|
expect(requestFn).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
|
@ -77,19 +139,16 @@ describe("probeFeishu", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns timeout error when request exceeds timeout", async () => {
|
it("returns timeout error when request exceeds timeout", async () => {
|
||||||
vi.useFakeTimers();
|
await withFakeTimers(async () => {
|
||||||
try {
|
|
||||||
const requestFn = vi.fn().mockImplementation(() => new Promise(() => {}));
|
const requestFn = vi.fn().mockImplementation(() => new Promise(() => {}));
|
||||||
createFeishuClientMock.mockReturnValue({ request: requestFn });
|
createFeishuClientMock.mockReturnValue({ request: requestFn });
|
||||||
|
|
||||||
const promise = probeFeishu({ appId: "cli_123", appSecret: "secret" }, { timeoutMs: 1_000 });
|
const promise = probeFeishu(DEFAULT_CREDS, { timeoutMs: 1_000 });
|
||||||
await vi.advanceTimersByTimeAsync(1_000);
|
await vi.advanceTimersByTimeAsync(1_000);
|
||||||
const result = await promise;
|
const result = await promise;
|
||||||
|
|
||||||
expect(result).toMatchObject({ ok: false, error: "probe timed out after 1000ms" });
|
expect(result).toMatchObject({ ok: false, error: "probe timed out after 1000ms" });
|
||||||
} finally {
|
});
|
||||||
vi.useRealTimers();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns aborted when abort signal is already aborted", async () => {
|
it("returns aborted when abort signal is already aborted", async () => {
|
||||||
|
|
@ -106,14 +165,9 @@ describe("probeFeishu", () => {
|
||||||
expect(createFeishuClientMock).not.toHaveBeenCalled();
|
expect(createFeishuClientMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it("returns cached result on subsequent calls within TTL", async () => {
|
it("returns cached result on subsequent calls within TTL", async () => {
|
||||||
const requestFn = setupClient({
|
const requestFn = setupSuccessClient();
|
||||||
code: 0,
|
|
||||||
bot: { bot_name: "TestBot", open_id: "ou_abc123" },
|
|
||||||
});
|
|
||||||
|
|
||||||
const creds = { appId: "cli_123", appSecret: "secret" }; // pragma: allowlist secret
|
const { first, second } = await readSequentialDefaultProbePair();
|
||||||
const first = await probeFeishu(creds);
|
|
||||||
const second = await probeFeishu(creds);
|
|
||||||
|
|
||||||
expect(first).toEqual(second);
|
expect(first).toEqual(second);
|
||||||
// Only one API call should have been made
|
// Only one API call should have been made
|
||||||
|
|
@ -121,76 +175,37 @@ describe("probeFeishu", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("makes a fresh API call after cache expires", async () => {
|
it("makes a fresh API call after cache expires", async () => {
|
||||||
vi.useFakeTimers();
|
await withFakeTimers(async () => {
|
||||||
try {
|
const requestFn = setupSuccessClient();
|
||||||
const requestFn = setupClient({
|
|
||||||
code: 0,
|
|
||||||
bot: { bot_name: "TestBot", open_id: "ou_abc123" },
|
|
||||||
});
|
|
||||||
|
|
||||||
const creds = { appId: "cli_123", appSecret: "secret" }; // pragma: allowlist secret
|
await expectFreshDefaultProbeAfter(requestFn, () => {
|
||||||
await probeFeishu(creds);
|
|
||||||
expect(requestFn).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
// Advance time past the success TTL
|
|
||||||
vi.advanceTimersByTime(10 * 60 * 1000 + 1);
|
vi.advanceTimersByTime(10 * 60 * 1000 + 1);
|
||||||
|
});
|
||||||
await probeFeishu(creds);
|
});
|
||||||
expect(requestFn).toHaveBeenCalledTimes(2);
|
|
||||||
} finally {
|
|
||||||
vi.useRealTimers();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("caches failed probe results (API error) for the error TTL", async () => {
|
it("caches failed probe results (API error) for the error TTL", async () => {
|
||||||
vi.useFakeTimers();
|
await withFakeTimers(async () => {
|
||||||
try {
|
await expectErrorResultCached({
|
||||||
const requestFn = makeRequestFn({ code: 99, msg: "token expired" });
|
requestFn: makeRequestFn({ code: 99, msg: "token expired" }),
|
||||||
createFeishuClientMock.mockReturnValue({ request: requestFn });
|
expectedError: "API error: token expired",
|
||||||
|
ttlMs: 60 * 1000,
|
||||||
const creds = { appId: "cli_123", appSecret: "secret" }; // pragma: allowlist secret
|
});
|
||||||
const first = await probeFeishu(creds);
|
});
|
||||||
const second = await probeFeishu(creds);
|
|
||||||
expect(first).toMatchObject({ ok: false, error: "API error: token expired" });
|
|
||||||
expect(second).toMatchObject({ ok: false, error: "API error: token expired" });
|
|
||||||
expect(requestFn).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
vi.advanceTimersByTime(60 * 1000 + 1);
|
|
||||||
|
|
||||||
await probeFeishu(creds);
|
|
||||||
expect(requestFn).toHaveBeenCalledTimes(2);
|
|
||||||
} finally {
|
|
||||||
vi.useRealTimers();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("caches thrown request errors for the error TTL", async () => {
|
it("caches thrown request errors for the error TTL", async () => {
|
||||||
vi.useFakeTimers();
|
await withFakeTimers(async () => {
|
||||||
try {
|
await expectErrorResultCached({
|
||||||
const requestFn = vi.fn().mockRejectedValue(new Error("network error"));
|
requestFn: vi.fn().mockRejectedValue(new Error("network error")),
|
||||||
createFeishuClientMock.mockReturnValue({ request: requestFn });
|
expectedError: "network error",
|
||||||
|
ttlMs: 60 * 1000,
|
||||||
const creds = { appId: "cli_123", appSecret: "secret" }; // pragma: allowlist secret
|
});
|
||||||
const first = await probeFeishu(creds);
|
});
|
||||||
const second = await probeFeishu(creds);
|
|
||||||
expect(first).toMatchObject({ ok: false, error: "network error" });
|
|
||||||
expect(second).toMatchObject({ ok: false, error: "network error" });
|
|
||||||
expect(requestFn).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
vi.advanceTimersByTime(60 * 1000 + 1);
|
|
||||||
|
|
||||||
await probeFeishu(creds);
|
|
||||||
expect(requestFn).toHaveBeenCalledTimes(2);
|
|
||||||
} finally {
|
|
||||||
vi.useRealTimers();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("caches per account independently", async () => {
|
it("caches per account independently", async () => {
|
||||||
const requestFn = setupClient({
|
const requestFn = setupClient(BOT1_RESPONSE);
|
||||||
code: 0,
|
|
||||||
bot: { bot_name: "Bot1", open_id: "ou_1" },
|
|
||||||
});
|
|
||||||
|
|
||||||
await probeFeishu({ appId: "cli_aaa", appSecret: "s1" }); // pragma: allowlist secret
|
await probeFeishu({ appId: "cli_aaa", appSecret: "s1" }); // pragma: allowlist secret
|
||||||
expect(requestFn).toHaveBeenCalledTimes(1);
|
expect(requestFn).toHaveBeenCalledTimes(1);
|
||||||
|
|
@ -205,10 +220,7 @@ describe("probeFeishu", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not share cache between accounts with same appId but different appSecret", async () => {
|
it("does not share cache between accounts with same appId but different appSecret", async () => {
|
||||||
const requestFn = setupClient({
|
const requestFn = setupClient(BOT1_RESPONSE);
|
||||||
code: 0,
|
|
||||||
bot: { bot_name: "Bot1", open_id: "ou_1" },
|
|
||||||
});
|
|
||||||
|
|
||||||
// First account with appId + secret A
|
// First account with appId + secret A
|
||||||
await probeFeishu({ appId: "cli_shared", appSecret: "secret_aaa" }); // pragma: allowlist secret
|
await probeFeishu({ appId: "cli_shared", appSecret: "secret_aaa" }); // pragma: allowlist secret
|
||||||
|
|
@ -221,10 +233,7 @@ describe("probeFeishu", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses accountId for cache key when available", async () => {
|
it("uses accountId for cache key when available", async () => {
|
||||||
const requestFn = setupClient({
|
const requestFn = setupClient(BOT1_RESPONSE);
|
||||||
code: 0,
|
|
||||||
bot: { bot_name: "Bot1", open_id: "ou_1" },
|
|
||||||
});
|
|
||||||
|
|
||||||
// Two accounts with same appId+appSecret but different accountIds are cached separately
|
// Two accounts with same appId+appSecret but different accountIds are cached separately
|
||||||
await probeFeishu({ accountId: "acct-1", appId: "cli_123", appSecret: "secret" }); // pragma: allowlist secret
|
await probeFeishu({ accountId: "acct-1", appId: "cli_123", appSecret: "secret" }); // pragma: allowlist secret
|
||||||
|
|
@ -239,19 +248,11 @@ describe("probeFeishu", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clearProbeCache forces fresh API call", async () => {
|
it("clearProbeCache forces fresh API call", async () => {
|
||||||
const requestFn = setupClient({
|
const requestFn = setupSuccessClient();
|
||||||
code: 0,
|
|
||||||
bot: { bot_name: "TestBot", open_id: "ou_abc123" },
|
|
||||||
});
|
|
||||||
|
|
||||||
const creds = { appId: "cli_123", appSecret: "secret" }; // pragma: allowlist secret
|
|
||||||
await probeFeishu(creds);
|
|
||||||
expect(requestFn).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
|
await expectFreshDefaultProbeAfter(requestFn, () => {
|
||||||
clearProbeCache();
|
clearProbeCache();
|
||||||
|
});
|
||||||
await probeFeishu(creds);
|
|
||||||
expect(requestFn).toHaveBeenCalledTimes(2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles response.data.bot fallback path", async () => {
|
it("handles response.data.bot fallback path", async () => {
|
||||||
|
|
@ -260,10 +261,8 @@ describe("probeFeishu", () => {
|
||||||
data: { bot: { bot_name: "DataBot", open_id: "ou_data" } },
|
data: { bot: { bot_name: "DataBot", open_id: "ou_data" } },
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await probeFeishu({ appId: "cli_123", appSecret: "secret" }); // pragma: allowlist secret
|
await expectDefaultSuccessResult(DEFAULT_CREDS, {
|
||||||
expect(result).toEqual({
|
...DEFAULT_SUCCESS_RESULT,
|
||||||
ok: true,
|
|
||||||
appId: "cli_123",
|
|
||||||
botName: "DataBot",
|
botName: "DataBot",
|
||||||
botOpenId: "ou_data",
|
botOpenId: "ou_data",
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue