e2e/wiki_test.go

v1.9.0
gitbay/e2e/wiki_test.go history · blame · raw

149 lines · 6260 bytes

  1package e2e
  2
  3import (
  4	"os"
  5	"path/filepath"
  6	"strings"
  7	"testing"
  8)
  9
 10func TestWikis(t *testing.T) {
 11	inst := startInstance(t)
 12	aliceKey := inst.newKey(t, "alice")
 13	bobKey := inst.newKey(t, "bob")
 14	inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
 15	inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub")
 16	if _, _, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
 17		t.Fatal("repo create failed")
 18	}
 19	if _, _, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/secretive", "--private"); code != 0 {
 20		t.Fatal("private repo create failed")
 21	}
 22
 23	// No wiki yet: the tab is absent, the page shows the push hint, and
 24	// cloning the companion says so.
 25	_, body := inst.get(t, "/alice/app")
 26	if strings.Contains(body, ">Wiki<") {
 27		t.Fatal("wiki tab shown with no wiki")
 28	}
 29	_, body = inst.get(t, "/alice/app/wiki")
 30	if !strings.Contains(body, "no wiki yet") {
 31		t.Fatal("missing-wiki hint absent")
 32	}
 33	env := inst.gitEnv(aliceKey)
 34	if out, code := gitRun(t, t.TempDir(), env, "clone", inst.sshURL("alice/app.wiki"), "w"); code == 0 || !strings.Contains(out, "no wiki yet") {
 35		t.Fatalf("clone of absent wiki: %d\n%s", code, out)
 36	}
 37
 38	// First push creates the wiki. A reader without write cannot push it.
 39	work := t.TempDir()
 40	mustGit(t, work, env, "init", "-q", "-b", "main", "w")
 41	dir := filepath.Join(work, "w")
 42	os.WriteFile(filepath.Join(dir, "Home.md"), []byte(
 43		"# welcome\n\nsee [Setup](Setup.md) and ![shot](shot.png)\n"), 0o644)
 44	os.WriteFile(filepath.Join(dir, "Setup.org"), []byte("* setup\n\nsteps here\n"), 0o644)
 45	os.WriteFile(filepath.Join(dir, "shot.png"), []byte{0x89, 0x50, 0x4e, 0x47}, 0o644)
 46	mustGit(t, dir, env, "add", ".")
 47	mustGit(t, dir, env, "commit", "-q", "-m", "wiki start")
 48	mustGit(t, dir, env, "push", "-q", inst.sshURL("alice/app.wiki"), "main")
 49
 50	benv := inst.gitEnv(bobKey)
 51	if out, code := gitRun(t, dir, benv, "push", inst.sshURL("alice/app.wiki"), "main"); code == 0 && !strings.Contains(out, "denied") {
 52		t.Fatalf("reader pushed the wiki: %d\n%s", code, out)
 53	}
 54
 55	// Rendering: home resolves, tab appears, links rewrite to wiki pages
 56	// and images to the wiki raw route; org pages render too.
 57	_, body = inst.get(t, "/alice/app")
 58	if !strings.Contains(body, ">Wiki<") {
 59		t.Fatal("wiki tab missing after push")
 60	}
 61	_, body = inst.get(t, "/alice/app/wiki")
 62	if !strings.Contains(body, "welcome") ||
 63		!strings.Contains(body, `href="/alice/app/wiki/Setup"`) ||
 64		!strings.Contains(body, `src="/alice/app/wiki/_raw/shot.png"`) {
 65		t.Fatalf("wiki home rendering:\n%s", body)
 66	}
 67	_, body = inst.get(t, "/alice/app/wiki/Setup")
 68	if !strings.Contains(body, "steps here") {
 69		t.Fatal("org wiki page missing")
 70	}
 71	if status, _ := inst.get(t, "/alice/app/wiki/Nope"); status != 404 {
 72		t.Fatalf("missing page: %d", status)
 73	}
 74	// The raw route serves the image bytes.
 75	status, raw := inst.get(t, "/alice/app/wiki/_raw/shot.png")
 76	if status != 200 || !strings.HasPrefix(raw, "\x89PNG") {
 77		t.Fatalf("wiki raw: %d", status)
 78	}
 79
 80	// A wiki is readable from every surface, not just a browser: the
 81	// commands are what the web dispatches, and what the CLI and the
 82	// JSON API reach.
 83	out, errOut, code := inst.ssh(t, aliceKey, "", "wiki", "list", "alice/app", "--json")
 84	if code != 0 {
 85		t.Fatalf("wiki list: %s", errOut)
 86	}
 87	if !strings.Contains(out, `"Home"`) || !strings.Contains(out, `"Setup"`) {
 88		t.Errorf("wiki list pages: %s", out)
 89	}
 90	if !strings.Contains(out, `"home":"Home"`) {
 91		t.Errorf("wiki list did not name the landing page: %s", out)
 92	}
 93	// shot.png is not a page.
 94	if strings.Contains(out, "shot") {
 95		t.Errorf("wiki list included a non-page file: %s", out)
 96	}
 97
 98	// Named page, and the landing page when none is named.
 99	out, _, code = inst.ssh(t, aliceKey, "", "wiki", "show", "alice/app", "Setup", "--json")
