| @@ -1,7 +1,10 @@ |
| 1 | 1 | package httpd |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | "net/http" |
| 4 | 5 | "net/http/httptest" |
| 6 | "net/url" |
| 7 | "strconv" |
| 5 | 8 | "strings" |
| 6 | 9 | "testing" |
| 7 | 10 | |
| @@ -48,3 +51,87 @@ func TestAccountPagePushToggleAndDevices(t *testing.T) { |
| 48 | 51 | t.Fatal("the page printed a device token in full") |
| 49 | 52 | } |
| 50 | 53 | } |
| 54 | |
| 55 | // submit posts an account settings form as u and returns the recorder. |
| 56 | func submitAccountForm(t *testing.T, s *Server, u store.User, form url.Values) *httptest.ResponseRecorder { |
| 57 | t.Helper() |
| 58 | req := httptest.NewRequest("POST", "/settings", strings.NewReader(form.Encode())) |
| 59 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
| 60 | rr := httptest.NewRecorder() |
| 61 | s.accountSubmit(rr, req, u) |
| 62 | return rr |
| 63 | } |
| 64 | |
| 65 | // Posting notify-push dispatches to notifications settings push, the same |
| 66 | // path the mail and watch toggles already use. |
| 67 | func TestAccountSubmitNotifyPush(t *testing.T) { |
| 68 | st, err := store.Open(":memory:") |
| 69 | if err != nil { |
| 70 | t.Fatal(err) |
| 71 | } |
| 72 | defer st.Close() |
| 73 | if err := st.MigrateUp(); err != nil { |
| 74 | t.Fatal(err) |
| 75 | } |
| 76 | uid, err := st.CreateUser("alice", false) |
| 77 | if err != nil { |
| 78 | t.Fatal(err) |
| 79 | } |
| 80 | u := store.User{ID: uid, Username: "alice"} |
| 81 | s := New(config.Default(), st) |
| 82 | |
| 83 | rr := submitAccountForm(t, s, u, url.Values{"field": {"notify-push"}, "push": {"on"}}) |
| 84 | if rr.Code != http.StatusSeeOther { |
| 85 | t.Fatalf("status %d, body %s", rr.Code, rr.Body.String()) |
| 86 | } |
| 87 | if on, err := st.PushEnabled(uid); err != nil || !on { |
| 88 | t.Fatalf("PushEnabled after notify-push=on: %v %v", on, err) |
| 89 | } |
| 90 | |
| 91 | submitAccountForm(t, s, u, url.Values{"field": {"notify-push"}}) |
| 92 | if on, err := st.PushEnabled(uid); err != nil || on { |
| 93 | t.Fatalf("PushEnabled after notify-push off: %v %v", on, err) |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | // Removing a device requires the typed confirmation, the same guard |
| 98 | // key-remove and pgp-remove carry, and then dispatches to notifications |
| 99 | // device remove, scoped to the caller's own account. |
| 100 | func TestAccountSubmitDeviceRemove(t *testing.T) { |
| 101 | st, err := store.Open(":memory:") |
| 102 | if err != nil { |
| 103 | t.Fatal(err) |
| 104 | } |
| 105 | defer st.Close() |
| 106 | if err := st.MigrateUp(); err != nil { |
| 107 | t.Fatal(err) |
| 108 | } |
| 109 | uid, err := st.CreateUser("alice", false) |
| 110 | if err != nil { |
| 111 | t.Fatal(err) |
| 112 | } |
| 113 | u := store.User{ID: uid, Username: "alice"} |
| 114 | token := strings.Repeat("b", 64) |
| 115 | id, err := st.AddPushDevice(uid, token, "iphone") |
| 116 | if err != nil { |
| 117 | t.Fatal(err) |
| 118 | } |
| 119 | s := New(config.Default(), st) |
| 120 | |
| 121 | want := prefix8(token) |
| 122 | idStr := strconv.FormatInt(id, 10) |
| 123 | |
| 124 | // Without the typed confirmation, the device survives. |
| 125 | submitAccountForm(t, s, u, url.Values{"field": {"device-remove"}, "id": {idStr}, "tokenprefix": {want}}) |
| 126 | if devices, _ := st.PushDevices(uid); len(devices) != 1 { |
| 127 | t.Fatalf("device removed without confirmation: %v", devices) |
| 128 | } |
| 129 | |
| 130 | rr := submitAccountForm(t, s, u, url.Values{"field": {"device-remove"}, "id": {idStr}, "tokenprefix": {want}, "confirm": {want}}) |
| 131 | if rr.Code != http.StatusSeeOther { |
| 132 | t.Fatalf("status %d, body %s", rr.Code, rr.Body.String()) |
| 133 | } |
| 134 | if devices, _ := st.PushDevices(uid); len(devices) != 0 { |
| 135 | t.Fatalf("device not removed: %v", devices) |
| 136 | } |
| 137 | } |