Commit a8ca19c99f
Verified · cmc
Layout: unified · split
internal/httpd/mrslist_test.go added +86
| @@ -0,0 +1,86 @@ | |||
| 1 | package httpd | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "net/http" | ||
| 5 | "net/http/httptest" | ||
| 6 | "strings" | ||
| 7 | "testing" | ||
| 8 | "time" | ||
| 9 | |||
| 10 | "gitbay.org/gitbay/internal/config" | ||
| 11 | "gitbay.org/gitbay/internal/store" | ||
| 12 | ) | ||
| 13 | |||
| 14 | // A repository's MR list offers the right next step by access level: a | ||
| 15 | // writer gets "New merge request", a signed-in reader without push gets | ||
| 16 | // a fork link, and a signed-out visitor gets a sign-in prompt (#270). | ||
| 17 | func TestMRsListContributionHintByAccess(t *testing.T) { | ||
| 18 | st, err := store.Open(":memory:") | ||
| 19 | if err != nil { | ||
| 20 | t.Fatal(err) | ||
| 21 | } | ||
| 22 | defer st.Close() | ||
| 23 | if err := st.MigrateUp(); err != nil { | ||
| 24 | t.Fatal(err) | ||
| 25 | } | ||
| 26 | owner, err := st.CreateUser("alice", false) | ||
| 27 | if err != nil { | ||
| 28 | t.Fatal(err) | ||
| 29 | } | ||
| 30 | reader, err := st.CreateUser("bob", false) | ||
| 31 | if err != nil { | ||
| 32 | t.Fatal(err) | ||
| 33 | } | ||
| 34 | if _, err := st.CreateRepo("user", owner, "app", "public"); err != nil { | ||
| 35 | t.Fatal(err) | ||
| 36 | } | ||
| 37 | |||
| 38 | cfg := config.Default() | ||
| 39 | cfg.Web.Mode = "accounts" | ||
| 40 | s := New(cfg, st) | ||
| 41 | |||
| 42 | // mrs reads the viewer through s.viewer(r), which resolves a | ||
| 43 | // session cookie (internal/httpd/accounts.go:37-47) rather than | ||
| 44 | // taking the viewer as a parameter the way a POST handler test | ||
| 45 | // does. Give a real viewer a real session; leave the request | ||
| 46 | // cookie-less for the anonymous case. | ||
| 47 | sessionFor := func(uid int64) *http.Cookie { | ||
| 48 | tok, hash, err := store.NewToken() | ||
| 49 | if err != nil { | ||
| 50 | t.Fatal(err) | ||
| 51 | } | ||
| 52 | if err := st.CreateWebSession(hash, uid, time.Hour); err != nil { | ||
| 53 | t.Fatal(err) | ||
| 54 | } | ||
| 55 | return s.sessionCookieFor(tok) | ||
| 56 | } | ||
| 57 | |||
| 58 | get := func(uid int64) string { | ||
| 59 | req := httptest.NewRequest("GET", "/alice/app/mrs", nil) | ||
| 60 | req.SetPathValue("owner", "alice") | ||
| 61 | req.SetPathValue("repo", "app") | ||
| 62 | if uid != 0 { | ||
| 63 | req.AddCookie(sessionFor(uid)) | ||
| 64 | } | ||
| 65 | rr := httptest.NewRecorder() | ||
| 66 | s.mrs(rr, req) | ||
| 67 | return rr.Body.String() | ||
| 68 | } | ||
| 69 | anonymous := get(0) | ||
| 70 | if !strings.Contains(anonymous, "Sign in to propose a change") { | ||
| 71 | t.Errorf("signed-out visitor: missing sign-in prompt:\n%s", anonymous) | ||
| 72 | } | ||
| 73 | if strings.Contains(anonymous, "New merge request") { | ||
| 74 | t.Error("signed-out visitor should not see New merge request") | ||
| 75 | } | ||
| 76 | |||
| 77 | readerOut := get(reader) | ||
| 78 | if !strings.Contains(readerOut, "Fork this repository to propose a change") { | ||
| 79 | t.Errorf("reader without push: missing fork hint:\n%s", readerOut) | ||
| 80 | } | ||
| 81 | |||
| 82 | ownerOut := get(owner) | ||
| 83 | if !strings.Contains(ownerOut, "New merge request") { | ||
| 84 | t.Errorf("owner: missing New merge request link:\n%s", ownerOut) | ||
| 85 | } | ||
| 86 | } | ||
internal/httpd/mrsrow_test.go +7 −6
| @@ -10,12 +10,13 @@ import ( | |||
| 10 | // mrsPageData mirrors the anonymous struct the mrs handler renders with. | 10 | // mrsPageData mirrors the anonymous struct the mrs handler renders with. |
| 11 | type mrsPageData struct { | 11 | type mrsPageData struct { |
| 12 | repoPage | 12 | repoPage |
| 13 | State string | 13 | State string |
| 14 | Query string | 14 | Query string |
| 15 | Filters []listFilter | 15 | Filters []listFilter |
| 16 | Facets []facetGroup | 16 | Facets []facetGroup |
| 17 | MRs []mrRow | 17 | MRs []mrRow |
| 18 | Older string | 18 | Older string |
| 19 | CanWrite bool | ||
| 19 | } | 20 | } |
| 20 | 21 | ||
| 21 | func renderMRs(t *testing.T, rows []mrRow, state string) string { | 22 | func renderMRs(t *testing.T, rows []mrRow, state string) string { |
internal/httpd/web.go +3 −1
| @@ -2037,6 +2037,7 @@ func (s *Server) mrs(w http.ResponseWriter, r *http.Request) { | |||
| 2037 | return | 2037 | return |
| 2038 | } | 2038 | } |
| 2039 | p.Tab = "merge requests" | 2039 | p.Tab = "merge requests" |
| 2040 | canWrite := s.canWriteRepo(r, p.Repo) | ||
| 2040 | state := r.URL.Query().Get("state") | 2041 | state := r.URL.Query().Get("state") |
| 2041 | if state == "" { | 2042 | if state == "" { |
| 2042 | state = "open" | 2043 | state = "open" |
| @@ -2096,9 +2097,10 @@ func (s *Server) mrs(w http.ResponseWriter, r *http.Request) { | |||
| 2096 | MRs []mrRow | 2097 | MRs []mrRow |
| 2097 | LabelColors map[string]template.CSS | 2098 | LabelColors map[string]template.CSS |
| 2098 | Older string | 2099 | Older string |
| 2100 | CanWrite bool | ||
| 2099 | }{p, state, mf.Search, | 2101 | }{p, state, mf.Search, |
| 2100 | activeFilters(state, [][2]string{{"label", mf.Label}, {"author", mf.Author}, {"milestone", mf.Milestone}}), | 2102 | activeFilters(state, [][2]string{{"label", mf.Label}, {"author", mf.Author}, {"milestone", mf.Milestone}}), |
| 2101 | facets, rows, s.labelColors(p.Repo), older}) | 2103 | facets, rows, s.labelColors(p.Repo), older, canWrite}) |
| 2102 | } | 2104 | } |
| 2103 | 2105 | ||
| 2104 | func (s *Server) mr(w http.ResponseWriter, r *http.Request) { | 2106 | func (s *Server) mr(w http.ResponseWriter, r *http.Request) { |
internal/web/templates/mrs.html +3 −1
| @@ -13,7 +13,9 @@ | |||
| 13 | </form> | 13 | </form> |
| 14 | {{range .Filters}}<p class="meta">{{.Key}}: {{if eq .Key "label"}}<span class="chip label" style="{{index $.LabelColors .Value}}">{{.Value}}</span>{{else}}<b>{{.Value}}</b>{{end}} <a href="{{.Clear}}">clear</a></p>{{end}} | 14 | {{range .Filters}}<p class="meta">{{.Key}}: {{if eq .Key "label"}}<span class="chip label" style="{{index $.LabelColors .Value}}">{{.Value}}</span>{{else}}<b>{{.Value}}</b>{{end}} <a href="{{.Clear}}">clear</a></p>{{end}} |
| 15 | </div> | 15 | </div> |
| 16 | {{if .Viewer}}<p class="meta"><a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/mrs/new">New merge request</a></p>{{end}} | 16 | {{if .CanWrite}}<p class="meta"><a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/mrs/new">New merge request</a></p> |
| 17 | {{else if .Viewer}}<p class="meta"><a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/fork">Fork this repository to propose a change</a></p> | ||
| 18 | {{else}}<p class="meta"><a href="/login">Sign in to propose a change</a></p>{{end}} | ||
| 17 | <ul class="issuelist rows"> | 19 | <ul class="issuelist rows"> |
| 18 | {{range .MRs}}{{$n := .Number}}<li> | 20 | {{range .MRs}}{{$n := .Number}}<li> |
| 19 | <div class="issuemain"> | 21 | <div class="issuemain"> |