140 lines
3.6 KiB
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")
|
|
}
|
|
}
|