Files
ThreadNet-Web/apps/web/test/unit-tests/indexing/EventIndex-test.ts
T
sorB 3da363517f
Docker / Docker Buildx (push) Has been cancelled
Build Debian package / Build package (release) Has been cancelled
Build and Deploy / prepare (release) Has been cancelled
Deploy release / Deploy to Cloudflare Pages (release) Has been cancelled
Build and Deploy / Trigger Pro pipeline (release) Has been cancelled
Build and Deploy / Windows arm64 (release) Has been cancelled
Build and Deploy / Windows x64 (release) Has been cancelled
Build and Deploy / macOS (release) Has been cancelled
Build and Deploy / Linux amd64 (sqlcipher static) (release) Has been cancelled
Build and Deploy / Linux arm64 (sqlcipher static) (release) Has been cancelled
Build and Deploy / ${{ needs.prepare.outputs.deploy == 'true' && 'Deploy' || 'Deploy (dry-run)' }} (release) Has been cancelled
Build and Deploy / Deploy builds to ESS (release) Has been cancelled
feat: show call participants in room list (Discord-style)
2026-05-10 14:25:35 +02:00

187 lines
7.5 KiB
TypeScript

/*
Copyright 2025 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { type Mocked } from "jest-mock";
import {
Direction,
type MatrixClient,
type IEvent,
MatrixEvent,
type Room,
ClientEvent,
SyncState,
} from "matrix-js-sdk/src/matrix";
import EventIndex from "../../../src/indexing/EventIndex.ts";
import { emitPromise, getMockClientWithEventEmitter, mockClientMethodsRooms, mockPlatformPeg } from "../../test-utils";
import type BaseEventIndexManager from "../../../src/indexing/BaseEventIndexManager.ts";
import { type ICrawlerCheckpoint } from "../../../src/indexing/BaseEventIndexManager.ts";
import SettingsStore from "../../../src/settings/SettingsStore.ts";
afterEach(() => {
jest.restoreAllMocks();
});
describe("EventIndex", () => {
it("crawls through the loaded checkpoints", async () => {
const mockIndexingManager = {
loadCheckpoints: jest.fn(),
removeCrawlerCheckpoint: jest.fn(),
isEventIndexEmpty: jest.fn().mockResolvedValue(false),
} as any as Mocked<BaseEventIndexManager>;
mockPlatformPeg({ getEventIndexingManager: () => mockIndexingManager });
const room1 = { roomId: "!room1:id" } as any as Room;
const room2 = { roomId: "!room2:id" } as any as Room;
const mockClient = getMockClientWithEventEmitter({
getEventMapper: () => (obj: Partial<IEvent>) => new MatrixEvent(obj),
createMessagesRequest: jest.fn(),
...mockClientMethodsRooms([room1, room2]),
});
jest.spyOn(SettingsStore, "getValueAt").mockImplementation((_level, settingName): any => {
if (settingName === "crawlerSleepTime") return 0;
return undefined;
});
mockIndexingManager.loadCheckpoints.mockResolvedValue([
{ roomId: "!room1:id", token: "token1", direction: Direction.Backward } as ICrawlerCheckpoint,
{ roomId: "!room2:id", token: "token2", direction: Direction.Forward } as ICrawlerCheckpoint,
]);
const indexer = new EventIndex();
await indexer.init();
let changedCheckpointPromise = emitPromise(indexer, "changedCheckpoint") as Promise<Room>;
indexer.startCrawler();
// Mock out the /messags request, and wait for the crawler to hit the first room
const mock1 = mockCreateMessagesRequest(mockClient);
let changedCheckpoint = await changedCheckpointPromise;
expect(changedCheckpoint.roomId).toEqual("!room1:id");
await mock1.called;
expect(mockClient.createMessagesRequest).toHaveBeenCalledWith("!room1:id", "token1", 100, "b");
// Continue, and wait for the crawler to hit the second room
changedCheckpointPromise = emitPromise(indexer, "changedCheckpoint") as Promise<Room>;
mock1.resolve({ chunk: [] });
changedCheckpoint = await changedCheckpointPromise;
expect(changedCheckpoint.roomId).toEqual("!room2:id");
// Mock out the /messages request again, and wait for it to be called
const mock2 = mockCreateMessagesRequest(mockClient);
await mock2.called;
expect(mockClient.createMessagesRequest).toHaveBeenCalledWith("!room2:id", "token2", 100, "f");
});
it("adds checkpoints for the encrypted rooms after the first sync", async () => {
const mockIndexingManager = {
loadCheckpoints: jest.fn().mockResolvedValue([]),
isEventIndexEmpty: jest.fn().mockResolvedValue(true),
addCrawlerCheckpoint: jest.fn(),
removeCrawlerCheckpoint: jest.fn(),
commitLiveEvents: jest.fn(),
} as any as Mocked<BaseEventIndexManager>;
mockPlatformPeg({ getEventIndexingManager: () => mockIndexingManager });
const room1 = {
roomId: "!room1:id",
getLiveTimeline: () => ({
getPaginationToken: () => "token1",
}),
} as any as Room;
const room2 = {
roomId: "!room2:id",
getLiveTimeline: () => ({
getPaginationToken: () => "token2",
}),
} as any as Room;
const mockCrypto = {
isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(true),
};
const mockClient = getMockClientWithEventEmitter({
getEventMapper: () => (obj: Partial<IEvent>) => new MatrixEvent(obj),
createMessagesRequest: jest.fn(),
getCrypto: () => mockCrypto as any,
...mockClientMethodsRooms([room1, room2]),
});
const commitLiveEventsCalled = Promise.withResolvers<void>();
mockIndexingManager.commitLiveEvents.mockImplementation(async () => {
commitLiveEventsCalled.resolve();
});
const indexer = new EventIndex();
await indexer.init();
// During the first sync, some events are added to the index, meaning that `isEventIndexEmpty` will now be false.
mockIndexingManager.isEventIndexEmpty.mockResolvedValue(false);
// The first sync completes:
mockClient.emit(ClientEvent.Sync, SyncState.Syncing, null, {});
// Wait for `commitLiveEvents` to be called, by which time the checkpoints should have been added.
await commitLiveEventsCalled.promise;
expect(mockIndexingManager.addCrawlerCheckpoint).toHaveBeenCalledTimes(4);
expect(mockIndexingManager.addCrawlerCheckpoint).toHaveBeenCalledWith({
roomId: "!room1:id",
token: "token1",
direction: Direction.Backward,
fullCrawl: true,
});
expect(mockIndexingManager.addCrawlerCheckpoint).toHaveBeenCalledWith({
roomId: "!room1:id",
token: "token1",
direction: Direction.Forward,
});
expect(mockIndexingManager.addCrawlerCheckpoint).toHaveBeenCalledWith({
roomId: "!room2:id",
token: "token2",
direction: Direction.Backward,
fullCrawl: true,
});
expect(mockIndexingManager.addCrawlerCheckpoint).toHaveBeenCalledWith({
roomId: "!room2:id",
token: "token2",
direction: Direction.Forward,
});
});
});
/**
* Mock out the `createMessagesRequest` method on the client, with an implementation that will block until a resolver is called.
*
* @returns An object with the following properties:
* * `called`: A promise that resolves when `createMessagesRequest` is called.
* * `resolve`: A function that can be called to allow `createMessagesRequest` to complete.
*/
function mockCreateMessagesRequest(mockClient: Mocked<MatrixClient>): {
called: Promise<void>;
resolve: (result: any) => void;
} {
const messagesCalledPromise = Promise.withResolvers<void>();
const messagesResultPromise = Promise.withResolvers();
mockClient.createMessagesRequest.mockImplementationOnce(() => {
messagesCalledPromise.resolve();
return messagesResultPromise.promise as any;
});
return {
called: messagesCalledPromise.promise,
resolve: messagesResultPromise.resolve,
};
}