import { z } from
"openclaw/plugin-sdk/zod" ;
import { beforeAll, beforeEach, describe, expect, it, vi } from
"vitest" ;
import type { MatrixRoomInfo } from
"./room-info.js" ;
type DirectRoomTrackerOptions = {
canPromoteRecentInvite?: (roomId: string) =>
boolean | Promise<
boolean >;
shouldKeepLocallyPromotedDirectRoom?:
| ((roomId: string) =>
boolean | undefined | Promise<
boolean | undefined>)
| undefined;
};
const hoisted = vi.hoisted(() => {
const createEmitter = () => {
const listeners =
new Map<string, Set<(...args: unknown[]) =>
void >>();
return {
on(event: string, listener: (...args: unknown[]) =>
void ) {
let bucket = listeners.get(event);
if (!bucket) {
bucket =
new Set();
listeners.set(event, bucket);
}
bucket.add(listener);
return this ;
},
off(event: string, listener: (...args: unknown[]) =>
void ) {
listeners.get(event)?.
delete (listener);
return this ;
},
emit(event: string, ...args: unknown[]) {
for (
const listener of listeners.get(event) ?? []) {
listener(...args);
}
return true ;
},
removeAllListeners() {
listeners.clear();
return this ;
},
};
};
const callOrder: string[] = [];
const state = {
startClientError:
null as Error |
null ,
};
const accountConfig = {
dm: {},
};
const inboundDeduper = {
claimEvent: vi.fn(() =>
true ),
commitEvent: vi.fn(async () => undefined),
releaseEvent: vi.fn(),
flush: vi.fn(async () => undefined),
stop: vi.fn(async () => undefined),
};
const createMatrixInboundEventDeduper = vi.fn(async () => inboundDeduper);
const client = Object.assign(createEmitter(), {
id:
"matrix-client" ,
hasPersistedSyncState: vi.fn(() =>
false ),
stopSyncWithoutPersist: vi.fn(),
drainPendingDecryptions: vi.fn(async () => undefined),
});
const createMatrixRoomMessageHandler = vi.fn(() => vi.fn());
const createDirectRoomTracker = vi.fn((_client: unknown, _opts?: DirectRoomTrackerOpti
ons) => ({
isDirectMessage: vi.fn(async () => false ),
}));
const getRoomInfo = vi.fn<
(roomId: string, opts?: { includeAliases?: boolean }) => Promise<MatrixRoomInfo>
>(async () => ({
altAliases: [],
nameResolved: true ,
aliasesResolved: true ,
}));
const getMemberDisplayName = vi.fn(async () => "Bot" );
const resolveTextChunkLimit = vi.fn<
(cfg: unknown, channel: unknown, accountId?: unknown) => number
>(() => 4000 );
const logger = {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
};
const stopThreadBindingManager = vi.fn();
const releaseSharedClientInstance = vi.fn(async () => true );
const resolveSharedMatrixClient = vi.fn(async (params: { startClient?: boolean }) => {
if (params.startClient === false ) {
callOrder.push("prepare-client" );
return client;
}
if (!callOrder.includes("create-manager" )) {
throw new Error("Matrix client started before thread bindings were registered" );
}
if (state.startClientError) {
throw state.startClientError;
}
callOrder.push("start-client" );
return client;
});
const setActiveMatrixClient = vi.fn();
const setMatrixRuntime = vi.fn();
const backfillMatrixAuthDeviceIdAfterStartup = vi.fn(async () => undefined);
const runMatrixStartupMaintenance = vi.fn<
(params: { abortSignal?: AbortSignal }) => Promise<void >
>(async () => undefined);
const setStatus = vi.fn();
return {
backfillMatrixAuthDeviceIdAfterStartup,
callOrder,
accountConfig,
client,
createDirectRoomTracker,
createMatrixInboundEventDeduper,
createMatrixRoomMessageHandler,
getMemberDisplayName,
getRoomInfo,
inboundDeduper,
logger,
registeredOnRoomMessage: null as null | ((roomId: string, event: unknown) => Promise<void >),
releaseSharedClientInstance,
resolveSharedMatrixClient,
resolveTextChunkLimit,
runMatrixStartupMaintenance,
registeredHealthySyncGetter: undefined as undefined | (() => number | undefined),
setActiveMatrixClient,
setMatrixRuntime,
setStatus,
state,
stopThreadBindingManager,
};
});
vi.mock("../../runtime-api.js" , () => {
const normalizeAccountId = (value: string | null | undefined) => value?.trim() || "default" ;
return {
DEFAULT_ACCOUNT_ID: "default" ,
GROUP_POLICY_BLOCKED_LABEL: {
room: "room" ,
},
MarkdownConfigSchema: z.any().optional(),
PAIRING_APPROVED_MESSAGE: "paired" ,
ToolPolicySchema: z.any().optional(),
addAllowlistUserEntriesFromConfigEntry: vi.fn(),
buildChannelConfigSchema: (schema: unknown) => schema,
buildChannelKeyCandidates: () => [],
buildProbeChannelStatusSummary: (
snapshot: Record<string, unknown>,
extra?: Record<string, unknown>,
) => ({
...snapshot,
...extra,
}),
buildSecretInputSchema: () => z.string(),
chunkTextForOutbound: vi.fn((text: string) => [text]),
collectStatusIssuesFromLastError: () => [],
createActionGate: () => () => true ,
createReplyPrefixOptions: () => ({}),
createTypingCallbacks: () => ({}),
formatDocsLink: (input: string) => input,
formatZonedTimestamp: () => "2026-03-27T00:00:00.000Z" ,
getAgentScopedMediaLocalRoots: () => [],
getSessionBindingService: () => ({}),
hasConfiguredSecretInput: (value: unknown) => Boolean (value),
mergeAllowlist: ({ existing, additions }: { existing: string[]; additions: string[] }) => [
...existing,
...additions,
],
normalizeAccountId,
normalizeOptionalAccountId: normalizeAccountId,
resolveThreadBindingIdleTimeoutMsForChannel: () => 24 * 60 * 60 * 1000 ,
resolveThreadBindingMaxAgeMsForChannel: () => 0 ,
resolveAllowlistProviderRuntimeGroupPolicy: () => ({
groupPolicy: "allowlist" ,
providerMissingFallbackApplied: false ,
}),
resolveChannelEntryMatch: ({
entries,
keys,
wildcardKey,
}: {
entries: Record<string, unknown>;
keys: string[];
wildcardKey: string;
}) => {
for (const key of keys) {
if (Object.prototype.hasOwnProperty.call(entries, key)) {
return {
entry: entries[key],
key,
wildcardEntry: Object.prototype.hasOwnProperty.call(entries, wildcardKey)
? entries[wildcardKey]
: undefined,
wildcardKey: Object.prototype.hasOwnProperty.call(entries, wildcardKey)
? wildcardKey
: undefined,
};
}
}
return {
entry: undefined,
key: undefined,
wildcardEntry: Object.prototype.hasOwnProperty.call(entries, wildcardKey)
? entries[wildcardKey]
: undefined,
wildcardKey: Object.prototype.hasOwnProperty.call(entries, wildcardKey)
? wildcardKey
: undefined,
};
},
resolveDefaultGroupPolicy: () => "allowlist" ,
resolveOutboundSendDep: () => null ,
resolveThreadBindingFarewellText: () => null ,
resolveAckReaction: () => null ,
readJsonFileWithFallback: vi.fn(),
readNumberParam: vi.fn(),
readReactionParams: vi.fn(),
readStringArrayParam: vi.fn(),
readStringParam: vi.fn(),
summarizeMapping: vi.fn(),
warnMissingProviderGroupPolicyFallbackOnce: vi.fn(),
};
});
vi.mock("../../resolve-targets.js" , () => ({
resolveMatrixTargets: vi.fn(async () => []),
}));
vi.mock("../../runtime.js" , () => ({
getMatrixRuntime: () => ({
config: {
loadConfig: () => ({
channels: {
matrix: hoisted.accountConfig,
},
}),
writeConfigFile: vi.fn(),
},
logging: {
getChildLogger: () => hoisted.logger,
shouldLogVerbose: () => false ,
},
channel: {
mentions: {
buildMentionRegexes: () => [],
},
text: {
resolveTextChunkLimit: (cfg: unknown, channel: unknown, accountId?: unknown) =>
hoisted.resolveTextChunkLimit(cfg, channel, accountId),
},
},
system: {
formatNativeDependencyHint: () => "" ,
},
media: {
loadWebMedia: vi.fn(),
},
}),
setMatrixRuntime: hoisted.setMatrixRuntime,
}));
vi.mock("../accounts.js" , async () => {
const actual = await vi.importActual<typeof import ("../accounts.js" )>("../accounts.js" );
return {
...actual,
resolveConfiguredMatrixBotUserIds: vi.fn(() => new Set<string>()),
resolveMatrixAccount: () => ({
accountId: "default" ,
config: hoisted.accountConfig,
}),
};
});
vi.mock("../active-client.js" , () => ({
setActiveMatrixClient: hoisted.setActiveMatrixClient,
}));
vi.mock("../client.js" , () => ({
backfillMatrixAuthDeviceIdAfterStartup: hoisted.backfillMatrixAuthDeviceIdAfterStartup,
isBunRuntime: () => false ,
resolveMatrixAuth: vi.fn(async () => ({
accountId: "default" ,
homeserver: "https://matrix.example.org ",
userId: "@bot:example.org" ,
accessToken: "token" ,
initialSyncLimit: 20 ,
encryption: false ,
})),
resolveMatrixAuthContext: vi.fn(() => ({
accountId: "default" ,
})),
resolveSharedMatrixClient: hoisted.resolveSharedMatrixClient,
}));
vi.mock("../client/shared.js" , () => ({
releaseSharedClientInstance: hoisted.releaseSharedClientInstance,
}));
vi.mock("../config-update.js" , () => ({
updateMatrixAccountConfig: vi.fn((cfg: unknown) => cfg),
}));
vi.mock("../device-health.js" , () => ({
summarizeMatrixDeviceHealth: vi.fn(() => ({
staleOpenClawDevices: [],
})),
}));
vi.mock("../profile.js" , () => ({
syncMatrixOwnProfile: vi.fn(async () => ({
displayNameUpdated: false ,
avatarUpdated: false ,
convertedAvatarFromHttp: false ,
resolvedAvatarUrl: undefined,
})),
}));
vi.mock("../thread-bindings.js" , () => ({
createMatrixThreadBindingManager: vi.fn(async () => {
hoisted.callOrder.push("create-manager" );
return {
accountId: "default" ,
stop: hoisted.stopThreadBindingManager,
};
}),
}));
vi.mock("./allowlist.js" , () => ({
normalizeMatrixUserId: (value: string) => value,
}));
vi.mock("./auto-join.js" , () => ({
registerMatrixAutoJoin: vi.fn(),
}));
vi.mock("./direct.js" , () => ({
createDirectRoomTracker: hoisted.createDirectRoomTracker,
}));
vi.mock("./events.js" , () => ({
registerMatrixMonitorEvents: vi.fn(
(params: {
getHealthySyncSinceMs?: () => number | undefined;
onRoomMessage: (roomId: string, event: unknown) => Promise<void >;
runDetachedTask?: (label: string, task: () => Promise<void >) => Promise<void >;
}) => {
hoisted.callOrder.push("register-events" );
hoisted.registeredHealthySyncGetter = params.getHealthySyncSinceMs;
hoisted.registeredOnRoomMessage = (roomId: string, event: unknown) =>
params.runDetachedTask
? params.runDetachedTask("test room message" , async () => {
await params.onRoomMessage(roomId, event);
})
: params.onRoomMessage(roomId, event);
},
),
}));
vi.mock("./handler.js" , () => ({
createMatrixRoomMessageHandler: hoisted.createMatrixRoomMessageHandler,
}));
vi.mock("./inbound-dedupe.js" , () => ({
createMatrixInboundEventDeduper: hoisted.createMatrixInboundEventDeduper,
}));
vi.mock("./legacy-crypto-restore.js" , () => ({
maybeRestoreLegacyMatrixBackup: vi.fn(),
}));
vi.mock("./room-info.js" , () => ({
createMatrixRoomInfoResolver: vi.fn(() => ({
getRoomInfo: hoisted.getRoomInfo,
getMemberDisplayName: hoisted.getMemberDisplayName,
})),
}));
vi.mock("./startup-verification.js" , () => ({
ensureMatrixStartupVerification: vi.fn(),
}));
vi.mock("./startup.js" , () => ({
runMatrixStartupMaintenance: hoisted.runMatrixStartupMaintenance,
}));
let monitorMatrixProvider: typeof import ("./index.js" ).monitorMatrixProvider;
describe("monitorMatrixProvider" , () => {
beforeAll(async () => {
({ monitorMatrixProvider } = await import ("./index.js" ));
});
async function flushUntil(predicate: () => boolean , message: string): Promise<void > {
for (let i = 0 ; i < 20 ; i++) {
if (predicate()) {
return ;
}
await Promise.resolve();
}
throw new Error(message);
}
async function waitForCallOrderEntry(entry: string): Promise<void > {
await flushUntil(
() => hoisted.callOrder.includes(entry),
`expected call order to include ${entry}`,
);
}
async function startMonitorAndAbortAfterStartup(): Promise<void > {
const abortController = new AbortController();
const monitorPromise = monitorMatrixProvider({ abortSignal: abortController.signal });
await waitForCallOrderEntry("start-client" );
abortController.abort();
await monitorPromise;
}
beforeEach(() => {
hoisted.callOrder.length = 0 ;
hoisted.state.startClientError = null ;
hoisted.accountConfig.dm = {};
delete (hoisted.accountConfig as { rooms?: Record<string, unknown> }).rooms;
hoisted.resolveTextChunkLimit.mockReset().mockReturnValue(4000 );
hoisted.releaseSharedClientInstance.mockReset().mockResolvedValue(true );
hoisted.resolveSharedMatrixClient
.mockReset()
.mockImplementation(async (params: { startClient?: boolean }) => {
if (params.startClient === false ) {
hoisted.callOrder.push("prepare-client" );
return hoisted.client;
}
if (!hoisted.callOrder.includes("create-manager" )) {
throw new Error("Matrix client started before thread bindings were registered" );
}
if (hoisted.state.startClientError) {
throw hoisted.state.startClientError;
}
hoisted.callOrder.push("start-client" );
return hoisted.client;
});
hoisted.createDirectRoomTracker.mockReset().mockReturnValue({
isDirectMessage: vi.fn(async () => false ),
});
hoisted.getRoomInfo.mockReset().mockResolvedValue({
altAliases: [],
nameResolved: true ,
aliasesResolved: true ,
});
hoisted.getMemberDisplayName.mockReset().mockResolvedValue("Bot" );
hoisted.registeredOnRoomMessage = null ;
hoisted.registeredHealthySyncGetter = undefined;
hoisted.setActiveMatrixClient.mockReset();
hoisted.stopThreadBindingManager.mockReset();
hoisted.client.removeAllListeners();
hoisted.client.hasPersistedSyncState.mockReset().mockReturnValue(false );
hoisted.client.stopSyncWithoutPersist.mockReset();
hoisted.client.drainPendingDecryptions.mockReset().mockResolvedValue(undefined);
hoisted.inboundDeduper.claimEvent.mockReset().mockReturnValue(true );
hoisted.inboundDeduper.commitEvent.mockReset().mockResolvedValue(undefined);
hoisted.inboundDeduper.releaseEvent.mockReset();
hoisted.inboundDeduper.flush.mockReset().mockResolvedValue(undefined);
hoisted.inboundDeduper.stop.mockReset().mockResolvedValue(undefined);
hoisted.createMatrixInboundEventDeduper.mockReset().mockResolvedValue(hoisted.inboundDeduper);
hoisted.backfillMatrixAuthDeviceIdAfterStartup.mockReset().mockResolvedValue(undefined);
hoisted.runMatrixStartupMaintenance.mockReset().mockResolvedValue(undefined);
hoisted.createMatrixRoomMessageHandler.mockReset().mockReturnValue(vi.fn());
hoisted.setStatus.mockReset();
Object.values(hoisted.logger).forEach((mock) => mock.mockReset());
});
it("returns immediately when the abort signal is already canceled" , async () => {
const abortController = new AbortController();
abortController.abort();
await monitorMatrixProvider({ abortSignal: abortController.signal });
expect(hoisted.callOrder).toEqual([]);
expect(hoisted.resolveTextChunkLimit).not.toHaveBeenCalled();
expect(hoisted.createMatrixRoomMessageHandler).not.toHaveBeenCalled();
expect(hoisted.setActiveMatrixClient).not.toHaveBeenCalled();
});
it("publishes disconnected startup status and connected sync status without failing the monitor" , async () => {
const abortController = new AbortController();
const monitorPromise = monitorMatrixProvider({
abortSignal: abortController.signal,
setStatus: hoisted.setStatus,
});
await waitForCallOrderEntry("start-client" );
expect(hoisted.setStatus).toHaveBeenCalledWith(
expect.objectContaining({
accountId: "default" ,
baseUrl: "https://matrix.example.org ",
connected: false ,
healthState: "starting" ,
}),
);
hoisted.client.emit("sync.state" , "SYNCING" , "RECONNECTING" , undefined);
expect(hoisted.setStatus).toHaveBeenCalledWith(
expect.objectContaining({
accountId: "default" ,
connected: true ,
healthState: "healthy" ,
lastError: null ,
}),
);
abortController.abort();
await expect(monitorPromise).resolves.toBeUndefined();
});
it("re-arms the healthy-sync milestone across reconnect transitions" , async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-04-10T16:21:00.000Z" ));
const abortController = new AbortController();
try {
const monitorPromise = monitorMatrixProvider({
abortSignal: abortController.signal,
setStatus: hoisted.setStatus,
});
await waitForCallOrderEntry("start-client" );
const getHealthySyncSinceMs = hoisted.registeredHealthySyncGetter;
if (!getHealthySyncSinceMs) {
throw new Error("expected healthy sync getter to be registered" );
}
expect(getHealthySyncSinceMs()).toBeUndefined();
hoisted.client.emit("sync.state" , "SYNCING" , "RECONNECTING" , undefined);
const firstHealthySyncSinceMs = Date.now();
expect(getHealthySyncSinceMs()).toBe(firstHealthySyncSinceMs);
await vi.advanceTimersByTimeAsync(3 _000 );
hoisted.client.emit("sync.state" , "CATCHUP" , "SYNCING" , undefined);
expect(getHealthySyncSinceMs()).toBe(firstHealthySyncSinceMs);
await vi.advanceTimersByTimeAsync(2 _000 );
hoisted.client.emit("sync.state" , "PREPARED" , "CATCHUP" , undefined);
expect(getHealthySyncSinceMs()).toBe(firstHealthySyncSinceMs);
await vi.advanceTimersByTimeAsync(5 _000 );
hoisted.client.emit("sync.state" , "RECONNECTING" , "SYNCING" , new Error("network flap" ));
expect(getHealthySyncSinceMs()).toBeUndefined();
await vi.advanceTimersByTimeAsync(7 _000 );
hoisted.client.emit("sync.state" , "SYNCING" , "RECONNECTING" , undefined);
const rearmedHealthySyncSinceMs = Date.now();
expect(getHealthySyncSinceMs()).toBe(rearmedHealthySyncSinceMs);
abortController.abort();
await expect(monitorPromise).resolves.toBeUndefined();
hoisted.client.emit("sync.state" , "RECONNECTING" , "SYNCING" , new Error("late noise" ));
expect(getHealthySyncSinceMs()).toBe(rearmedHealthySyncSinceMs);
} finally {
vi.useRealTimers();
}
});
it("contains room-message handler rejections inside monitor task tracking" , async () => {
const abortController = new AbortController();
const unhandled: unknown[] = [];
const onUnhandled = (reason: unknown) => {
unhandled.push(reason);
};
hoisted.createMatrixRoomMessageHandler.mockReturnValue(
vi.fn(async () => {
throw new Error("room handler exploded" );
}),
);
process.on("unhandledRejection" , onUnhandled);
try {
const monitorPromise = monitorMatrixProvider({ abortSignal: abortController.signal });
await waitForCallOrderEntry("start-client" );
const onRoomMessage = hoisted.registeredOnRoomMessage;
if (!onRoomMessage) {
throw new Error("expected room message handler to be registered" );
}
await onRoomMessage("!room:example.org" , { event_id: "$event" });
await Promise.resolve();
expect(unhandled).toHaveLength(0 );
expect(hoisted.logger.warn).toHaveBeenCalledWith(
"matrix background task failed" ,
expect.objectContaining({
task: "test room message" ,
error: "Error: room handler exploded" ,
}),
);
abortController.abort();
await monitorPromise;
} finally {
process.off("unhandledRejection" , onUnhandled);
}
});
it("fails the channel task when Matrix sync emits an unexpected fatal error" , async () => {
const abortController = new AbortController();
const monitorPromise = monitorMatrixProvider({
abortSignal: abortController.signal,
setStatus: hoisted.setStatus,
});
await waitForCallOrderEntry("start-client" );
hoisted.client.emit("sync.unexpected_error" , new Error("sync exploded" ));
await expect(monitorPromise).rejects.toThrow("sync exploded" );
expect(hoisted.releaseSharedClientInstance).toHaveBeenCalledWith(hoisted.client, "persist" );
expect(hoisted.setStatus).toHaveBeenCalledWith(
expect.objectContaining({
accountId: "default" ,
connected: false ,
healthState: "error" ,
lastError: "sync exploded" ,
}),
);
});
it("marks early startup failures as error before the monitor loop starts" , async () => {
hoisted.resolveSharedMatrixClient.mockImplementation(
async (params: { startClient?: boolean }) => {
if (params.startClient === false ) {
throw new Error("prepare failed" );
}
hoisted.callOrder.push("start-client" );
return hoisted.client;
},
);
await expect(
monitorMatrixProvider({
setStatus: hoisted.setStatus,
}),
).rejects.toThrow("prepare failed" );
expect(hoisted.releaseSharedClientInstance).not.toHaveBeenCalled();
expect(hoisted.setStatus).toHaveBeenLastCalledWith(
expect.objectContaining({
accountId: "default" ,
connected: false ,
healthState: "error" ,
lastError: "prepare failed" ,
}),
);
});
it("releases the prepared client when startup fails before later resources exist" , async () => {
hoisted.createMatrixInboundEventDeduper.mockRejectedValue(new Error("deduper failed" ));
await expect(
monitorMatrixProvider({
setStatus: hoisted.setStatus,
}),
).rejects.toThrow("deduper failed" );
expect(hoisted.releaseSharedClientInstance).toHaveBeenCalledWith(hoisted.client, "persist" );
expect(hoisted.inboundDeduper.stop).not.toHaveBeenCalled();
expect(hoisted.setStatus).toHaveBeenLastCalledWith(
expect.objectContaining({
accountId: "default" ,
connected: false ,
healthState: "error" ,
lastError: "deduper failed" ,
}),
);
});
it("aborts stalled startup promptly and releases the shared client without persist" , async () => {
const abortController = new AbortController();
hoisted.resolveSharedMatrixClient.mockImplementation(
async (params: { startClient?: boolean ; abortSignal?: AbortSignal }) => {
if (params.startClient === false ) {
hoisted.callOrder.push("prepare-client" );
return hoisted.client;
}
hoisted.callOrder.push("start-client" );
return await new Promise<typeof hoisted.client>((_resolve, reject) => {
params.abortSignal?.addEventListener(
"abort" ,
() => {
const error = new Error("Matrix startup aborted" );
error.name = "AbortError" ;
reject(error);
},
{ once: true },
);
});
},
);
const monitorPromise = monitorMatrixProvider({ abortSignal: abortController.signal });
await waitForCallOrderEntry("start-client" );
abortController.abort();
await expect(monitorPromise).resolves.toBeUndefined();
expect(hoisted.releaseSharedClientInstance).toHaveBeenCalledWith(hoisted.client, "stop" );
expect(hoisted.client.drainPendingDecryptions).not.toHaveBeenCalled();
});
it("aborts during startup maintenance and releases the shared client without persist" , async () => {
const abortController = new AbortController();
hoisted.runMatrixStartupMaintenance.mockImplementation(
async (params: { abortSignal?: AbortSignal }) =>
await new Promise<void >((_resolve, reject) => {
params.abortSignal?.addEventListener(
"abort" ,
() => {
const error = new Error("Matrix startup aborted" );
error.name = "AbortError" ;
reject(error);
},
{ once: true },
);
}),
);
const monitorPromise = monitorMatrixProvider({ abortSignal: abortController.signal });
await flushUntil(
() => hoisted.runMatrixStartupMaintenance.mock.calls.length === 1 ,
"expected startup maintenance to run" ,
);
abortController.abort();
await expect(monitorPromise).resolves.toBeUndefined();
expect(hoisted.releaseSharedClientInstance).toHaveBeenCalledWith(hoisted.client, "stop" );
expect(hoisted.client.drainPendingDecryptions).not.toHaveBeenCalled();
});
it("registers Matrix thread bindings before starting the client" , async () => {
await startMonitorAndAbortAfterStartup();
expect(hoisted.callOrder).toEqual([
"prepare-client" ,
"create-manager" ,
"register-events" ,
"start-client" ,
]);
expect(hoisted.stopThreadBindingManager).toHaveBeenCalledTimes(1 );
});
it("resolves text chunk limit for the effective Matrix account" , async () => {
await startMonitorAndAbortAfterStartup();
expect(hoisted.resolveTextChunkLimit).toHaveBeenCalledWith(
expect.anything(),
"matrix" ,
"default" ,
);
});
it("starts monitoring without waiting for best-effort deviceId backfill" , async () => {
hoisted.backfillMatrixAuthDeviceIdAfterStartup.mockImplementation(
() => new Promise<undefined>(() => {}),
);
const abortController = new AbortController();
const monitorPromise = monitorMatrixProvider({ abortSignal: abortController.signal });
await waitForCallOrderEntry("start-client" );
expect(hoisted.backfillMatrixAuthDeviceIdAfterStartup).toHaveBeenCalledTimes(1 );
expect(hoisted.backfillMatrixAuthDeviceIdAfterStartup).toHaveBeenCalledWith(
expect.objectContaining({
abortSignal: abortController.signal,
}),
);
abortController.abort();
await expect(monitorPromise).resolves.toBeUndefined();
});
it("cleans up thread bindings and shared clients when startup fails" , async () => {
hoisted.state.startClientError = new Error("start failed" );
await expect(monitorMatrixProvider()).rejects.toThrow("start failed" );
expect(hoisted.stopThreadBindingManager).toHaveBeenCalledTimes(1 );
expect(hoisted.releaseSharedClientInstance).toHaveBeenCalledTimes(1 );
expect(hoisted.releaseSharedClientInstance).toHaveBeenCalledWith(hoisted.client, "persist" );
expect(hoisted.setActiveMatrixClient).toHaveBeenNthCalledWith(1 , hoisted.client, "default" );
expect(hoisted.setActiveMatrixClient).toHaveBeenNthCalledWith(2 , null , "default" );
});
it("disables cold-start backlog dropping only when sync state is cleanly persisted" , async () => {
hoisted.client.hasPersistedSyncState.mockReturnValue(true );
await startMonitorAndAbortAfterStartup();
expect(hoisted.createMatrixRoomMessageHandler).toHaveBeenCalledWith(
expect.objectContaining({
dropPreStartupMessages: false ,
}),
);
});
it("stops sync, drains decryptions, then waits for in-flight handlers before persisting" , async () => {
const abortController = new AbortController();
let resolveHandler: (() => void ) | null = null ;
hoisted.createMatrixRoomMessageHandler.mockReturnValue(
vi.fn(() => {
hoisted.callOrder.push("handler-start" );
return new Promise<void >((resolve) => {
resolveHandler = () => {
hoisted.callOrder.push("handler-done" );
resolve();
};
});
}),
);
hoisted.client.stopSyncWithoutPersist.mockImplementation(() => {
hoisted.callOrder.push("pause-client" );
});
hoisted.client.drainPendingDecryptions.mockImplementation(async () => {
hoisted.callOrder.push("drain-decrypts" );
});
hoisted.stopThreadBindingManager.mockImplementation(() => {
hoisted.callOrder.push("stop-manager" );
});
hoisted.releaseSharedClientInstance.mockImplementation(async () => {
hoisted.callOrder.push("release-client" );
return true ;
});
hoisted.inboundDeduper.stop.mockImplementation(async () => {
hoisted.callOrder.push("stop-deduper" );
});
const monitorPromise = monitorMatrixProvider({ abortSignal: abortController.signal });
await waitForCallOrderEntry("start-client" );
const onRoomMessage = hoisted.registeredOnRoomMessage;
if (!onRoomMessage) {
throw new Error("expected room message handler to be registered" );
}
const roomMessagePromise = onRoomMessage("!room:example.org" , { event_id: "$event" });
abortController.abort();
await waitForCallOrderEntry("pause-client" );
expect(hoisted.callOrder).not.toContain("stop-deduper" );
if (resolveHandler === null ) {
throw new Error("expected in-flight handler to be pending" );
}
(resolveHandler as () => void )();
await roomMessagePromise;
await monitorPromise;
expect(hoisted.callOrder.indexOf("pause-client" )).toBeLessThan(
hoisted.callOrder.indexOf("drain-decrypts" ),
);
expect(hoisted.callOrder.indexOf("drain-decrypts" )).toBeLessThan(
hoisted.callOrder.indexOf("handler-done" ),
);
expect(hoisted.callOrder.indexOf("handler-done" )).toBeLessThan(
hoisted.callOrder.indexOf("stop-manager" ),
);
expect(hoisted.callOrder.indexOf("stop-manager" )).toBeLessThan(
hoisted.callOrder.indexOf("stop-deduper" ),
);
expect(hoisted.callOrder.indexOf("stop-deduper" )).toBeLessThan(
hoisted.callOrder.indexOf("release-client" ),
);
});
it("wires recent-invite promotion to fail closed when room metadata is unresolved" , async () => {
await startMonitorAndAbortAfterStartup();
const trackerOpts = hoisted.createDirectRoomTracker.mock.calls[0 ]?.[1 ];
if (!trackerOpts?.canPromoteRecentInvite) {
throw new Error("recent invite promotion callback was not wired" );
}
hoisted.getRoomInfo.mockResolvedValueOnce({
altAliases: [],
nameResolved: false ,
aliasesResolved: false ,
});
await expect(trackerOpts.canPromoteRecentInvite("!room:example.org" )).resolves.toBe(false );
});
it("wires recent-invite promotion to reject named rooms" , async () => {
await startMonitorAndAbortAfterStartup();
const trackerOpts = hoisted.createDirectRoomTracker.mock.calls[0 ]?.[1 ];
if (!trackerOpts?.canPromoteRecentInvite) {
throw new Error("recent invite promotion callback was not wired" );
}
hoisted.getRoomInfo.mockResolvedValueOnce({
name: "Ops Room" ,
altAliases: [],
nameResolved: true ,
aliasesResolved: true ,
});
await expect(trackerOpts.canPromoteRecentInvite("!room:example.org" )).resolves.toBe(false );
});
it("wires recent-invite promotion to reject wildcard-configured rooms" , async () => {
(hoisted.accountConfig as { rooms?: Record<string, unknown> }).rooms = {
"*" : { enabled: false },
};
await startMonitorAndAbortAfterStartup();
const trackerOpts = hoisted.createDirectRoomTracker.mock.calls[0 ]?.[1 ];
if (!trackerOpts?.canPromoteRecentInvite) {
throw new Error("recent invite promotion callback was not wired" );
}
hoisted.getRoomInfo.mockResolvedValueOnce({
altAliases: [],
nameResolved: true ,
aliasesResolved: true ,
});
await expect(trackerOpts.canPromoteRecentInvite("!room:example.org" )).resolves.toBe(false );
});
it("treats unresolved room metadata as indeterminate for local promotion revalidation" , async () => {
await startMonitorAndAbortAfterStartup();
const trackerOpts = hoisted.createDirectRoomTracker.mock.calls[0 ]?.[1 ];
if (!trackerOpts?.shouldKeepLocallyPromotedDirectRoom) {
throw new Error("local promotion revalidation callback was not wired" );
}
hoisted.getRoomInfo.mockResolvedValueOnce({
altAliases: [],
nameResolved: false ,
aliasesResolved: false ,
});
await expect(
trackerOpts.shouldKeepLocallyPromotedDirectRoom("!room:example.org" ),
).resolves.toBeUndefined();
});
});
Messung V0.5 in Prozent C=92 H=92 G=91
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.37Bemerkung:
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland