tinyrelayfile
tinyrelay/internal/daemon/views_http_test.go/

internal/daemon/views_http_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 TestNamedViewsRespectTheirOwnAudience(t *testing.T) {
16 app, tenant := testTenant(t)
17 ctx := context.Background()
18 p := tenant.Policy()
19 p.Reads = "open"
20 p.DirectoryPublic = false
21 if err := tenant.applyPolicy(ctx, p); err != nil {
22 t.Fatal(err)
23 }
24 for _, tc := range []struct {
25 name string
26 signed bool
27 status int
28 }{
29 {"articles", false, http.StatusOK},
30 {"profiles", false, http.StatusUnauthorized},
31 {"profiles", true, http.StatusOK},
32 {"missing", false, http.StatusNotFound},
33 } {
34 req := httptest.NewRequest(http.MethodGet, "http://relay.test/r/main/view/"+tc.name, nil)
35 if tc.signed {
36 signRequest(t, req, "")
37 }
38 res := httptest.NewRecorder()
39 app.ServeHTTP(res, req)
40 if res.Code != tc.status {
41 t.Fatalf("%s signed=%v: status %d, %s", tc.name, tc.signed, res.Code, res.Body.String())
42 }
43 if res.Code == http.StatusOK {
44 e, err := event.Parse(res.Body.Bytes())
45 if err != nil || event.Tag(e, "d") != "bind.ws/view/"+tc.name {
46 t.Fatalf("signed view response: %s, %v", res.Body.String(), err)
47 }
48 if res.Header().Get("Cache-Control") != "private, no-store" {
49 t.Fatal("view response permits shared caching")
50 }
51 }
52 }
53 // A members-only projection must not become readable as a stored event.
54 var private int
55 err := tenant.store.DB().QueryRowContext(ctx, `SELECT count(*) FROM events WHERE kind=? AND d=?`, event.KIND_VIEW, "bind.ws/view/profiles").Scan(&private)
56 if err != nil || private != 0 {
57 t.Fatalf("private view persisted: %d, %v", private, err)
58 }
59}
60
61func TestSchedulerWakesForDurableWriteViewDeadline(t *testing.T) {
62 _, tenant := testTenant(t)
63 ctx := context.Background()
64 var first int64
65 waitFor := func(check func() bool) {
66 t.Helper()
67 deadline := time.NewTimer(8 * time.Second)
68 defer deadline.Stop()
69 ticker := time.NewTicker(20 * time.Millisecond)
70 defer ticker.Stop()
71 for !check() {
72 select {
73 case <-deadline.C:
74 t.Fatal("view deadline was not serviced")
75 case <-ticker.C:
76 }
77 }
78 }
79 waitFor(func() bool {
80 return tenant.store.GetSetting(ctx, "records.view.articles.at", &first) == nil && first != 0
81 })
82 e := event.Event{Kind: 30023, CreatedAt: time.Now().Unix(), Tags: [][]string{{"d", "scheduled"}, {"title", "Scheduled article"}}, Content: "body"}
83 if err := event.Sign(&e, strings.Repeat("0", 63)+"1"); err != nil {
84 t.Fatal(err)
85 }
86 if _, err := tenant.store.Save(ctx, e, storage.SaveOptions{Now: time.Now().Unix()}); err != nil {
87 t.Fatal(err)
88 }
89 // Model a durable dirty marker recovered nine seconds into its debounce.
90 if err := tenant.records.MarkView(ctx, "articles", time.Now().Unix()-9); err != nil {
91 t.Fatal(err)
92 }
93 tenant.scheduleViews()
94 waitFor(func() bool {
95 result, err := tenant.store.Query(ctx, event.Filter{Kinds: []int{event.KIND_VIEW}, Tags: map[string][]string{"d": {"bind.ws/view/articles"}}}, storage.QueryOptions{Now: time.Now().Unix(), Access: storage.Access{All: true}})
96 if err != nil || len(result.Events) != 1 {
97 return false
98 }
99 return event.Tag(result.Events[0], "a") == "30023:"+e.PubKey+":scheduled"
100 })
101}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

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

Refs