|
|
|
internal/gates/rooms_test.go raw
| 1 | package gates | | 2 | | | 3 | import ( | | 4 | "context" | | 5 | "database/sql" | | 6 | "encoding/json" | | 7 | "strings" | | 8 | "testing" | | 9 | "time" | | 10 | | | 11 | "github.com/FelineStateMachine/tinyrelay/internal/community" | | 12 | "github.com/FelineStateMachine/tinyrelay/internal/event" | | 13 | "github.com/FelineStateMachine/tinyrelay/internal/policy" | | 14 | "github.com/FelineStateMachine/tinyrelay/internal/relay" | | 15 | "github.com/FelineStateMachine/tinyrelay/internal/storage" | | 16 | ) | | 17 | | | 18 | func roomGate(t *testing.T) (*Gate, *community.Service, map[string]string, policy.Policy) { | | 19 | t.Helper() | | 20 | ctx := context.Background() | | 21 | st, err := storage.Open(ctx, t.TempDir()+"/relay.db" ) | | 22 | if err != nil { | | 23 | t.Fatal(err) | | 24 | } | | 25 | t.Cleanup(func() { _ = st.Close() }) | | 26 | secrets := map[string]string{"owner" : strings.Repeat("0" , 63) + "1" , "alice" : strings.Repeat("0" , 63) + "2" , "bob" : strings.Repeat("0" , 63) + "3" , "stranger" : strings.Repeat("0" , 63) + "4" } | | 27 | keys := map[string]string{} | | 28 | for name, secret := range secrets { | | 29 | keys[name], _ = event.PublicKey(secret) | | 30 | } | | 31 | p := policy.Defaults(keys["owner" ]) | | 32 | svc, err := community.New(ctx, st, keys["owner" ]) | | 33 | if err != nil { | | 34 | t.Fatal(err) | | 35 | } | | 36 | svc.ConfigurePolicy(func() policy.Policy { return p }) | | 37 | if err := svc.EnsureSlugRoom(ctx, "main" , community.RoomOpen); err != nil { | | 38 | t.Fatal(err) | | 39 | } | | 40 | for _, name := range []string{"alice" , "bob" } { | | 41 | if _, err := svc.Execute(ctx, keys["owner" ], "setmember" , []json.RawMessage{mustRaw(keys[name]), mustRaw(map[string]any{"role" : "member" })}); err != nil { | | 42 | t.Fatal(err) | | 43 | } | | 44 | } | | 45 | sign := func(who string, kind int, tags [][]string) event.Event { | | 46 | e := event.Event{Kind: kind, CreatedAt: time.Now().Unix(), Tags: tags, Content: "x" } | | 47 | if err := event.Sign(&e, secrets[who]); err != nil { | | 48 | t.Fatal(err) | | 49 | } | | 50 | return e | | 51 | } | | 52 | persist := func(e event.Event) func(*sql.Tx) error { | | 53 | return func(tx *sql.Tx) error { | | 54 | _, err := storage.SaveTx(ctx, tx, e, storage.SaveOptions{Now: time.Now().Unix()}) | | 55 | return err | | 56 | } | | 57 | } | | 58 | for _, room := range []struct{ id, access string }{{"pub" , "open" }, {"priv" , "members" }} { | | 59 | e := sign("alice" , event.KIND_CREATE_GROUP, [][]string{{"h" , room.id}, {"visibility" , room.access}}) | | 60 | if _, err := svc.HandleRoomEventTx(ctx, e, persist(e)); err != nil { | | 61 | t.Fatal(err) | | 62 | } | | 63 | } | | 64 | g, err := New(Config{Store: st, Community: svc, Policy: func() policy.Policy { return p }, Slug: "main" }) | | 65 | if err != nil { | | 66 | t.Fatal(err) | | 67 | } | | 68 | keys["secret:alice" ], keys["secret:bob" ], keys["secret:stranger" ], keys["secret:owner" ] = secrets["alice" ], secrets["bob" ], secrets["stranger" ], secrets["owner" ] | | 69 | return g, svc, keys, p | | 70 | } | | 71 | | | 72 | func TestRoomWritesNeedMembership(t *testing.T) { | | 73 | g, _, keys, _ := roomGate(t) | | 74 | ctx := context.Background() | | 75 | now := time.Now().Unix() | | 76 | sign := func(who string, kind int, tags [][]string) event.Event { | | 77 | e := event.Event{Kind: kind, CreatedAt: now, Tags: tags, Content: "x" } | | 78 | if err := event.Sign(&e, keys["secret:" +who]); err != nil { | | 79 | t.Fatal(err) | | 80 | } | | 81 | return e | | 82 | } | | 83 | for _, tc := range []struct { | | 84 | name string | | 85 | e event.Event | | 86 | reject string | | 87 | }{ | | 88 | {"unknown room" , sign("alice" , 9, [][]string{{"h" , "nope" }}), "invalid: unknown room" }, | | 89 | {"room owner posts" , sign("alice" , 9, [][]string{{"h" , "priv" }}), "" }, | | 90 | {"tenant member outside a members-only room" , sign("bob" , 9, [][]string{{"h" , "priv" }}), "restricted:" }, | | 91 | {"tenant owner moderates any room" , sign("owner" , 9, [][]string{{"h" , "priv" }}), "" }, | | 92 | {"tenant member in an open room" , sign("bob" , 9, [][]string{{"h" , "pub" }}), "" }, | | 93 | {"stranger in an open room" , sign("stranger" , 9, [][]string{{"h" , "pub" }}), "restricted:" }, | | 94 | {"reaction in an open room" , sign("bob" , 7, [][]string{{"h" , "pub" }, {"e" , strings.Repeat("a" , 64)}}), "" }, | | 95 | {"rich content in an open room" , sign("bob" , event.KIND_RICH_CONTENT, [][]string{{"h" , "pub" }}), "" }, | | 96 | {"presence from a member" , sign("bob" , event.KIND_ROOM_PRESENCE, [][]string{{"h" , "pub" }}), "" }, | | 97 | {"presence from a stranger" , sign("stranger" , event.KIND_ROOM_TYPING, [][]string{{"h" , "pub" }}), "restricted:" }, | | 98 | {"member notices are relay signed" , sign("alice" , event.KIND_ROOM_MEMBER_ADDED, [][]string{{"h" , "pub" }, {"p" , keys["bob" ]}}), "blocked:" }, | | 99 | {"room creation names a new room" , sign("bob" , event.KIND_CREATE_GROUP, [][]string{{"h" , "fresh" }}), "" }, | | 100 | {"join request reaches the room handler" , sign("stranger" , event.KIND_JOIN, [][]string{{"h" , "pub" }}), "" }, | | 101 | {"slug room keeps the open write rule" , sign("stranger" , 9, [][]string{{"h" , "main" }}), "" }, | | 102 | {"no h tag is outside rooms" , sign("stranger" , 9, nil), "" }, | | 103 | } { | | 104 | t.Run(tc.name, func(t *testing.T) { | | 105 | err := g.Write(ctx, tc.e, relay.Session{PubKeys: []string{tc.e.PubKey}}, now) | | 106 | if tc.reject == "" && err != nil { | | 107 | t.Fatalf("rejected: %v" , err) | | 108 | } | | 109 | if tc.reject != "" && (err == nil || !strings.HasPrefix(err.Error(), tc.reject)) { | | 110 | t.Fatalf("want %q, got %v" , tc.reject, err) | | 111 | } | | 112 | }) | | 113 | } | | 114 | if err := g.Import(ctx, sign("alice" , 9, [][]string{{"h" , "gone" }}), now); err == nil { | | 115 | t.Fatal("import accepted an unknown room" ) | | 116 | } | | 117 | if err := g.Import(ctx, sign("stranger" , 9, [][]string{{"h" , "priv" }}), now); err != nil { | | 118 | t.Fatalf("import applies room existence only: %v" , err) | | 119 | } | | 120 | } | | 121 | | | 122 | func TestRoomReadsFollowRoomAccess(t *testing.T) { | | 123 | g, _, keys, _ := roomGate(t) | | 124 | ctx := context.Background() | | 125 | private := event.Event{Kind: 9, PubKey: keys["alice" ], CreatedAt: 1, Tags: [][]string{{"h" , "priv" }}, Content: "secret" } | | 126 | public := event.Event{Kind: 9, PubKey: keys["alice" ], CreatedAt: 1, Tags: [][]string{{"h" , "pub" }}, Content: "open" } | | 127 | members := event.Event{Kind: event.KIND_GROUP_MEMBERS, PubKey: strings.Repeat("f" , 64), CreatedAt: 1, Tags: [][]string{{"-" }, {"d" , "priv" }, {"p" , keys["alice" ]}}} | | 128 | main := event.Event{Kind: 9, PubKey: keys["alice" ], CreatedAt: 1, Tags: [][]string{{"h" , "main" }}, Content: "main" } | | 129 | for _, tc := range []struct { | | 130 | name string | | 131 | e event.Event | | 132 | who []string | | 133 | want bool | | 134 | }{ | | 135 | {"member sees a members-only room" , private, []string{keys["alice" ]}, true}, | | 136 | {"tenant owner sees every room" , private, []string{keys["owner" ]}, true}, | | 137 | {"tenant member outside the room does not" , private, []string{keys["bob" ]}, false}, | | 138 | {"anonymous does not" , private, nil, false}, | | 139 | {"member list of a members-only room is hidden" , members, []string{keys["bob" ]}, false}, | | 140 | {"member list is served to members" , members, []string{keys["alice" ]}, true}, | | 141 | {"open rooms follow the tenant read rule" , public, nil, true}, | | 142 | {"slug room is unchanged" , main, nil, true}, | | 143 | } { | | 144 | t.Run(tc.name, func(t *testing.T) { | | 145 | if got := g.CanSee(ctx, tc.e, relay.Session{PubKeys: tc.who}, nil); got != tc.want { | | 146 | t.Fatalf("visible = %v, want %v" , got, tc.want) | | 147 | } | | 148 | }) | | 149 | } | | 150 | hint, err := g.Read(ctx, []event.Filter{{Kinds: []int{9}, Tags: map[string][]string{"h" : {"priv" }}}}, relay.Session{}) | | 151 | if err != nil || !hint { | | 152 | t.Fatalf("members-only room read hint = %v %v" , hint, err) | | 153 | } | | 154 | hint, err = g.Read(ctx, []event.Filter{{Kinds: []int{9}, Tags: map[string][]string{"h" : {"pub" }}}}, relay.Session{}) | | 155 | if err != nil || hint { | | 156 | t.Fatalf("open room read hint = %v %v" , hint, err) | | 157 | } | | 158 | } | | 159 | | | 160 | func mustRaw(v any) json.RawMessage { | | 161 | b, _ := json.Marshal(v) | | 162 | return b | | 163 | } |
|
|
About| owner | c01503e8035f |
|---|
| visibility | public |
|---|
| head | d730679639bf |
|---|
Maintainers- c01503e8035f owner
- ba1b5beed5b5 agent hermes
Clonehttps://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git
|