tinyrelayfile
tinyrelay/internal/daemon/daemon_test.go/

internal/daemon/daemon_test.go raw
1package daemon
2
3import (
4 "context"
5 "crypto/sha256"
6 "encoding/base64"
7 "encoding/hex"
8 "encoding/json"
9 "net/http"
10 "net/http/httptest"
11 "strings"
12 "testing"
13 "time"
14
15 "github.com/FelineStateMachine/tinyrelay/internal/blob"
16 "github.com/FelineStateMachine/tinyrelay/internal/event"
17)
18
19func TestTenantIsolationAndDurableHTTPPublish(t *testing.T) {
20 ctx := context.Background()
21 app, err := New(ctx, Config{DataDir: t.TempDir(), DefaultTenant: "alice"})
22 if err != nil {
23 t.Fatal(err)
24 }
25 t.Cleanup(func() {
26 if err := app.Close(ctx); err != nil {
27 t.Error(err)
28 }
29 })
30 owner, err := event.PublicKey(strings.Repeat("0", 63) + "1")
31 if err != nil {
32 t.Fatal(err)
33 }
34 for _, name := range []string{"alice", "bob"} {
35 if _, err := app.Create(ctx, CreateOptions{Name: name, Owner: owner, Template: "default"}); err != nil {
36 t.Fatal(err)
37 }
38 }
39 e := event.Event{Kind: 1, CreatedAt: time.Now().Unix(), Tags: [][]string{}, Content: "hello tiny"}
40 if err := event.Sign(&e, strings.Repeat("0", 63)+"1"); err != nil {
41 t.Fatal(err)
42 }
43 raw, err := json.Marshal(e)
44 if err != nil {
45 t.Fatal(err)
46 }
47 request := httptest.NewRequest(http.MethodPost, "http://relay.test/r/alice/events", strings.NewReader(string(raw)))
48 signRequest(t, request, string(raw))
49 response := httptest.NewRecorder()
50 app.ServeHTTP(response, request)
51 if response.Code != 200 {
52 t.Fatalf("publish %d %s", response.Code, response.Body.String())
53 }
54 for _, tc := range []struct {
55 name string
56 count int
57 }{{"alice", 1}, {"bob", 0}} {
58 request := httptest.NewRequest(http.MethodPost, "http://relay.test/r/"+tc.name+"/query", strings.NewReader(`[{"kinds":[1]}]`))
59 signRequest(t, request, `[{"kinds":[1]}]`)
60 response := httptest.NewRecorder()
61 app.ServeHTTP(response, request)
62 var events []event.Event
63 if err := json.Unmarshal(response.Body.Bytes(), &events); err != nil {
64 t.Fatalf("query %s: %s %v", tc.name, response.Body.String(), err)
65 }
66 if len(events) != tc.count {
67 t.Fatalf("%s: got%d want%d", tc.name, len(events), tc.count)
68 }
69 }
70}
71
72func signRequest(t *testing.T, r *http.Request, body string) {
73 t.Helper()
74 hash := sha256.Sum256([]byte(body))
75 tags := [][]string{{"u", r.URL.String()}, {"method", r.Method}, {"payload", hex.EncodeToString(hash[:])}}
76 if i := strings.Index(r.URL.Path, ".git/"); i >= 0 {
77 tags = [][]string{{"u", r.URL.Scheme + "://" + r.URL.Host + r.URL.Path[:i+4]}, {"method", http.MethodGet}}
78 }
79 e := event.Event{Kind: 27235, CreatedAt: time.Now().Unix(), Content: "", Tags: tags}
80 if err := event.Sign(&e, strings.Repeat("0", 63)+"1"); err != nil {
81 t.Fatal(err)
82 }
83 raw, err := json.Marshal(e)
84 if err != nil {
85 t.Fatal(err)
86 }
87 r.Header.Set("Authorization", "Nostr "+base64.StdEncoding.EncodeToString(raw))
88}
89
90func TestBUD13RemoteNIP98AcceptsAbsentOrEmptyPayloadOnly(t *testing.T) {
91 _, tenant := testTenant(t)
92 sha := strings.Repeat("a", 64)
93 empty := sha256.Sum256(nil)
94 for name, payload := range map[string]string{"absent": "", "empty": hex.EncodeToString(empty[:]), "wrong": strings.Repeat("b", 64)} {
95 t.Run(name, func(t *testing.T) {
96 req := httptest.NewRequest(http.MethodPut, "http://relay.test/"+sha+"?url=https%3A%2F%2Forigin.example%2Fblob", http.NoBody)
97 tags := [][]string{{"u", req.URL.String()}, {"method", "PUT"}}
98 if payload != "" {
99 tags = append(tags, []string{"payload", payload})
100 }
101 e := event.Event{Kind: 27235, CreatedAt: time.Now().Unix(), Tags: tags}
102 if err := event.Sign(&e, strings.Repeat("0", 63)+"1"); err != nil {
103 t.Fatal(err)
104 }
105 raw, _ := json.Marshal(e)
106 req.Header.Set("Authorization", "Nostr "+base64.StdEncoding.EncodeToString(raw))
107 _, err := tenant.authorizeBlob(req, blob.ActionUpload)
108 if name == "wrong" && err == nil {
109 t.Fatal("accepted wrong empty-body payload")
110 }
111 if name != "wrong" && err != nil {
112 t.Fatalf("remote authorization: %v", err)
113 }
114 })
115 }
116}
117
118func TestCreationRequiresOwnerAndInformationHasNoEconomy(t *testing.T) {
119 ctx := context.Background()
120 app, err := New(ctx, Config{DataDir: t.TempDir(), DefaultTenant: "main"})
121 if err != nil {
122 t.Fatal(err)
123 }
124 t.Cleanup(func() {
125 if err := app.Close(ctx); err != nil {
126 t.Error(err)
127 }
128 })
129 if _, err := app.Create(ctx, CreateOptions{Name: "unowned", Template: "default"}); err == nil {
130 t.Fatal("unowned relay allowed")
131 }
132 owner, err := event.PublicKey(strings.Repeat("0", 63) + "1")
133 if err != nil {
134 t.Fatal(err)
135 }
136 if _, err := app.Create(ctx, CreateOptions{Name: "main", Owner: owner, Template: "default"}); err != nil {
137 t.Fatal(err)
138 }
139 req := httptest.NewRequest(http.MethodGet, "http://relay.test/", nil)
140 req.Header.Set("Accept", "application/nostr+json")
141 res := httptest.NewRecorder()
142 app.ServeHTTP(res, req)
143 if res.Code != 200 {
144 t.Fatalf("info %d %s", res.Code, res.Body.String())
145 }
146 var info map[string]json.RawMessage
147 if err := json.Unmarshal(res.Body.Bytes(), &info); err != nil {
148 t.Fatal(err)
149 }
150 for _, key := range []string{"fees", "payments_url", "fuel", "lease"} {
151 if _, ok := info[key]; ok {
152 t.Fatalf("hosted economy leaked: %s", key)
153 }
154 }
155}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

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

Refs