Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

395 commit(s) on main

update readme Keenan Nemetz · 24m ago 32290114
history blame raw 4.1 KiB
1package forge
2
3import (
4 "crypto/ed25519"
5 "crypto/rand"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10
11 "golang.org/x/crypto/ssh"
12 "rodent.software/vole/forge/config"
13)
14
15func TestInitOpenAndRepoLifecycle(t *testing.T) {
16 t.Parallel()
17 dir := t.TempDir()
18 root, err := Init(dir)
19 if err != nil {
20 t.Fatalf("init: %v", err)
21 }
22 cfg, err := config.Load(filepath.Join(root, "vole.toml"))
23 if err != nil {
24 t.Fatal(err)
25 }
26 inst, err := Open(cfg, "vole")
27 if err != nil {
28 t.Fatalf("open: %v", err)
29 }
30 defer inst.Close()
31
32 // init creates the instance repository with the starter files, and indexes it
33 // so the home page lists it like any other repository.
34 theme, err := inst.Repos.Open(cfg.InstanceRepo)
35 if err != nil {
36 t.Fatalf("init did not create the instance repository: %v", err)
37 }
38 if _, err := inst.Index.Get(cfg.InstanceRepo); err != nil {
39 t.Fatalf("the instance repository is not indexed: %v", err)
40 }
41 readme, err := theme.Git().BlobAt("refs/heads/"+theme.DefaultBranch, "README.md")
42 if err != nil || !strings.Contains(string(readme), "Instance customization") {
43 t.Fatalf("instance README = %q, %v", readme, err)
44 }
45 custom, err := theme.Git().BlobAt("refs/heads/"+theme.DefaultBranch, "profile/custom.css")
46 if err != nil || !strings.Contains(string(custom), ":root[data-theme]") {
47 t.Fatalf("starter custom.css does not show the override selector: %q, %v", custom, err)
48 }
49 if !strings.Contains(string(readme), "profile/themes/<id>.css") {
50 t.Fatal("the starter README does not document the theme file convention")
51 }
52
53 pub, _, err := ed25519.GenerateKey(rand.Reader)
54 if err != nil {
55 t.Fatal(err)
56 }
57 key, err := ssh.NewPublicKey(pub)
58 if err != nil {
59 t.Fatal(err)
60 }
61 alice, err := inst.Identity.Create("alice", strings.TrimSpace(string(ssh.MarshalAuthorizedKey(key))), "test")
62 if err != nil {
63 t.Fatal(err)
64 }
65 m, err := inst.Repos.Create("demo", alice.Name, "a demo")
66 if err != nil {
67 t.Fatal(err)
68 }
69 if err := inst.Index.Upsert(m); err != nil {
70 t.Fatal(err)
71 }
72 if _, err := inst.Index.Get("demo"); err != nil {
73 t.Fatalf("index does not know the repo: %v", err)
74 }
75
76 if err := inst.RenameRepo("demo", "renamed"); err != nil {
77 t.Fatalf("rename: %v", err)
78 }
79 if _, err := inst.Repos.Open("renamed"); err != nil {
80 t.Fatalf("renamed repo is missing: %v", err)
81 }
82 if _, err := inst.Index.Get("renamed"); err != nil {
83 t.Fatalf("index did not follow the rename: %v", err)
84 }
85
86 if err := inst.DeleteRepo("renamed", "keenan"); err != nil {
87 t.Fatalf("delete: %v", err)
88 }
89 if _, err := inst.Repos.Open("renamed"); !os.IsNotExist(err) {
90 t.Fatalf("repo survived delete: %v", err)
91 }
92 if _, err := inst.Index.Get("renamed"); err == nil {
93 t.Fatal("index row survived delete")
94 }
95}
96
97// An instance that cannot write its audit log refuses to start. Discovering that
98// during an incident is too late: every privileged change it served would have
99// been unrecorded.
100func TestOpenRefusesAnUnwritableAuditLog(t *testing.T) {
101 t.Parallel()
102 dir := t.TempDir()
103 root, err := Init(dir)
104 if err != nil {
105 t.Fatalf("init: %v", err)
106 }
107 cfg, err := config.Load(filepath.Join(root, "vole.toml"))
108 if err != nil {
109 t.Fatal(err)
110 }
111 if err := os.RemoveAll(cfg.AuditLog()); err != nil {
112 t.Fatal(err)
113 }
114 if err := os.Mkdir(cfg.AuditLog(), 0o700); err != nil {
115 t.Fatal(err)
116 }
117 if inst, err := Open(cfg, "vole"); err == nil {
118 inst.Close()
119 t.Fatal("an instance opened with a directory in place of its audit log")
120 }
121}
122
123// A symlink lets somebody outside the service point the log at /dev/null, which
124// is the same as not having one.
125func TestOpenRefusesASymlinkedAuditLog(t *testing.T) {
126 t.Parallel()
127 dir := t.TempDir()
128 root, err := Init(dir)
129 if err != nil {
130 t.Fatalf("init: %v", err)
131 }
132 cfg, err := config.Load(filepath.Join(root, "vole.toml"))
133 if err != nil {
134 t.Fatal(err)
135 }
136 if err := os.RemoveAll(cfg.AuditLog()); err != nil {
137 t.Fatal(err)
138 }
139 target := filepath.Join(dir, "elsewhere.log")
140 if err := os.WriteFile(target, nil, 0o600); err != nil {
141 t.Fatal(err)
142 }
143 if err := os.Symlink(target, cfg.AuditLog()); err != nil {
144 t.Fatal(err)
145 }
146 if inst, err := Open(cfg, "vole"); err == nil {
147 inst.Close()
148 t.Fatal("an instance opened with a symlinked audit log")
149 }
150}
151