Files
Warp/server/internal/storage/delayed_test.go
T
tajniak81andClaude Opus 5.5 76dfc98a86
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
CI / Admin panel (push) Canceled after 0s
CI / Web client (push) Canceled after 0s
Group voice and video calls with MatrixRTC and LiveKit
Every call in the web client now goes through MatrixRTC with a LiveKit
SFU, the model Element Call and Element X use; the legacy 1:1 m.call.*
calls are gone. In a room of two the other person is rung and can answer
or decline; in a bigger room a banner offers to join the call. Calls show
a grid of participant tiles with mute, camera, screen sharing and leave,
and media is end-to-end encrypted in encrypted rooms.

Server:
- MSC4140 delayed events (send/state with a delay, plus the
  delayed_events list/restart/cancel/send API), stored and scheduled, so
  a member whose client drops off is taken out of the call.
- POST /user/{userId}/openid/request_token, which lk-jwt-service uses to
  hand out LiveKit tokens.
- org.matrix.msc4143.rtc_foci in /.well-known/matrix/client, configured
  under client_api.rtc.
- Room presets let members send call membership state.
- Migration 0014 for both SQL dialects.

Deploy: LiveKit and lk-jwt-service in compose (calls.yml) and the helm
chart. Docs: docs/calls.md, configuration and deployment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:33:29 +02:00

148 lines
5.1 KiB
Go

