Skip to main content

max / makenotwork

Seal the loose status assertions the workflow-only ratchet could not see The 830 in tests/workflows/ went in an earlier pass, but the count only ever walked that directory, so the two files every workflow test depends on kept the loose form: harness/mod.rs and load/runner.rs took is_success() || is_redirection() on signup, login, create-project and create-item. A route changing shape underneath the whole suite would have gone unnoticed in the one place it is least visible. Codes measured rather than guessed, the same way the 824 were: a temporary probe recorded every observation across a full suite run. Signup answers 200, login 303, both creates 200, over 2,700 observations with no variance. Half of the login assertion was dead. The count now reads all of tests/, excluding its own source, which holds the search strings as literals. load/scenarios.rs stays loose on purpose: those calls are a virtual user deciding whether to continue a cycle, not assertions. Also renames the test_user_id fixture added in eae9061f to recipient_id. It is a helper, not a test, but the prefix ratchet counts lines and had been failing at 177 against a high water of 176 since that commit.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-07 02:11 UTC
Signed with PGP, not checked
Commit: 9bfe22d766eae8fcb3e9111ebe59882b5e8c5157
Parent: aaf6d23
4 files changed, +55 insertions, -49 deletions
@@ -18,7 +18,7 @@
18 18 use std::fs;
19 19 use std::path::{Path, PathBuf};
20 20
21 - /// Loose status assertions in `tests/workflows/`: 6 on 2026-08-06, down from 830.
21 + /// Loose status assertions across `tests/`: 6 on 2026-08-06, down from 830.
22 22 ///
23 23 /// `assert!(resp.status.is_success())` passes on a 200 when the handler promised
24 24 /// 201, and `is_client_error()` passes on the 404 you get when a route silently
@@ -43,6 +43,21 @@
43 43 /// cases that answer with different codes, and two are login paths that render a
44 44 /// 200 page with the error in the body, where the status is not where the
45 45 /// contract lives.
46 + ///
47 + /// The count reads all of `tests/`, not just `tests/workflows/`, since 2026-08-06.
48 + /// Scoping it to the workflow modules left the two files every workflow test
49 + /// depends on unsealed, and both had kept the loose form: `harness/mod.rs` and
50 + /// `load/runner.rs` took `is_success() || is_redirection()` on signup, login,
51 + /// create-project and create-item, so a route changing shape underneath the whole
52 + /// suite would have gone unnoticed in the one place it is least visible. Measured
53 + /// the same way as the 824: signup answers 200, login 303, both creates 200, over
54 + /// 2,700 observations with no variance.
55 + ///
56 + /// `tests/load/scenarios.rs` is left alone deliberately. Its `is_success()` calls
57 + /// are a virtual user deciding whether to continue a cycle, not assertions, and
58 + /// they do not match the pattern below anyway (the receiver is a local, not
59 + /// `.status`). This file excludes itself for the obvious reason: the strings it
60 + /// searches for are in its own source.
46 61 const LOOSE_STATUS_HIGH_WATER: usize = 6;
47 62
48 63 /// `test_`-prefixed test functions across `src/` and `tests/`: 176 on 2026-08-03.
@@ -67,11 +82,18 @@
67 82
68 83 const WORKFLOWS_DIR: &str = "tests/workflows";
69 84
85 + /// The whole test tree: the harness and the load runner are as much a part of the
86 + /// suite's contract as the workflow modules are.
87 + const TESTS_DIR: &str = "tests";
88 +
70 89 #[test]
71 90 fn loose_status_assertions_do_not_increase() {
72 91 let mut per_file: Vec<(String, usize)> = Vec::new();
73 - for path in rs_files(Path::new(WORKFLOWS_DIR)) {
74 - let text = fs::read_to_string(&path).expect("read workflow module");
92 + for path in rs_files(Path::new(TESTS_DIR)) {
93 + if file_name(&path) == "test_hygiene.rs" {
94 + continue;
95 + }
96 + let text = fs::read_to_string(&path).expect("read test module");
75 97 let n = text.matches(".status.is_success()").count()
76 98 + text.matches(".status.is_client_error()").count();
77 99 if n > 0 {
@@ -583,11 +583,10 @@
583 583 );
584 584
585 585 let resp = self.client.post_form("/join/step/account", &body).await;
586 - assert!(
587 - resp.status.is_success() || resp.status.is_redirection(),
586 + assert_eq!(
587 + resp.status, 200,
588 588 "Signup failed with status {}: {}",
589 - resp.status,
590 - resp.text
589 + resp.status, resp.text
591 590 );
592 591
593 592 // Login rotates the CSRF token, fetch the new one
@@ -755,11 +754,10 @@
755 754 );
756 755
757 756 let resp = self.client.post_form("/login", &body).await;
758 - assert!(
759 - resp.status.is_success() || resp.status.is_redirection(),
757 + assert_eq!(
758 + resp.status, 303,
760 759 "Login failed with status {}: {}",
761 - resp.status,
762 - resp.text
760 + resp.status, resp.text
763 761 );
764 762
765 763 // Login rotates the CSRF token, fetch the new one
@@ -796,11 +794,7 @@
796 794 .client
797 795 .post_form("/api/projects", &format!("slug={slug}&title=Test+Project"))
798 796 .await;
799 - assert!(
800 - resp.status.is_success(),
801 - "Create project failed: {}",
802 - resp.text
803 - );
797 + assert_eq!(resp.status, 200, "Create project failed: {}", resp.text);
804 798 let project: serde_json::Value = resp.json();
805 799 let project_id = project["id"].as_str().unwrap().to_string();
806 800
@@ -811,11 +805,7 @@
811 805 &format!("title=Test+Item&item_type={item_type}&price_cents={price_cents}"),
812 806 )
813 807 .await;
814 - assert!(
815 - resp.status.is_success(),
816 - "Create item failed: {}",
817 - resp.text
818 - );
808 + assert_eq!(resp.status, 200, "Create item failed: {}", resp.text);
819 809 let item: serde_json::Value = resp.json();
820 810 let item_id = item["id"].as_str().unwrap().to_string();
821 811
@@ -265,12 +265,10 @@
265 265 let body =
266 266 format!("username={username}&email={username}%40seed.local&password=seedpass123");
267 267 let resp = client.post_form("/join/step/account", &body).await;
268 - assert!(
269 - resp.status.is_success() || resp.status.is_redirection(),
268 + assert_eq!(
269 + resp.status, 200,
270 270 "Seed signup failed for {}: {} {}",
271 - username,
272 - resp.status,
273 - resp.text
271 + username, resp.status, resp.text
274 272 );
275 273
276 274 // Grant creator via SQL
@@ -291,12 +289,10 @@
291 289 client.fetch_csrf_token().await;
292 290 let body = format!("login={username}&password=seedpass123");
293 291 let resp = client.post_form("/login", &body).await;
294 - assert!(
295 - resp.status.is_success() || resp.status.is_redirection(),
292 + assert_eq!(
293 + resp.status, 303,
296 294 "Seed login failed for {}: {} {}",
297 - username,
298 - resp.status,
299 - resp.text
295 + username, resp.status, resp.text
300 296 );
301 297
302 298 // Create project
@@ -306,11 +302,10 @@
306 302 i
307 303 );
308 304 let resp = client.post_form("/api/projects", &body).await;
309 - assert!(
310 - resp.status.is_success(),
305 + assert_eq!(
306 + resp.status, 200,
311 307 "Seed create project failed: {} {}",
312 - resp.status,
313 - resp.text
308 + resp.status, resp.text
314 309 );
315 310 let project: serde_json::Value = resp.json();
316 311 let project_id = project["id"].as_str().expect("project should have id");
@@ -329,11 +324,10 @@
329 324 let resp = client
330 325 .post_form(&format!("/api/projects/{project_id}/items"), &item_body)
331 326 .await;
332 - assert!(
333 - resp.status.is_success(),
327 + assert_eq!(
328 + resp.status, 200,
334 329 "Seed create item failed: {} {}",
335 - resp.status,
336 - resp.text
330 + resp.status, resp.text
337 331 );
338 332 let item: serde_json::Value = resp.json();
339 333 let item_id = item["id"].as_str().expect("item should have id");
@@ -912,7 +912,7 @@
912 912 /// A stand-in recipient id for the Optional senders. `client_with_capture`
913 913 /// builds a pool-less client, so `dispatch` sends without consulting a
914 914 /// preference and these tests stay about the composed message.
915 - fn test_user_id() -> crate::db::UserId {
915 + fn recipient_id() -> crate::db::UserId {
916 916 crate::db::UserId::from(uuid::Uuid::nil())
917 917 }
918 918
@@ -929,7 +929,7 @@
929 929 let (client, captured) = client_with_capture();
930 930 client
931 931 .send_sale_notification(
932 - test_user_id(),
932 + recipient_id(),
933 933 "seller@example.com",
934 934 Some("Sasha"),
935 935 "buyer42",
@@ -955,7 +955,7 @@
955 955 // greeting(None) → empty; body should still build coherently.
956 956 let (client, captured) = client_with_capture();
957 957 client
958 - .send_sale_notification(test_user_id(), "s@x", None, "buyer", "Item", "$5", None)
958 + .send_sale_notification(recipient_id(), "s@x", None, "buyer", "Item", "$5", None)
959 959 .await
960 960 .unwrap();
961 961 let (_, _, body, unsub) = captured.last();
@@ -971,7 +971,7 @@
971 971 let (client, captured) = client_with_capture();
972 972 client
973 973 .send_tip_notification(
974 - test_user_id(),
974 + recipient_id(),
975 975 "c@x",
976 976 None,
977 977 "Alex",
@@ -993,7 +993,7 @@
993 993 // without-message branch must NOT include the "with a message:" preamble.
994 994 let (client, captured) = client_with_capture();
995 995 client
996 - .send_tip_notification(test_user_id(), "c@x", None, "Alex", "$3", None, None)
996 + .send_tip_notification(recipient_id(), "c@x", None, "Alex", "$3", None, None)
997 997 .await
998 998 .unwrap();
999 999 let (_, _, body, _) = captured.last();
@@ -1146,7 +1146,7 @@
1146 1146 let (client, captured) = client_with_capture();
1147 1147 client
1148 1148 .send_new_issue_notification(
1149 - test_user_id(),
1149 + recipient_id(),
1150 1150 "owner@x",
1151 1151 Some("Jordan"),
1152 1152 "alex",
@@ -1177,7 +1177,7 @@
1177 1177 let (client, captured) = client_with_capture();
1178 1178 client
1179 1179 .send_issue_comment_notification(
1180 - test_user_id(),
1180 + recipient_id(),
1181 1181 "owner@x",
1182 1182 None,
1183 1183 "alex",
@@ -1210,7 +1210,7 @@
1210 1210 let (client, captured) = client_with_capture();
1211 1211 client
1212 1212 .send_status_notification(
1213 - test_user_id(),
1213 + recipient_id(),
1214 1214 "u@x",
1215 1215 None,
1216 1216 "operational",
@@ -1229,7 +1229,7 @@
1229 1229 let (client, captured) = client_with_capture();
1230 1230 client
1231 1231 .send_status_notification(
1232 - test_user_id(),
1232 + recipient_id(),
1233 1233 "u@x",
1234 1234 None,
1235 1235 "degraded",
@@ -1248,7 +1248,7 @@
1248 1248 let (client, captured) = client_with_capture();
1249 1249 client
1250 1250 .send_status_notification(
1251 - test_user_id(),
1251 + recipient_id(),
1252 1252 "u@x",
1253 1253 None,
1254 1254 "outage",