package server_test import ( "fmt" "log" "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "strings" "testing" "misaki/db" "misaki/server" "golang.org/x/crypto/bcrypt" ) func TestMain(m *testing.M) { if err := os.Chdir(".."); err != nil { log.Fatalf("chdir to project root: %v", err) } os.Exit(m.Run()) } func newTestDB(t *testing.T) *db.DB { t.Helper() d, err := db.Open(filepath.Join(t.TempDir(), "test.db")) if err != nil { t.Fatalf("db.Open: %v", err) } t.Cleanup(func() { d.Close() }) return d } func newTestServer(t *testing.T) (http.Handler, *db.DB) { t.Helper() d := newTestDB(t) srv := server.New(d, server.ServiceURLs{}, nil, server.SMTPConfig{}, server.Config{}) return srv, d } func makeApprovedUser(t *testing.T, d *db.DB, username, password string) int64 { t.Helper() h, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost) if err != nil { t.Fatalf("bcrypt: %v", err) } id, err := d.CreateUser(username, string(h), "test bio") if err != nil { t.Fatalf("CreateUser(%q): %v", username, err) } if err := d.SetUserStatus(id, "approved"); err != nil { t.Fatalf("SetUserStatus: %v", err) } return id } func makeAdminUser(t *testing.T, d *db.DB, username, password string) int64 { t.Helper() id := makeApprovedUser(t, d, username, password) if err := d.SetUserAdmin(id, true); err != nil { t.Fatalf("SetUserAdmin: %v", err) } return id } func doLogin(t *testing.T, srv http.Handler, username, password string) *http.Cookie { t.Helper() rr := doPost(srv, "/login", url.Values{ "username": {username}, "password": {password}, }, nil) for _, c := range rr.Result().Cookies() { if c.Name == "session" { return c } } t.Fatalf("login failed: status %d, location %q", rr.Code, rr.Header().Get("Location")) return nil } func doGet(srv http.Handler, path string, cookie *http.Cookie) *httptest.ResponseRecorder { req := httptest.NewRequest(http.MethodGet, path, nil) if cookie != nil { req.AddCookie(cookie) } rr := httptest.NewRecorder() srv.ServeHTTP(rr, req) return rr } func doPost(srv http.Handler, path string, form url.Values, cookie *http.Cookie) *httptest.ResponseRecorder { req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") if cookie != nil { req.AddCookie(cookie) } rr := httptest.NewRecorder() srv.ServeHTTP(rr, req) return rr } func assertStatus(t *testing.T, rr *httptest.ResponseRecorder, want int) { t.Helper() if rr.Code != want { t.Errorf("status: got %d, want %d", rr.Code, want) } } func assertRedirectTo(t *testing.T, rr *httptest.ResponseRecorder, wantPrefix string) { t.Helper() assertStatus(t, rr, http.StatusSeeOther) if loc := rr.Header().Get("Location"); !strings.HasPrefix(loc, wantPrefix) { t.Errorf("Location: got %q, want prefix %q", loc, wantPrefix) } } func TestPublicPages(t *testing.T) { srv, _ := newTestServer(t) for _, path := range []string{"/", "/apply", "/applied", "/login"} { t.Run(path, func(t *testing.T) { assertStatus(t, doGet(srv, path, nil), http.StatusOK) }) } } func TestNotFound(t *testing.T) { srv, _ := newTestServer(t) assertStatus(t, doGet(srv, "/no-such-page", nil), http.StatusNotFound) } func TestApply_Validation(t *testing.T) { srv, _ := newTestServer(t) cases := []struct { name string form url.Values }{ { "missing password", url.Values{"username": {"alice"}, "email": {"a@b.com"}, "agree": {"on"}}, }, { "password mismatch", url.Values{"username": {"alice"}, "password": {"abc"}, "password_confirm": {"xyz"}, "email": {"a@b.com"}, "agree": {"on"}}, }, { "username too long", url.Values{"username": {strings.Repeat("a", 22)}, "password": {"p"}, "password_confirm": {"p"}, "email": {"a@b.com"}, "agree": {"on"}}, }, { "no agreement", url.Values{"username": {"alice"}, "password": {"p"}, "password_confirm": {"p"}, "email": {"a@b.com"}}, }, { "invalid email", url.Values{"username": {"alice"}, "password": {"p"}, "password_confirm": {"p"}, "email": {"notanemail"}, "agree": {"on"}}, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { assertRedirectTo(t, doPost(srv, "/apply", tc.form, nil), "/apply?err=") }) } } func TestApply_DuplicateUsername(t *testing.T) { srv, d := newTestServer(t) makeApprovedUser(t, d, "alice", "password") rr := doPost(srv, "/apply", url.Values{ "username": {"alice"}, "password": {"p"}, "password_confirm": {"p"}, "email": {"x@x.com"}, "agree": {"on"}, }, nil) assertRedirectTo(t, rr, "/apply?err=") } func TestApply_Success(t *testing.T) { srv, d := newTestServer(t) rr := doPost(srv, "/apply", url.Values{ "username": {"newuser"}, "password": {"securepass"}, "password_confirm": {"securepass"}, "email": {"new@example.com"}, "bio": {"hello world"}, "agree": {"on"}, }, nil) assertRedirectTo(t, rr, "/applied") u, err := d.GetUserByUsername("newuser") if err != nil { t.Fatalf("user not in db after apply: %v", err) } if u.Status != "pending" { t.Errorf("expected pending, got %q", u.Status) } if u.Note != "I love misaki!" { t.Errorf("default note not set: got %q", u.Note) } } func TestLogin_Success(t *testing.T) { srv, d := newTestServer(t) makeApprovedUser(t, d, "alice", "password") rr := doPost(srv, "/login", url.Values{"username": {"alice"}, "password": {"password"}}, nil) assertRedirectTo(t, rr, "/dashboard") hasCookie := false for _, c := range rr.Result().Cookies() { if c.Name == "session" { hasCookie = true } } if !hasCookie { t.Error("no session cookie set after successful login") } } func TestLogin_WrongPassword(t *testing.T) { srv, d := newTestServer(t) makeApprovedUser(t, d, "alice", "password") assertRedirectTo(t, doPost(srv, "/login", url.Values{"username": {"alice"}, "password": {"wrong"}}, nil), "/login?err=") } func TestLogin_PendingUser(t *testing.T) { srv, d := newTestServer(t) h, _ := bcrypt.GenerateFromPassword([]byte("pass"), bcrypt.MinCost) d.CreateUser("pending", string(h), "") assertRedirectTo(t, doPost(srv, "/login", url.Values{"username": {"pending"}, "password": {"pass"}}, nil), "/login?err=") } func TestLogin_RejectedUser(t *testing.T) { srv, d := newTestServer(t) id := makeApprovedUser(t, d, "alice", "password") d.SetUserStatus(id, "rejected") assertRedirectTo(t, doPost(srv, "/login", url.Values{"username": {"alice"}, "password": {"password"}}, nil), "/login?err=") } func TestLogout(t *testing.T) { srv, d := newTestServer(t) makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") rr := doPost(srv, "/logout", url.Values{}, cookie) assertRedirectTo(t, rr, "/login") cleared := false for _, c := range rr.Result().Cookies() { if c.Name == "session" && c.MaxAge < 0 { cleared = true } } if !cleared { t.Error("session cookie not cleared after logout") } } func TestDashboard_RequiresAuth(t *testing.T) { srv, _ := newTestServer(t) assertRedirectTo(t, doGet(srv, "/dashboard", nil), "/login") } func TestDashboard_Authenticated(t *testing.T) { srv, d := newTestServer(t) makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") assertStatus(t, doGet(srv, "/dashboard", cookie), http.StatusOK) } func TestHelp_RequiresAuth(t *testing.T) { srv, _ := newTestServer(t) assertRedirectTo(t, doGet(srv, "/help", nil), "/login") } func TestHelp_Authenticated(t *testing.T) { srv, d := newTestServer(t) makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") assertStatus(t, doGet(srv, "/help", cookie), http.StatusOK) } func TestUpdateNote(t *testing.T) { srv, d := newTestServer(t) id := makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doPost(srv, "/dashboard/note", url.Values{"note": {"my new biography"}}, cookie), "/dashboard") u, _ := d.GetUserByID(id) if u.Note != "my new biography" { t.Errorf("note not persisted: got %q", u.Note) } } func TestUpdateNote_TooLong(t *testing.T) { srv, d := newTestServer(t) makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doPost(srv, "/dashboard/note", url.Values{"note": {strings.Repeat("x", 2001)}}, cookie), "/dashboard?err=") } func TestUpdateNote_RequiresAuth(t *testing.T) { srv, _ := newTestServer(t) assertRedirectTo(t, doPost(srv, "/dashboard/note", url.Values{"note": {"hi"}}, nil), "/login") } func TestApplyService_Form(t *testing.T) { srv, d := newTestServer(t) d.SetServiceEnabled("mail", true) makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doPost(srv, "/dashboard/apply", url.Values{ "service": {"mail"}, "handle": {"alice"}, "password": {"mailpass"}, "reason": {"need email"}, }, cookie), "/dashboard") } func TestApplyService_Request(t *testing.T) { srv, d := newTestServer(t) d.SetServiceEnabled("cerca", true) makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doPost(srv, "/dashboard/apply", url.Values{ "service": {"cerca"}, }, cookie), "/dashboard") } func TestApplyService_AlreadyApplied(t *testing.T) { srv, d := newTestServer(t) d.SetServiceEnabled("mail", true) id := makeApprovedUser(t, d, "alice", "password") d.ApplyForService(id, "mail") cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doPost(srv, "/dashboard/apply", url.Values{ "service": {"mail"}, "handle": {"alice"}, "password": {"p"}, }, cookie), "/dashboard?err=") } func TestApplyService_InvalidService(t *testing.T) { srv, d := newTestServer(t) makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doPost(srv, "/dashboard/apply", url.Values{ "service": {"notreal"}, }, cookie), "/dashboard?err=") } func TestApplyService_LinkTypeRejected(t *testing.T) { srv, d := newTestServer(t) d.SetServiceEnabled("gancio", true) makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doPost(srv, "/dashboard/apply", url.Values{ "service": {"gancio"}, }, cookie), "/dashboard?err=") } func TestApplyService_RequiresAuth(t *testing.T) { srv, _ := newTestServer(t) assertRedirectTo(t, doPost(srv, "/dashboard/apply", url.Values{"service": {"mail"}}, nil), "/login") } func TestRequestReset(t *testing.T) { srv, d := newTestServer(t) d.SetServiceEnabled("mail", true) id := makeApprovedUser(t, d, "alice", "password") d.ApplyForService(id, "mail") d.SetUserServiceEnabled(id, "mail", true) cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doPost(srv, "/dashboard/request-reset", url.Values{ "service": {"mail"}, }, cookie), "/dashboard") } func TestRequestReset_RequiresAuth(t *testing.T) { srv, _ := newTestServer(t) assertRedirectTo(t, doPost(srv, "/dashboard/request-reset", url.Values{"service": {"mail"}}, nil), "/login") } func TestProsodyApplyService_Request(t *testing.T) { srv, d := newTestServer(t) d.SetServiceEnabled("prosody", true) makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doPost(srv, "/dashboard/apply", url.Values{ "service": {"prosody"}, }, cookie), "/dashboard") } func TestAdminSetlink_Prosody(t *testing.T) { srv, d := newTestServer(t) makeAdminUser(t, d, "admin", "adminpass") adminCookie := doLogin(t, srv, "admin", "adminpass") d.SetServiceEnabled("prosody", true) uid := makeApprovedUser(t, d, "alice", "pass") d.ApplyForService(uid, "prosody") link := "https://xmpp.example.com/register/xyz" assertRedirectTo(t, doPost(srv, "/admin/user-service", url.Values{ "user_id": {fmt.Sprintf("%d", uid)}, "service": {"prosody"}, "action": {"setlink"}, "data": {link}, }, adminCookie), "/admin") svcs, _ := d.ListUserServices(uid) if len(svcs) != 1 || svcs[0].Data != link { t.Errorf("prosody link not set: %+v", svcs) } } func TestErgoApplyService_Request(t *testing.T) { srv, d := newTestServer(t) d.SetServiceEnabled("ergo", true) makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doPost(srv, "/dashboard/apply", url.Values{ "service": {"ergo"}, }, cookie), "/dashboard") } func TestErgoApplyService_GrantedNoSetlink(t *testing.T) { srv, d := newTestServer(t) makeAdminUser(t, d, "admin", "adminpass") adminCookie := doLogin(t, srv, "admin", "adminpass") d.SetServiceEnabled("ergo", true) uid := makeApprovedUser(t, d, "alice", "pass") d.ApplyForService(uid, "ergo") // Grant ergo — no data/link needed assertRedirectTo(t, doPost(srv, "/admin/user-service", url.Values{ "user_id": {fmt.Sprintf("%d", uid)}, "service": {"ergo"}, "action": {"grant"}, }, adminCookie), "/admin") svcs, _ := d.ListUserServices(uid) if len(svcs) != 1 || !svcs[0].Granted { t.Errorf("ergo not granted: %+v", svcs) } } func TestCercaRequestReset(t *testing.T) { srv, d := newTestServer(t) d.SetServiceEnabled("cerca", true) id := makeApprovedUser(t, d, "alice", "password") d.ApplyForService(id, "cerca") d.SetUserServiceEnabled(id, "cerca", true) cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doPost(srv, "/dashboard/request-reset", url.Values{ "service": {"cerca"}, }, cookie), "/dashboard") } func TestRequestNameChange(t *testing.T) { srv, d := newTestServer(t) d.SetServiceEnabled("cerca", true) id := makeApprovedUser(t, d, "alice", "password") d.ApplyForService(id, "cerca") d.SetUserServiceEnabled(id, "cerca", true) cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doPost(srv, "/dashboard/request-namechange", url.Values{ "service": {"cerca"}, "new_name": {"alice_new"}, }, cookie), "/dashboard") } func TestRequestNameChange_EmptyName(t *testing.T) { srv, d := newTestServer(t) makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doPost(srv, "/dashboard/request-namechange", url.Values{ "service": {"cerca"}, "new_name": {""}, }, cookie), "/dashboard?err=") } func TestRequestNameChange_InvalidService(t *testing.T) { srv, d := newTestServer(t) makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doPost(srv, "/dashboard/request-namechange", url.Values{ "service": {"notreal"}, "new_name": {"alice_new"}, }, cookie), "/dashboard?err=") } func TestRequestNameChange_RequiresAuth(t *testing.T) { srv, _ := newTestServer(t) assertRedirectTo(t, doPost(srv, "/dashboard/request-namechange", url.Values{ "service": {"cerca"}, "new_name": {"alice_new"}, }, nil), "/login") } func TestAdmin_RequiresAuth(t *testing.T) { srv, _ := newTestServer(t) assertRedirectTo(t, doGet(srv, "/admin", nil), "/login") } func TestAdmin_RegularUserBlocked(t *testing.T) { srv, d := newTestServer(t) makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doGet(srv, "/admin", cookie), "/login") } func TestAdmin_AdminAccess(t *testing.T) { srv, d := newTestServer(t) makeAdminUser(t, d, "admin", "adminpass") cookie := doLogin(t, srv, "admin", "adminpass") assertStatus(t, doGet(srv, "/admin", cookie), http.StatusOK) } func TestAdminAction_RequiresAdmin(t *testing.T) { srv, d := newTestServer(t) makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doPost(srv, "/admin/action", url.Values{ "id": {"1"}, "action": {"approve"}, }, cookie), "/login") } func TestAdminApproveUser(t *testing.T) { srv, d := newTestServer(t) makeAdminUser(t, d, "admin", "adminpass") adminCookie := doLogin(t, srv, "admin", "adminpass") h, _ := bcrypt.GenerateFromPassword([]byte("p"), bcrypt.MinCost) uid, _ := d.CreateUser("pending", string(h), "") assertRedirectTo(t, doPost(srv, "/admin/action", url.Values{ "id": {fmt.Sprintf("%d", uid)}, "action": {"approve"}, }, adminCookie), "/admin") u, _ := d.GetUserByID(uid) if u.Status != "approved" { t.Errorf("expected approved, got %q", u.Status) } } func TestAdminRejectUser(t *testing.T) { srv, d := newTestServer(t) makeAdminUser(t, d, "admin", "adminpass") adminCookie := doLogin(t, srv, "admin", "adminpass") uid := makeApprovedUser(t, d, "alice", "pass") assertRedirectTo(t, doPost(srv, "/admin/action", url.Values{ "id": {fmt.Sprintf("%d", uid)}, "action": {"reject"}, }, adminCookie), "/admin") u, _ := d.GetUserByID(uid) if u.Status != "rejected" { t.Errorf("expected rejected, got %q", u.Status) } } func TestAdminReinstateUser(t *testing.T) { srv, d := newTestServer(t) makeAdminUser(t, d, "admin", "adminpass") adminCookie := doLogin(t, srv, "admin", "adminpass") uid := makeApprovedUser(t, d, "alice", "pass") d.SetUserStatus(uid, "rejected") assertRedirectTo(t, doPost(srv, "/admin/action", url.Values{ "id": {fmt.Sprintf("%d", uid)}, "action": {"reinstate"}, }, adminCookie), "/admin") u, _ := d.GetUserByID(uid) if u.Status != "approved" { t.Errorf("expected approved after reinstate, got %q", u.Status) } } func TestAdminAction_InvalidAction(t *testing.T) { srv, d := newTestServer(t) makeAdminUser(t, d, "admin", "adminpass") adminCookie := doLogin(t, srv, "admin", "adminpass") rr := doPost(srv, "/admin/action", url.Values{ "id": {"1"}, "action": {"delete"}, }, adminCookie) assertStatus(t, rr, http.StatusBadRequest) } func TestAdminToggleService(t *testing.T) { srv, d := newTestServer(t) makeAdminUser(t, d, "admin", "adminpass") adminCookie := doLogin(t, srv, "admin", "adminpass") assertRedirectTo(t, doPost(srv, "/admin/service", url.Values{ "service": {"mail"}, "enabled": {"1"}, }, adminCookie), "/admin") svcs, _ := d.ListServices() for _, s := range svcs { if s.Name == "mail" && !s.Enabled { t.Error("expected mail to be enabled") } } doPost(srv, "/admin/service", url.Values{"service": {"mail"}, "enabled": {"0"}}, adminCookie) svcs, _ = d.ListServices() for _, s := range svcs { if s.Name == "mail" && s.Enabled { t.Error("expected mail to be disabled after toggle") } } } func TestAdminUserService_Grant(t *testing.T) { srv, d := newTestServer(t) makeAdminUser(t, d, "admin", "adminpass") adminCookie := doLogin(t, srv, "admin", "adminpass") d.SetServiceEnabled("mail", true) uid := makeApprovedUser(t, d, "alice", "pass") d.ApplyForService(uid, "mail") assertRedirectTo(t, doPost(srv, "/admin/user-service", url.Values{ "user_id": {fmt.Sprintf("%d", uid)}, "service": {"mail"}, "action": {"grant"}, }, adminCookie), "/admin") svcs, _ := d.ListUserServices(uid) if len(svcs) != 1 || !svcs[0].Granted { t.Errorf("expected granted: %+v", svcs) } } func TestAdminUserService_Revoke(t *testing.T) { srv, d := newTestServer(t) makeAdminUser(t, d, "admin", "adminpass") adminCookie := doLogin(t, srv, "admin", "adminpass") d.SetServiceEnabled("mail", true) uid := makeApprovedUser(t, d, "alice", "pass") d.ApplyForService(uid, "mail") d.SetUserServiceEnabled(uid, "mail", true) assertRedirectTo(t, doPost(srv, "/admin/user-service", url.Values{ "user_id": {fmt.Sprintf("%d", uid)}, "service": {"mail"}, "action": {"revoke"}, }, adminCookie), "/admin") svcs, _ := d.ListUserServices(uid) if len(svcs) != 1 || svcs[0].Granted { t.Errorf("expected revoked: %+v", svcs) } } func TestAdminUserService_Setlink(t *testing.T) { srv, d := newTestServer(t) makeAdminUser(t, d, "admin", "adminpass") adminCookie := doLogin(t, srv, "admin", "adminpass") d.SetServiceEnabled("cerca", true) uid := makeApprovedUser(t, d, "alice", "pass") d.ApplyForService(uid, "cerca") link := "https://forum.example.com/register/xyz" assertRedirectTo(t, doPost(srv, "/admin/user-service", url.Values{ "user_id": {fmt.Sprintf("%d", uid)}, "service": {"cerca"}, "action": {"setlink"}, "data": {link}, }, adminCookie), "/admin") svcs, _ := d.ListUserServices(uid) if len(svcs) != 1 || svcs[0].Data != link { t.Errorf("link not set: %+v", svcs) } } func TestCookieService_AppearsInDashboard(t *testing.T) { d := newTestDB(t) srv := server.New(d, server.ServiceURLs{}, nil, server.SMTPConfig{}, server.Config{ CookieURLs: map[string]string{"notes": "https://notes.example.com"}, }) d.SetServiceEnabled("notes", true) makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") rr := doGet(srv, "/dashboard", cookie) assertStatus(t, rr, http.StatusOK) body := rr.Body.String() if !strings.Contains(body, "HedgeDoc") { t.Error("expected notes service label to appear on dashboard") } if !strings.Contains(body, "https://notes.example.com") { t.Error("expected notes URL to appear on dashboard") } } func TestApplyService_CookieTypeBlocked(t *testing.T) { d := newTestDB(t) srv := server.New(d, server.ServiceURLs{}, nil, server.SMTPConfig{}, server.Config{}) d.SetServiceEnabled("notes", true) makeApprovedUser(t, d, "alice", "password") cookie := doLogin(t, srv, "alice", "password") assertRedirectTo(t, doPost(srv, "/dashboard/apply", url.Values{ "service": {"notes"}, }, cookie), "/dashboard?err=") } func TestAdminUserService_InvalidAction(t *testing.T) { srv, d := newTestServer(t) makeAdminUser(t, d, "admin", "adminpass") adminCookie := doLogin(t, srv, "admin", "adminpass") rr := doPost(srv, "/admin/user-service", url.Values{ "user_id": {"1"}, "service": {"mail"}, "action": {"noop"}, }, adminCookie) assertStatus(t, rr, http.StatusBadRequest) } func TestAdminUserService_Deny(t *testing.T) { srv, d := newTestServer(t) makeAdminUser(t, d, "admin", "adminpass") adminCookie := doLogin(t, srv, "admin", "adminpass") d.SetServiceEnabled("ergo", true) uid := makeApprovedUser(t, d, "alice", "pass") d.ApplyForService(uid, "ergo") deny := func(reason string) *httptest.ResponseRecorder { return doPost(srv, "/admin/user-service", url.Values{ "user_id": {fmt.Sprintf("%d", uid)}, "service": {"ergo"}, "action": {"deny"}, "reason": {reason}, }, adminCookie) } assertStatus(t, deny(strings.Repeat("x", 281)), 400) assertRedirectTo(t, deny(" not\nnow "), "/admin") svcs, _ := d.ListUserServices(uid) if len(svcs) != 1 || !svcs[0].Denied || svcs[0].Applied || svcs[0].DenyReason != "not now" { t.Fatalf("not denied: %+v", svcs[0]) } if body := doGet(srv, "/dashboard", doLogin(t, srv, "alice", "pass")).Body.String(); !strings.Contains(body, "Request denied: not <b>now</b>") { t.Error("denial reason not shown escaped on dashboard") } if body := doGet(srv, "/admin", adminCookie).Body.String(); !strings.Contains(body, "Service requests (0)") { t.Error("denied request still queued") } if err := d.ApplyForService(uid, "ergo"); err != nil { t.Fatal(err) } svcs, _ = d.ListUserServices(uid) if !svcs[0].Applied || svcs[0].Denied || svcs[0].DenyReason != "" { t.Errorf("re-request should clear denial: %+v", svcs[0]) } d.SetUserServiceEnabled(uid, "ergo", true) assertRedirectTo(t, deny("late"), "/admin") svcs, _ = d.ListUserServices(uid) if !svcs[0].Granted || svcs[0].Denied { t.Errorf("deny must not touch a granted service: %+v", svcs[0]) } }