- lighthouse: Registry of namespace authorizers (Result, Allowed, Denied), ParseChannel, ChannelID with PHP (int)-cast semantics (PHPInt, pinned by a php -r table test), FormatChannels, WithClientID/ClientID - centrifugo: ProxyHandler (constant-time X-Centrifugo-Secret, HTTP 200 generic deny, info [] on allow, presence allow/override merge, 64 KiB body cap) mounted as the ServerToServer subscribe route - README: proxy contract, registry and channel rules
121 lines
3.8 KiB
Go
121 lines
3.8 KiB
Go
package lighthouse
|
||
|
||
import (
|
||
"context"
|
||
"math"
|
||
"reflect"
|
||
"testing"
|
||
)
|
||
|
||
// TestChannelIDMatchesPHP pins ChannelID to PHP's (int) cast of the id
|
||
// segment. The expected values were printed by PHP 8.5.10:
|
||
//
|
||
// php -r 'foreach ([...] as $s) var_dump((int)$s);'
|
||
func TestChannelIDMatchesPHP(t *testing.T) {
|
||
cases := []struct {
|
||
segment string
|
||
want int64
|
||
}{
|
||
{"5", 5}, {"05", 5}, {" 5", 5}, {"5abc", 5}, {"abc", 0}, {"-3", -3}, {"", 0},
|
||
{"1e3", 1000}, {"0x1A", 0}, {"9999999999999999999", math.MaxInt64},
|
||
{"\t5", 5}, {"\n5", 5}, {"5 ", 5}, {"+5", 5}, {" -7x", -7}, {"1.9", 1}, {"-0", 0},
|
||
{"1e20", math.MaxInt64}, {"0b11", 0}, {"٥", 0}, {" ", 0},
|
||
{"-9999999999999999999", math.MinInt64}, {"-1e20", math.MinInt64}, {"1e-3", 0},
|
||
{".5", 0}, {"5.", 5}, {"1e", 1}, {"1e+2", 100}, {"\v5", 5}, {"\f5", 5}, {"\r5", 5},
|
||
{"NaN", 0}, {"INF", 0}, {"1E3", 1000}, {"9223372036854775807", math.MaxInt64},
|
||
{"9223372036854775808", math.MaxInt64}, {"1.5e3", 1500}, {"-.5e1", -5}, {"0005", 5},
|
||
{"00", 0}, {"- 5", 0}, {"5e3abc", 5000}, {"12.7e1x", 127}, {"1e400", 0},
|
||
}
|
||
for _, tc := range cases {
|
||
if got := ChannelID("collection:" + tc.segment); got != tc.want {
|
||
t.Errorf("ChannelID(collection:%q) = %d, want %d", tc.segment, got, tc.want)
|
||
}
|
||
}
|
||
if got := ChannelID("collection"); got != 0 {
|
||
t.Errorf("missing segment = %d, want 0", got)
|
||
}
|
||
if got := ChannelID("presence:collection:5"); got != 0 {
|
||
t.Errorf("presence:collection:5 = %d, want 0 (segment 1 is \"collection\")", got)
|
||
}
|
||
if got := ChannelID("wishlist:12:extra"); got != 12 {
|
||
t.Errorf("wishlist:12:extra = %d, want 12", got)
|
||
}
|
||
}
|
||
|
||
func TestParseChannel(t *testing.T) {
|
||
cases := []struct {
|
||
channel string
|
||
namespace string
|
||
presence bool
|
||
}{
|
||
{"collection:5", "collection", false},
|
||
{"ns:entity:id", "ns", false},
|
||
{"presence:acme:room:1", "acme", true},
|
||
{"presence:collection:5", "collection", true},
|
||
{"presence:presence:x", "", true},
|
||
{"a:b:c:d", "", false},
|
||
{"presence:a:b:c:d", "", true},
|
||
{"", "", false},
|
||
{":5", "", false},
|
||
{"Collection:5", "Collection", false},
|
||
}
|
||
for _, tc := range cases {
|
||
ns, presence := ParseChannel(tc.channel)
|
||
if ns != tc.namespace || presence != tc.presence {
|
||
t.Errorf("ParseChannel(%q) = (%q, %t), want (%q, %t)", tc.channel, ns, presence, tc.namespace, tc.presence)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestFormatChannels(t *testing.T) {
|
||
got := FormatChannels("Acme", []string{"Collection:5", "acme:user:1", "ACME:x"})
|
||
want := []string{"acme:collection:5", "acme:user:1", "acme:x"}
|
||
if !reflect.DeepEqual(got, want) {
|
||
t.Fatalf("FormatChannels = %v, want %v", got, want)
|
||
}
|
||
if got := FormatChannels("", []string{"Collection:5"}); !reflect.DeepEqual(got, []string{"collection:5"}) {
|
||
t.Fatalf("no namespace = %v", got)
|
||
}
|
||
}
|
||
|
||
func TestClientID(t *testing.T) {
|
||
if ClientID(context.Background()) != "" {
|
||
t.Fatal("empty ctx has a client id")
|
||
}
|
||
if got := ClientID(WithClientID(context.Background(), "c-1")); got != "c-1" {
|
||
t.Fatalf("ClientID = %q", got)
|
||
}
|
||
}
|
||
|
||
func TestRegistry(t *testing.T) {
|
||
r := NewRegistry()
|
||
allow := AuthorizerFunc(func(context.Context, uint, string) Result { return Allowed(nil) })
|
||
if err := r.Register("b", allow); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := r.Register("a", allow); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
for _, bad := range []struct {
|
||
ns string
|
||
a Authorizer
|
||
}{{"", allow}, {"x:y", allow}, {"c", nil}, {"a", allow}} {
|
||
if err := r.Register(bad.ns, bad.a); err == nil {
|
||
t.Errorf("Register(%q) accepted", bad.ns)
|
||
}
|
||
}
|
||
if got := r.Namespaces(); !reflect.DeepEqual(got, []string{"a", "b"}) {
|
||
t.Fatalf("Namespaces = %v", got)
|
||
}
|
||
if _, ok := r.Get("A"); ok {
|
||
t.Fatal("lookup is not case-sensitive")
|
||
}
|
||
if a, ok := r.Get("a"); !ok || !a.Authorize(context.Background(), 1, "a:1").Allowed {
|
||
t.Fatal("Get(a) failed")
|
||
}
|
||
d := Denied("why")
|
||
if d.Allowed || d.Reason() != "why" {
|
||
t.Fatalf("Denied = %+v", d)
|
||
}
|
||
}
|