100	if code != 0 || !strings.Contains(out, "steps here") {
101		t.Errorf("wiki show Setup: %s", out)
102	}
103	out, _, code = inst.ssh(t, aliceKey, "", "wiki", "show", "alice/app", "--json")
104	if code != 0 || !strings.Contains(out, "welcome") {
105		t.Errorf("wiki show default page: %s", out)
106	}
107	// An extension is accepted and ignored, as the web's routes do.
108	if _, _, code := inst.ssh(t, aliceKey, "", "wiki", "show", "alice/app", "Setup.org"); code != 0 {
109		t.Error("wiki show rejected a page named with its extension")
110	}
111	if _, _, code := inst.ssh(t, aliceKey, "", "wiki", "show", "alice/app", "Nope"); code == 0 {
112		t.Error("a missing wiki page resolved")
113	}
114	// A page name cannot climb out of the wiki.
115	if _, _, code := inst.ssh(t, aliceKey, "", "wiki", "show", "alice/app", "../../etc/passwd"); code == 0 {
116		t.Error("wiki show escaped the repository")
117	}
118	// A repository with no wiki says so rather than failing oddly.
119	if _, errOut, code := inst.ssh(t, aliceKey, "", "wiki", "list", "alice/secretive"); code == 0 ||
120		!strings.Contains(errOut, "no wiki") {
121		t.Errorf("wiki list on a repo without one: %d %s", code, errOut)
122	}
123	// Wiki access derives from the parent: a stranger gets nothing.
124	if _, _, code := inst.ssh(t, bobKey, "", "wiki", "list", "alice/secretive"); code == 0 {
125		t.Error("a stranger listed a private repository's wiki")
126	}
127
128	// 404-parity: a private repo's wiki is invisible, over web and git.
129	mustGit(t, dir, env, "push", "-q", inst.sshURL("alice/secretive.wiki"), "main")
130	if status, _ := inst.get(t, "/alice/secretive/wiki"); status != 404 {
131		t.Fatalf("private wiki page: %d", status)
132	}
133	if out, code := gitRun(t, t.TempDir(), benv, "clone", inst.sshURL("alice/secretive.wiki"), "x"); code == 0 || !strings.Contains(out, "not found") {
134		t.Fatalf("private wiki clone by outsider: %d\n%s", code, out)
135	}
136
137	// Repo names ending .wiki are refused (companion namespace).
138	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/notes.wiki"); code != 2 || !strings.Contains(errOut, "reserved") {
139		t.Fatalf(".wiki name allowed: %d %s", code, errOut)
140	}
141
142	// Deleting the repo removes the wiki companion.
143	if _, _, code := inst.ssh(t, aliceKey, "", "repo", "delete", "alice/secretive", "--yes"); code != 0 {
144		t.Fatal("repo delete failed")
145	}
146	if _, err := os.Stat(filepath.Join(inst.root, "repos", "alice", "secretive.wiki.git")); err == nil {
147		t.Fatal("wiki survived repo delete")
148	}
149}