e2e/design_test.go

v0.3.0
gitbay/e2e/design_test.go history · blame · raw

215 lines · 9272 bytes

  1package e2e
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6	"net/http"
  7	"net/url"
  8	"os"
  9	"path/filepath"
 10	"strings"
 11	"testing"
 12)
 13
 14func TestReadmeRelativeLinks(t *testing.T) {
 15	inst := startInstance(t)
 16	aliceKey := inst.newKey(t, "alice")
 17	inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
 18	if _, _, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/site"); code != 0 {
 19		t.Fatal("repo create failed")
 20	}
 21	work := t.TempDir()
 22	env := inst.gitEnv(aliceKey)
 23	mustGit(t, work, env, "clone", inst.sshURL("alice/site"), "w")
 24	dir := filepath.Join(work, "w")
 25	os.MkdirAll(filepath.Join(dir, "docs"), 0o755)
 26	os.MkdirAll(filepath.Join(dir, "img"), 0o755)
 27	os.WriteFile(filepath.Join(dir, "README.md"), []byte(
 28		"# site\n\n[guide](docs/guide.md) and [export](docs/paper.html) and "+
 29			"[abs](https://example.org/x) here\n\n![logo](img/logo.png)\n"+
 30			"![ext](https://example.org/pic.png)\n\n"+
 31			"| flag | effect |\n|------|--------|\n| `-v` | verbose |\n"), 0o644)
 32	os.WriteFile(filepath.Join(dir, "docs", "guide.md"), []byte("# guide\n"), 0o644)
 33	os.WriteFile(filepath.Join(dir, "docs", "paper.org"), []byte("* paper\n"), 0o644)
 34	os.WriteFile(filepath.Join(dir, "img", "logo.png"), []byte{0x89, 0x50}, 0o644)
 35	mustGit(t, dir, env, "checkout", "-q", "-b", "main")
 36	mustGit(t, dir, env, "add", ".")
 37	mustGit(t, dir, env, "commit", "-q", "-m", "base")
 38	mustGit(t, dir, env, "push", "-q", "origin", "main")
 39
 40	status, body := inst.get(t, "/alice/site")
 41	if status != 200 {
 42		t.Fatalf("tree: %d", status)
 43	}
 44	for _, want := range []string{
 45		`href="/alice/site/blob/main/docs/guide.md"`,   // relative link
 46		`href="/alice/site/blob/main/docs/paper.org"`,  // .html mapped to .org source
 47		`src="/alice/site/raw/main/img/logo.png"`,      // relative image via raw
 48		`src="https://example.org/pic.png"`,            // remote image untouched
 49		`href="https://example.org/x"`,                 // absolute untouched
 50		"<table>", "<td>verbose</td>",                  // GFM table renders
 51		`href="/alice/site/blob/main/README.md">README.md</a>`, // clickable card header
 52		`<th>name</th>`, // file table column headers
 53	} {
 54		if !strings.Contains(body, want) {
 55			t.Errorf("missing %q", want)
 56		}
 57	}
 58	// Branch dropdown lists branches.
 59	if !strings.Contains(body, `class="refmenu"`) || !strings.Contains(body, ">all refs") {
 60		t.Error("branch dropdown missing")
 61	}
 62	// Per-file history: ?path= filters the log; blob pages link to it.
 63	os.WriteFile(filepath.Join(dir, "docs", "notes.txt"), []byte("n\n"), 0o644)
 64	mustGit(t, dir, env, "add", ".")
 65	mustGit(t, dir, env, "commit", "-q", "-m", "touch only the notes")
 66	mustGit(t, dir, env, "push", "-q", "origin", "main")
 67	if _, body := inst.get(t, "/alice/site/log?path=docs/notes.txt"); !strings.Contains(body, "touch only the notes") ||
 68		strings.Contains(body, ">base<") || !strings.Contains(body, "history of") {
 69		t.Fatalf("per-file log wrong:\n%s", body)
 70	}
 71	if _, body := inst.get(t, "/alice/site/log?path=no/such/file"); !strings.Contains(body, "nothing touches") {
 72		t.Fatalf("empty per-file log:\n%s", body)
 73	}
 74	if _, body := inst.get(t, "/alice/site/blob/main/docs/guide.md"); !strings.Contains(body, `log/main?path=docs%2fguide.md">history</a>`) {
 75		t.Fatalf("blob history link missing:\n%s", body)
 76	}
 77	// CLI parity: repo log --path.
 78	logOut, _, _ := inst.ssh(t, aliceKey, "", "repo", "log", "alice/site", "--path", "docs/notes.txt", "--json")
 79	if !strings.Contains(logOut, "touch only the notes") || strings.Contains(logOut, `"subject":"base"`) {
 80		t.Fatalf("repo log --path wrong:\n%s", logOut)
 81	}
 82	// Raw serves images with their real type (nosniff otherwise blocks
 83	// <img>); everything else stays inert text/plain.
 84	resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d/alice/site/raw/main/img/logo.png", inst.httpPort))
 85	if err != nil {
 86		t.Fatal(err)
 87	}
 88	resp.Body.Close()
 89	if ct := resp.Header.Get("Content-Type"); ct != "image/png" {
 90		t.Errorf("raw png content-type = %q", ct)
 91	}
 92	resp, err = http.Get(fmt.Sprintf("http://127.0.0.1:%d/alice/site/raw/main/README.md", inst.httpPort))
 93	if err != nil {
 94		t.Fatal(err)
 95	}
 96	resp.Body.Close()
 97	if ct := resp.Header.Get("Content-Type"); !strings.HasPrefix(ct, "text/plain") {
 98		t.Errorf("raw md content-type = %q", ct)
 99	}
