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.

Blame · forge/instance_test.go

Line attribution on main · 150 line(s)

c5902451
Keenan Nemetz
16d ago
1 package forge
2
3 import (
c36d5073
Keenan Nemetz
16d ago
4 "crypto/ed25519"
5 "crypto/rand"
c5902451
Keenan Nemetz
16d ago
6 "os"
7 "path/filepath"
c36d5073
Keenan Nemetz
16d ago
8 "strings"
c5902451
Keenan Nemetz
16d ago
9 "testing"
10
c36d5073
Keenan Nemetz
16d ago
11 "golang.org/x/crypto/ssh"
d37d301f
Keenan Nemetz
18h ago
12 "rodent.software/vole/forge/config"
c5902451
Keenan Nemetz
16d ago
13 )
14
15 func 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
3e7c5e67
Keenan Nemetz
1d ago
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 }
bc95767f
Keenan Nemetz
1d ago
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 }
d222b0cf
Keenan Nemetz
1d ago
49 if !strings.Contains(string(readme), "profile/themes/<id>.css") {
50 t.Fatal("the starter README does not document the theme file convention")
f7a34d87
Keenan Nemetz
1d ago
51 }
3e7c5e67
Keenan Nemetz
1d ago
52
c36d5073
Keenan Nemetz
16d ago
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 }
62576fa8
Keenan Nemetz
14d ago
61 alice, err := inst.Identity.Create("alice", strings.TrimSpace(string(ssh.MarshalAuthorizedKey(key))), "test")
c5902451
Keenan Nemetz
16d ago
62 if err != nil {
63 t.Fatal(err)
64 }
62576fa8
Keenan Nemetz
14d ago
65 m, err := inst.Repos.Create("demo", alice.Name, "a demo")
c5902451
Keenan Nemetz
16d ago
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
b1499c72
Keenan Nemetz
13d ago
86 if err := inst.DeleteRepo("renamed", "keenan"); err != nil {
c5902451
Keenan Nemetz
16d ago
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 }
b1499c72
Keenan Nemetz
13d ago
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.
100 func 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.
125 func 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 }