Files
openclaw/extensions/qa-matrix/src/substrate/sync.test.ts
2026-04-15 07:36:35 -04:00

280 lines
8.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { MatrixQaObservedEvent } from "./events.js";
import {
createMatrixQaRoomObserver,
primeMatrixQaRoom,
waitForOptionalMatrixQaRoomEvent,
} from "./sync.js";
describe("matrix sync helpers", () => {
it("primes the Matrix sync cursor without recording observed events", async () => {
const fetchImpl: typeof fetch = async () =>
new Response(JSON.stringify({ next_batch: "primed-sync-cursor" }), {
status: 200,
headers: { "content-type": "application/json" },
});
await expect(
primeMatrixQaRoom({
accessToken: "token",
baseUrl: "http://127.0.0.1:28008/",
fetchImpl,
}),
).resolves.toBe("primed-sync-cursor");
});
it("returns a typed no-match result while preserving the latest sync token", async () => {
const fetchImpl: typeof fetch = async () =>
new Response(
JSON.stringify({
next_batch: "next-batch-2",
rooms: {
join: {
"!room:matrix-qa.test": {
timeline: {
events: [
{
event_id: "$driver",
sender: "@driver:matrix-qa.test",
type: "m.room.message",
content: { body: "hello", msgtype: "m.text" },
},
],
},
},
},
},
}),
{ status: 200, headers: { "content-type": "application/json" } },
);
const observedEvents: MatrixQaObservedEvent[] = [];
const result = await waitForOptionalMatrixQaRoomEvent({
accessToken: "token",
baseUrl: "http://127.0.0.1:28008/",
fetchImpl,
observedEvents,
predicate: (event) => event.sender === "@sut:matrix-qa.test",
roomId: "!room:matrix-qa.test",
since: "start-batch",
timeoutMs: 1,
});
expect(result).toEqual({
matched: false,
since: "next-batch-2",
});
expect(observedEvents).toEqual(
expect.arrayContaining([
expect.objectContaining({
kind: "message",
body: "hello",
eventId: "$driver",
roomId: "!room:matrix-qa.test",
sender: "@driver:matrix-qa.test",
type: "m.room.message",
}),
]),
);
});
it("keeps recording later same-batch events after the first match", async () => {
const fetchImpl: typeof fetch = async () =>
new Response(
JSON.stringify({
next_batch: "next-batch-2",
rooms: {
join: {
"!room:matrix-qa.test": {
timeline: {
events: [
{
event_id: "$sut",
sender: "@sut:matrix-qa.test",
type: "m.room.message",
content: { body: "target", msgtype: "m.text" },
},
{
event_id: "$driver",
sender: "@driver:matrix-qa.test",
type: "m.room.message",
content: { body: "trailing event", msgtype: "m.text" },
},
],
},
},
},
},
}),
{ status: 200, headers: { "content-type": "application/json" } },
);
const observedEvents: MatrixQaObservedEvent[] = [];
const result = await waitForOptionalMatrixQaRoomEvent({
accessToken: "token",
baseUrl: "http://127.0.0.1:28008/",
fetchImpl,
observedEvents,
predicate: (event) => event.eventId === "$sut",
roomId: "!room:matrix-qa.test",
since: "start-batch",
timeoutMs: 1,
});
expect(result).toEqual({
event: expect.objectContaining({
eventId: "$sut",
}),
matched: true,
since: "next-batch-2",
});
expect(observedEvents).toEqual(
expect.arrayContaining([
expect.objectContaining({
kind: "message",
body: "target",
eventId: "$sut",
}),
expect.objectContaining({
kind: "message",
body: "trailing event",
eventId: "$driver",
}),
]),
);
});
it("lets a second wait reuse later same-batch events without another /sync", async () => {
let calls = 0;
const fetchImpl: typeof fetch = async () => {
calls += 1;
return new Response(
JSON.stringify({
next_batch: "next-batch-2",
rooms: {
join: {
"!room:matrix-qa.test": {
timeline: {
events: [
{
event_id: "$preview",
sender: "@sut:matrix-qa.test",
type: "m.room.message",
content: { body: "preview", msgtype: "m.notice" },
},
{
event_id: "$final",
sender: "@sut:matrix-qa.test",
type: "m.room.message",
content: {
body: "final",
msgtype: "m.text",
"m.relates_to": {
rel_type: "m.replace",
event_id: "$preview",
"m.new_content": { body: "final", msgtype: "m.text" },
},
},
},
],
},
},
},
},
}),
{ status: 200, headers: { "content-type": "application/json" } },
);
};
const observedEvents: MatrixQaObservedEvent[] = [];
const observer = createMatrixQaRoomObserver({
accessToken: "token",
baseUrl: "http://127.0.0.1:28008/",
fetchImpl,
observedEvents,
since: "start-batch",
});
const preview = await observer.waitForRoomEvent({
predicate: (event) => event.eventId === "$preview",
roomId: "!room:matrix-qa.test",
timeoutMs: 1_000,
});
const finalized = await observer.waitForRoomEvent({
predicate: (event) => event.eventId === "$final",
roomId: "!room:matrix-qa.test",
timeoutMs: 1_000,
});
expect(preview.event.eventId).toBe("$preview");
expect(finalized.event.eventId).toBe("$final");
expect(calls).toBe(1);
});
it("shares one in-flight /sync poll across concurrent waits", async () => {
let calls = 0;
const fetchImpl: typeof fetch = async () => {
calls += 1;
await new Promise((resolve) => setTimeout(resolve, 10));
return new Response(
JSON.stringify({
next_batch: "next-batch-2",
rooms: {
join: {
"!room:matrix-qa.test": {
timeline: {
events: [
{
event_id: "$reply",
sender: "@sut:matrix-qa.test",
type: "m.room.message",
content: { body: "reply", msgtype: "m.text" },
},
{
event_id: "$notice",
sender: "@sut:matrix-qa.test",
type: "m.room.message",
content: { body: "notice", msgtype: "m.notice" },
},
],
},
},
},
},
}),
{ status: 200, headers: { "content-type": "application/json" } },
);
};
const observer = createMatrixQaRoomObserver({
accessToken: "token",
baseUrl: "http://127.0.0.1:28008/",
fetchImpl,
observedEvents: [],
since: "start-batch",
});
const [reply, notice] = await Promise.all([
observer.waitForRoomEvent({
predicate: (event) => event.eventId === "$reply",
roomId: "!room:matrix-qa.test",
timeoutMs: 1_000,
}),
observer.waitForOptionalRoomEvent({
predicate: (event) => event.eventId === "$notice",
roomId: "!room:matrix-qa.test",
timeoutMs: 1_000,
}),
]);
expect(reply.event.eventId).toBe("$reply");
expect(notice).toMatchObject({
event: expect.objectContaining({
eventId: "$notice",
}),
matched: true,
});
expect(calls).toBe(1);
});
});