Watch
1
0
Fork
You've already forked mautrix-whatsapp
0
mirror of https://github.com/mautrix/whatsapp.git synced 2026-08-24 13:04:56 -04:00
mautrix-whatsapp/pkg/connector/voip/matrixrtc_test.go
2026-07-30 04:47:36 +02:00

229 lines
7.6 KiB
Go

package voip
import (
"testing"
"time"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
func TestSupportedMatrixRTCEventTypesHaveExplicitClasses(t *testing.T) {
types := SupportedMatrixRTCEventTypes()
if len(types) != 10 {
t.Fatalf("SupportedMatrixRTCEventTypes returned %d types, want 10", len(types))
}
for _, evtType := range types {
switch evtType.Type {
case EventTypeGroupCall, EventTypeGroupCallMember:
if evtType.Class != event.StateEventType {
t.Fatalf("%s class = %v, want state", evtType.Type, evtType.Class)
}
case EventTypeRTCMembership:
if evtType.Class != event.StateEventType && evtType.Class != event.MessageEventType {
t.Fatalf("%s class = %v, want state or message", evtType.Type, evtType.Class)
}
case EventTypeRTCNotification, EventTypeCallNotify, EventTypeRTCDecline,
EventTypeElementCallReaction, event.EventReaction.Type, event.EventRedaction.Type:
if evtType.Class != event.MessageEventType {
t.Fatalf("%s class = %v, want message", evtType.Type, evtType.Class)
}
default:
t.Fatalf("unexpected MatrixRTC event type %s", evtType.Type)
}
}
}
func TestParseElementCallReactionEvent(t *testing.T) {
evt := &event.Event{
ID: id.EventID("$reaction"),
Type: ElementCallReactionEventType(),
RoomID: id.RoomID("!room:example.com"),
Sender: id.UserID("@alice:example.com"),
Content: event.Content{Raw: map[string]any{
"m.relates_to": map[string]any{
"rel_type": "m.reference",
"event_id": "$membership",
},
"emoji": "❤️",
"name": "generic",
}},
}
parsed, ok := ParseMatrixRTCEvent(evt)
if !ok {
t.Fatal("ParseMatrixRTCEvent did not recognize Element Call reaction")
}
if parsed.Kind != MatrixRTCEventKindCallReaction ||
parsed.EventID != "$reaction" ||
parsed.RelatesToEventID != "$membership" ||
parsed.ReactionEmoji != "❤️" {
t.Fatalf("unexpected parsed reaction: %+v", parsed)
}
}
func TestSupportedWhatsAppCallReactions(t *testing.T) {
for _, emoji := range []string{"👍", "❤️", "😂", "😮", "😢", "🙏"} {
normalized, ok := NormalizeWhatsAppCallReaction(emoji)
if !ok || normalized != emoji {
t.Fatalf("NormalizeWhatsAppCallReaction(%q) = %q, %t", emoji, normalized, ok)
}
}
if normalized, ok := NormalizeWhatsAppCallReaction("🎉"); ok || normalized != "" {
t.Fatalf("unsupported reaction normalized to %q, %t", normalized, ok)
}
}
func TestParseMatrixRTCDeclineEvent(t *testing.T) {
evt := &event.Event{
Type: event.Type{Type: EventTypeRTCDecline, Class: event.MessageEventType},
RoomID: id.RoomID("!room:example.com"),
Sender: id.UserID("@alice:example.com"),
Content: event.Content{Raw: map[string]any{
"call_id": "call-1",
"device_id": "DEVICE",
"session_id": "SESSION",
}},
}
parsed, ok := ParseMatrixRTCEvent(evt)
if !ok {
t.Fatalf("ParseMatrixRTCEvent did not recognize decline event")
}
if parsed.Kind != MatrixRTCEventKindRTCDecline {
t.Fatalf("kind = %q, want %q", parsed.Kind, MatrixRTCEventKindRTCDecline)
}
if parsed.CallID != "call-1" || parsed.DeviceID != "DEVICE" || parsed.SessionID != "SESSION" {
t.Fatalf("unexpected parsed event: %+v", parsed)
}
}
func TestParseMatrixRTCMembershipEvent(t *testing.T) {
stateKey := "@alice:example.com"
evt := &event.Event{
Type: event.Type{Type: EventTypeRTCMembership, Class: event.StateEventType},
RoomID: id.RoomID("!room:example.com"),
Sender: id.UserID("@alice:example.com"),
StateKey: &stateKey,
Content: event.Content{Raw: map[string]any{
"memberships": []any{map[string]any{
"call_id": "call-2",
"device_id": "DEVICE",
"session_id": "SESSION",
"lifetime_ms": float64(60000),
"foci_preferred": []any{map[string]any{
"type": "livekit",
"livekit_service_url": "https://rtc.example.com/jwt",
}},
}},
}},
}
parsed, ok := ParseMatrixRTCEvent(evt)
if !ok {
t.Fatalf("ParseMatrixRTCEvent did not recognize membership event")
}
if parsed.Kind != MatrixRTCEventKindRTCMembership || parsed.StateKey != stateKey {
t.Fatalf("unexpected parsed event metadata: %+v", parsed)
}
if parsed.CallID != "call-2" || parsed.DeviceID != "DEVICE" || parsed.SessionID != "SESSION" {
t.Fatalf("unexpected parsed event identifiers: %+v", parsed)
}
if parsed.LifetimeMS != 60000 {
t.Fatalf("LifetimeMS = %d, want 60000", parsed.LifetimeMS)
}
if len(parsed.FociPreferred) != 1 || parsed.FociPreferred[0].LiveKitServiceURL != "https://rtc.example.com/jwt" {
t.Fatalf("unexpected foci: %+v", parsed.FociPreferred)
}
}
func TestBuildRTCMembershipContent(t *testing.T) {
session := MatrixRTCSession{
UserID: "@wa_123:example.com",
DeviceID: "WADEVICE",
Focus: Focus{
Type: "livekit",
LiveKitServiceURL: "https://rtc.example.com/jwt",
},
}
content := BuildRTCMembershipContent(session)
if content["slot_id"] != MatrixRTCDefaultSlotID {
t.Fatalf("slot_id = %q, want %q", content["slot_id"], MatrixRTCDefaultSlotID)
}
member := content["member"].(map[string]any)
if member["user_id"] != "@wa_123:example.com" || member["device_id"] != "WADEVICE" {
t.Fatalf("unexpected member: %+v", member)
}
transports := content["rtc_transports"].([]map[string]any)
if len(transports) != 1 || transports[0]["livekit_service_url"] != "https://rtc.example.com/jwt" {
t.Fatalf("unexpected transports: %+v", transports)
}
}
func TestParseBuiltRTCMembershipContent(t *testing.T) {
content := BuildRTCMembershipContent(MatrixRTCSession{
UserID: "@wa_123:example.com",
DeviceID: "WADEVICE",
Intent: "audio",
Focus: Focus{
Type: "livekit",
LiveKitServiceURL: "https://rtc.example.com/jwt",
},
})
evt := &event.Event{
Type: RTCMembershipEventType(event.MessageEventType),
RoomID: id.RoomID("!room:example.com"),
Sender: id.UserID("@wa_123:example.com"),
Content: event.Content{Raw: content},
}
parsed, ok := ParseMatrixRTCEvent(evt)
if !ok {
t.Fatalf("ParseMatrixRTCEvent did not recognize membership event")
}
if parsed.Intent != "audio" {
t.Fatalf("Intent = %q, want audio", parsed.Intent)
}
if len(parsed.FociPreferred) != 1 || parsed.FociPreferred[0].LiveKitServiceURL != "https://rtc.example.com/jwt" {
t.Fatalf("unexpected foci: %+v", parsed.FociPreferred)
}
if !MatrixRTCEventHasJoinContent(parsed) {
t.Fatalf("MatrixRTCEventHasJoinContent returned false for a built membership")
}
}
func TestMatrixRTCEventHasJoinContentRejectsStickyCleanup(t *testing.T) {
evt := MatrixRTCEvent{
Kind: MatrixRTCEventKindRTCMembership,
Raw: EmptyMatrixRTCContent("sticky"),
}
if MatrixRTCEventHasJoinContent(evt) {
t.Fatalf("MatrixRTCEventHasJoinContent returned true for sticky cleanup content")
}
}
func TestMatrixRTCEventHasJoinContentRejectsMetadataWithoutFocus(t *testing.T) {
for _, raw := range []map[string]any{
{
"application": map[string]any{"type": MatrixRTCApplicationCall},
},
{
"slot_id": MatrixRTCDefaultSlotID,
"application": map[string]any{"type": MatrixRTCApplicationCall},
"member": map[string]any{"user_id": "@alice:example.com", "device_id": "DEVICE"},
},
} {
evt := MatrixRTCEvent{Kind: MatrixRTCEventKindRTCMembership, Raw: raw}
if MatrixRTCEventHasJoinContent(evt) {
t.Fatalf("MatrixRTCEventHasJoinContent returned true for metadata-only content: %+v", raw)
}
}
}
func TestBuildRTCNotificationContentCapsLifetime(t *testing.T) {
content := BuildRTCNotificationContent(testTime, 5*time.Minute, "audio")
if content["notification_type"] != "ring" {
t.Fatalf("notification_type = %q, want ring", content["notification_type"])
}
if content["lifetime"] != int64(90000) {
t.Fatalf("lifetime = %v, want 90000", content["lifetime"])
}
}
var testTime = time.Unix(123, 0)