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/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

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

Summarize accounts, activity, and configuration on the Admin overview

0db980ee6b820127a0310be661b5d2c98fcdde92 · Keenan Nemetz · 4d ago · parent 43e681d8

Summarize accounts, activity, and configuration on the Admin overview

The overview is the whole admin surface at a glance now, so it repeats the
head of the other tabs: active people, bots, instance admins, and retired
accounts from the roster; the newest audit records with their detail and a
link into the log; and the configuration warnings, including a browser that
cannot sign anyone in.

Refs #198 (01M6MK0NNZ2XR1D5TQKNWYT4HN).

server/admin_page_test.go +112

@@ -4,10 +4,13 @@ import (
4 4 "fmt"
5 5 "net/http"
6 6 "os"
7 "path/filepath"
7 8 "strings"
8 9 "testing"
9 10 "time"
10 11
12 "github.com/rodent-software/vole/forge/access"
13 "github.com/rodent-software/vole/forge/config"
11 14 "github.com/rodent-software/vole/internal/audit"
12 15 )
13 16
@@ -203,3 +206,112 @@ func TestAdminAuditLogEscapesDetailAndIsNotCached(t *testing.T) {
203 206 t.Fatalf("audit log Cache-Control = %q, want no-store", got)
204 207 }
205 208 }
209
210 // cardValue reads the <strong> value that follows a card's muted label. The
211 // overview repeats counts as cards, so a test has to look past the label to say
212 // which number belongs to it.
213 func cardValue(body, label string) string {
214 idx := strings.Index(body, label)
215 if idx < 0 {
216 return ""
217 }
218 rest := body[idx:]
219 start := strings.Index(rest, "<strong>")
220 if start < 0 {
221 return ""
222 }
223 rest = rest[start+len("<strong>"):]
224 end := strings.Index(rest, "</strong>")
225 if end < 0 {
226 return ""
227 }
228 return strings.TrimSpace(rest[:end])
229 }
230
231 func TestAdminOverviewSummarizesAccountsAndRecentActivity(t *testing.T) {
232 t.Parallel()
233 f := newFixture(t)
234 if _, err := f.srv.Identity.SetAdmin(f.ownerID, true, "test"); err != nil {
235 t.Fatal(err)
236 }
237 f.addWriter(t)
238 pub, _ := keypair(t, filepath.Join(f.cfg.DataDir, "id_bot"))
239 if _, err := f.srv.Identity.CreateBot("ci", access.Contribute, pub, "test"); err != nil {
240 t.Fatal(err)
241 }
242 if _, err := f.srv.Identity.Retire(f.writerID, "test"); err != nil {
243 t.Fatal(err)
244 }
245 _, cookie := f.browserSession(t, f.ownerID)
246 seedAudit(t, f.cfg.AuditLog(), 8, "overview")
247
248 resp, body := f.browserGet(t, "/admin", cookie)
249 if resp.StatusCode != http.StatusOK {
250 t.Fatalf("GET /admin = %d:\n%s", resp.StatusCode, body)
251 }
252 for label, want := range map[string]string{
253 "Active people": "1",
254 "Bots": "1",
255 "Instance admins": "1",
256 "Retired": "1",
257 } {
258 if got := cardValue(body, label); got != want {
259 t.Errorf("%s = %q, want %q:\n%s", label, got, want, body)
260 }
261 }
262 // The overview shows the newest few records, not the whole log.
263 for _, want := range []string{"overview-7", "overview-2", "Read the full audit log"} {
264 if !strings.Contains(body, want) {
265 t.Fatalf("the overview does not show %q:\n%s", want, body)
266 }
267 }
268 for _, gone := range []string{"overview-1", "overview-0"} {
269 if strings.Contains(body, gone) {
270 t.Fatalf("the overview reached past its recent-activity limit: %q:\n%s", gone, body)
271 }
272 }
273 }
274
275 func TestAdminOverviewShowsConfigurationWarnings(t *testing.T) {
276 t.Parallel()
277 f := newFixtureWith(t, func(c *config.Config) {
278 c.DevOnly = true
279 c.DevLogin = "keenan"
280 })
281 if _, err := f.srv.Identity.SetAdmin(f.ownerID, true, "test"); err != nil {
282 t.Fatal(err)
283 }
284 _, cookie := f.browserSession(t, f.ownerID)
285
286 resp, body := f.browserGet(t, "/admin", cookie)
287 if resp.StatusCode != http.StatusOK {
288 t.Fatalf("GET /admin = %d:\n%s", resp.StatusCode, body)
289 }
290 for _, want := range []string{
291 "Configuration attention", "dev_only", "dev_login", "browser sign-in",
292 "See every effective setting",
293 } {
294 if !strings.Contains(body, want) {
295 t.Fatalf("the overview does not show the configuration warning %q:\n%s", want, body)
296 }
297 }
298 }
299
300 func TestAdminOverviewOmitsConfigurationAttentionWhenClean(t *testing.T) {
301 t.Parallel()
302 f := newFixtureWith(t, func(c *config.Config) {
303 c.PublicURL = "https://forge.example.com"
304 })
305 if _, err := f.srv.Identity.SetAdmin(f.ownerID, true, "test"); err != nil {
306 t.Fatal(err)
307 }
308 _, cookie := f.browserSession(t, f.ownerID)
309
310 resp, body := f.browserGet(t, "/admin", cookie)
311 if resp.StatusCode != http.StatusOK {
312 t.Fatalf("GET /admin = %d:\n%s", resp.StatusCode, body)
313 }
314 if strings.Contains(body, "Configuration attention") {
315 t.Fatalf("a clean configuration still shows a warnings section:\n%s", body)
316 }
317 }