max / makenotwork
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
1 file changed,
+74 insertions,
-18 deletions
| @@ -210,6 +210,40 @@ | |||
| 210 | 210 | } | |
| 211 | 211 | } | |
| 212 | 212 | ||
| 213 | + | /// A key on the ssh-keys screen, added the way the screen's own form adds one. | |
| 214 | + | /// | |
| 215 | + | /// Shared by the test below and by `a_described_delete_answers_with_the_screen_it_is_on`, | |
| 216 | + | /// which cannot press a Remove that no row emitted. | |
| 217 | + | const SEED_KEY: &str = | |
| 218 | + | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB2n4ZVGJoGZ8pM5vJXVv0kL3T5V7wQ9dNqR8mY1uH6c"; | |
| 219 | + | ||
| 220 | + | /// Put a row on a screen, for the screens that can grow one through their own | |
| 221 | + | /// description. | |
| 222 | + | /// | |
| 223 | + | /// Only `user_ssh_keys` today, because it is the one screen whose row this suite | |
| 224 | + | /// can create by posting the form the description itself emitted. The others | |
| 225 | + | /// need fixtures that do not exist yet, and the pressed-count guard in | |
| 226 | + | /// `a_described_delete_answers_with_the_screen_it_is_on` is what keeps that from | |
| 227 | + | /// being silent: it fails if the loop pressed nothing at all, so a screen | |
| 228 | + | /// growing a fixture joins the coverage and a screen losing one is noticed. | |
| 229 | + | async fn seed_a_deletable_row(h: &mut TestHarness, screen: &str) { | |
| 230 | + | if screen != "user_ssh_keys" { | |
| 231 | + | return; | |
| 232 | + | } | |
| 233 | + | let added = h | |
| 234 | + | .client | |
| 235 | + | .post_form( | |
| 236 | + | "/api/users/me/ssh-keys", | |
| 237 | + | &format!("public_key={}&label=fw13", urlencoding::encode(SEED_KEY)), | |
| 238 | + | ) | |
| 239 | + | .await; | |
| 240 | + | assert_eq!( | |
| 241 | + | added.status, 200, | |
| 242 | + | "seeding a key failed: {} {}", | |
| 243 | + | added.status, added.text | |
| 244 | + | ); | |
| 245 | + | } | |
| 246 | + | ||
| 213 | 247 | /// Removing a key removes it, and answers with the pane rather than a list. | |
| 214 | 248 | /// | |
| 215 | 249 | /// The defect this file exists for, pinned end to end: seed a key, find the | |
| @@ -219,21 +253,7 @@ | |||
| 219 | 253 | async fn pressing_remove_on_a_key_removes_it_and_redraws_the_pane() { | |
| 220 | 254 | let mut h = viewing("user_ssh_keys").await; | |
| 221 | 255 | ||
| 222 | - | // A real key, added the way the screen's own form adds one. | |
| 223 | - | let key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB2n4ZVGJoGZ8pM5vJXVv0kL3T5V7wQ9dNqR8mY1uH6c"; | |
| 224 | - | let added = h | |
| 225 | - | .client | |
| 226 | - | .post_form( | |
| 227 | - | "/api/users/me/ssh-keys", | |
| 228 | - | &format!("public_key={}&label=fw13", urlencoding::encode(key)), | |
| 229 | - | ) | |
| 230 | - | .await; | |
| 231 | - | assert!( | |
| 232 | - | added.status.is_success(), | |
| 233 | - | "seeding a key failed: {} {}", | |
| 234 | - | added.status, | |
| 235 | - | added.text | |
| 236 | - | ); | |
| 256 | + | seed_a_deletable_row(&mut h, "user_ssh_keys").await; | |
| 237 | 257 | ||
| 238 | 258 | let html = h.client.htmx_get("/dashboard/tabs/ssh-keys").await.text; | |
| 239 | 259 | assert!(html.contains("fw13"), "the key is on the screen: {html}"); | |
| @@ -284,19 +304,44 @@ | |||
| 284 | 304 | /// screen's own region: an answer that names nothing is an answer htmx puts | |
| 285 | 305 | /// inside the pressed button, and an answer that names another screen's region | |
| 286 | 306 | /// swaps the wrong part of the page. | |
| 307 | + | /// | |
| 308 | + | /// # It has to be given a row first | |
| 309 | + | /// | |
| 310 | + | /// This pressed nothing at all until 2026-08-13. The reader is freshly signed | |
| 311 | + | /// up, so every list on every screen was empty, so no row emitted a Remove and | |
| 312 | + | /// the loop below ran zero times on all six screens. It passed throughout, | |
| 313 | + | /// which is what a vacuous test does. [`seed_a_deletable_row`] is what gives it | |
| 314 | + | /// something to press, and `pressed` is what stops the vacuity coming back: | |
| 315 | + | /// a loop that asserts nothing now fails rather than reporting green. | |
| 287 | 316 | #[tokio::test] | |
| 288 | 317 | async fn a_described_delete_answers_with_the_screen_it_is_on() { | |
| 318 | + | let mut pressed = 0_usize; | |
| 289 | 319 | for (screen, path, region) in SCREENS { | |
| 290 | 320 | let mut h = viewing(screen).await; | |
| 321 | + | seed_a_deletable_row(&mut h, screen).await; | |
| 291 | 322 | let html = h.client.htmx_get(path).await.text; | |
| 292 | 323 | ||
| 293 | 324 | for control in controls(&html).into_iter().filter(|c| c.method == "DELETE") { | |
| 294 | 325 | let resp = h.client.htmx_delete(&control.address).await; | |
| 295 | - | if !resp.status.is_success() { | |
| 296 | - | // A delete of something this seeded account does not have is | |
| 297 | - | // fine; what is not fine is a success that says nothing. | |
| 326 | + | // A delete of something this seeded account does not have is fine, | |
| 327 | + | // and 404 is how that is said. Every other code is the answer being | |
| 328 | + | // wrong rather than the fixture being thin, so it is asserted | |
| 329 | + | // instead of skipped. | |
| 330 | + | // | |
| 331 | + | // 200 exactly, not any 2xx. A 204 is the `library_contacts` bug in | |
| 332 | + | // the module header: htmx never swaps one, so the row stays put | |
| 333 | + | // whatever headers ride along with it. Tolerating the whole 2xx | |
| 334 | + | // range here would have let the second of the two bugs this file | |
| 335 | + | // was written for pass, since a 204 carrying an `HX-Retarget` it | |
| 336 | + | // never acts on satisfies the assertion below. | |
| 337 | + | if resp.status == 404 { | |
| 298 | 338 | continue; | |
| 299 | 339 | } | |
| 340 | + | assert_eq!( | |
| 341 | + | resp.status, 200, | |
| 342 | + | "{screen}: DELETE {} answered {} rather than the region", | |
| 343 | + | control.address, resp.status | |
| 344 | + | ); | |
| 300 | 345 | let retarget = resp | |
| 301 | 346 | .headers | |
| 302 | 347 | .get("HX-Retarget") | |
| @@ -308,8 +353,19 @@ | |||
| 308 | 353 | "{screen}: DELETE {} succeeded without naming {region}", | |
| 309 | 354 | control.address | |
| 310 | 355 | ); | |
| 356 | + | pressed += 1; | |
| 311 | 357 | } | |
| 312 | 358 | } | |
| 359 | + | ||
| 360 | + | // The guard the module header claims for controls in general, applied to | |
| 361 | + | // the destructive ones: `a_screen_offers_something` sees the screens still | |
| 362 | + | // emit controls, and cannot see that none of them is a DELETE. | |
| 363 | + | assert!( | |
| 364 | + | pressed > 0, | |
| 365 | + | "no described DELETE was pressed on any of the {} screens, so this test \ | |
| 366 | + | asserted nothing. Seed a row for whichever screen lost its fixture.", | |
| 367 | + | SCREENS.len() | |
| 368 | + | ); | |
| 313 | 369 | } | |
| 314 | 370 | ||
| 315 | 371 | /// The screen list here covers every screen the description layer can mount. |