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
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>
148 lines
5.1 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|