tinyrelayfile
tinyrelay/internal/daemon/session_test.go/

internal/daemon/session_test.go raw
1package daemon
2
3import (
4 "context"
5 "net/http"
6 "net/http/httptest"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/FelineStateMachine/tinyrelay/internal/event"
12 "github.com/FelineStateMachine/tinyrelay/internal/storage"
13)
14
15func testTenant(t *testing.T) (*App, *Tenant) {
16 t.Helper()
17 ctx := context.Background()
18 a, err := New(ctx, Config{DataDir: t.TempDir(), DefaultTenant: "main"})
19 if err != nil {
20 t.Fatal(err)
21 }
22 t.Cleanup(func() {
23 if err := a.Close(ctx); err != nil {
24 t.Error(err)
25 }
26 })
27 pk, err := event.PublicKey(strings.Repeat("0", 63) + "1")
28 if err != nil {
29 t.Fatal(err)
30 }
31 meta, err := a.Create(ctx, CreateOptions{Name: "main", Owner: pk, Template: "default"})
32 if err != nil {
33 t.Fatal(err)
34 }
35 tenant, err := a.tenant(ctx, meta, "http://relay.test")
36 if err != nil {
37 t.Fatal(err)
38 }
39 return a, tenant
40}
41
42func TestSignedSessionSurvivesNavigationAndRevokes(t *testing.T) {
43 a, tenant := testTenant(t)
44 req := httptest.NewRequest("POST", "http://relay.test/session", strings.NewReader(""))
45 signRequest(t, req, "")
46 res := httptest.NewRecorder()
47 a.ServeHTTP(res, req)
48 if res.Code != 200 {
49 t.Fatalf("login %d: %s", res.Code, res.Body.String())
50 }
51 cookies := res.Result().Cookies()
52 if len(cookies) != 1 || !cookies[0].HttpOnly || cookies[0].SameSite != http.SameSiteStrictMode {
53 t.Fatalf("session cookie %+v", cookies)
54 }
55 page := httptest.NewRequest("GET", "http://relay.test/manage", nil)
56 page.AddCookie(cookies[0])
57 actor, err := tenant.resolveUIActor(page)
58 if err != nil || actor != tenant.Policy().Owner {
59 t.Fatalf("session actor %s %v", actor, err)
60 }
61 // Reads through the query bridge use the session when the request comes
62 // from this origin; foreign origins and publishing still need a signature.
63 query := func(path, origin string) int {
64 req := httptest.NewRequest("POST", "http://relay.test"+path, strings.NewReader(`[{"kinds":[10318],"limit":1}]`))
65 req.Header.Set("Content-Type", "application/json")
66 if origin != "" {
67 req.Header.Set("Origin", origin)
68 }
69 req.AddCookie(cookies[0])
70 res := httptest.NewRecorder()
71 a.ServeHTTP(res, req)
72 return res.Code
73 }
74 if code := query("/query", "http://relay.test"); code != 200 {
75 t.Fatalf("session query %d", code)
76 }
77 if code := query("/query", "https://foreign.test"); code != 401 {
78 t.Fatalf("foreign origin query %d", code)
79 }
80 if code := query("/query", ""); code != 401 {
81 t.Fatalf("query without origin %d", code)
82 }
83 if code := query("/events", "http://relay.test"); code != 401 {
84 t.Fatalf("cookie publish %d", code)
85 }
86 logout := httptest.NewRequest("POST", "http://relay.test/session/logout", nil)
87 logout.AddCookie(cookies[0])
88 logout.Header.Set("Origin", "https://foreign.test")
89 denied := httptest.NewRecorder()
90 a.ServeHTTP(denied, logout)
91 if denied.Code != 403 {
92 t.Fatal("foreign logout accepted")
93 }
94 logout.Header.Set("Origin", "http://relay.test")
95 ok := httptest.NewRecorder()
96 a.ServeHTTP(ok, logout)
97 if ok.Code != 200 {
98 t.Fatalf("logout %d", ok.Code)
99 }
100 actor, err = tenant.resolveUIActor(page)
101 if err != nil || actor != "" {
102 t.Fatalf("revoked session %s %v", actor, err)
103 }
104}
105
106func TestChatPreferencesArePrivateAndDurablePerAccount(t *testing.T) {
107 a, tenant := testTenant(t)
108 login := httptest.NewRequest(http.MethodPost, "http://relay.test/session", strings.NewReader(""))
109 signRequest(t, login, "")
110 loginResult := httptest.NewRecorder()
111 a.ServeHTTP(loginResult, login)
112 if loginResult.Code != http.StatusOK {
113 t.Fatalf("login: %d %s", loginResult.Code, loginResult.Body.String())
114 }
115 cookie := loginResult.Result().Cookies()[0]
116 read := httptest.NewRequest(http.MethodGet, "http://relay.test/chat/preferences", nil)
117 read.AddCookie(cookie)
118 result := httptest.NewRecorder()
119 a.ServeHTTP(result, read)
120 if result.Code != http.StatusOK || result.Body.String() != `{"share_presence":false}` {
121 t.Fatalf("default preference: %d %s", result.Code, result.Body.String())
122 }
123 update := httptest.NewRequest(http.MethodPost, "http://relay.test/chat/preferences", strings.NewReader(`{"share_presence":true}`))
124 signRequest(t, update, `{"share_presence":true}`)
125 updated := httptest.NewRecorder()
126 a.ServeHTTP(updated, update)
127 if updated.Code != http.StatusOK || updated.Body.String() != `{"share_presence":true}` {
128 t.Fatalf("updated preference: %d %s", updated.Code, updated.Body.String())
129 }
130 if !tenant.chatSharePresence(context.Background(), tenant.Policy().Owner) {
131 t.Fatal("preference was not persisted")
132 }
133 reloaded := httptest.NewRequest(http.MethodGet, "http://relay.test/chat/preferences", nil)
134 reloaded.AddCookie(cookie)
135 reloadedResult := httptest.NewRecorder()
136 a.ServeHTTP(reloadedResult, reloaded)
137 if reloadedResult.Code != http.StatusOK || reloadedResult.Body.String() != `{"share_presence":true}` {
138 t.Fatalf("reloaded preference: %d %s", reloadedResult.Code, reloadedResult.Body.String())
139 }
140 guest := httptest.NewRecorder()
141 a.ServeHTTP(guest, httptest.NewRequest(http.MethodGet, "http://relay.test/chat/preferences", nil))
142 if guest.Code != http.StatusUnauthorized {
143 t.Fatalf("guest preference: %d", guest.Code)
144 }
145}
146
147func TestTenantMaintenanceUsesPersistedRetention(t *testing.T) {
148 _, tenant := testTenant(t)
149 ctx := context.Background()
150 now := time.Now().Unix()
151 e := event.Event{Kind: 1, CreatedAt: now - 3*86400, Tags: [][]string{}, Content: "aged note"}
152 if err := event.Sign(&e, strings.Repeat("0", 63)+"2"); err != nil {
153 t.Fatal(err)
154 }
155 if _, err := tenant.store.Save(ctx, e, storage.SaveOptions{Now: now}); err != nil {
156 t.Fatal(err)
157 }
158 if _, err := tenant.store.DB().ExecContext(ctx, "INSERT OR REPLACE INTO community_retention(kind,days) VALUES(1,1)"); err != nil {
159 t.Fatal(err)
160 }
161 if err := tenant.sweep(ctx, now); err != nil {
162 t.Fatal(err)
163 }
164 rows, err := tenant.store.Query(ctx, event.Filter{IDs: []string{e.ID}}, storage.QueryOptions{Now: now})
165 if err != nil || len(rows.Events) != 0 {
166 t.Fatalf("retention did not remove old event: %+v %v", rows, err)
167 }
168}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs