tinyrelayfile
tinyrelay/tinyclient/people_test.go/

tinyclient/people_test.go raw
1package tinyclient
2
3import (
4 "context"
5 "encoding/json"
6 "net/http"
7 "net/http/httptest"
8 "strings"
9 "testing"
10 "time"
11
12 "github.com/FelineStateMachine/tinyrelay/internal/policy"
13)
14
15// peopleBackend answers listjoinrequests with the rows it holds, pending
16// first as the relay lists them, and refuses callers other than the owner.
17type peopleBackend struct {
18 fakeBackend
19 requests []any
20 calls []string
21}
22
23var (
24 askerPending = strings.Repeat("1", 64)
25 askerQuiet = strings.Repeat("2", 64)
26 askerDenied = strings.Repeat("3", 64)
27)
28
29func (b *peopleBackend) Query(_ context.Context, method string, params []json.RawMessage, actor string) (any, error) {
30 b.calls = append(b.calls, method+":"+actor)
31 if method == "listjoinrequests" {
32 if actor != b.policy.Owner {
33 return nil, context.Canceled
34 }
35 return b.requests, nil
36 }
37 return map[string]any{"method": method}, nil
38}
39
40func TestPeoplePageListsAccessRequestsWithDecisions(t *testing.T) {
41 now := time.Now().Unix()
42 owner := strings.Repeat("a", 64)
43 backend := &peopleBackend{fakeBackend: fakeBackend{policy: policy.Defaults(owner)}, requests: []any{
44 map[string]any{"pubkey": askerPending, "reason": "Met you at the meetup, would like to follow the build.", "requested_at": now - 600, "status": "pending", "decided_by": "", "decided_at": 0},
45 map[string]any{"pubkey": askerQuiet, "reason": "", "requested_at": now - 60, "status": "pending", "decided_by": "", "decided_at": 0},
46 map[string]any{"pubkey": askerDenied, "reason": "spam", "requested_at": now - 7200, "status": "denied", "decided_by": owner, "decided_at": now - 3600},
47 }}
48 app, err := New(backend, Options{Actor: func(*http.Request) (string, error) { return owner, nil }})
49 if err != nil {
50 t.Fatal(err)
51 }
52 recorder := httptest.NewRecorder()
53 app.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/manage/people", nil))
54 body := recorder.Body.String()
55 if recorder.Code != http.StatusOK {
56 t.Fatalf("status=%d body=%s", recorder.Code, body)
57 }
58 if strings.Contains(body, "class=") {
59 t.Fatal("people page carries a class attribute")
60 }
61 if strings.Contains(body, "\u00b7") {
62 t.Fatal("people page contains a middle dot")
63 }
64 if backend.calls[0] != "listjoinrequests:"+owner {
65 t.Fatalf("requests were not loaded server-side: %v", backend.calls)
66 }
67 for _, want := range []string{
68 `<h3>Access requests</h3>`,
69 `<table id="requests"><thead><tr><th>person</th><th>reason</th><th>asked</th><th></th></tr></thead>`,
70 `<tr><td><nostr-name pubkey="` + askerPending + `" title="` + askerPending + `">` + shortID(askerPending) + `</nostr-name></td><td>Met you at the meetup, would like to follow the build.</td><td><time datetime="`,
71 `<rpc-form method="approvejoin" refresh><input type="hidden" name="param" value="&quot;` + askerPending + `&quot;"><button>Approve</button></rpc-form><rpc-form method="denyjoin" refresh><input type="hidden" name="param" value="&quot;` + askerPending + `&quot;"><button>Deny</button></rpc-form></td></tr>`,
72 `<td><small>no reason given</small></td>`,
73 `<rpc-form method="approvejoin" refresh><input type="hidden" name="param" value="&quot;` + askerQuiet + `&quot;"><button>Approve</button></rpc-form>`,
74 `<ul id="decided"><li data-state="denied"><nostr-name pubkey="` + askerDenied + `"`,
75 `<small>denied <time datetime="`,
76 ` by <nostr-name pubkey="` + owner + `"`,
77 `<h4>People</h4><table><tr><th>requests</th><td>2 pending</td></tr></table>`,
78 `<h3>Invites</h3>`,
79 } {
80 if !strings.Contains(body, want) {
81 t.Errorf("people page missing %q", want)
82 }
83 }
84 if strings.Index(body, `<h3>Access requests</h3>`) > strings.Index(body, `<h3>Invites</h3>`) {
85 t.Fatal("access requests are not above invites")
86 }
87 if strings.Contains(body, `value="&quot;`+askerDenied+`&quot;"`) {
88 t.Fatal("a decided request offers buttons")
89 }
90 if strings.Contains(body, "No pending requests.") {
91 t.Fatal("empty state shown with pending requests")
92 }
93
94 // With nothing pending the section says so and the panel counts zero.
95 backend.requests = []any{backend.requests[2]}
96 recorder = httptest.NewRecorder()
97 app.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/manage/people", nil))
98 body = recorder.Body.String()
99 for _, want := range []string{`<h3>Access requests</h3>`, `<p>No pending requests.</p>`, `<ul id="decided">`, `<td>0 pending</td>`} {
100 if !strings.Contains(body, want) {
101 t.Errorf("empty people page missing %q", want)
102 }
103 }
104 if strings.Contains(body, `<table id="requests">`) {
105 t.Fatal("empty people page renders the requests table")
106 }
107
108 // A member who may not review sees neither the section nor the count.
109 memberApp, err := New(backend, Options{Actor: func(*http.Request) (string, error) { return strings.Repeat("c", 64), nil }})
110 if err != nil {
111 t.Fatal(err)
112 }
113 recorder = httptest.NewRecorder()
114 memberApp.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/manage/people", nil))
115 body = recorder.Body.String()
116 if strings.Contains(body, "Access requests") || strings.Contains(body, "pending</td>") {
117 t.Fatal("a member sees the access requests section")
118 }
119 if !strings.Contains(body, `<h3>Invites</h3>`) {
120 t.Fatal("a member lost the rest of the page")
121 }
122}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

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

Refs