tinyrelayfile
tinyrelay/internal/daemon/client_browse_test.go/

internal/daemon/client_browse_test.go raw
1package daemon
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "testing"
11
12 "github.com/FelineStateMachine/tinyrelay/internal/blob"
13)
14
15func TestClientFileBrowsingPermissionsAndDownload(t *testing.T) {
16 _, tenant := testTenant(t)
17 ctx := context.Background()
18 entry, err := tenant.blobs.Put(ctx, blob.PutOptions{Reader: strings.NewReader("<script>secret source</script>"), Type: "text/html", Uploader: tenant.Policy().Owner})
19 if err != nil {
20 t.Fatal(err)
21 }
22 if _, err := tenant.Execute(ctx, "", "browsefiles", nil); err == nil {
23 t.Fatal("anonymous uploader inventory exposed")
24 }
25 value, err := tenant.Execute(ctx, tenant.Policy().Owner, "browsefiles", nil)
26 if err != nil {
27 t.Fatal(err)
28 }
29 raw, _ := json.Marshal(value)
30 if !strings.Contains(string(raw), entry.SHA256) {
31 t.Fatalf("missing blob %s", raw)
32 }
33 req := httptest.NewRequest(http.MethodGet, "http://relay.test/files/raw?hash="+entry.SHA256, nil)
34 res := httptest.NewRecorder()
35 tenant.ServeHTTP(res, req)
36 if res.Code != 200 || res.Body.String() != "<script>secret source</script>" {
37 t.Fatalf("download %d %s", res.Code, res.Body.String())
38 }
39 if !strings.HasPrefix(res.Header().Get("Content-Disposition"), "attachment") || res.Header().Get("Content-Security-Policy") != "sandbox" {
40 t.Fatal("active download lacks isolation")
41 }
42 p := tenant.Policy()
43 p.Reads = "members"
44 if err := tenant.applyPolicy(ctx, p); err != nil {
45 t.Fatal(err)
46 }
47 res = httptest.NewRecorder()
48 tenant.ServeHTTP(res, req)
49 if res.Code == 200 {
50 t.Fatal("private download exposed")
51 }
52}
53
54func TestClientStatusRequiresOwnerAndPreservesErrors(t *testing.T) {
55 _, tenant := testTenant(t)
56 if _, err := tenant.Execute(context.Background(), "", "browsestatus", nil); err == nil {
57 t.Fatal("anonymous operational status exposed")
58 }
59 value, err := tenant.Execute(context.Background(), tenant.Policy().Owner, "browsestatus", nil)
60 if err != nil {
61 t.Fatal(err)
62 }
63 raw, _ := json.Marshal(value)
64 for _, field := range []string{"capabilities", "git_sync", "storage", "jobs", "delivery", "backups"} {
65 if !strings.Contains(string(raw), `"`+field+`"`) {
66 t.Fatalf("missing %s: %s", field, raw)
67 }
68 }
69}
70
71func TestFileSearchPaginationPreservesEveryUploaderMatch(t *testing.T) {
72 _, tenant := testTenant(t)
73 ctx := context.Background()
74 uploader := strings.Repeat("b", 64)
75 var want []string
76 for i, typ := range []string{"text/plain", "image/png", "text/plain", "text/plain", "text/plain"} {
77 entry, err := tenant.blobs.Put(ctx, blob.PutOptions{Reader: strings.NewReader(fmt.Sprintf("file %d", i)), Type: typ, Uploader: uploader})
78 if err != nil {
79 t.Fatal(err)
80 }
81 if _, err := tenant.store.DB().ExecContext(ctx, "UPDATE blobs SET uploaded=? WHERE sha256=?", 100-i, entry.SHA256); err != nil {
82 t.Fatal(err)
83 }
84 if typ == "text/plain" {
85 want = append(want, entry.SHA256)
86 }
87 }
88 if _, err := tenant.blobs.Put(ctx, blob.PutOptions{Reader: strings.NewReader("another user's matching file"), Type: "text/plain", Uploader: tenant.Policy().Owner}); err != nil {
89 t.Fatal(err)
90 }
91 var got []string
92 cursor := ""
93 for page := 0; page < 5; page++ {
94 params, err := json.Marshal(map[string]any{"q": "text/plain", "limit": 2, "cursor": cursor})
95 if err != nil {
96 t.Fatal(err)
97 }
98 result, err := tenant.Execute(ctx, uploader, "browsefiles", []json.RawMessage{params})
99 if err != nil {
100 t.Fatal(err)
101 }
102 row := result.(map[string]any)
103 for _, item := range row["items"].([]map[string]any) {
104 got = append(got, item["sha256"].(string))
105 }
106 cursor = row["next_cursor"].(string)
107 if cursor == "" {
108 break
109 }
110 }
111 if fmt.Sprint(got) != fmt.Sprint(want) {
112 t.Fatalf("matching files = %v, want %v", got, want)
113 }
114}
115
116func TestOwnerFileSearchUsesLiteralTextBeforePagination(t *testing.T) {
117 _, tenant := testTenant(t)
118 ctx := context.Background()
119 for i, typ := range []string{"text/plain", "application/x_foo", "image/png"} {
120 if _, err := tenant.blobs.Put(ctx, blob.PutOptions{Reader: strings.NewReader(fmt.Sprintf("owner file %d", i)), Type: typ, Uploader: tenant.Policy().Owner}); err != nil {
121 t.Fatal(err)
122 }
123 }
124 result, err := tenant.Execute(ctx, tenant.Policy().Owner, "browsefiles", []json.RawMessage{json.RawMessage(`{"q":"_","limit":1}`)})
125 if err != nil {
126 t.Fatal(err)
127 }
128 row := result.(map[string]any)
129 items := row["items"].([]map[string]any)
130 if len(items) != 1 || items[0]["type"] != "application/x_foo" || row["next_cursor"] != "" {
131 t.Fatalf("literal search = %#v", row)
132 }
133}
134
135func TestLibraryRowsGroupsLegacyUnnamedBinaryAndHidesMarkedChunks(t *testing.T) {
136 rows := []map[string]any{
137 {"sha256": strings.Repeat("a", 64), "name": "", "path": "", "type": "application/octet-stream"},
138 {"sha256": strings.Repeat("b", 64), "name": "", "path": "", "type": "application/octet-stream", "purpose": "chunk"},
139 {"sha256": strings.Repeat("c", 64), "name": "notes.txt", "path": "notes.txt", "type": "text/plain"},
140 }
141 got := libraryRows(rows, nil)
142 if len(got) != 2 {
143 t.Fatalf("library rows = %d, want 2", len(got))
144 }
145 if got[0]["path"] != "Unorganized uploads/aaaaaaaaaaaa" || got[0]["name"] != "aaaaaaaaaaaa" {
146 t.Fatalf("legacy row = %#v", got[0])
147 }
148 if got[1]["path"] != "notes.txt" {
149 t.Fatalf("named row changed = %#v", got[1])
150 }
151}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

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

Refs