Files
Warp/server/internal/syncapi/token_test.go
T
tajniak81andClaude Opus 5.5 b507753172 Phase 8: push and notifications
Implements PLAN.md Phase 8. internal/pushrules is the rule language: the
spec's default ruleset, all six condition kinds, glob and word-boundary
matching, intentional mentions switching off the legacy body rules, and
rule ordering as Synapse has it. internal/pushserver keeps the rules,
pushers and notifications and talks to push gateways.

Push rules are the m.push_rules account data, written only by the push
server; reads keep the user's own rules and the enabled/actions of each
default and take the rest from the current defaults, so new default rules
reach every user without a migration. PUT account_data/m.push_rules is
refused with 405.

The push server listens on the roomserver's emit stage and evaluates each
event on its own worker for every local member and an invite's target.
Notifications and per-room unread counts are stored in migration 0010;
counts are a seventh sync token stream, so /sync reports
unread_notifications and picks up count changes made after the event or by
a receipt from another device. m.read and m.read.private receipts clear
the counts; leaving a room clears it.

Pushers (http kind only) push through a bounded sender pool with retries,
honour event_id_only, drop push keys the gateway rejects, and go away with
their device. GET /notifications pages the history. New config section
push: enabled, include_content, gateway_timeout, notification_retention.

Account data writes now wake the user's blocked syncs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:43:36 +02:00

113 lines
3.4 KiB
Go

package syncapi_test
import (
"testing"
"github.com/dariuszjaniak/warp/server/internal/syncapi"
)
func TestTokenRoundTrips(t *testing.T) {
for _, tc := range []struct {
name string
token syncapi.Token
want string
}{
{"the initial token", syncapi.Token{}, "s0"},
{"events only", syncapi.Token{PDU: 12}, "s12"},
{"events and receipts", syncapi.Token{PDU: 12, Receipt: 3}, "s12_3"},
{
"every stream",
syncapi.Token{PDU: 1, Receipt: 2, ToDevice: 3, AccountData: 4, Presence: 5, DeviceList: 6, Notification: 7},
"s1_2_3_4_5_6_7",
},
{
// A Phase 7 token, from before the notification stream.
"six streams",
syncapi.Token{PDU: 1, Receipt: 2, ToDevice: 3, AccountData: 4, Presence: 5, DeviceList: 6},
"s1_2_3_4_5_6",
},
{
// /messages pages back into history backfilled from other
// servers, which is numbered downwards from -1.
"a position inside backfilled history",
syncapi.Token{PDU: -7},
"s-7",
},
{
// A zero in the middle has to be written, because the
// positions are read by place.
"a gap in the middle",
syncapi.Token{PDU: 1, Presence: 5},
"s1_0_0_0_5",
},
} {
t.Run(tc.name, func(t *testing.T) {
if got := tc.token.String(); got != tc.want {
t.Fatalf("String() = %q, want %q", got, tc.want)
}
parsed, err := syncapi.ParseToken(tc.want)
if err != nil {
t.Fatalf("ParseToken(%q): %v", tc.want, err)
}
if parsed != tc.token {
t.Errorf("ParseToken(%q) = %+v, want %+v", tc.want, parsed, tc.token)
}
})
}
}
func TestParseTokenAcceptsAnEmptyString(t *testing.T) {
// /sync without ?since= and /messages without ?from= both mean "from
// the beginning"; refusing the empty string would make every caller
// special-case it.
parsed, err := syncapi.ParseToken("")
if err != nil {
t.Fatalf("ParseToken(\"\"): %v", err)
}
if !parsed.IsInitial() {
t.Errorf("ParseToken(\"\") = %+v, want the initial token", parsed)
}
}
func TestParseTokenAcceptsAShortToken(t *testing.T) {
// A token a client stored before a stream existed must go on working:
// the stream it does not mention reads as "seen nothing", so the
// client is sent that stream in full once and is in step afterwards.
parsed, err := syncapi.ParseToken("s7")
if err != nil {
t.Fatalf("ParseToken(\"s7\"): %v", err)
}
if parsed.PDU != 7 {
t.Errorf("PDU = %d, want 7", parsed.PDU)
}
if parsed.Receipt != 0 || parsed.ToDevice != 0 || parsed.AccountData != 0 || parsed.Presence != 0 {
t.Errorf("the streams a short token omits must read as zero: %+v", parsed)
}
}
func TestParseTokenRejectsNonsense(t *testing.T) {
for _, raw := range []string{
"nonsense", // no prefix
"t1_2", // a prefix this build does not write
"s", // a prefix and nothing else
"s1_-1", // a negative position outside the event stream
"sx", // not a number
"s1_", // a trailing separator
"s1_2_3_4_5_6_7_8", // more positions than there are streams
"s1.5",
} {
if _, err := syncapi.ParseToken(raw); err == nil {
t.Errorf("ParseToken(%q) was accepted", raw)
}
}
}
func TestParseTokenIsNotFooledByOverflow(t *testing.T) {
// A position larger than an int64 is not a position this server ever
// handed out, and silently wrapping it would mean a client being sent
// the whole of every room again.
if _, err := syncapi.ParseToken("s99999999999999999999"); err == nil {
t.Error("a position that does not fit in an int64 was accepted")
}
}