Room channel: other T5DE users' community products on their avatars
CI / build (push) Successful in 28s
CI / build (push) Successful in 28s
The proxy follows the client's IMQ chat subscriptions and tells the hub which chats it's in (and the room, from chat.getOrMakeChat). For the other T5DE users the hub says are in a chat, it gives the client the community products it has packs for, as *use and *remove lines from them, and adds them to the whole outfits they send. It also takes community products out of the outfit the client posts to IMVU's chat participant endpoint, which had been sending them to IMVU. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
e394f1dc24
commit
93fa2f5d90
11 files changed
+858
-12
No files matched your search
@@ -0,0 +1,126 @@
|
||||
package products
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"git.t5de.dev/t5de/t5de-proxy/internal/hub"
|
||||
"git.t5de.dev/t5de/t5de-proxy/internal/imq"
|
||||
"git.t5de.dev/t5de/t5de-proxy/internal/proxy"
|
||||
"git.t5de.dev/t5de/t5de-proxy/internal/xmlrpc"
|
||||
)
|
||||
|
||||
// fakeObserver records what the hub client would be told.
|
||||
type fakeObserver struct {
|
||||
mu sync.Mutex
|
||||
events []string
|
||||
}
|
||||
|
||||
func (f *fakeObserver) add(e string) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.events = append(f.events, e)
|
||||
}
|
||||
func (f *fakeObserver) Session(int64) {}
|
||||
func (f *fakeObserver) Outfit(int64, []hub.Item) {}
|
||||
func (f *fakeObserver) Join(chat, room string) { f.add("join " + chat + " " + room) }
|
||||
func (f *fakeObserver) Leave(chat string) { f.add("leave " + chat) }
|
||||
|
||||
// delivered reads the chat lines delivered to a client: "from: text".
|
||||
func delivered(t *testing.T, b *bytes.Buffer) []string {
|
||||
t.Helper()
|
||||
var lines []string
|
||||
for b.Len() > 0 {
|
||||
f, n, err := imq.Next(b.Bytes())
|
||||
if err != nil || n == 0 {
|
||||
t.Fatalf("bad frames: %v", err)
|
||||
}
|
||||
b.Next(n)
|
||||
m, err := imq.ParseDelivered(f.Data)
|
||||
if err != nil || m.Queue != "/chat/555" || m.Mount != "messages" {
|
||||
t.Fatalf("delivered %+v %v", m, err)
|
||||
}
|
||||
var msg struct {
|
||||
Message string
|
||||
UserID int64 `json:"userId"`
|
||||
}
|
||||
json.Unmarshal(m.Message, &msg)
|
||||
lines = append(lines, fmt.Sprintf("%s(%d): %s", m.UserID, msg.UserID, msg.Message))
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func TestRoomChannel(t *testing.T) {
|
||||
h, _ := newHarness(t)
|
||||
obs := &fakeObserver{}
|
||||
h.p.Observer = obs
|
||||
local := h.p.byProductID("blue-coat").local
|
||||
|
||||
// Joining a room: getOrMakeChat says which chat it is, then the client subscribes to its queue.
|
||||
h.do("POST", "http://chat.imvu.com/api/xmlrpc/chat.php", call("chat.getOrMakeChat",
|
||||
xmlrpc.Struct{{Name: "userId", Value: int64(7)}, {Name: "activity", Value: "publicroom-12-3"}}), false, false)
|
||||
var toClient bytes.Buffer
|
||||
c := proxy.NewIMQConn(1, &toClient)
|
||||
h.p.Subscribed(c, []string{"/user/7", "/chat/555"})
|
||||
if got := strings.Join(obs.events, "; "); got != "join 555 12-3" {
|
||||
t.Fatalf("observer heard %q", got)
|
||||
}
|
||||
|
||||
// Someone else wears the coat (and something not in the packs): the client is given it from them.
|
||||
h.p.Incoming(c, imq.Delivered{UserID: "/user/88", Queue: "/chat/555", Mount: "messages", Message: []byte(`{"message":"hi","userId":88}`)})
|
||||
h.p.Others("555", []hub.Avatar{{IMVUID: 88, Items: []hub.Item{{ID: "blue-coat", Name: "Coat"}, {ID: "elsewhere", Name: "Hat"}}}})
|
||||
if got := delivered(t, &toClient); len(got) != 1 || got[0] != fmt.Sprintf("/user/88(88): *use %d", local) {
|
||||
t.Fatalf("delivered %q", got)
|
||||
}
|
||||
// Told again with no change: nothing new.
|
||||
h.p.Others("555", []hub.Avatar{{IMVUID: 88, Items: []hub.Item{{ID: "blue-coat", Name: "Coat"}}}})
|
||||
if toClient.Len() != 0 {
|
||||
t.Fatal("delivered again")
|
||||
}
|
||||
|
||||
// Their whole outfit (which IMVU sends without it) gets it added; other lines don't.
|
||||
whole := []byte(`{"message":"*use 80 1 2","userId":88,"chatId":555,"to":0}`)
|
||||
out := h.p.Incoming(c, imq.Delivered{UserID: "/user/88", Queue: "/chat/555", Mount: "messages", Message: whole})
|
||||
if !bytes.Contains(out, []byte(fmt.Sprintf(`"message":"*use 80 1 2 %d"`, local))) || !bytes.Contains(out, []byte(`"chatId":555`)) {
|
||||
t.Fatalf("whole outfit %s", out)
|
||||
}
|
||||
part := []byte(`{"message":"*use 3","userId":88}`)
|
||||
if out := h.p.Incoming(c, imq.Delivered{UserID: "/user/88", Queue: "/chat/555", Mount: "messages", Message: part}); !bytes.Equal(out, part) {
|
||||
t.Fatalf("a single product changed: %s", out)
|
||||
}
|
||||
other := []byte(`{"message":"*use 80 1","userId":99}`)
|
||||
if out := h.p.Incoming(c, imq.Delivered{UserID: "/user/99", Queue: "/chat/555", Mount: "messages", Message: other}); !bytes.Equal(out, other) {
|
||||
t.Fatalf("someone else's outfit changed: %s", out)
|
||||
}
|
||||
|
||||
// They take it off, then leave: the client is told to take it off once.
|
||||
h.p.Others("555", []hub.Avatar{{IMVUID: 88}})
|
||||
h.p.Others("555", []hub.Avatar{})
|
||||
if got := delivered(t, &toClient); len(got) != 1 || got[0] != fmt.Sprintf("/user/88(88): *remove %d", local) {
|
||||
t.Fatalf("delivered %q", got)
|
||||
}
|
||||
|
||||
// The connection closing leaves the chat.
|
||||
h.p.Closed(c)
|
||||
if got := obs.events[len(obs.events)-1]; got != "leave 555" {
|
||||
t.Fatalf("last event %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParticipantPostKeepsCommunityProducts(t *testing.T) {
|
||||
h, _ := newHarness(t)
|
||||
local := h.p.byProductID("blue-coat").local
|
||||
h.do("POST", "https://secure.imvu.com/api/xmlrpc/client.php", call("test.avatarInfoForLogin2"), false, false)
|
||||
body := []byte(fmt.Sprintf(`{"legacy_outfit_message": "*use 80 1 %d", "seat_number": 2}`, local))
|
||||
if status, _ := h.do("POST", "https://api.imvu.com/chat/chat-555/participants/user-7", body, true, false); status != 200 {
|
||||
t.Fatalf("status %d", status)
|
||||
}
|
||||
got := h.imvu.asked["participant"]
|
||||
if len(got) != 1 || strings.Contains(got[0], fmt.Sprint(local)) || !strings.Contains(got[0], `"legacy_outfit_message":"*use 80 1"`) || !strings.Contains(got[0], `"seat_number":2`) {
|
||||
t.Fatalf("IMVU was sent %q", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user