MWSE/internal/protocol/protocol_test.go

140 lines
3.6 KiB
Go

package protocol
import (
"encoding/json"
"testing"
)
func decodeString(t *testing.T, s string) *Envelope {
t.Helper()
env, err := Decode([]byte(s))
if err != nil {
t.Fatalf("Decode(%q) error: %v", s, err)
}
return env
}
func TestDecodeRequest(t *testing.T) {
// [message, numericId, "R"] -> a request that wants an "E"-terminated reply.
env := decodeString(t, `[{"type":"my/socketid"}, 7, "R"]`)
if env.Message.Type() != "my/socketid" {
t.Fatalf("type = %q", env.Message.Type())
}
if !env.HasID {
t.Fatal("expected HasID")
}
flag, ok := env.WantsReply()
if !ok || flag != FlagEnd {
t.Fatalf("WantsReply = (%q,%v), want (E,true)", flag, ok)
}
if env.IsBroadcast() {
t.Fatal("request must not be a broadcast")
}
}
func TestDecodeStream(t *testing.T) {
env := decodeString(t, `[{"type":"sub"}, 9, "S"]`)
flag, ok := env.WantsReply()
if !ok || flag != FlagContinue {
t.Fatalf("WantsReply = (%q,%v), want (C,true)", flag, ok)
}
}
func TestDecodeFireAndForget(t *testing.T) {
// The SDK's SendOnly path: [message, "R"]. The "R" sits in the id slot as a
// string, so a handler runs but no reply is produced.
env := decodeString(t, `[{"type":"connection/packsending","value":0}, "R"]`)
if !env.HasID {
t.Fatal("string id should set HasID")
}
if _, ok := env.WantsReply(); ok {
t.Fatal("fire-and-forget must not want a reply")
}
if env.IsBroadcast() {
t.Fatal("fire-and-forget is not a broadcast")
}
if env.Message.Truthy("value") {
t.Fatal("value:0 should be falsey")
}
}
func TestDecodeBroadcast(t *testing.T) {
// A bare [message] frame: no id -> the broadcast branch.
env := decodeString(t, `[{"type":"hello"}]`)
if env.HasID {
t.Fatal("bare frame should not have an id")
}
if !env.IsBroadcast() {
t.Fatal("bare frame should be a broadcast")
}
if _, ok := env.WantsReply(); ok {
t.Fatal("broadcast must not want a reply")
}
}
func TestDecodeMissingTypeObject(t *testing.T) {
// arr[0] not an object -> Message is nil and Type() is empty.
env := decodeString(t, `["not-an-object", 1, "R"]`)
if env.Message != nil {
t.Fatalf("expected nil Message, got %v", env.Message)
}
if env.Message.Type() != "" {
t.Fatal("nil message type should be empty")
}
}
func TestDecodeErrors(t *testing.T) {
if _, err := Decode([]byte(`{"type":"x"}`)); err == nil {
t.Fatal("object (not array) should error")
}
if _, err := Decode([]byte(`[]`)); err != ErrEmptyFrame {
t.Fatalf("empty array err = %v, want ErrEmptyFrame", err)
}
if _, err := Decode([]byte(`not json`)); err == nil {
t.Fatal("invalid json should error")
}
}
func TestNumericIDRoundTripsAsInteger(t *testing.T) {
// A numeric id must come back out as an integer, not "7.0", so the SDK's
// integer-keyed event pool matches it.
env := decodeString(t, `[{"type":"x"}, 7, "R"]`)
reply := Reply("ok", env.ID, FlagEnd)
b, err := json.Marshal(reply)
if err != nil {
t.Fatal(err)
}
if got, want := string(b), `["ok",7,"E"]`; got != want {
t.Fatalf("reply = %s, want %s", got, want)
}
}
func TestSignalShape(t *testing.T) {
b, err := json.Marshal(Signal("room/joined", map[string]any{"id": "abc"}))
if err != nil {
t.Fatal(err)
}
if got, want := string(b), `[{"id":"abc"},"room/joined"]`; got != want {
t.Fatalf("signal = %s, want %s", got, want)
}
}
func TestMessageAccessors(t *testing.T) {
m := Message{"type": "t", "to": "peer-1", "n": float64(42), "b": true, "s": "x"}
if m.Str("to") != "peer-1" {
t.Fatal("Str")
}
if m.Int("n") != 42 {
t.Fatal("Int")
}
if !m.Bool("b") {
t.Fatal("Bool")
}
if !m.Truthy("s") || m.Truthy("missing") {
t.Fatal("Truthy")
}
if !m.Has("to") || m.Has("nope") {
t.Fatal("Has")
}
}