e2e/settingsweb_test.go
109 lines · 5108 bytes
1package e2e
2
3import (
4 "net/url"
5 "strings"
6 "testing"
7)
8
9// TestRepoSettingsWeb drives the settings page: each control runs the
10// command the CLI runs, so repo show and settings show are the check.
11func TestRepoSettingsWeb(t *testing.T) {
12 t.Parallel()
13 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
14 aliceKey := inst.newKey(t, "alice")
15 bobKey := inst.newKey(t, "bob")
16 inst.admin(t, "admin", "user", "create", "alice",
17 "--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
18 inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub")
19 if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
20 t.Fatalf("repo create: %s", errOut)
21 }
22
23 alice := inst.login(t, aliceKey)
24 set := inst.base() + "/alice/app/settings"
25
26 // Only admins reach the page, and only they see the tab.
27 if _, body := browserGet(t, alice, inst.base()+"/alice/app"); !strings.Contains(body, "/alice/app/settings") {
28 t.Fatalf("no settings tab for the owner:\n%s", body)
29 }
30 if status, _ := browserGet(t, inst.login(t, bobKey), set); status != 403 && status != 404 {
31 t.Fatalf("reader reached settings: %d", status)
32 }
33
34 post := func(v url.Values) string {
35 t.Helper()
36 status, body := browserPost(t, alice, set, v)
37 if status != 200 {
38 t.Fatalf("settings post %v: %d", v, status)
39 }
40 return body
41 }
42
43 if body := post(url.Values{"field": {"description"}, "description": {"a thing"}}); !strings.Contains(body, `class="notice" role="status">Saved the description.`) {
44 t.Fatalf("no success flash after saving the description:\n%s", body)
45 }
46 if body := post(url.Values{"field": {"topics"}, "topics": {"cli, forge"}}); !strings.Contains(body, `value="cli, forge"`) {
47 t.Fatalf("topics field is not prefilled after save:\n%s", body)
48 }
49 if body := post(url.Values{"field": {"topics"}, "topics": {"forge"}}); strings.Contains(body, `>cli<`) || !strings.Contains(body, `value="forge"`) {
50 t.Fatalf("removing a topic through the field failed:\n%s", body)
51 }
52 if body := post(url.Values{"field": {"website"}, "website": {"javascript:alert(1)"}}); !strings.Contains(body, `class="error"`) || !strings.Contains(body, `value="javascript:alert(1)"`) {
53 t.Fatalf("error does not keep the submitted website:\n%s", body)
54 }
55
56 post(url.Values{"field": {"description"}, "description": {"a fine tool"}})
57 post(url.Values{"field": {"website"}, "website": {"https://tool.example"}})
58 // Saving required contexts turns the checks gate on, and the page
59 // shows it ticked with the contexts in its hint (#258).
60 if body := post(url.Values{"field": {"require-contexts"}, "contexts": {"ext/deploy lint"}}); !strings.Contains(body, `id="require-checks" name="require-checks" value="on" checked`) ||
61 !strings.Contains(body, `Also waits for <code>ext/deploy</code>, <code>lint</code> until they report.`) {
62 t.Fatalf("required contexts did not show as turning required checks on:\n%s", body)
63 }
64 post(url.Values{"field": {"require-approvals"}, "approvals": {"2"}})
65 post(url.Values{"field": {"protect"}, "branch": {"main"}})
66
67 out, _, _ := inst.ssh(t, aliceKey, "", "repo", "show", "alice/app", "--json")
68 for _, want := range []string{"a fine tool", "https://tool.example", `"forge"`} {
69 if !strings.Contains(out, want) {
70 t.Fatalf("repo show missing %q:\n%s", want, out)
71 }
72 }
73 out, _, _ = inst.ssh(t, aliceKey, "", "repo", "settings", "show", "alice/app", "--json")
74 for _, want := range []string{`"require_checks":true`, `"required_contexts":["ext/deploy","lint"]`, `"require_approvals":2`, `"main"`} {
75 if !strings.Contains(out, want) {
76 t.Fatalf("settings show missing %q:\n%s", want, out)
77 }
78 }
79
80 // Visibility is a new command; the web form drives it both ways.
81 post(url.Values{"field": {"visibility"}, "visibility": {"private"}})
82 if out, _, _ := inst.ssh(t, aliceKey, "", "repo", "show", "alice/app", "--json"); !strings.Contains(out, `"visibility":"private"`) {
83 t.Fatalf("not private:\n%s", out)
84 }
85 // A private repo disappears from anonymous surfaces.
86 if status, _ := inst.get(t, "/alice/app"); status != 404 {
87 t.Fatalf("private repo still public: %d", status)
88 }
89 post(url.Values{"field": {"visibility"}, "visibility": {"public"}})
90 if status, _ := inst.get(t, "/alice/app"); status != 200 {
91 t.Fatalf("public repo not restored: %d", status)
92 }
93
94 // Archiving is reversible from the page; unchecking the box unarchives.
95 post(url.Values{"field": {"archive"}, "archive": {"on"}})
96 if out, _, _ := inst.ssh(t, aliceKey, "", "repo", "show", "alice/app", "--json"); !strings.Contains(out, `"archived":true`) {
97 t.Fatalf("not archived:\n%s", out)
98 }
99 post(url.Values{"field": {"archive"}})
100 if out, _, _ := inst.ssh(t, aliceKey, "", "repo", "show", "alice/app", "--json"); strings.Contains(out, `"archived":true`) {
101 t.Fatalf("still archived:\n%s", out)
102 }
103
104 // Unprotecting works.
105 post(url.Values{"field": {"unprotect"}, "branch": {"main"}})
106 if out, _, _ := inst.ssh(t, aliceKey, "", "repo", "settings", "show", "alice/app", "--json"); strings.Contains(out, `"protected_branches"`) {
107 t.Fatalf("branch still protected:\n%s", out)
108 }
109}