tinyrelayfile
tinyrelay/tinygit/browse_test.go/

tinygit/browse_test.go raw
1package tinygit
2
3import (
4 "bytes"
5 "context"
6 "os"
7 "os/exec"
8 "path/filepath"
9 "strings"
10 "testing"
11)
12
13func browseFixture(t *testing.T) (*GitRelay, Repository, string) {
14 t.Helper()
15 root := t.TempDir()
16 r := Repository{Owner: strings.Repeat("a", 64), Identifier: "browse", Head: "refs/heads/main"}
17 g := &GitRelay{root: root, repos: map[string]Repository{}}
18 dir := g.repoPath(r)
19 if err := os.MkdirAll(dir, 0700); err != nil {
20 t.Fatal(err)
21 }
22 run := func(args ...string) string {
23 t.Helper()
24 c := exec.Command("git", append([]string{"-C", dir}, args...)...)
25 c.Env = append(os.Environ(), "GIT_AUTHOR_NAME=Browser Test", "GIT_AUTHOR_EMAIL=test@example.test", "GIT_COMMITTER_NAME=Browser Test", "GIT_COMMITTER_EMAIL=test@example.test")
26 b, err := c.CombinedOutput()
27 if err != nil {
28 t.Fatalf("git %v: %v %s", args, err, b)
29 }
30 return strings.TrimSpace(string(b))
31 }
32 run("init", "-b", "main")
33 for name, body := range map[string]string{"source.go": "package main\n// <script>alert(1)</script>\n", "[literal]*.txt": "literal path", "large.txt": strings.Repeat("x", 300*1024), "binary.bin": "a\x00b"} {
34 if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0600); err != nil {
35 t.Fatal(err)
36 }
37 }
38 run("add", ".")
39 run("commit", "-m", "first source")
40 first := run("rev-parse", "HEAD")
41 if err := os.WriteFile(filepath.Join(dir, "source.go"), []byte("package main\n// second\n"), 0600); err != nil {
42 t.Fatal(err)
43 }
44 run("commit", "-am", "second source")
45 r.Refs = map[string]string{"refs/heads/main": run("rev-parse", "HEAD")}
46 g.repos[key(r.Owner, r.Identifier)] = r
47 // Browsing normally uses a bare repository; this fixture switches --git-dir to .git.
48 g.root = filepath.Join(root, "bare")
49 if err := os.MkdirAll(filepath.Dir(g.repoPath(r)), 0700); err != nil {
50 t.Fatal(err)
51 }
52 cmd := exec.Command("git", "clone", "--bare", dir, g.repoPath(r))
53 if b, err := cmd.CombinedOutput(); err != nil {
54 t.Fatalf("bare clone: %v %s", err, b)
55 }
56 return g, r, first
57}
58
59func TestBrowseRepositorySourceAndHistory(t *testing.T) {
60 g, r, first := browseFixture(t)
61 ctx := context.Background()
62 q := BrowseRequest{Owner: r.Owner, Repo: r.Identifier}
63 page, err := g.Browse(ctx, q)
64 if err != nil || len(page.Entries) != 4 {
65 t.Fatalf("tree: %+v %v", page, err)
66 }
67 q.View, q.Path, q.Ref = "file", "[literal]*.txt", first
68 page, err = g.Browse(ctx, q)
69 if err != nil || page.Content != "literal path" {
70 t.Fatalf("literal: %+v %v", page, err)
71 }
72 q.Path = "source.go"
73 page, err = g.Browse(ctx, q)
74 if err != nil || !strings.Contains(page.Content, "<script>") {
75 t.Fatalf("historical source: %+v %v", page, err)
76 }
77 q.View, q.Ref, q.Path, q.Limit = "history", "main", "", 1
78 page, err = g.Browse(ctx, q)
79 if err != nil || len(page.Commits) != 1 || page.NextOffset != 1 {
80 t.Fatalf("history: %+v %v", page, err)
81 }
82 q.View = "commit"
83 page, err = g.Browse(ctx, q)
84 if err != nil || !strings.Contains(page.Diff, "+// second") {
85 t.Fatalf("diff: %+v %v", page, err)
86 }
87}
88
89func TestFetchMissingUsesCompleteLocalObjects(t *testing.T) {
90 g, repo, _ := browseFixture(t)
91 expected := map[string]string{"refs/heads/repaired": repo.Refs["refs/heads/main"]}
92 if err := g.FetchMissing(context.Background(), repo, nil, expected); err != nil {
93 t.Fatalf("complete local inventory should not need a network source: %v", err)
94 }
95 native, err := g.nativeRefs(repo)
96 if err != nil || native["refs/heads/repaired"] != expected["refs/heads/repaired"] {
97 t.Fatalf("locally available objects were not projected to expected refs: %v %v", native, err)
98 }
99}
100
101func TestBrowseRejectsUnsafeOrUnpublishedObjects(t *testing.T) {
102 g, r, _ := browseFixture(t)
103 cmd := exec.Command("git", "--git-dir", g.repoPath(r), "hash-object", "-w", "--stdin")
104 cmd.Stdin = strings.NewReader("unpublished secret")
105 id, err := cmd.Output()
106 if err != nil {
107 t.Fatal(err)
108 }
109 for _, q := range []BrowseRequest{
110 {Owner: r.Owner, Repo: r.Identifier, Ref: "--output=/tmp/nope"},
111 {Owner: r.Owner, Repo: r.Identifier, Ref: strings.TrimSpace(string(id))},
112 {Owner: r.Owner, Repo: r.Identifier, Path: "../config"},
113 {Owner: r.Owner, Repo: r.Identifier, Path: "/etc/passwd"},
114 } {
115 if _, err := g.Browse(context.Background(), q); err == nil {
116 t.Fatalf("accepted unsafe request: %+v", q)
117 }
118 }
119}
120
121func TestBrowseFilePreviewIsBoundedAndBinaryAware(t *testing.T) {
122 g, r, _ := browseFixture(t)
123 for _, name := range []string{"large.txt", "binary.bin"} {
124 page, err := g.Browse(context.Background(), BrowseRequest{Owner: r.Owner, Repo: r.Identifier, View: "file", Path: name})
125 if err != nil {
126 t.Fatal(err)
127 }
128 if name == "large.txt" && (!page.Truncated || len(page.Content) > 256*1024) {
129 t.Fatal("unbounded preview")
130 }
131 if name == "binary.bin" && (!page.Binary || page.Content != "") {
132 t.Fatal("binary shown as source")
133 }
134 }
135}
136
137func TestBrowseHiddenCommitAndFullBinaryDownload(t *testing.T) {
138 g, r, _ := browseFixture(t)
139 cmd := g.browseCommand(context.Background(), r, "rev-parse", "HEAD^{tree}")
140 tree, err := cmd.Output()
141 if err != nil {
142 t.Fatal(err)
143 }
144 cmd = g.browseCommand(context.Background(), r, "commit-tree", strings.TrimSpace(string(tree)))
145 cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=test", "GIT_AUTHOR_EMAIL=test@example.test", "GIT_COMMITTER_NAME=test", "GIT_COMMITTER_EMAIL=test@example.test")
146 cmd.Stdin = strings.NewReader("unpublished commit")
147 hidden, err := cmd.Output()
148 if err != nil {
149 t.Fatal(err)
150 }
151 oid := strings.TrimSpace(string(hidden))
152 if err := g.browseCommand(context.Background(), r, "update-ref", "refs/tinyrelay/pending/secret", oid).Run(); err != nil {
153 t.Fatal(err)
154 }
155 if _, err := g.Browse(context.Background(), BrowseRequest{Owner: r.Owner, Repo: r.Identifier, Ref: oid}); err == nil {
156 t.Fatal("unpublished commit exposed")
157 }
158 var result bytes.Buffer
159 if err := g.WriteSource(context.Background(), BrowseRequest{Owner: r.Owner, Repo: r.Identifier, Path: "large.txt"}, &result); err != nil {
160 t.Fatal(err)
161 }
162 if result.Len() != 300*1024 {
163 t.Fatalf("download truncated: %d", result.Len())
164 }
165}
166
167func TestBrowseTreePaginationHasNoMissingEntries(t *testing.T) {
168 g, r, _ := browseFixture(t)
169 names := map[string]bool{}
170 q := BrowseRequest{Owner: r.Owner, Repo: r.Identifier, Limit: 1}
171 for {
172 page, err := g.Browse(context.Background(), q)
173 if err != nil {
174 t.Fatal(err)
175 }
176 for _, entry := range page.Entries {
177 if names[entry.Name] {
178 t.Fatal("duplicate entry")
179 }
180 names[entry.Name] = true
181 }
182 if page.NextOffset < 0 {
183 break
184 }
185 q.Offset = page.NextOffset
186 }
187 if len(names) != 4 {
188 t.Fatalf("missing entries: %v", names)
189 }
190}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

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

Refs