package services import ( "fmt" "testing" "git.saqut.com/saqut/mwse/internal/ws" ) // These tests cover the 1.0.0 engine-parity issues #27 (rooms), #28 (pairing) and // #29 (virtual addressing), plus the leak-hardening done for high-scale operation. // They share the helpers defined in services_test.go. // ---- #27 Room system parity --------------------------------------------- func TestRoomJoinTypes(t *testing.T) { hub := newHub() owner, _ := connect(hub, "owner") create := func(name, joinType, cred string) { fields := []any{ "accessType", "public", "joinType", joinType, "notifyActionInvite", false, "notifyActionJoined", true, "notifyActionEjected", true, "description", "d", "name", name, } if cred != "" { fields = append(fields, "credential", cred) } r := asMap(t, hub.Handle(owner, msg("create-room", fields...))) if r["status"] != "success" { t.Fatalf("create-room(%s) = %v", joinType, r) } } create("free-room", "free", "") create("lock-room", "lock", "") create("pw-room", "password", "secret") joiner, _ := connect(hub, "joiner") if r := asMap(t, hub.Handle(joiner, msg("joinroom", "name", "free-room"))); r["status"] != "success" { t.Fatalf("free join = %v", r) } if r := asMap(t, hub.Handle(joiner, msg("joinroom", "name", "lock-room"))); r["message"] != "LOCKED-ROOM" { t.Fatalf("lock join = %v, want LOCKED-ROOM", r) } if r := asMap(t, hub.Handle(joiner, msg("joinroom", "name", "pw-room", "credential", "wrong"))); r["message"] != "WRONG-PASSWORD" { t.Fatalf("pw wrong = %v, want WRONG-PASSWORD", r) } if r := asMap(t, hub.Handle(joiner, msg("joinroom", "name", "pw-room", "credential", "secret"))); r["status"] != "success" { t.Fatalf("pw right = %v, want success", r) } } func TestRoomIfExistsJoin(t *testing.T) { hub := newHub() a, _ := connect(hub, "a") b, _ := connect(hub, "b") common := []any{ "accessType", "public", "joinType", "free", "notifyActionInvite", false, "notifyActionJoined", true, "notifyActionEjected", true, "description", "d", "name", "shared", } if r := asMap(t, hub.Handle(a, msg("create-room", common...))); r["status"] != "success" { t.Fatalf("first create = %v", r) } // Without ifexistsJoin: duplicate name fails. if r := asMap(t, hub.Handle(b, msg("create-room", common...))); r["message"] != "ALREADY-EXISTS" { t.Fatalf("dup create = %v, want ALREADY-EXISTS", r) } // With ifexistsJoin: b joins the existing room instead of failing. withFlag := append(append([]any{}, common...), "ifexistsJoin", true) r := asMap(t, hub.Handle(b, msg("create-room", withFlag...))) if r["status"] != "success" { t.Fatalf("ifexistsJoin create = %v", r) } roomID := asMap(t, r["room"])["id"].(string) if !b.InRoom(roomID) { t.Fatal("b should have joined the existing room via ifexistsJoin") } } func TestRoomPerConnectionNotifySuppression(t *testing.T) { hub := newHub() a, fa := connect(hub, "a") created := asMap(t, hub.Handle(a, msg("create-room", "accessType", "public", "joinType", "free", "notifyActionInvite", false, "notifyActionJoined", true, "notifyActionEjected", true, "description", "d", "name", "R", ))) if created["status"] != "success" { t.Fatalf("create = %v", created) } // a opts out of peer-info notifications; it must NOT receive room/joined. hub.Handle(a, msg("connection/pairinfo", "value", float64(0))) beforeJoined := func() bool { _, ok := findSignal(fa, "room/joined"); return ok }() b, _ := connect(hub, "b") if r := asMap(t, hub.Handle(b, msg("joinroom", "name", "R"))); r["status"] != "success" { t.Fatalf("b join = %v", r) } // Give any (erroneous) delivery time to land, then assert nothing new arrived. waitFor(t, func() bool { return true }) if afterJoined := func() bool { _, ok := findSignal(fa, "room/joined"); return ok }(); afterJoined && !beforeJoined { t.Fatal("a disabled pairinfo but still received room/joined") } } func TestRoomInviteFlow(t *testing.T) { hub := newHub() owner, fo := connect(hub, "owner") created := asMap(t, hub.Handle(owner, msg("create-room", "accessType", "public", "joinType", "invite", "notifyActionInvite", false, "notifyActionJoined", true, "notifyActionEjected", true, "description", "d", "name", "club", ))) roomID := asMap(t, created["room"])["id"].(string) guest, fg := connect(hub, "guest") resp := asMap(t, hub.Handle(guest, msg("joinroom", "name", "club"))) if resp["message"] != "INVITE-REQUESTED" { t.Fatalf("invite join = %v, want INVITE-REQUESTED", resp) } if inv := waitSignal(t, fo, "room/invite"); inv["id"] != "guest" { t.Fatalf("owner invite signal = %v", inv) } accepted := asMap(t, hub.Handle(owner, msg("accept/invite-room", "roomId", roomID, "clientId", "guest"))) if accepted["status"] != "success" { t.Fatalf("accept invite = %v", accepted) } if st := waitSignal(t, fg, "room/invite/status"); st["status"] != "accepted" { t.Fatalf("guest status signal = %v", st) } if !guest.InRoom(roomID) { t.Fatal("guest should be in the room after accepted invite") } } // ---- #28 Pairing parity -------------------------------------------------- func TestPairRejectAndEnd(t *testing.T) { hub := newHub() a, fa := connect(hub, "a") b, _ := connect(hub, "b") hub.Handle(a, msg("request/pair", "to", "b")) hub.Handle(b, msg("accept/pair", "to", "a")) if !isPaired(a, b) { t.Fatal("precondition: a and b paired") } if r := asMap(t, hub.Handle(b, msg("end/pair", "to", "a"))); r["status"] != "success" { t.Fatalf("end/pair = %v", r) } waitSignal(t, fa, "end/pair") if isPaired(a, b) || a.HasPair("b") || b.HasPair("a") { t.Fatal("end/pair should fully unpair both sides") } } func TestIsReachable(t *testing.T) { hub := newHub() a, _ := connect(hub, "a") b, _ := connect(hub, "b") // b is public by default -> reachable. if r := hub.Handle(a, msg("is/reachable", "to", "b")); r != true { t.Fatalf("public reachable = %v, want true", r) } // b goes private -> not reachable until paired. hub.Handle(b, msg("auth/private")) if r := hub.Handle(a, msg("is/reachable", "to", "b")); r != false { t.Fatalf("private unpaired reachable = %v, want false", r) } hub.Handle(a, msg("request/pair", "to", "b")) hub.Handle(b, msg("accept/pair", "to", "a")) if r := hub.Handle(a, msg("is/reachable", "to", "b")); r != true { t.Fatalf("private paired reachable = %v, want true", r) } } func TestPairListMutualOnly(t *testing.T) { hub := newHub() a, _ := connect(hub, "a") b, _ := connect(hub, "b") // One-directional request: not yet mutual, so pair/list is empty. hub.Handle(a, msg("request/pair", "to", "b")) if list := asMap(t, hub.Handle(a, msg("pair/list")))["value"]; list != nil { if arr, ok := list.([]string); ok && len(arr) != 0 { t.Fatalf("pair/list before accept = %v, want empty", arr) } } hub.Handle(b, msg("accept/pair", "to", "a")) list, _ := asMap(t, hub.Handle(a, msg("pair/list")))["value"].([]string) if len(list) != 1 || list[0] != "b" { t.Fatalf("pair/list after accept = %v, want [b]", list) } } // ---- #29 Virtual addressing (IPPressure) parity -------------------------- func TestIPPressureReallocAndRelease(t *testing.T) { hub := newHub() a, _ := connect(hub, "a") n1 := asMap(t, hub.Handle(a, msg("alloc/APNumber")))["number"].(int) // realloc gives a different number and frees the old one. n2 := asMap(t, hub.Handle(a, msg("realloc/APNumber")))["number"].(int) if n1 == n2 { t.Fatalf("realloc returned same number %d", n1) } if who := asMap(t, hub.Handle(a, msg("whois/APNumber", "whois", float64(n1)))); who["status"] != "fail" { t.Fatalf("old number should be free after realloc, whois = %v", who) } // release clears it from the client and the table. hub.Handle(a, msg("release/APNumber")) if a.APNumber() != 0 { t.Fatal("number not cleared on release") } // shortcode + ip alloc work and are non-empty. if code := asMap(t, hub.Handle(a, msg("alloc/APShortCode")))["code"].(string); len(code) != 3 { t.Fatalf("shortcode = %q, want 3 letters", code) } if ip := asMap(t, hub.Handle(a, msg("alloc/APIPAddress")))["ip"].(string); ip == "" { t.Fatal("ip alloc returned empty") } } // ---- leak hardening (high-scale, no unbounded growth) -------------------- func TestDisconnectCleansIncomingPairEdge(t *testing.T) { hub := newHub() a, _ := connect(hub, "a") b, _ := connect(hub, "b") // One-directional pending request: a -> b. a.pairs has b; b.pairedBy has a. hub.Handle(a, msg("request/pair", "to", "b")) if !a.HasPair("b") { t.Fatal("precondition: a has outgoing edge to b") } // b disconnects without ever responding. a must not retain a stale edge. hub.Disconnect(b) if a.HasPair("b") { t.Fatal("stale pair edge to a disconnected client was left behind (leak)") } if len(a.PairedBy()) != 0 { t.Fatalf("a.PairedBy() = %v, want empty", a.PairedBy()) } } func TestDisconnectCleansWaitingList(t *testing.T) { hub := newHub() owner, _ := connect(hub, "owner") created := asMap(t, hub.Handle(owner, msg("create-room", "accessType", "public", "joinType", "invite", "notifyActionInvite", false, "notifyActionJoined", true, "notifyActionEjected", true, "description", "d", "name", "club", ))) roomID := asMap(t, created["room"])["id"].(string) guest, _ := connect(hub, "guest") hub.Handle(guest, msg("joinroom", "name", "club")) room, _ := hub.Room(roomID) if !room.IsWaiting("guest") { t.Fatal("precondition: guest should be on the waiting list") } hub.Disconnect(guest) if room.IsWaiting("guest") { t.Fatal("disconnected client left on the room waiting list (leak)") } } // TestHighChurnLeavesNoResidualState drives many connect/pair/room/disconnect // cycles and asserts the hub holds no clients or rooms afterwards — i.e. nothing // accumulates across churn at scale. func TestHighChurnLeavesNoResidualState(t *testing.T) { hub := newHub() for cycle := 0; cycle < 20; cycle++ { roomName := fmt.Sprintf("room-%d", cycle) clients := make([]*ws.Client, 0, 25) for i := 0; i < 25; i++ { c, _ := connect(hub, fmt.Sprintf("c%d-%d", cycle, i)) clients = append(clients, c) } owner := clients[0] hub.Handle(owner, msg("create-room", "accessType", "public", "joinType", "free", "notifyActionInvite", false, "notifyActionJoined", true, "notifyActionEjected", true, "description", "d", "name", roomName, )) for _, c := range clients[1:] { hub.Handle(c, msg("joinroom", "name", roomName)) hub.Handle(c, msg("request/pair", "to", owner.ID)) hub.Handle(c, msg("alloc/APNumber")) } for _, c := range clients { hub.Disconnect(c) } } if n := hub.ClientCount(); n != 0 { t.Fatalf("residual clients after churn: %d (leak)", n) } if rooms := hub.Rooms(); len(rooms) != 0 { t.Fatalf("residual rooms after churn: %d (leak)", len(rooms)) } }