package storage_test
import (
"database/sql"
"errors"
"os"
"testing"
"github.com/dariuszjaniak/warp/server/internal/storage"
)
// migratedDelayedDB opens a migrated database with empty delayed event and
// OpenID tables.
func migratedDelayedDB(t *testing.T, driver string) storage.Database {
t.Helper()
db := openTestDB(t, driver)
if err := db.Migrate(t.Context()); err != nil {
t.Fatalf("Migrate: %v", err)
}
if driver == storage.DriverPostgres {
pool, err := sql.Open("pgx", os.Getenv("WARP_TEST_POSTGRES_DSN"))
if err != nil {
t.Fatalf("open postgres: %v", err)
}
defer func() { _ = pool.Close() }()
if _, err := pool.ExecContext(t.Context(), `TRUNCATE delayed_events, openid_tokens`); err != nil {
t.Fatalf("reset the tables: %v", err)
}
}
return db
}
func TestDelayedEvents(t *testing.T) {
for _, driver := range testDrivers {
t.Run(driver, func(t *testing.T) {
ctx := t.Context()
store := migratedDelayedDB(t, driver).DelayedEvents()
if _, ok, err := store.NextDelayedEventDue(ctx); err != nil || ok {
t.Fatalf("NextDelayedEventDue on an empty table = %v, %v", ok, err)
}
key := "_@alice:warp.example_DEV_m.call"
state := storage.DelayedEvent{
DelayID: "d1", UserID: "@alice:warp.example", DeviceID: "DEV",
RoomID: "!r:warp.example", Type: "org.matrix.msc3401.call.member",
StateKey: &key, Content: []byte(`{}`),
DelayMS: 5000, RunningSinceMS: 1000, SendAtMS: 6000,
}
timeline := storage.DelayedEvent{
DelayID: "d2", UserID: "@alice:warp.example", RoomID: "!r:warp.example",
Type: "m.room.message", Content: []byte(`{"body":"hi"}`),
DelayMS: 1000, RunningSinceMS: 1000, SendAtMS: 2000,
}
for _, e := range []storage.DelayedEvent{state, timeline} {
if err := store.InsertDelayedEvent(ctx, e); err != nil {
t.Fatalf("InsertDelayedEvent: %v", err)
}
}
if n, err := store.CountScheduledDelayedEvents(ctx, "@alice:warp.example"); err != nil || n != 2 {
t.Errorf("count = %d, %v", n, err)
}
if next, ok, err := store.NextDelayedEventDue(ctx); err != nil || !ok || next != 2000 {
t.Errorf("next due = %d, %v, %v", next, ok, err)
}
got, err := store.DelayedEvent(ctx, "@alice:warp.example", "d1")
if err != nil || got.StateKey == nil || *got.StateKey != key || string(got.Content) != `{}` {
t.Errorf("DelayedEvent = %+v, %v", got, err)
}
if got, err := store.DelayedEvent(ctx, "@alice:warp.example", "d2"); err != nil || got.StateKey != nil {
t.Errorf("a timeline event has a state key: %+v, %v", got, err)
}
if _, err := store.DelayedEvent(ctx, "@bob:warp.example", "d1"); !errors.Is(err, storage.ErrNoSuchDelayedEvent) {
t.Errorf("bob read alice's event: %v", err)
}
due, err := store.DueDelayedEvents(ctx, 3000, 10)
if err != nil || len(due) != 1 || due[0].DelayID != "d2" {
t.Errorf("due at 3000 = %+v, %v", due, err)
}
if err := store.RestartDelayedEvent(ctx, "d1", 4000); err != nil {
t.Fatalf("RestartDelayedEvent: %v", err)
}
got, _ = store.ScheduledDelayedEvent(ctx, "d1")
if got.RunningSinceMS != 4000 || got.SendAtMS != 9000 {
t.Errorf("after restart = %+v", got)
}
pending, err := store.ScheduledDelayedStateEvents(ctx, "!r:warp.example", "org.matrix.msc3401.call.member", key)
if err != nil || len(pending) != 1 {
t.Errorf("ScheduledDelayedStateEvents = %+v, %v", pending, err)
}
sent := timeline
sent.FinalisedAtMS, sent.Outcome, sent.Reason, sent.EventID = 2500, "send", "delay", "$e"
if err := store.FinaliseDelayedEvent(ctx, sent); err != nil {
t.Fatalf("FinaliseDelayedEvent: %v", err)
}
if err := store.FinaliseDelayedEvent(ctx, sent); !errors.Is(err, storage.ErrNoSuchDelayedEvent) {
t.Errorf("finalising twice = %v", err)
}
if err := store.RestartDelayedEvent(ctx, "d2", 3000); !errors.Is(err, storage.ErrNoSuchDelayedEvent) {
t.Errorf("restarting a finalised event = %v", err)
}
all, err := store.DelayedEvents(ctx, "@alice:warp.example")
if err != nil || len(all) != 2 || all[0].DelayID != "d1" || all[1].EventID != "$e" {
t.Errorf("DelayedEvents = %+v, %v", all, err)
}
if err := store.PurgeFinalisedDelayedEvents(ctx, 3000); err != nil {
t.Fatal(err)
}
if all, _ := store.DelayedEvents(ctx, "@alice:warp.example"); len(all) != 1 {
t.Errorf("after purge = %+v", all)
}
})
}
}
func TestOpenIDTokens(t *testing.T) {
for _, driver := range testDrivers {
t.Run(driver, func(t *testing.T) {
ctx := t.Context()
store := migratedDelayedDB(t, driver).OpenIDTokens()
if err := store.InsertOpenIDToken(ctx, "hash", "@alice:warp.example", 5000); err != nil {
t.Fatal(err)
}
if user, err := store.OpenIDTokenUser(ctx, "hash", 4000); err != nil || user != "@alice:warp.example" {
t.Errorf("OpenIDTokenUser = %q, %v", user, err)
}
if _, err := store.OpenIDTokenUser(ctx, "hash", 6000); !errors.Is(err, storage.ErrNoSuchOpenIDToken) {
t.Errorf("an expired token = %v", err)
}
if err := store.PurgeOpenIDTokens(ctx, 6000); err != nil {
t.Fatal(err)
}
if _, err := store.OpenIDTokenUser(ctx, "hash", 0); !errors.Is(err, storage.ErrNoSuchOpenIDToken) {
t.Errorf("a purged token = %v", err)
}
})
}
}