100	// Blob pages preview images inline.
101	if _, body := inst.get(t, "/alice/site/blob/main/img/logo.png"); !strings.Contains(body, `<img src="/alice/site/raw/main/img/logo.png"`) {
102		t.Errorf("blob image preview missing:\n%s", body)
103	}
104	// Explore rows carry topics, license, and updated date.
105	inst.ssh(t, aliceKey, "", "repo", "topics", "add", "alice/site", "web")
106	// Bare 0BSD grant (no notice-retention clause), wrapped mid-sentence.
107	os.WriteFile(filepath.Join(dir, "LICENSE"), []byte("Permission to use, copy, modify,\nand/or distribute this software for any\npurpose with or without fee is hereby granted.\n"), 0o644)
108	mustGit(t, dir, env, "add", ".")
109	mustGit(t, dir, env, "commit", "-q", "-m", "license")
110	mustGit(t, dir, env, "push", "-q", "origin", "main")
111	_, body = inst.get(t, "/explore")
112	for _, want := range []string{`href="/explore?q=web"`, "0BSD", "updated 20"} {
113		if !strings.Contains(body, want) {
114			t.Errorf("explore row missing %q", want)
115		}
116	}
117}
118
119func TestWebInteractions(t *testing.T) {
120	inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
121	aliceKey := inst.newKey(t, "alice")
122	inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
123	if _, _, code := inst.ssh(t, aliceKey, "", "org", "create", "theorg"); code != 0 {
124		t.Fatal("org create failed")
125	}
126
127	out, _, code := inst.ssh(t, aliceKey, "", "web", "login", "--json")
128	if code != 0 {
129		t.Fatal("web login failed")
130	}
131	var env2 struct {
132		Data struct {
133			URL string `json:"url"`
134		} `json:"data"`
135	}
136	json.Unmarshal([]byte(out), &env2)
137	browser := newBrowser(t)
138	browserGet(t, browser, inst.base()+env2.Data.URL[strings.Index(env2.Data.URL, "/login"):])
139
140	// Create a repo under the org through the web form.
141	if status, _ := browserPost(t, browser, inst.base()+"/new",
142		url.Values{"owner": {"theorg"}, "name": {"webborn"}, "visibility": {"public"}}); status != 200 {
143		t.Fatalf("org repo via web: %d", status)
144	}
145	if out, _, code := inst.ssh(t, aliceKey, "", "repo", "show", "theorg/webborn"); code != 0 {
146		t.Fatalf("org repo missing: %s", out)
147	}
148
149	// Pin from the web; the repo header reflects it and the dashboard
150	// lists it; a second toggle unpins.
151	if status, _ := browserPost(t, browser, inst.base()+"/theorg/webborn/pin", url.Values{}); status != 200 {
152		t.Fatal("pin toggle failed")
153	}
154	_, body := browserGet(t, browser, inst.base()+"/theorg/webborn")
155	if !strings.Contains(body, "★ pinned") {
156		t.Fatal("repo header not pinned")
157	}
158	if _, body = browserGet(t, browser, inst.base()+"/"); !strings.Contains(body, "theorg<span") {
159		t.Fatal("dashboard missing pinned repo")
160	}
161	browserPost(t, browser, inst.base()+"/theorg/webborn/pin", url.Values{})
162	if _, body = browserGet(t, browser, inst.base()+"/theorg/webborn"); !strings.Contains(body, "☆ pin") {
163		t.Fatal("unpin failed")
164	}
165
166	// New issue with labels through the form; label chips filter.
167	if status, _ := browserPost(t, browser, inst.base()+"/theorg/webborn/issues/new",
168		url.Values{"title": {"styled"}, "body": {"b"}, "labels": {"bug ui"}}); status != 200 {
169		t.Fatal("issue via web failed")
170	}
171	if status, _ := browserPost(t, browser, inst.base()+"/theorg/webborn/issues/new",
172		url.Values{"title": {"plain"}}); status != 200 {
173		t.Fatal("second issue failed")
174	}
175	_, body = browserGet(t, browser, inst.base()+"/theorg/webborn/issues?label=bug")
176	if !strings.Contains(body, "styled") || strings.Contains(body, ">plain<") {
177		t.Fatalf("label filter wrong:\n%s", body)
178	}
179	_, body = browserGet(t, browser, inst.base()+"/theorg/webborn/issues/1")
180	if !strings.Contains(body, `href="/theorg/webborn/issues?label=bug"`) ||
181		!strings.Contains(body, `href="/alice">alice</a>`) {
182		t.Fatal("issue page chips/author not linked")
183	}
184}
185
186func TestCommitParentLinks(t *testing.T) {
187	inst := startInstance(t)
188	aliceKey := inst.newKey(t, "alice")
189	inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
190	if _, _, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
191		t.Fatal("repo create failed")
192	}
193	work := t.TempDir()
194	env := inst.gitEnv(aliceKey)
195	mustGit(t, work, env, "clone", inst.sshURL("alice/app"), "w")
196	dir := filepath.Join(work, "w")
197	os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a\n"), 0o644)
198	mustGit(t, dir, env, "checkout", "-q", "-b", "main")
199	mustGit(t, dir, env, "add", ".")
200	mustGit(t, dir, env, "commit", "-q", "-m", "first")
201	first := strings.TrimSpace(mustGit(t, dir, env, "rev-parse", "HEAD"))
202	os.WriteFile(filepath.Join(dir, "a.txt"), []byte("b\n"), 0o644)
203	mustGit(t, dir, env, "commit", "-qam", "second")
204	head := strings.TrimSpace(mustGit(t, dir, env, "rev-parse", "HEAD"))
205	mustGit(t, dir, env, "push", "-q", "origin", "main")
206
207	_, body := inst.get(t, "/alice/app/commit/"+head)
208	if !strings.Contains(body, `href="/alice/app/commit/`+first+`"`) {
209		t.Fatal("parent commit not linked")
210	}
211	_, body = inst.get(t, "/alice/app/commit/"+first)
212	if strings.Contains(body, ">parent") {
213		t.Fatal("root commit shows a parent")
214	}
215}