| @@ -175,3 +175,112 @@ func TestAccountPageMasksAShortDeviceToken(t *testing.T) { |
| 175 | 175 | t.Fatalf("the device table has no id column:\n%s", body) |
| 176 | 176 | } |
| 177 | 177 | } |
| 178 | |
| 179 | // assertAudited fails the test unless an audit row with the given action |
| 180 | // prefix exists — proof a handler dispatched through the control |
| 181 | // registry rather than writing the store directly, since only Dispatch |
| 182 | // itself calls Store.Audit. |
| 183 | func assertAudited(t *testing.T, st *store.Store, prefix string) { |
| 184 | t.Helper() |
| 185 | entries, err := st.AuditEntries(store.AuditFilter{ActionPrefix: prefix, Limit: 10}) |
| 186 | if err != nil { |
| 187 | t.Fatal(err) |
| 188 | } |
| 189 | if len(entries) == 0 { |
| 190 | t.Fatalf("no audit row with action prefix %q", prefix) |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | // Pinning writes through the repo pin command, not the store directly, |
| 195 | // so it carries the same audit trail and write budget as every other |
| 196 | // mutating command (#261). |
| 197 | func TestPinToggleDispatchesRepoPin(t *testing.T) { |
| 198 | st, err := store.Open(":memory:") |
| 199 | if err != nil { |
| 200 | t.Fatal(err) |
| 201 | } |
| 202 | defer st.Close() |
| 203 | if err := st.MigrateUp(); err != nil { |
| 204 | t.Fatal(err) |
| 205 | } |
| 206 | uid, err := st.CreateUser("alice", false) |
| 207 | if err != nil { |
| 208 | t.Fatal(err) |
| 209 | } |
| 210 | u := store.User{ID: uid, Username: "alice"} |
| 211 | if _, err := st.CreateRepo("user", uid, "app", "public"); err != nil { |
| 212 | t.Fatal(err) |
| 213 | } |
| 214 | |
| 215 | s := New(config.Default(), st) |
| 216 | req := httptest.NewRequest("POST", "/alice/app/pin", nil) |
| 217 | req.SetPathValue("owner", "alice") |
| 218 | req.SetPathValue("repo", "app") |
| 219 | rr := httptest.NewRecorder() |
| 220 | s.pinToggle(rr, req, u) |
| 221 | |
| 222 | repo, err := st.RepoByPath("alice/app") |
| 223 | if err != nil { |
| 224 | t.Fatal(err) |
| 225 | } |
| 226 | if !st.IsPinned(uid, repo.ID) { |
| 227 | t.Fatal("pin did not take effect") |
| 228 | } |
| 229 | assertAudited(t, st, "cmd repo pin") |
| 230 | |
| 231 | rr2 := httptest.NewRecorder() |
| 232 | s.pinToggle(rr2, req, u) |
| 233 | if st.IsPinned(uid, repo.ID) { |
| 234 | t.Fatal("second toggle should have unpinned") |
| 235 | } |
| 236 | assertAudited(t, st, "cmd repo unpin") |
| 237 | } |
| 238 | |
| 239 | // The watch button cycles default, watching, muted — the three states |
| 240 | // repo watch/repo mute/repo unwatch already support — rather than the |
| 241 | // two the store-writing version offered (#261, #271). |
| 242 | func TestWatchToggleCyclesThroughMuted(t *testing.T) { |
| 243 | st, err := store.Open(":memory:") |
| 244 | if err != nil { |
| 245 | t.Fatal(err) |
| 246 | } |
| 247 | defer st.Close() |
| 248 | if err := st.MigrateUp(); err != nil { |
| 249 | t.Fatal(err) |
| 250 | } |
| 251 | uid, err := st.CreateUser("alice", false) |
| 252 | if err != nil { |
| 253 | t.Fatal(err) |
| 254 | } |
| 255 | u := store.User{ID: uid, Username: "alice"} |
| 256 | if _, err := st.CreateRepo("user", uid, "app", "public"); err != nil { |
| 257 | t.Fatal(err) |
| 258 | } |
| 259 | repo, err := st.RepoByPath("alice/app") |
| 260 | if err != nil { |
| 261 | t.Fatal(err) |
| 262 | } |
| 263 | |
| 264 | s := New(config.Default(), st) |
| 265 | req := httptest.NewRequest("POST", "/alice/app/watch", nil) |
| 266 | req.SetPathValue("owner", "alice") |
| 267 | req.SetPathValue("repo", "app") |
| 268 | |
| 269 | click := func() string { |
| 270 | rr := httptest.NewRecorder() |
| 271 | s.watchToggle(rr, req, u) |
| 272 | return st.RepoWatchState(repo.ID, uid) |
| 273 | } |
| 274 | if got := click(); got != "watching" { |
| 275 | t.Fatalf("first click: got %q, want watching", got) |
| 276 | } |
| 277 | assertAudited(t, st, "cmd repo watch") |
| 278 | if got := click(); got != "muted" { |
| 279 | t.Fatalf("second click: got %q, want muted", got) |
| 280 | } |
| 281 | assertAudited(t, st, "cmd repo mute") |
| 282 | if got := click(); got != "" { |
| 283 | t.Fatalf("third click: got %q, want default (unwatched)", got) |
| 284 | } |
| 285 | assertAudited(t, st, "cmd repo unwatch") |
| 286 | } |