clone

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

feat/ui-updates
tinyrelay / internal / daemon / chat_activity_test.go
chat_activity_test.go 4.3K
1package daemon23import (4 "encoding/json"5 "github.com/FelineStateMachine/tinyrelay/internal/event"6 "testing"7)89func TestChatActivitySharesApprovalStateAndScopesRooms(t *testing.T) {10 h := newRoomHarness(t)11 request := h.must("alice", 5000, [][]string{{"h", "main"}, {"p", h.keys["bob"]}, {"subject", "Build preview"}}, "Build preview")12 h.must("bob", 7000, [][]string{{"h", "main"}, {"e", request.ID}, {"p", h.keys["alice"]}, {"status", "processing", "Running checks"}}, "2 of 3 checks complete")13 decision := h.must("alice", 9, [][]string{{"h", "main"}, {"request", "approve"}, {"p", h.keys["bob"]}, {"subject", "Publish preview"}}, "Publish it?")14 read := func(who, room string) map[string]any {15 t.Helper()16 raw, err := h.tenant.Execute(h.ctx, h.keys[who], "browsechatactivity", []json.RawMessage{rawJSON(map[string]string{"room": room})})17 if err != nil {18 t.Fatal(err)19 }20 return raw.(map[string]any)21 }22 data := read("bob", "main")23 jobs := data["jobs"].([]jobItem)24 if len(jobs) != 1 || jobs[0].Status != "processing" {25 t.Fatalf("jobs=%v", jobs)26 }27 approvals := data["approvals"].([]approvalItem)28 if len(approvals) != 1 || approvals[0].State != "open" {29 t.Fatalf("approvals=%v", approvals)30 }31 h.must("bob", 7, [][]string{{"h", "main"}, {"e", decision.ID}, {"p", h.keys["alice"]}}, "+")32 data = read("bob", "main")33 if data["approvals"].([]approvalItem)[0].State != "answered" {34 t.Fatal("inline approval not settled")35 }36 detail, err := h.tenant.Execute(h.ctx, h.keys["bob"], "browseapproval", []json.RawMessage{rawJSON(map[string]string{"id": decision.ID})})37 if err != nil {38 t.Fatal(err)39 }40 if detail.(map[string]any)["item"].(approvalItem).State != "answered" {41 t.Fatal("Approvals disagrees")42 }43 h.must("alice", event.KIND_CREATE_GROUP, [][]string{{"h", "private-chat"}, {"name", "Private"}, {"visibility", "members"}}, "")44 private := h.must("alice", 5000, [][]string{{"h", "private-chat"}, {"p", h.keys["alice"]}}, "Private work")45 if _, err := h.tenant.Execute(h.ctx, h.keys["bob"], "browsechatactivity", []json.RawMessage{rawJSON(map[string]string{"room": "private-chat"})}); err == nil {46 t.Fatal("outsider read private activity")47 }48 for _, job := range read("bob", "main")["jobs"].([]jobItem) {49 if job.ID == private.ID {50 t.Fatal("private job leaked")51 }52 }53 if _, err := h.publish("alice", 7000, [][]string{{"e", private.ID}, {"p", h.keys["alice"]}, {"status", "processing"}}, "private progress"); err == nil {54 t.Fatal("unscoped progress accepted for a private room task")55 }56}57func TestChatPresenceIsEphemeralAndRoomAuthorized(t *testing.T) {58 h := newRoomHarness(t)59 h.must("alice", event.KIND_CREATE_GROUP, [][]string{{"h", "private-chat"}, {"visibility", "members"}}, "")60 for _, kind := range []int{20001, 20002} {61 e := h.must("alice", kind, [][]string{{"h", "private-chat"}}, "typing")62 if rows := h.query("alice", event.Filter{IDs: []string{e.ID}}); len(rows) != 0 {63 t.Fatal("presence persisted")64 }65 if _, err := h.publish("bob", kind, [][]string{{"h", "private-chat"}}, "typing"); err == nil {66 t.Fatal("outsider presence accepted")67 }68 }69}7071func TestChatActivityIncludesParentOnlyRequestsInCanonicalThread(t *testing.T) {72 h := newRoomHarness(t)73 root := h.must("alice", 9, [][]string{{"h", "main"}}, "Root")74 parent := h.must("bob", 9, [][]string{{"h", "main"}, {"e", root.ID, "", "reply"}}, "Parent")75 approval := h.must("alice", 9, [][]string{{"h", "main"}, {"e", parent.ID, "", "reply"}, {"request", "approve"}, {"p", h.keys["bob"]}}, "Approve the nested action?")76 job := h.must("alice", 5000, [][]string{{"h", "main"}, {"e", parent.ID}, {"p", h.keys["bob"]}}, "Nested job")77 other := h.must("alice", 9, [][]string{{"h", "main"}}, "Another root")78 h.must("alice", 9, [][]string{{"h", "main"}, {"e", other.ID, "", "root"}, {"request", "approve"}, {"p", h.keys["bob"]}}, "Unrelated action")79 raw, err := h.tenant.Execute(h.ctx, h.keys["bob"], "browsechatactivity", []json.RawMessage{rawJSON(map[string]string{"room": "main", "root": root.ID})})80 if err != nil {81 t.Fatal(err)82 }83 data := raw.(map[string]any)84 approvals, jobs := data["approvals"].([]approvalItem), data["jobs"].([]jobItem)85 if len(approvals) != 1 || approvals[0].ID != approval.ID || len(jobs) != 1 || jobs[0].ID != job.ID {86 t.Fatalf("activity omitted nested request or included another thread: %v", data)87 }88}