clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/tinyagent
tinyrelay / internal / records / rooms_test.go
rooms_test.go 5.2K
1package records23import (4 "context"5 "database/sql"6 "encoding/json"7 "strings"8 "testing"9 "time"1011 "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/storage"15)1617func TestRoomRecordsAndNotices(t *testing.T) {18 s, ctx := testStore(t)19 secrets := map[string]string{"owner": strings.Repeat("0", 63) + "1", "alice": strings.Repeat("0", 63) + "2", "bob": strings.Repeat("0", 63) + "3", "carol": strings.Repeat("0", 63) + "4"}20 keys := map[string]string{}21 for name, secret := range secrets {22 keys[name], _ = event.PublicKey(secret)23 }24 p := policy.Defaults(keys["owner"])25 svc, err := community.New(ctx, s, keys["owner"])26 if err != nil {27 t.Fatal(err)28 }29 svc.ConfigurePolicy(func() policy.Policy { return p })30 if err := svc.EnsureSlugRoom(ctx, "main", community.RoomOpen); err != nil {31 t.Fatal(err)32 }33 raw := func(v any) json.RawMessage { b, _ := json.Marshal(v); return b }34 for _, name := range []string{"alice", "bob", "carol"} {35 if _, err := svc.Execute(ctx, keys["owner"], "setmember", []json.RawMessage{raw(keys[name]), raw(map[string]any{"role": "member"})}); err != nil {36 t.Fatal(err)37 }38 }39 send := func(who string, kind int, tags [][]string) {40 t.Helper()41 e := event.Event{Kind: kind, CreatedAt: time.Now().Unix(), Tags: tags}42 if err := event.Sign(&e, secrets[who]); err != nil {43 t.Fatal(err)44 }45 if _, err := svc.HandleRoomEventTx(ctx, e, func(tx *sql.Tx) error {46 _, err := storage.SaveTx(ctx, tx, e, storage.SaveOptions{Now: time.Now().Unix()})47 return err48 }); err != nil {49 t.Fatal(err)50 }51 }52 send("alice", event.KIND_CREATE_GROUP, [][]string{{"h", "team"}, {"name", "Team"}, {"about", "Ours"}, {"picture", "https://img.example/t.png"}, {"closed"}})53 send("alice", event.KIND_PUT_USER, [][]string{{"h", "team"}, {"p", keys["bob"], "admin"}})54 send("alice", event.KIND_PUT_USER, [][]string{{"h", "team"}, {"p", keys["carol"]}})5556 var generated []event.Event57 r, err := New(ctx, Config{Community: emptyCommunityReader{}, Store: s, Policy: func() policy.Policy { return p }, RelayURL: "wss://relay.example", GroupID: "main", OnGenerated: func(ctx context.Context, e event.Event) error {58 generated = append(generated, e)59 _, err := s.Save(ctx, e, storage.SaveOptions{Now: e.CreatedAt})60 return err61 }})62 if err != nil {63 t.Fatal(err)64 }65 records, err := r.PublishRoom(ctx, "team", 100)66 if err != nil {67 t.Fatal(err)68 }69 if len(records) != 3 || records[0].Kind != event.KIND_GROUP_METADATA || records[1].Kind != event.KIND_GROUP_ADMINS || records[2].Kind != event.KIND_GROUP_MEMBERS {70 t.Fatalf("records = %+v", records)71 }72 meta := records[0]73 if event.Tag(meta, "d") != "team" || event.Tag(meta, "name") != "Team" || event.Tag(meta, "about") != "Ours" || event.Tag(meta, "picture") != "https://img.example/t.png" {74 t.Fatalf("metadata tags = %v", meta.Tags)75 }76 if !hasBareTag(meta, "closed") || hasBareTag(meta, "private") || !hasBareTag(meta, "public") {77 t.Fatalf("access tags = %v", meta.Tags)78 }79 admins := map[string]string{}80 for _, tag := range records[1].Tags {81 if tag[0] == "p" {82 admins[tag[1]] = tag[2]83 }84 }85 if len(admins) != 2 || admins[keys["alice"]] != "owner" || admins[keys["bob"]] != "admin" {86 t.Fatalf("admins = %v", admins)87 }88 if got := event.TagValues(records[2], "p"); len(got) != 3 {89 t.Fatalf("members = %v", got)90 }91 for _, rec := range records {92 if rec.PubKey != r.PublicKey() || event.Validate(rec) != nil {93 t.Fatalf("record not relay-signed: %+v", rec)94 }95 }96 p.Reads = "members"97 records, err = r.PublishRoom(ctx, "team", 101)98 if err != nil || !hasBareTag(records[0], "private") {99 t.Fatalf("private marker missing: %v %v", records[0].Tags, err)100 }101 notice, err := r.RoomNotice(ctx, "team", keys["carol"], true, 102)102 if err != nil || notice.Kind != event.KIND_ROOM_MEMBER_ADDED || event.Tag(notice, "h") != "team" || event.Tag(notice, "p") != keys["carol"] {103 t.Fatalf("added notice = %+v %v", notice, err)104 }105 notice, err = r.RoomNotice(ctx, "team", keys["carol"], false, 103)106 if err != nil || notice.Kind != event.KIND_ROOM_MEMBER_REMOVED {107 t.Fatalf("removed notice = %+v %v", notice, err)108 }109 stored, err := s.Query(ctx, event.Filter{Kinds: []int{event.KIND_GROUP_METADATA, event.KIND_GROUP_ADMINS, event.KIND_GROUP_MEMBERS}, Authors: []string{r.PublicKey()}, Tags: map[string][]string{"d": {"team"}}}, storage.QueryOptions{Access: storage.Access{All: true}})110 if err != nil || len(stored.Events) != 3 {111 t.Fatalf("stored records = %d %v", len(stored.Events), err)112 }113 if err := r.RetireRoom(ctx, "team"); err != nil {114 t.Fatal(err)115 }116 stored, err = s.Query(ctx, event.Filter{Kinds: []int{event.KIND_GROUP_METADATA}, Authors: []string{r.PublicKey()}, Tags: map[string][]string{"d": {"team"}}}, storage.QueryOptions{Access: storage.Access{All: true}})117 if err != nil || len(stored.Events) != 0 {118 t.Fatalf("retired records remain: %d %v", len(stored.Events), err)119 }120 if len(generated) < 8 {121 t.Fatalf("generated %d events", len(generated))122 }123}124125func hasBareTag(e event.Event, name string) bool {126 for _, tag := range e.Tags {127 if len(tag) == 1 && tag[0] == name {128 return true129 }130 }131 return false132}