2026-08-01 22:06:14 +02:00
|
|
|
package ws_test
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/owncord/server/db"
|
|
|
|
|
"github.com/owncord/server/ws"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// absenceWindow is how long an "it must NOT arrive" assertion waits. The
|
|
|
|
|
// dispatch it is watching for is synchronous, so anything that was going to
|
|
|
|
|
// land has landed well inside it.
|
|
|
|
|
const absenceWindow = 100 * time.Millisecond
|
|
|
|
|
|
|
|
|
|
// ─── helpers ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
// seedGroupDM creates a group DM containing every listed user.
|
|
|
|
|
func seedGroupDM(t *testing.T, database *db.DB, name string, userIDs ...int64) int64 {
|
|
|
|
|
t.Helper()
|
|
|
|
|
ch, err := database.CreateGroupDMChannel(context.Background(), name, userIDs)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("seedGroupDM: %v", err)
|
|
|
|
|
}
|
|
|
|
|
return ch.ID
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func callMsg(msgType string, channelID int64) []byte {
|
|
|
|
|
raw, _ := json.Marshal(map[string]any{
|
|
|
|
|
"type": msgType,
|
|
|
|
|
"payload": map[string]any{"channel_id": channelID},
|
|
|
|
|
})
|
|
|
|
|
return raw
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── group DM fan-out ───────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
// A chat_send into a group DM must reach every other participant, not just
|
|
|
|
|
// "the recipient" — the single-recipient assumption the DM path started with.
|
|
|
|
|
func TestGroupDM_ChatSendFansOutToAllParticipants(t *testing.T) {
|
|
|
|
|
hub, database := newHandlerHub(t)
|
|
|
|
|
alice := seedOwnerUser(t, database, "grp-send-alice")
|
|
|
|
|
bob := seedMemberUser(t, database, "grp-send-bob")
|
|
|
|
|
carol := seedMemberUser(t, database, "grp-send-carol")
|
|
|
|
|
chID := seedGroupDM(t, database, "Trio", alice.ID, bob.ID, carol.ID)
|
|
|
|
|
|
|
|
|
|
sendAlice := make(chan []byte, 64)
|
|
|
|
|
sendBob := make(chan []byte, 64)
|
|
|
|
|
sendCarol := make(chan []byte, 64)
|
|
|
|
|
cAlice := ws.NewTestClientWithUser(hub, alice, chID, sendAlice)
|
|
|
|
|
cBob := ws.NewTestClientWithUser(hub, bob, chID, sendBob)
|
|
|
|
|
cCarol := ws.NewTestClientWithUser(hub, carol, chID, sendCarol)
|
|
|
|
|
hub.Register(cAlice)
|
|
|
|
|
hub.Register(cBob)
|
|
|
|
|
hub.Register(cCarol)
|
|
|
|
|
waitRegistered(t, hub, cCarol)
|
|
|
|
|
|
|
|
|
|
hub.HandleMessageForTest(cAlice, dmChatSendMsg(chID, "hello everyone"))
|
|
|
|
|
|
|
|
|
|
if dmWaitMsgType(sendBob, "chat_message", waitTimeout) == nil {
|
|
|
|
|
t.Error("bob did not receive the group message")
|
|
|
|
|
}
|
|
|
|
|
if dmWaitMsgType(sendCarol, "chat_message", waitTimeout) == nil {
|
|
|
|
|
t.Error("carol did not receive the group message")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Every recipient's dm_channel_open must describe the group from *their* seat:
|
|
|
|
|
// they never appear in their own recipients list, and the list is the other two.
|
|
|
|
|
func TestGroupDM_ChannelOpenIsPerViewer(t *testing.T) {
|
|
|
|
|
hub, database := newHandlerHub(t)
|
|
|
|
|
alice := seedOwnerUser(t, database, "grp-open-alice")
|
|
|
|
|
bob := seedMemberUser(t, database, "grp-open-bob")
|
|
|
|
|
carol := seedMemberUser(t, database, "grp-open-carol")
|
|
|
|
|
chID := seedGroupDM(t, database, "Openers", alice.ID, bob.ID, carol.ID)
|
|
|
|
|
|
|
|
|
|
// A closed DM is what makes the send re-open it and emit dm_channel_open.
|
|
|
|
|
if err := database.CloseDM(context.Background(), bob.ID, chID); err != nil {
|
|
|
|
|
t.Fatalf("CloseDM: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if err := database.CloseDM(context.Background(), carol.ID, chID); err != nil {
|
|
|
|
|
t.Fatalf("CloseDM: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sendAlice := make(chan []byte, 64)
|
|
|
|
|
sendBob := make(chan []byte, 64)
|
|
|
|
|
cAlice := ws.NewTestClientWithUser(hub, alice, chID, sendAlice)
|
|
|
|
|
cBob := ws.NewTestClientWithUser(hub, bob, chID, sendBob)
|
|
|
|
|
hub.Register(cAlice)
|
|
|
|
|
hub.Register(cBob)
|
|
|
|
|
waitRegistered(t, hub, cBob)
|
|
|
|
|
|
|
|
|
|
hub.HandleMessageForTest(cAlice, dmChatSendMsg(chID, "ping"))
|
|
|
|
|
|
|
|
|
|
env := dmWaitMsgType(sendBob, "dm_channel_open", waitTimeout)
|
|
|
|
|
if env == nil {
|
|
|
|
|
t.Fatal("bob did not receive dm_channel_open")
|
|
|
|
|
}
|
|
|
|
|
payload, _ := env["payload"].(map[string]any)
|
|
|
|
|
if payload["is_group"] != true {
|
|
|
|
|
t.Errorf("expected is_group=true, got %v", payload["is_group"])
|
|
|
|
|
}
|
|
|
|
|
if payload["name"] != "Openers" {
|
|
|
|
|
t.Errorf("expected the group name on the wire, got %v", payload["name"])
|
|
|
|
|
}
|
|
|
|
|
recips, _ := payload["recipients"].([]any)
|
|
|
|
|
if len(recips) != 2 {
|
|
|
|
|
t.Fatalf("expected bob to see 2 other participants, got %d", len(recips))
|
|
|
|
|
}
|
|
|
|
|
for _, r := range recips {
|
|
|
|
|
m, _ := r.(map[string]any)
|
|
|
|
|
if int64(m["id"].(float64)) == bob.ID {
|
|
|
|
|
t.Error("bob appears in his own recipients list")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Typing already fanned out over dm_participants; this pins that a group's
|
|
|
|
|
// third member is included and the sender is not.
|
|
|
|
|
func TestGroupDM_TypingReachesAllOthers(t *testing.T) {
|
|
|
|
|
hub, database := newHandlerHub(t)
|
|
|
|
|
alice := seedOwnerUser(t, database, "grp-type-alice")
|
|
|
|
|
bob := seedMemberUser(t, database, "grp-type-bob")
|
|
|
|
|
carol := seedMemberUser(t, database, "grp-type-carol")
|
|
|
|
|
chID := seedGroupDM(t, database, "Typers", alice.ID, bob.ID, carol.ID)
|
|
|
|
|
|
|
|
|
|
sendAlice := make(chan []byte, 64)
|
|
|
|
|
sendBob := make(chan []byte, 64)
|
|
|
|
|
sendCarol := make(chan []byte, 64)
|
|
|
|
|
cAlice := ws.NewTestClientWithUser(hub, alice, chID, sendAlice)
|
|
|
|
|
cBob := ws.NewTestClientWithUser(hub, bob, chID, sendBob)
|
|
|
|
|
cCarol := ws.NewTestClientWithUser(hub, carol, chID, sendCarol)
|
|
|
|
|
hub.Register(cAlice)
|
|
|
|
|
hub.Register(cBob)
|
|
|
|
|
hub.Register(cCarol)
|
|
|
|
|
waitRegistered(t, hub, cCarol)
|
|
|
|
|
|
|
|
|
|
hub.HandleMessageForTest(cAlice, dmTypingMsg(chID))
|
|
|
|
|
|
|
|
|
|
if dmWaitMsgType(sendBob, "typing", waitTimeout) == nil {
|
|
|
|
|
t.Error("bob did not receive the typing indicator")
|
|
|
|
|
}
|
|
|
|
|
if dmWaitMsgType(sendCarol, "typing", waitTimeout) == nil {
|
|
|
|
|
t.Error("carol did not receive the typing indicator")
|
|
|
|
|
}
|
|
|
|
|
if got := dmFindMsgType(dmDrainAll(sendAlice), "typing"); got != nil {
|
|
|
|
|
t.Error("the typist received their own typing indicator")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A block between two members of a group must NOT silence the group: the
|
|
|
|
|
// composer gate is a 1:1 rule, and dropping one member's messages for one
|
|
|
|
|
// other member would leave them reading different conversations.
|
|
|
|
|
func TestGroupDM_BlockDoesNotSilenceGroupSend(t *testing.T) {
|
|
|
|
|
hub, database := newHandlerHub(t)
|
|
|
|
|
alice := seedOwnerUser(t, database, "grp-blk-alice")
|
|
|
|
|
bob := seedMemberUser(t, database, "grp-blk-bob")
|
|
|
|
|
carol := seedMemberUser(t, database, "grp-blk-carol")
|
|
|
|
|
chID := seedGroupDM(t, database, "Blockers", alice.ID, bob.ID, carol.ID)
|
|
|
|
|
|
|
|
|
|
if err := database.BlockUser(context.Background(), bob.ID, alice.ID); err != nil {
|
|
|
|
|
t.Fatalf("BlockUser: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sendAlice := make(chan []byte, 64)
|
|
|
|
|
sendCarol := make(chan []byte, 64)
|
|
|
|
|
cAlice := ws.NewTestClientWithUser(hub, alice, chID, sendAlice)
|
|
|
|
|
cCarol := ws.NewTestClientWithUser(hub, carol, chID, sendCarol)
|
|
|
|
|
hub.Register(cAlice)
|
|
|
|
|
hub.Register(cCarol)
|
|
|
|
|
waitRegistered(t, hub, cCarol)
|
|
|
|
|
|
|
|
|
|
hub.HandleMessageForTest(cAlice, dmChatSendMsg(chID, "still a room"))
|
|
|
|
|
|
|
|
|
|
if dmWaitMsgType(sendAlice, "chat_send_ok", waitTimeout) == nil {
|
|
|
|
|
t.Error("a group send was refused because of a block between two members")
|
|
|
|
|
}
|
|
|
|
|
if dmWaitMsgType(sendCarol, "chat_message", waitTimeout) == nil {
|
|
|
|
|
t.Error("carol did not receive the group message")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A 1:1 block must still bite — the group exemption is not a general one.
|
|
|
|
|
func TestOneToOneDM_BlockStillRefusesSend(t *testing.T) {
|
|
|
|
|
hub, database := newHandlerHub(t)
|
|
|
|
|
alice := seedOwnerUser(t, database, "one-blk-alice")
|
|
|
|
|
bob := seedMemberUser(t, database, "one-blk-bob")
|
|
|
|
|
chID := seedDMChannel(t, database, alice.ID, bob.ID)
|
|
|
|
|
|
|
|
|
|
if err := database.BlockUser(context.Background(), bob.ID, alice.ID); err != nil {
|
|
|
|
|
t.Fatalf("BlockUser: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sendAlice := make(chan []byte, 64)
|
|
|
|
|
cAlice := ws.NewTestClientWithUser(hub, alice, chID, sendAlice)
|
|
|
|
|
hub.Register(cAlice)
|
|
|
|
|
waitRegistered(t, hub, cAlice)
|
|
|
|
|
|
|
|
|
|
hub.HandleMessageForTest(cAlice, dmChatSendMsg(chID, "blocked"))
|
|
|
|
|
|
|
|
|
|
if code := dmFindErrorCode(dmCollectAll(sendAlice, absenceWindow)); code != "FORBIDDEN" {
|
|
|
|
|
t.Errorf("expected FORBIDDEN on a blocked 1:1 send, got %q", code)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── call ringing ───────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
func TestCallRing_ForwardsToOtherParticipants(t *testing.T) {
|
|
|
|
|
hub, database := newHandlerHub(t)
|
|
|
|
|
alice := seedOwnerUser(t, database, "ring-alice")
|
|
|
|
|
bob := seedMemberUser(t, database, "ring-bob")
|
|
|
|
|
carol := seedMemberUser(t, database, "ring-carol")
|
|
|
|
|
chID := seedGroupDM(t, database, "Ringers", alice.ID, bob.ID, carol.ID)
|
|
|
|
|
|
|
|
|
|
sendAlice := make(chan []byte, 64)
|
|
|
|
|
sendBob := make(chan []byte, 64)
|
|
|
|
|
sendCarol := make(chan []byte, 64)
|
|
|
|
|
cAlice := ws.NewTestClientWithUser(hub, alice, chID, sendAlice)
|
|
|
|
|
cBob := ws.NewTestClientWithUser(hub, bob, chID, sendBob)
|
|
|
|
|
cCarol := ws.NewTestClientWithUser(hub, carol, chID, sendCarol)
|
|
|
|
|
hub.Register(cAlice)
|
|
|
|
|
hub.Register(cBob)
|
|
|
|
|
hub.Register(cCarol)
|
|
|
|
|
waitRegistered(t, hub, cCarol)
|
|
|
|
|
|
|
|
|
|
hub.HandleMessageForTest(cAlice, callMsg("call_ring", chID))
|
|
|
|
|
|
|
|
|
|
env := dmWaitMsgType(sendBob, "call_incoming", waitTimeout)
|
|
|
|
|
if env == nil {
|
|
|
|
|
t.Fatal("bob did not receive call_incoming")
|
|
|
|
|
}
|
|
|
|
|
payload, _ := env["payload"].(map[string]any)
|
|
|
|
|
if int64(payload["channel_id"].(float64)) != chID {
|
|
|
|
|
t.Errorf("call_incoming carried channel %v, want %d", payload["channel_id"], chID)
|
|
|
|
|
}
|
|
|
|
|
if int64(payload["from_user"].(float64)) != alice.ID {
|
|
|
|
|
t.Errorf("call_incoming carried from_user %v, want %d", payload["from_user"], alice.ID)
|
|
|
|
|
}
|
|
|
|
|
if dmWaitMsgType(sendCarol, "call_incoming", waitTimeout) == nil {
|
|
|
|
|
t.Error("carol did not receive call_incoming")
|
|
|
|
|
}
|
|
|
|
|
// The ringer must not ring themselves.
|
|
|
|
|
if got := dmFindMsgType(dmDrainAll(sendAlice), "call_incoming"); got != nil {
|
|
|
|
|
t.Error("the ringer received their own call_incoming")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCallRing_NonParticipantForbidden(t *testing.T) {
|
|
|
|
|
hub, database := newHandlerHub(t)
|
|
|
|
|
alice := seedOwnerUser(t, database, "ring-perm-alice")
|
|
|
|
|
bob := seedMemberUser(t, database, "ring-perm-bob")
|
|
|
|
|
mallory := seedMemberUser(t, database, "ring-perm-mallory")
|
|
|
|
|
chID := seedDMChannel(t, database, alice.ID, bob.ID)
|
|
|
|
|
|
|
|
|
|
sendMallory := make(chan []byte, 64)
|
|
|
|
|
sendBob := make(chan []byte, 64)
|
|
|
|
|
cMallory := ws.NewTestClientWithUser(hub, mallory, chID, sendMallory)
|
|
|
|
|
cBob := ws.NewTestClientWithUser(hub, bob, chID, sendBob)
|
|
|
|
|
hub.Register(cMallory)
|
|
|
|
|
hub.Register(cBob)
|
|
|
|
|
waitRegistered(t, hub, cBob)
|
|
|
|
|
|
|
|
|
|
hub.HandleMessageForTest(cMallory, callMsg("call_ring", chID))
|
|
|
|
|
|
|
|
|
|
if code := dmFindErrorCode(dmCollectAll(sendMallory, absenceWindow)); code != "FORBIDDEN" {
|
|
|
|
|
t.Errorf("expected FORBIDDEN ringing a DM she is not in, got %q", code)
|
|
|
|
|
}
|
|
|
|
|
if got := dmFindMsgType(dmDrainAll(sendBob), "call_incoming"); got != nil {
|
|
|
|
|
t.Error("a non-participant's ring reached a participant")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCallDecline_ForwardsToOtherParticipants(t *testing.T) {
|
|
|
|
|
hub, database := newHandlerHub(t)
|
|
|
|
|
alice := seedOwnerUser(t, database, "decline-alice")
|
|
|
|
|
bob := seedMemberUser(t, database, "decline-bob")
|
|
|
|
|
chID := seedDMChannel(t, database, alice.ID, bob.ID)
|
|
|
|
|
|
|
|
|
|
sendAlice := make(chan []byte, 64)
|
|
|
|
|
sendBob := make(chan []byte, 64)
|
|
|
|
|
cAlice := ws.NewTestClientWithUser(hub, alice, chID, sendAlice)
|
|
|
|
|
cBob := ws.NewTestClientWithUser(hub, bob, chID, sendBob)
|
|
|
|
|
hub.Register(cAlice)
|
|
|
|
|
hub.Register(cBob)
|
|
|
|
|
waitRegistered(t, hub, cBob)
|
|
|
|
|
|
|
|
|
|
hub.HandleMessageForTest(cBob, callMsg("call_decline", chID))
|
|
|
|
|
|
|
|
|
|
env := dmWaitMsgType(sendAlice, "call_declined", waitTimeout)
|
|
|
|
|
if env == nil {
|
|
|
|
|
t.Fatal("the ringer did not receive call_declined")
|
|
|
|
|
}
|
|
|
|
|
payload, _ := env["payload"].(map[string]any)
|
|
|
|
|
if int64(payload["from_user"].(float64)) != bob.ID {
|
|
|
|
|
t.Errorf("call_declined carried from_user %v, want %d", payload["from_user"], bob.ID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCallRing_RateLimited(t *testing.T) {
|
|
|
|
|
hub, database := newHandlerHub(t)
|
|
|
|
|
alice := seedOwnerUser(t, database, "ringlimit-alice")
|
|
|
|
|
bob := seedMemberUser(t, database, "ringlimit-bob")
|
|
|
|
|
chID := seedDMChannel(t, database, alice.ID, bob.ID)
|
|
|
|
|
|
|
|
|
|
sendAlice := make(chan []byte, 64)
|
|
|
|
|
cAlice := ws.NewTestClientWithUser(hub, alice, chID, sendAlice)
|
|
|
|
|
hub.Register(cAlice)
|
|
|
|
|
waitRegistered(t, hub, cAlice)
|
|
|
|
|
|
|
|
|
|
hub.HandleMessageForTest(cAlice, callMsg("call_ring", chID))
|
|
|
|
|
hub.HandleMessageForTest(cAlice, callMsg("call_ring", chID))
|
|
|
|
|
|
|
|
|
|
if code := dmFindErrorCode(dmCollectAll(sendAlice, absenceWindow)); code != "RATE_LIMITED" {
|
|
|
|
|
t.Errorf("expected RATE_LIMITED on a second immediate ring, got %q", code)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-07 21:20:48 +02:00
|
|
|
func TestCallDecline_RateLimited(t *testing.T) {
|
|
|
|
|
hub, database := newHandlerHub(t)
|
|
|
|
|
alice := seedOwnerUser(t, database, "declinelimit-alice")
|
|
|
|
|
bob := seedMemberUser(t, database, "declinelimit-bob")
|
|
|
|
|
chID := seedDMChannel(t, database, alice.ID, bob.ID)
|
|
|
|
|
|
|
|
|
|
sendAlice := make(chan []byte, 64)
|
|
|
|
|
cAlice := ws.NewTestClientWithUser(hub, alice, chID, sendAlice)
|
|
|
|
|
hub.Register(cAlice)
|
|
|
|
|
waitRegistered(t, hub, cAlice)
|
|
|
|
|
|
|
|
|
|
// Same shape as call_ring: each frame costs participant + block lookups
|
|
|
|
|
// and a fan-out to every other participant, so it needs the same limit.
|
|
|
|
|
hub.HandleMessageForTest(cAlice, callMsg("call_decline", chID))
|
|
|
|
|
hub.HandleMessageForTest(cAlice, callMsg("call_decline", chID))
|
|
|
|
|
|
|
|
|
|
if code := dmFindErrorCode(dmCollectAll(sendAlice, absenceWindow)); code != "RATE_LIMITED" {
|
|
|
|
|
t.Errorf("expected RATE_LIMITED on a second immediate decline, got %q", code)
|
|
|
|
|
}
|
|
|
|
|
_ = bob
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A block must silence the 1:1 ring like every other DM sink: without it a
|
|
|
|
|
// blocked user could still make the blocker's client ring (A-2026-08-03).
|
|
|
|
|
func TestCallRing_BlockedOneToOneForbidden(t *testing.T) {
|
|
|
|
|
hub, database := newHandlerHub(t)
|
|
|
|
|
alice := seedOwnerUser(t, database, "ring-blk-alice")
|
|
|
|
|
bob := seedMemberUser(t, database, "ring-blk-bob")
|
|
|
|
|
chID := seedDMChannel(t, database, alice.ID, bob.ID)
|
|
|
|
|
|
|
|
|
|
if err := database.BlockUser(context.Background(), bob.ID, alice.ID); err != nil {
|
|
|
|
|
t.Fatalf("BlockUser: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sendAlice := make(chan []byte, 64)
|
|
|
|
|
sendBob := make(chan []byte, 64)
|
|
|
|
|
cAlice := ws.NewTestClientWithUser(hub, alice, chID, sendAlice)
|
|
|
|
|
cBob := ws.NewTestClientWithUser(hub, bob, chID, sendBob)
|
|
|
|
|
hub.Register(cAlice)
|
|
|
|
|
hub.Register(cBob)
|
|
|
|
|
waitRegistered(t, hub, cBob)
|
|
|
|
|
|
|
|
|
|
hub.HandleMessageForTest(cAlice, callMsg("call_ring", chID))
|
|
|
|
|
|
|
|
|
|
if code := dmFindErrorCode(dmCollectAll(sendAlice, absenceWindow)); code != "FORBIDDEN" {
|
|
|
|
|
t.Errorf("expected FORBIDDEN ringing a blocked 1:1 DM, got %q", code)
|
|
|
|
|
}
|
|
|
|
|
if got := dmFindMsgType(dmDrainAll(sendBob), "call_incoming"); got != nil {
|
|
|
|
|
t.Error("a blocked user's ring reached the blocker")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The group exemption applies to rings exactly as it does to sends: a block
|
|
|
|
|
// between two members must not silence the room's call signal for everyone.
|
|
|
|
|
func TestCallRing_GroupWithInternalBlockStillRings(t *testing.T) {
|
|
|
|
|
hub, database := newHandlerHub(t)
|
|
|
|
|
alice := seedOwnerUser(t, database, "ring-gblk-alice")
|
|
|
|
|
bob := seedMemberUser(t, database, "ring-gblk-bob")
|
|
|
|
|
carol := seedMemberUser(t, database, "ring-gblk-carol")
|
|
|
|
|
chID := seedGroupDM(t, database, "BlockedRingers", alice.ID, bob.ID, carol.ID)
|
|
|
|
|
|
|
|
|
|
if err := database.BlockUser(context.Background(), bob.ID, alice.ID); err != nil {
|
|
|
|
|
t.Fatalf("BlockUser: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sendBob := make(chan []byte, 64)
|
|
|
|
|
sendCarol := make(chan []byte, 64)
|
|
|
|
|
cAlice := ws.NewTestClientWithUser(hub, alice, chID, make(chan []byte, 64))
|
|
|
|
|
cBob := ws.NewTestClientWithUser(hub, bob, chID, sendBob)
|
|
|
|
|
cCarol := ws.NewTestClientWithUser(hub, carol, chID, sendCarol)
|
|
|
|
|
hub.Register(cAlice)
|
|
|
|
|
hub.Register(cBob)
|
|
|
|
|
hub.Register(cCarol)
|
|
|
|
|
waitRegistered(t, hub, cCarol)
|
|
|
|
|
|
|
|
|
|
hub.HandleMessageForTest(cAlice, callMsg("call_ring", chID))
|
|
|
|
|
|
|
|
|
|
if dmWaitMsgType(sendBob, "call_incoming", waitTimeout) == nil {
|
|
|
|
|
t.Error("a group ring was silenced by a block between two members")
|
|
|
|
|
}
|
|
|
|
|
if dmWaitMsgType(sendCarol, "call_incoming", waitTimeout) == nil {
|
|
|
|
|
t.Error("carol did not receive the group call_incoming")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-01 22:06:14 +02:00
|
|
|
func TestCallRing_RejectsNonPositiveChannel(t *testing.T) {
|
|
|
|
|
hub, database := newHandlerHub(t)
|
|
|
|
|
alice := seedOwnerUser(t, database, "ringbad-alice")
|
|
|
|
|
|
|
|
|
|
sendAlice := make(chan []byte, 64)
|
|
|
|
|
cAlice := ws.NewTestClientWithUser(hub, alice, 0, sendAlice)
|
|
|
|
|
hub.Register(cAlice)
|
|
|
|
|
waitRegistered(t, hub, cAlice)
|
|
|
|
|
|
|
|
|
|
hub.HandleMessageForTest(cAlice, callMsg("call_ring", 0))
|
|
|
|
|
|
|
|
|
|
if code := dmFindErrorCode(dmCollectAll(sendAlice, absenceWindow)); code != "BAD_REQUEST" {
|
|
|
|
|
t.Errorf("expected BAD_REQUEST for channel_id 0, got %q", code)
|
|
|
|
|
}
|
|
|
|
|
}
|