# Call Integration Tests Integration tests for the call module. Each file targets a specific feature area — add new tests to the matching file, or create a new file if the feature area doesn't exist yet. ## File Map | File | Feature area covered | |------|---------------------| | `common.ts` | Shared test context (`CallTestContext`, `setupCallTestContext`), `DEFAULT_PROFILE_PICTURE`, `validateSignedProfilePicture` helper | | `mockedCallPortsDI.ts` | Mocks `CallProvider` with `MockCallProvider` (in `setup/mocks/call/`) so tests don't hit the real video provider (Stream.io). Supports `failNextCalls('method', N)` for retry-behaviour tests. | | `call.endpoints.test.ts` | General call endpoints — find participants, find non-participants, invite participants, end call, find last active call (grpc), profile picture signing | | `call.oneToOne.test.ts` | 1-1 calls — creation, init/notify, accept, miss, leave, reject, streamio webhook events (`participant_left`, `call_session_ended`), session group transition, race conditions between simultaneous events | | `call.group.test.ts` | Group calls — creation in group chats, multi-participant init/notify, miss/reject across many users, joined creator state, streamio `participant_joined` webhook, multi-session active tracking | | `call.raceConditions.test.ts` | `acceptCall` transactional / retry semantics — concurrent accepts (different + same user), provider-failure recovery, idempotent group transition, SERIALIZABLE retry behaviour. Uses `MockCallProvider.failNextCalls` and bootstraps its own community (no `setupCallTestContext`) | ## Available Commands Call-specific commands live in `commands/call/`: | Command | Purpose | |---------|---------| | `CreateCall` | Create a new call on a chat (1-1 or group) | | `InitCall` | Initialize the call (sends notifications to other participants) | | `AcceptCall` | A participant accepts an incoming call | | `RejectCall` | A participant rejects an incoming call | | `MissCall` | A participant misses the call (timeout) | | `LeaveCall` | A participant leaves an active call | | `EndCall` | End the call from any participant | | `CallSessionEnd` | Streamio webhook — `call_session_ended` event | | `ParticipantJoin` | Streamio webhook — `participant_joined` event | | `InviteParticipants` | Invite additional participants to an existing call | | `FindCallParticipants` | List participants of a call (paginated, name-searchable) | | `FindCallNonParticipants` | List instance users that are not participants (paginated, name-searchable) | | `FindActiveCallGrpc` | gRPC endpoint to find the last active call by metadata | Tests also need commands from other modules: - `commands/chats/` — `CreateOneToOneChat`, `CreateGroupChat` (a chat is required to start a call) - `commands/cloudNotifications/` — `CreateFirebaseToken`, `CreateApnToken` (so notifications can be asserted) - `commands/communityFeatures/` — `AddCommunityFeature` to enable `VIEW_CALL` - `commands/capabilities/` — `AddInstanceCapabilities` for `CREATE_REGULAR_GROUP_CHAT` - `commands/instances/`, `commands/users/` — instance + user creation, login Browse the relevant folder before writing a raw `apiPost`/`apiGet` — the command likely already exists. ## Where to Add New Tests - **New endpoint scenario (find/invite participants, end call, last active call, etc.)** → `call.endpoints.test.ts`. - **New 1-1 call flow scenario** (states, webhooks, notifications between two users) → `call.oneToOne.test.ts`. - **New group call flow scenario** (multi-participant behavior, group chat creator semantics, multi-session) → `call.group.test.ts`. - **New transactional / retry / concurrency scenario** (provider failures, SERIALIZABLE retry, idempotency under concurrent FE retries) → `call.raceConditions.test.ts`. These tests assert on per-`requestId` mock-call counts and use `MockCallProvider.failNextCalls('method', N)` to inject failures, so they need a custom bootstrap that doesn't share state with `setupCallTestContext`. - **A call feature that doesn't fit any of these** (e.g. report download, stats, livestream, kiosk-style call) → create a new `call.{feature}.test.ts` file following the bootstrap pattern below. ## Bootstrap Pattern Every call test file uses the shared `setupCallTestContext` from `common.ts`. It bootstraps an instance with: - `VIEW_CALL` community feature enabled (APP scope) - `CREATE_REGULAR_GROUP_CHAT` instance capability - 4 users (`user1`–`user4`), each with web/android Firebase tokens + an APN token, and a logged-in `UserSession` (`sessionU1`–`sessionU4`) - A 1-1 chat between `user1` and `user2` (always) - A group chat created by `user3` containing `user1`, `user2`, `user4` (only when `{ withGroupChat: true }` is passed) - The `ENABLE_CALL_CONSUMER` feature flag set to `true` for the instance ```typescript describe('My Call Feature', () => { let session: UserSession; let instance: InstanceDTO; let user1: UserDTO; let sessionU1: UserSession; let user2: UserDTO; let sessionU2: UserSession; let chat: ChatableDTO; beforeAll(async () => { ({ session, instance, user1, sessionU1, user2, sessionU2, chat } = await setupCallTestContext()); }); it('...', async () => { const newCall = await sessionU1.execute(new CreateCall(chat.chatId)); // ... }); }); ``` For tests that need a group chat: ```typescript beforeAll(async () => { const ctx = await setupCallTestContext({ withGroupChat: true }); ({ session, instance, user1, sessionU1, /* ... */ } = ctx); groupChat = ctx.groupChat!; }); ``` **Don't reimplement the setup.** If you need extra prerequisites (e.g. an extra user, a different community feature), add them inside your `describe`'s `beforeAll` after calling `setupCallTestContext`. Only extend `common.ts` if the new prerequisite is genuinely shared across files. ## Mocked Call Provider `mockedCallPortsDI.ts` swaps the real `CallProvider` with `MockCallProvider` (in `setup/mocks/call/mockCallProvider.ts`). Tests must not assume any real Stream.io interaction — assert on: - DAOs (`CallDAO`, `CallSessionDAO`, `CallParticipantDAO`) via `runSelectQueryOnMainDb` / `runCountQueryOnMainDb` - Cloud notification mocks (`MockFirebaseService`, `MockApnService`) via `getMock(FirebaseService.name)` - Real-time notifications (`MockRealTimeNotificator`) via `getMock(RealTimeNotificatorService.name)` - Event handler messages via `checkMessageSentToEventHandler` Streamio webhooks are simulated by sending HTTP requests with the `CallSessionEnd` and `ParticipantJoin` commands — there is no actual webhook delivery. ## Avoiding Flaky Tests Tests run in parallel across 14 workers sharing the same database. Any test that creates, modifies, or deletes data can break other tests or be broken by them if not properly isolated. **Before writing a test that creates or deletes elements, ask:** - Could another test running in parallel create data that interferes with mine? - Could my deletions or mutations affect data that another test depends on? **Rules:** - **Each `describe` gets its own community via `setupCallTestContext`.** Never share users/instances across files. - **Create a dedicated chat per test** if the test mutates chat or call state in a way that could leak between `it` blocks. The default `chat` from the shared context is fine for read-only scenarios, but if your test creates calls and asserts on "the latest active call" or "non-participants of the instance", create a fresh chat (or fresh users) to avoid cross-test interference inside the same file. - **Create a dedicated user per test** (via `CreateUser` + `LoginToApi`) when the test mutates user state (e.g. soft-deletes the user, changes role). Mutating `user1`–`user4` in one `it` will corrupt every other `it` that relies on them. - **Never assert on total counts** without scoping by `instanceId`, `callId`, or `userId`. The `Calls` / `CallParticipants` / `CallSessions` tables contain rows from every parallel test. - **Never DELETE or UPDATE without a WHERE clause** scoped to your own data (`instanceId`, `callId`, etc.). - **Reset notification mocks per test if you assert on call counts.** Mocks accumulate calls across tests in the same describe. Prefer `mock.someMethod.getCalls(requestId)` to scope to the exact request, or `getAllCallsByMethodName('someMethod')` filtered by something instance-specific. - **Restore mutated state in `afterAll`** if you had no choice but to reuse a shared resource (e.g. re-enable a feature flag you disabled mid-test). ## Notification Assertion Patterns Call tests heavily assert that the right cloud + real-time notifications fire on each state transition. Common pattern: ```typescript const firebase = getMock(FirebaseService.name); const apn = getMock(ApnService.name); const realTime = getMock(RealTimeNotificatorService.name); await sessionU1.executeNoResult(new InitCall(callId)); await waitUntil(() => firebase.sendToTokens.getAllCalls().length >= 1); // then assert on payload ``` Use `waitUntil` (from `async-wait-until`) when assertions depend on async event-handler processing — never fixed `setTimeout` delays.