test(zalo): cache lifecycle monitor imports

This commit is contained in:
Peter Steinberger
2026-04-23 08:30:22 +01:00
parent ceadb9ddc4
commit 107fbe88a8
5 changed files with 56 additions and 16 deletions

View File

@@ -9,7 +9,7 @@ import {
import {
getUpdatesMock,
getZaloRuntimeMock,
loadLifecycleMonitorModule,
loadCachedLifecycleMonitorModule,
resetLifecycleTestState,
sendMessageMock,
} from "../test-support/monitor-mocks-test-support.js";
@@ -40,7 +40,7 @@ describe("Zalo polling image handling", () => {
})
.mockImplementation(() => new Promise(() => {}));
const { monitorZaloProvider } = await loadLifecycleMonitorModule();
const { monitorZaloProvider } = await loadCachedLifecycleMonitorModule("zalo-image-polling");
const abort = new AbortController();
const runtime = createRuntimeEnv();
const { account, config } = createLifecycleMonitorSetup({
@@ -79,7 +79,7 @@ describe("Zalo polling image handling", () => {
})
.mockImplementation(() => new Promise(() => {}));
const { monitorZaloProvider } = await loadLifecycleMonitorModule();
const { monitorZaloProvider } = await loadCachedLifecycleMonitorModule("zalo-image-polling");
const abort = new AbortController();
const runtime = createRuntimeEnv();
const { account, config } = createLifecycleMonitorSetup({

View File

@@ -44,7 +44,10 @@ describe("Zalo pairing lifecycle", () => {
}
it("emits one pairing reply across duplicate webhook replay and scopes reads and writes to accountId", async () => {
const monitor = await startWebhookLifecycleMonitor(createPairingMonitorSetup());
const monitor = await startWebhookLifecycleMonitor({
...createPairingMonitorSetup(),
cacheKey: "zalo-pairing-lifecycle",
});
try {
await withServer(
@@ -100,7 +103,10 @@ describe("Zalo pairing lifecycle", () => {
it("does not emit a second pairing reply when replay arrives after the first send fails", async () => {
sendMessageMock.mockRejectedValueOnce(new Error("pairing send failed"));
const monitor = await startWebhookLifecycleMonitor(createPairingMonitorSetup());
const monitor = await startWebhookLifecycleMonitor({
...createPairingMonitorSetup(),
cacheKey: "zalo-pairing-lifecycle",
});
try {
await withServer(

View File

@@ -9,7 +9,7 @@ import {
} from "../test-support/lifecycle-test-support.js";
import {
getUpdatesMock,
loadLifecycleMonitorModule,
loadCachedLifecycleMonitorModule,
resetLifecycleTestState,
sendPhotoMock,
setLifecycleRuntimeCore,
@@ -95,7 +95,9 @@ describe("Zalo polling media replies", () => {
})
.mockImplementation(() => new Promise(() => {}));
const { monitorZaloProvider } = await loadLifecycleMonitorModule();
const { monitorZaloProvider } = await loadCachedLifecycleMonitorModule(
"zalo-polling-media-reply",
);
const abort = new AbortController();
const runtime = createRuntimeEnv();
const { account, config } = createLifecycleMonitorSetup({
@@ -155,7 +157,9 @@ describe("Zalo polling media replies", () => {
})
.mockImplementation(() => new Promise(() => {}));
const { monitorZaloProvider } = await loadLifecycleMonitorModule();
const { monitorZaloProvider } = await loadCachedLifecycleMonitorModule(
"zalo-polling-media-reply",
);
const abort = new AbortController();
const runtime = createRuntimeEnv();
const { account, config } = createLifecycleMonitorSetup({
@@ -195,7 +199,9 @@ describe("Zalo polling media replies", () => {
setActivePluginRegistry(firstRegistry);
getUpdatesMock.mockImplementation(() => new Promise(() => {}));
const { monitorZaloProvider } = await loadLifecycleMonitorModule();
const { monitorZaloProvider } = await loadCachedLifecycleMonitorModule(
"zalo-polling-media-reply",
);
const firstAbort = new AbortController();
const firstRuntime = createRuntimeEnv();
const { account, config } = createLifecycleMonitorSetup({

View File

@@ -65,7 +65,10 @@ describe("Zalo reply-once lifecycle", () => {
},
);
const monitor = await startWebhookLifecycleMonitor(createReplyOnceMonitorSetup());
const monitor = await startWebhookLifecycleMonitor({
...createReplyOnceMonitorSetup(),
cacheKey: "zalo-reply-once-lifecycle",
});
try {
await withServer(
@@ -131,7 +134,10 @@ describe("Zalo reply-once lifecycle", () => {
},
);
const monitor = await startWebhookLifecycleMonitor(createReplyOnceMonitorSetup());
const monitor = await startWebhookLifecycleMonitor({
...createReplyOnceMonitorSetup(),
cacheKey: "zalo-reply-once-lifecycle",
});
try {
await withServer(

View File

@@ -21,6 +21,8 @@ const runtimeModuleId = new URL("../src/runtime.js", import.meta.url).pathname;
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
type AsyncUnknownMock = Mock<(...args: unknown[]) => Promise<unknown>>;
const loadedMonitorModules = new Set<MonitorModule>();
const cachedMonitorModules = new Map<string, Promise<MonitorModule>>();
let cachedWebhookModule: Promise<WebhookModule> | undefined;
type ZaloLifecycleMocks = {
setWebhookMock: AsyncUnknownMock;
@@ -102,17 +104,17 @@ async function importSecretInputModule(cacheBust: string): Promise<SecretInputMo
)) as SecretInputModule;
}
async function importWebhookModule(cacheBust: string): Promise<WebhookModule> {
return (await import(`${webhookModuleUrl}?t=${cacheBust}-${Date.now()}`)) as WebhookModule;
async function importCachedWebhookModule(): Promise<WebhookModule> {
cachedWebhookModule ??= import(webhookModuleUrl) as Promise<WebhookModule>;
return await cachedWebhookModule;
}
export async function resetLifecycleTestState() {
vi.clearAllMocks();
(await importWebhookModule("reset-webhook")).clearZaloWebhookSecurityStateForTest();
(await importCachedWebhookModule()).clearZaloWebhookSecurityStateForTest();
for (const module of loadedMonitorModules) {
module.__testing.clearHostedMediaRouteRefsForTest();
}
loadedMonitorModules.clear();
setActivePluginRegistry(createEmptyPluginRegistry());
}
@@ -130,12 +132,30 @@ export async function loadLifecycleMonitorModule(): Promise<MonitorModule> {
return await importMonitorModule({ cacheBust: "monitor", mocked: true });
}
export async function loadCachedLifecycleMonitorModule(cacheKey: string): Promise<MonitorModule> {
const key = cacheKey.trim();
if (!key) {
throw new Error("cacheKey is required");
}
const cached =
cachedMonitorModules.get(key) ??
(async () => {
installLifecycleModuleMocks();
const module = (await import(`${monitorModuleUrl}?t=${key}`)) as MonitorModule;
loadedMonitorModules.add(module);
return module;
})();
cachedMonitorModules.set(key, cached);
return await cached;
}
export async function startWebhookLifecycleMonitor(params: {
account: ResolvedZaloAccount;
config: OpenClawConfig;
token?: string;
webhookUrl?: string;
webhookSecret?: string;
cacheKey?: string;
}) {
const registry = createEmptyPluginRegistry();
setActivePluginRegistry(registry);
@@ -149,7 +169,9 @@ export async function startWebhookLifecycleMonitor(params: {
const { normalizeSecretInputString } = await importSecretInputModule("secret-input");
const webhookSecret =
params.webhookSecret ?? normalizeSecretInputString(params.account.config?.webhookSecret);
const { monitorZaloProvider } = await loadLifecycleMonitorModule();
const { monitorZaloProvider } = params.cacheKey
? await loadCachedLifecycleMonitorModule(params.cacheKey)
: await loadLifecycleMonitorModule();
const run = monitorZaloProvider({
token: params.token ?? "zalo-token",
account: params.account,