main
tag: launch-2026-06-01
tag: magicmirror-v0.1.1
tag: magicmirror-v0.3.0
tag: mnw-cli-v0.1.2
tag: mnw-cli-v0.1.3
tag: mnw-cli-v0.1.4
tag: pom-v0.4.1
tag: pom-v0.4.2
tag: pom-v0.4.3
tag: pom-v0.4.4
tag: pom-v0.4.5
tag: wam-v0.3.0
tag: wam-v0.3.1
Files
Commits
Tags
Notes
Issues
Delete comments that restate the line under them
Twenty-two lines across six files said what the next statement already
said: "Create the community" over create_community, "Upsert author" over
upsert_user, "Check extension" over the extension match, "Check APP1
marker is absent" over the assertion that checks it.
Kept the neighbours that look similar and are not. storage.rs keeps the
cross-validate and authoritative-check lines, which name why the three
format checks run in that order. link_preview.rs keeps the content-first
loop's parenthetical, which is the only record that some sites emit
content before property, and losing it invites deleting the loop. The
internal API keeps "Get the opening post ID" over a paginated list call
with limit 1, since that names intent the arguments do not.
internal_auth.rs restated four properties of the module header verbatim
twelve lines below it. Point at the header instead; the rest of that doc
is local and stays.
Converts thirteen box-drawing banners in four test files to the ASCII
form brand.md calls house style, and drops "as before" from a routes/mod
comment, which referred to an arrangement nothing in the tree records.
Spends the two lines saved in the harness on what the test client
actually does: it carries a cookie jar and scrapes the CSRF token out of
every HTML response, which is why the no-csrf and wrong-token variants
have to exist to test rejection.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
12 files changed,
+31 insertions,
-41 deletions
5010
5010
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
5011
5011
5012
5012
[[patch.unused]]
5013
-
name = "synckit-client"
5014
-
version = "0.6.0"
5015
-
5016
-
[[patch.unused]]
5017
-
name = "synckit-config"
5018
-
version = "0.1.2"
5013
+
name = "supernote-push"
5014
+
version = "0.1.0"
5019
5015
5020
5016
[[patch.unused]]
5021
5017
name = "kberg"
5026
5022
version = "0.1.0"
5027
5023
5028
5024
[[patch.unused]]
5029
-
name = "supernote-push"
5030
-
version = "0.1.0"
5025
+
name = "synckit-client"
5026
+
version = "0.6.0"
5027
+
5028
+
[[patch.unused]]
5029
+
name = "synckit-config"
5030
+
version = "0.1.2"
196
196
Ok(timestamp)
197
197
}
198
198
199
-
/// Verify a signed internal request, binding method + path + nonce. A nonce is
200
-
/// mandatory, a request without one is rejected (401), never downgraded. This
201
-
/// is the single verification path; the legacy v1 (timestamp+body) fallback was
202
-
/// deleted once the MNW signer moved fully to v2. Freshness is checked first.
199
+
/// Verify a signed internal request, binding method + path + nonce. Freshness
200
+
/// is checked first. The module header carries why the nonce is mandatory and
201
+
/// what a missing one does.
203
202
///
204
203
/// Headers are passed as `Option<&str>` so callers can extract them with any
205
204
/// strategy (axum `HeaderMap`, manual `Bytes`, tests).
223
223
return None;
224
224
}
225
225
226
-
// Read body in chunks, capping at MAX_BODY_SIZE
227
226
let mut body = Vec::new();
228
227
let mut stream = resp;
229
228
while body.len() < MAX_BODY_SIZE {
239
238
let og_title = extract_og_meta(&html, "og:title");
240
239
let og_desc = extract_og_meta(&html, "og:description");
241
240
242
-
// Fall back to <title> tag if no og:title
243
241
let title = og_title.or_else(|| extract_html_title(&html));
244
242
245
243
if title.is_some() || og_desc.is_some() {
335
333
.unwrap()
336
334
});
337
335
338
-
// Try property-first order
339
336
for caps in OG_RE.captures_iter(html) {
340
337
if &caps[1] == property {
341
338
let val = caps[2].trim().to_string();
255
255
return Err("Empty file.");
256
256
}
257
257
258
-
// Check extension
259
258
let ext = filename
260
259
.rsplit('.')
261
260
.next()
270
269
_ => return Err("Allowed types: png, jpg, gif, webp."),
271
270
};
272
271
273
-
// Check content type
274
272
let ct: &'static str = ALLOWED_CONTENT_TYPES
275
273
.iter()
276
274
.find(|&&ct| ct == content_type)
341
339
continue;
342
340
}
343
341
344
-
// Read segment length
345
342
if i + 3 >= data.len() {
346
343
out.extend_from_slice(&data[i..]);
347
344
break;
511
508
let result = strip_exif_jpeg(&jpeg);
512
509
// Should contain SOI, APP0, SOS+data, but NOT APP1
513
510
assert!(result.len() < jpeg.len(), "EXIF should be stripped");
514
-
// Check APP1 marker is absent
515
511
let has_app1 = result.windows(2).any(|w| w == [0xFF, 0xE1]);
516
512
assert!(!has_app1, "APP1 should be removed");
517
-
// Check APP0 is preserved
518
513
let has_app0 = result.windows(2).any(|w| w == [0xFF, 0xE0]);
519
514
assert!(has_app0, "APP0 should be preserved");
520
515
}
107
107
.await
108
108
.map_err(db_error)?;
109
109
110
-
// Create the community
111
110
let community_id = mt_db::mutations::create_community(
112
111
&state.db,
113
112
&req.name,
135
134
.map_err(db_error)?;
136
135
}
137
136
138
-
// Create owner membership
139
137
mt_db::mutations::ensure_membership_with_role(
140
138
&state.db,
141
139
req.owner_mnw_id,
182
180
}));
183
181
}
184
182
185
-
// Look up community
186
183
let community = mt_db::queries::get_community_by_slug(&state.db, &req.community_slug)
187
184
.await
188
185
.map_err(db_error)?
227
224
}
228
225
};
229
226
230
-
// Upsert author
231
227
mt_db::mutations::upsert_user(
232
228
&state.db,
233
229
req.author_mnw_id,
305
301
(StatusCode::BAD_REQUEST, "Invalid request body").into_response()
306
302
})?;
307
303
308
-
// Verify thread exists
309
304
if !mt_db::queries::thread_exists(&state.db, thread_id)
310
305
.await
311
306
.map_err(db_error)?
313
308
return Err((StatusCode::NOT_FOUND, "Thread not found").into_response());
314
309
}
315
310
316
-
// Upsert author
317
311
mt_db::mutations::upsert_user(
318
312
&state.db,
319
313
req.author_mnw_id,
13
13
mod tracking;
14
14
mod uploads;
15
15
16
-
// Re-export helpers so submodules can `use super::*` as before.
16
+
// Re-export helpers so submodules can `use super::*`.
17
17
pub(crate) use helpers::*;
18
18
pub(crate) use scope::CommunityScope;
19
19
1
1
//! Cookie-aware in-process HTTP client for integration tests.
2
+
//!
3
+
//! Carries a cookie jar across requests and scrapes the CSRF token out of every
4
+
//! HTML response, injecting it on each mutating method, so a test writes the
5
+
//! request it means rather than the session bookkeeping around it.
6
+
//! `post_form_no_csrf` and `post_form_with_token` opt out on purpose: they exist
7
+
//! to test that rejection works.
2
8
3
9
use axum::Router;
4
10
use axum::body::Body;
149
149
pub(crate) async fn login_as(&mut self, username: &str) -> Uuid {
150
150
let user_id = Uuid::new_v4();
151
151
152
-
// Insert user into the database
153
152
sqlx::query(
154
153
"INSERT INTO users (mnw_account_id, username, display_name)
155
154
VALUES ($1, $2, $3)
174
174
);
175
175
}
176
176
177
-
// ── Perks refresh (`POST /auth/refresh`) ──
177
+
// --- perks refresh (`POST /auth/refresh`)
178
178
//
179
179
// Refresh re-hits MNW's `/oauth/userinfo` using the cached access token and
180
180
// overwrites the session's `perks`. These tests use wiremock to stand in for
9
9
use axum::http::StatusCode;
10
10
use uuid::Uuid;
11
11
12
-
// ── Setup ──
12
+
// --- setup
13
13
14
14
/// Build a community in the requested state with a category, returning
15
15
/// (community_id, category_id).
25
25
(comm_id, cat_id)
26
26
}
27
27
28
-
// ── Restricted: block new threads, allow replies ──
28
+
// --- restricted: block new threads, allow replies
29
29
30
30
#[tokio::test]
31
31
async fn restricted_blocks_member_new_thread() {
85
85
);
86
86
}
87
87
88
-
// ── Frozen: block all member writes ──
88
+
// --- frozen: block all member writes
89
89
90
90
#[tokio::test]
91
91
async fn frozen_blocks_member_reply() {
170
170
);
171
171
}
172
172
173
-
// ── Superadmin override ──
173
+
// --- superadmin override
174
174
175
175
#[tokio::test]
176
176
async fn superadmin_can_reply_in_frozen_without_role() {
224
224
);
225
225
}
226
226
227
-
// ── Archived: same as frozen + hidden from default listing ──
227
+
// --- archived: same as frozen + hidden from default listing
228
228
229
229
#[tokio::test]
230
230
async fn archived_blocks_member_reply() {
330
330
assert_ne!(resp.text.trim(), "Not found", "must not be bare plaintext");
331
331
}
332
332
333
-
// ── State-change route ──
333
+
// --- state-change route
334
334
335
335
#[tokio::test]
336
336
async fn owner_can_change_state() {
12
12
use axum::http::StatusCode;
13
13
use uuid::Uuid;
14
14
15
-
// ── Helpers ──
15
+
// --- helpers
16
16
17
17
/// Log in as `username` and set their denormalised perk flags + (optionally) a
18
18
/// pre-rendered signature. The `/_test/login` endpoint also stuffs perks into
70
70
comm_id
71
71
}
72
72
73
-
// ── Embed gate ──
73
+
// --- embed gate
74
74
75
75
#[tokio::test]
76
76
async fn free_user_image_embed_rejected() {
149
149
assert!(html.contains("<img"), "creator should get image rendered");
150
150
}
151
151
152
-
// ── Signature edit gate ──
152
+
// --- signature edit gate
153
153
154
154
#[tokio::test]
155
155
async fn free_user_cannot_save_signature() {
254
254
assert_eq!(resp.status, StatusCode::UNPROCESSABLE_ENTITY);
255
255
}
256
256
257
-
// ── Render-time visibility ──
257
+
// --- render-time visibility
258
258
259
259
#[tokio::test]
260
260
async fn plus_badge_and_signature_render_in_thread() {
352
352
assert!(!resp.text.contains("post-signature"));
353
353
}
354
354
355
-
// ── Account page gating ──
355
+
// --- account page gating
356
356
357
357
#[tokio::test]
358
358
async fn account_page_shows_upsell_for_free_user() {
583
583
assert_eq!(categories.len(), 7); // 6 default + 1 auto-created
584
584
}
585
585
586
-
// ── v2 signing: method/path binding + nonce replay protection ──
586
+
// --- v2 signing: method/path binding + nonce replay protection
587
587
588
588
fn community_body(slug: &str) -> String {
589
589
serde_json::json!({