1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
|
package main
import (
"bytes"
"log"
"os"
"path/filepath"
"strings"
"testing"
)
func TestSplitRepo(t *testing.T) {
f := testForge(t)
author := f.store.forgeAuthor()
for _, path := range []string{"notes", "infra", "infra/dns"} {
repo := Repo{Visibility: visPublic, Issues: featDisabled, Wiki: featDisabled, Labels: []string{}, Grants: map[string]string{}}
if err := f.store.CreateRepo(path, repo, author); err != nil {
t.Fatal(err)
}
}
cases := []struct{ url, repo, kind, rest string }{
{"/notes", "notes", "", ""},
{"/notes/", "notes", "", ""},
{"/notes/settings", "notes", "", "settings"},
{"/infra/dns", "infra/dns", "", ""}, // the section wins over the repo
{"/infra/dns/log/", "infra/dns", "", "log/"}, // and keeps winning deeper
{"/infra/other", "infra", "", "other"},
{"/infra/dns.issues/log/", "infra/dns", "issues", "log/"},
{"/notes.wiki/tree/", "notes", "wiki", "tree/"},
{"/nothing", "", "", ""},
}
for _, c := range cases {
repo, kind, rest, ok := f.store.splitRepo(c.url)
if repo != c.repo || kind != c.kind || rest != c.rest || ok != (c.repo != "") {
t.Errorf("%s resolved to %q %q %q %v", c.url, repo, kind, rest, ok)
}
}
}
// One process per datadir is a correctness rule: two lose updates to
// users.json with nothing in any log to say so.
func TestDatadirLockIsExclusive(t *testing.T) {
dir := t.TempDir()
unlock, err := lockDatadir(dir)
if err != nil {
t.Fatalf("the first lock was refused: %v", err)
}
if _, err := lockDatadir(dir); err == nil {
t.Error("a second process took a datadir that was already being served")
}
if _, err := lockDatadir(t.TempDir()); err != nil {
t.Errorf("a different datadir was refused: %v", err)
}
unlock()
if unlock, err = lockDatadir(dir); err != nil {
t.Errorf("the lock outlived the process that held it: %v", err)
}
unlock()
}
// A directory whose name could never be a repository path must not be
// servable, and must not stop the forge starting either.
func TestStrayDirectoryIsSkipped(t *testing.T) {
dir := t.TempDir()
s, err := openStore(dir)
if err != nil {
t.Fatal(err)
}
for _, name := range []string{"Uppercase.git", "with space.git", "settings.git"} {
if err := os.MkdirAll(filepath.Join(s.reposDir, name), 0700); err != nil {
t.Fatal(err)
}
}
s, err = openStore(dir)
if err != nil {
t.Fatalf("a stray directory stopped the forge starting: %v", err)
}
if repos := s.Repos(); len(repos) != 0 {
t.Errorf("a directory that is not a usable path is being served: %v", repos)
}
}
// The scan exists to report what arrived by hand, and a report buried in noise
// is not a report: a repository owns two sibling directories, and reading
// either as a section warns about every file git keeps inside it.
func TestScanIsQuietOnAHealthyDatadir(t *testing.T) {
f := testForge(t)
addRepo(t, f, "notes", Repo{Visibility: visPublic, Issues: featUsers, Wiki: featUsers})
addRepo(t, f, "infra/dns", Repo{Visibility: visPublic, Issues: featUsers, Wiki: featUsers})
var buf bytes.Buffer
scan := func() string {
t.Helper()
buf.Reset()
old := log.Writer()
log.SetOutput(&buf)
err := f.store.scanRepos()
log.SetOutput(old)
if err != nil {
t.Fatal(err)
}
return strings.TrimSpace(buf.String())
}
if out := scan(); out != "" {
t.Errorf("a healthy datadir warned about itself:\n%s", out)
}
for _, path := range []string{"notes", "infra/dns"} {
if _, ok := f.store.Repo(path); !ok {
t.Errorf("%s did not survive the scan", path)
}
}
// And the one thing it is for is still said.
if err := os.MkdirAll(filepath.Join(f.store.reposDir, "Uppercase.git"), 0700); err != nil {
t.Fatal(err)
}
if out := scan(); !strings.Contains(out, "Uppercase") {
t.Errorf("a stray directory was not reported:\n%s", out)
}
}
// The scan is the one place that reports a name nothing has validated, so a
// directory can be named to look like the next line of the log.
func TestTheScanCannotBeMadeToForgeALogLine(t *testing.T) {
f := testForge(t)
// No slash: this has to be one name on disk, not a path.
forged := "innocent\n2026-08-22 00:00:00 203.0.113.9 GET x 200 0 1ms"
repos := f.store.reposDir
// One of each thing the scan reports: a stray file, a directory whose name
// is not a usable path, and a stray file inside a section.
if err := os.WriteFile(filepath.Join(repos, forged), nil, 0600); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Join(repos, forged+".git"), 0700); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Join(repos, "sect"), 0700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(repos, "sect", forged), nil, 0600); err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
old := log.Writer()
log.SetOutput(&buf)
err := f.store.scanRepos()
log.SetOutput(old)
if err != nil {
t.Fatal(err)
}
out := strings.TrimRight(buf.String(), "\n")
lines := strings.Split(out, "\n")
if len(lines) != 3 {
t.Fatalf("three strays wrote %d lines:\n%s", len(lines), out)
}
for _, line := range lines {
if !strings.Contains(line, "repos: ") || !strings.Contains(line, `\n`) {
t.Errorf("the line break was not escaped: %q", line)
}
}
}
// Every single-account edit found the account before the write. Writing an
// absent key back would put a record in users.json under a name nothing else
// knows about, with an origin the next start refuses to load.
func TestEditingAnAccountThatIsGoneWritesNothing(t *testing.T) {
dir := t.TempDir()
store, err := openStore(dir)
if err != nil {
t.Fatal(err)
}
ran := false
err = store.UpdateUser("ghost", "users: sign out every session for ghost", store.forgeAuthor(),
func(acct *User) error {
ran = true
acct.Epoch++
return nil
})
if err != nil {
t.Fatalf("editing an account that is gone: %v", err)
}
if ran {
t.Error("the edit ran against an account that is not there")
}
if _, ok := store.User("ghost"); ok {
t.Fatal("the account was recreated")
}
// The consequence that matters: the datadir still starts.
if _, err := openStore(dir); err != nil {
t.Errorf("a fresh start refuses the datadir: %v", err)
}
}
|