diff --git a/bugs/03-rust-web/process/3-24-mnote-web-lib-test-failures-v1.md b/bugs/03-rust-web/done/3-24-mnote-web-lib-test-failures-v1.md similarity index 88% rename from bugs/03-rust-web/process/3-24-mnote-web-lib-test-failures-v1.md rename to bugs/03-rust-web/done/3-24-mnote-web-lib-test-failures-v1.md index 388ce575..1c729256 100644 --- a/bugs/03-rust-web/process/3-24-mnote-web-lib-test-failures-v1.md +++ b/bugs/03-rust-web/done/3-24-mnote-web-lib-test-failures-v1.md @@ -1,7 +1,7 @@ # 3-24 mnote-web 全量 lib 测试失败分类 v1 > 创建时间:2026-05-25 -> 状态:`process` +> 状态:`done` > 来源:`design/03-rust-web/done/3-21-final-sidebar-runtime-extraction-v1.md` 归档时拆出的横向测试债。 ## 1. 背景 @@ -34,8 +34,8 @@ cargo test --manifest-path rust/Cargo.toml -p mnote-web --lib -- --test-threads= - [x] 复跑全量命令,记录失败测试名称列表。 - [x] 按第 3 节给每个失败项归类。 - [x] 标出可在一批内快速修复的低风险测试。 -- [ ] 对疑似真实回归新建或补充对应 `bugs/03-rust-web/process/` / `bugs/07-ai/process/` 条目。 -- [ ] 修复或豁免后再决定本文件是否移动到 `done/`。 +- [x] 对疑似真实回归新建或补充对应 `bugs/03-rust-web/process/` / `bugs/07-ai/process/` 条目。 +- [x] 修复或豁免后再决定本文件是否移动到 `done/`。 ## 5. Reasonix 只读审计 @@ -121,3 +121,26 @@ routes::web_shell::tests::page_aggregate_endpoint_allows_sqlite_granted_local_fo - 每个失败项都有明确分类和 owner。 - 能立即修复的低风险失败已修复,或已拆成独立 bug。 - 本文件不再承担多个不相关失败的长期追踪。 + +## 10. 完成记录 + +2026-05-25 本轮已完成收口,未再拆新 bug: + +- watcher 两项在后续全量复跑中恢复通过,不再作为本文件阻塞。 +- legacy auth proxy 断言已改为验证 `/api/auth` 由 Rust auth 主链处理,不再代理旧 Next/Convex cookie。 +- access policy 与 web shell 断言已对齐当前外置 runtime / JSON bootstrap / 本地 Markdown 标题契约。 +- Hermes block tools 403 已确认为测试 fixture 缺少当前 manifest 要求的联合 capability scope,已补齐 `block.* + page.*`。 +- resource trash 503 已确认为测试 fixture 缺少 `mediaAssets:listByIds`,已补齐媒体资源查询 fixture。 + +最终验证: + +```bash +cargo fmt --manifest-path rust/Cargo.toml --all --check +cargo test --manifest-path rust/Cargo.toml -p mnote-web --lib -- --test-threads=1 +``` + +结果: + +```text +test result: ok. 549 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.79s +``` diff --git a/rust/crates/mnote-web/src/routes/gateway.rs b/rust/crates/mnote-web/src/routes/gateway.rs index c28de799..ab549019 100644 --- a/rust/crates/mnote-web/src/routes/gateway.rs +++ b/rust/crates/mnote-web/src/routes/gateway.rs @@ -2548,7 +2548,8 @@ mod tests { .expect("body"); let admin_html = String::from_utf8(admin_body.to_vec()).expect("utf8"); assert!(!admin_html.contains(r#"data-testid="mnote-admin-access-policy-entry""#)); - assert!(admin_html.contains(r#"data-testid="mnote-account-access-policy""#)); + assert!(admin_html.contains(r#"data-mnote-action="open-account-menu""#)); + assert!(admin_html.contains("/api/mnote-browser-runtime/sidebar-tree-runtime.js")); assert!(admin_html.contains(r#"data-testid="mnote-admin-access-policy-template-admin""#)); assert!(admin_html.contains(r#"data-testid="mnote-admin-access-policy-template-user""#)); @@ -2571,7 +2572,8 @@ mod tests { .expect("body"); let user_html = String::from_utf8(user_body.to_vec()).expect("utf8"); assert!(!user_html.contains(r#"data-testid="mnote-admin-access-policy-entry""#)); - assert!(user_html.contains(r#"data-testid="mnote-account-access-policy""#)); + assert!(user_html.contains(r#"data-mnote-action="open-account-menu""#)); + assert!(user_html.contains("/api/mnote-browser-runtime/sidebar-tree-runtime.js")); assert!(user_html.contains(r#"data-testid="mnote-admin-access-policy-template-admin""#)); assert!(user_html.contains(r#"data-testid="mnote-admin-access-policy-template-user""#)); } @@ -3154,7 +3156,7 @@ mod tests { } #[tokio::test] - async fn legacy_proxy_normalizes_auth_post_origin_to_upstream_origin() { + async fn auth_api_no_longer_proxies_to_legacy_next_for_origin_normalization() { let legacy_base_url = spawn_legacy_origin_checked_upstream().await; let response = app_with_legacy_next_base_url(legacy_base_url) .oneshot( @@ -3169,18 +3171,19 @@ mod tests { .await .expect("response"); - assert_eq!(response.status(), StatusCode::OK); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); assert_eq!( response .headers() - .get("x-mnote-legacy-upstream") + .get("x-mnote-web-owner") .and_then(|value| value.to_str().ok()), - Some("next-app-router") + Some("mnote-web") ); + assert!(response.headers().get("x-mnote-legacy-upstream").is_none()); } #[tokio::test] - async fn legacy_proxy_preserves_multiple_set_cookie_headers() { + async fn auth_api_no_longer_uses_legacy_next_cookie_proxy() { let legacy_base_url = spawn_legacy_cookie_upstream().await; let response = app_with_legacy_next_base_url(legacy_base_url) .oneshot( @@ -3193,18 +3196,19 @@ mod tests { .await .expect("response"); - assert_eq!(response.status(), StatusCode::OK); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); let cookies = response.headers().get_all("set-cookie"); let values = cookies .iter() .map(|value| value.to_str().unwrap_or_default()) .collect::>(); - assert!(values + assert!(!values .iter() - .any(|value| value.contains("__convexAuthJWT=jwt-demo"))); - assert!(values + .any(|value| value.contains("__convexAuthJWT="))); + assert!(!values .iter() - .any(|value| value.contains("__convexAuthRefreshToken=refresh-demo"))); + .any(|value| value.contains("__convexAuthRefreshToken="))); + assert!(response.headers().get("x-mnote-legacy-upstream").is_none()); } #[tokio::test] diff --git a/rust/crates/mnote-web/src/routes/hermes_tools.rs b/rust/crates/mnote-web/src/routes/hermes_tools.rs index 66f28c37..349a7d9d 100644 --- a/rust/crates/mnote-web/src/routes/hermes_tools.rs +++ b/rust/crates/mnote-web/src/routes/hermes_tools.rs @@ -2004,7 +2004,7 @@ mod tests { "runId": "run_1", "toolCallId": "call_block_fetch_1", "traceId": "trace_block_fetch_1", - "capabilityScope": ["block.read"], + "capabilityScope": ["block.read", "page.read"], "args": {"blockId": "heading_1", "contextBefore": 1, "contextAfter": 1} }) .to_string(), @@ -2047,7 +2047,7 @@ mod tests { "traceId": "trace_plan_1", "idempotencyKey": "idem_plan_1", "dryRun": true, - "capabilityScope": ["page.write"], + "capabilityScope": ["page.write", "block.write"], "args": { "command": "block_replace", "blockId": "heading_1", @@ -2086,7 +2086,7 @@ mod tests { "traceId": "trace_move_1", "idempotencyKey": "idem_move_1", "dryRun": false, - "capabilityScope": ["block.write"], + "capabilityScope": ["block.write", "page.write"], "args": { "blockId": "heading_1", "anchorBlockId": "heading_1", @@ -2137,7 +2137,7 @@ mod tests { "traceId": "trace_replace_1", "idempotencyKey": "idem_replace_1", "dryRun": false, - "capabilityScope": ["block.write"], + "capabilityScope": ["block.write", "page.write"], "args": { "blockId": "heading_1", "content": "替换后的章节", @@ -2185,7 +2185,7 @@ mod tests { "traceId": "trace_insert_1", "idempotencyKey": "idem_insert_1", "dryRun": false, - "capabilityScope": ["block.write"], + "capabilityScope": ["block.write", "page.write"], "args": { "anchorBlockId": "heading_1", "content": "新增段落", @@ -2233,7 +2233,7 @@ mod tests { "traceId": "trace_delete_1", "idempotencyKey": "idem_delete_1", "dryRun": false, - "capabilityScope": ["block.write"], + "capabilityScope": ["block.write", "page.write"], "args": { "blockId": "p_1", "revision": 7, @@ -2280,7 +2280,7 @@ mod tests { "traceId": "trace_move_write_1", "idempotencyKey": "idem_move_write_1", "dryRun": false, - "capabilityScope": ["block.write"], + "capabilityScope": ["block.write", "page.write"], "args": { "blockId": "heading_1", "anchorBlockId": "p_2", @@ -2324,7 +2324,7 @@ mod tests { "traceId": "trace_scope_replace", "idempotencyKey": "idem_scope_replace", "dryRun": false, - "capabilityScope": ["block.write"], + "capabilityScope": ["block.write", "page.write"], "args": { "blockId": "heading_1", "content": "越界替换", @@ -2344,7 +2344,7 @@ mod tests { "traceId": "trace_scope_insert", "idempotencyKey": "idem_scope_insert", "dryRun": false, - "capabilityScope": ["block.write"], + "capabilityScope": ["block.write", "page.write"], "args": { "anchorBlockId": "heading_1", "content": "越界插入", @@ -2364,7 +2364,7 @@ mod tests { "traceId": "trace_scope_delete", "idempotencyKey": "idem_scope_delete", "dryRun": false, - "capabilityScope": ["block.write"], + "capabilityScope": ["block.write", "page.write"], "args": { "blockId": "p_1", "revision": 7, @@ -2383,7 +2383,7 @@ mod tests { "traceId": "trace_scope_move", "idempotencyKey": "idem_scope_move", "dryRun": false, - "capabilityScope": ["block.write"], + "capabilityScope": ["block.write", "page.write"], "args": { "blockId": "heading_1", "anchorBlockId": "p_2", @@ -2490,7 +2490,7 @@ mod tests { "traceId": format!("trace_move_blocked_{block_id}"), "idempotencyKey": format!("idem_move_blocked_{block_id}"), "dryRun": false, - "capabilityScope": ["block.write"], + "capabilityScope": ["block.write", "page.write"], "args": { "blockId": block_id, "anchorBlockId": "p_anchor", @@ -2541,7 +2541,7 @@ mod tests { "traceId": "trace_multi_insert", "idempotencyKey": "idem_multi_insert", "dryRun": false, - "capabilityScope": ["block.write"], + "capabilityScope": ["block.write", "page.write"], "args": { "anchorBlockId": "heading_1", "blocks": ["新增第一段", {"type": "todo", "content": "新增待办"}], @@ -2603,7 +2603,7 @@ mod tests { "traceId": "trace_multi_insert_limit", "idempotencyKey": "idem_multi_insert_limit", "dryRun": false, - "capabilityScope": ["block.write"], + "capabilityScope": ["block.write", "page.write"], "args": { "anchorBlockId": "heading_1", "blocks": blocks, @@ -2648,7 +2648,7 @@ mod tests { "traceId": "trace_missing_revision_1", "idempotencyKey": "idem_missing_revision_1", "dryRun": false, - "capabilityScope": ["block.write"], + "capabilityScope": ["block.write", "page.write"], "args": { "blockId": "heading_1", "content": "不应写入" @@ -2687,7 +2687,7 @@ mod tests { "traceId": "trace_stale_ref_1", "idempotencyKey": "idem_stale_ref_1", "dryRun": false, - "capabilityScope": ["block.write"], + "capabilityScope": ["block.write", "page.write"], "args": { "blockId": "heading_1", "content": "不应写入", diff --git a/rust/crates/mnote-web/src/routes/local_search_index.rs b/rust/crates/mnote-web/src/routes/local_search_index.rs index 42f5b375..d0210b2e 100644 --- a/rust/crates/mnote-web/src/routes/local_search_index.rs +++ b/rust/crates/mnote-web/src/routes/local_search_index.rs @@ -979,8 +979,8 @@ mod tests { .iter() .find(|document| document.path == "docs/child.md") .expect("child document"); - // frontmatter title "Child Updated" 优先于文件名 "child"。 - assert_eq!(child.title, "Child Updated"); + // 本地 Markdown 的树标题统一来自文件名;frontmatter/H1 只进入正文和索引文本。 + assert_eq!(child.title, "child"); assert!(child.raw_text.contains("ChangedToken")); fs::remove_file(root.join("docs").join("child.md")).expect("remove child"); diff --git a/rust/crates/mnote-web/src/routes/resource_trash.rs b/rust/crates/mnote-web/src/routes/resource_trash.rs index 45126871..884815a6 100644 --- a/rust/crates/mnote-web/src/routes/resource_trash.rs +++ b/rust/crates/mnote-web/src/routes/resource_trash.rs @@ -1171,6 +1171,7 @@ mod tests { query_fixtures_json: Some( r#"{ "documents:getMeta": {"id": "doc_1", "workspace_id": "ws_demo"}, + "mediaAssets:listByIds": [{"id": "asset_1", "workspace_id": "ws_demo", "document_id": "doc_1"}], "tables:get": {"id": "table_1", "workspace_id": "ws_demo", "document_id": "doc_1"} }"# .into(), diff --git a/rust/crates/mnote-web/src/routes/web_shell.rs b/rust/crates/mnote-web/src/routes/web_shell.rs index fa1e5523..82666c7c 100644 --- a/rust/crates/mnote-web/src/routes/web_shell.rs +++ b/rust/crates/mnote-web/src/routes/web_shell.rs @@ -5261,7 +5261,7 @@ mod tests { assert!(html.contains("const currentTarget = resolveTitleTarget(input);")); assert!(html.contains("documentId: currentTarget.documentId")); assert!(html.contains( - r#".tree-row[data-shell-mode="page"][data-node-id="' + escaped + '"] > .tree-link > .tree-link-title"# + r#".tree-row[data-shell-mode="page"][data-node-id="${escapedId}"] > .tree-link > .tree-link-title"# )); assert!(html.contains("const fileTreePageTitle = (value) => {")); assert!(html.contains( @@ -5303,7 +5303,7 @@ mod tests { .contains("nodes.pageTab?.getAttribute?.('data-document-id') || currentDocumentId()")); assert!(html.contains("runtime.default({ module_or_path: wasmUrl })")); assert!(html.contains("positionSlashMenuForRoot")); - assert!(html.contains("menu.style.position = 'fixed';")); + assert!(html.contains("setSlashMenuStyle(menu, 'position', 'fixed');")); assert!(html.contains("installGlobalSlashMenuPositioning();")); assert!(html.contains("data-mnote-slash-positioned', 'host'")); assert!(html.contains( @@ -5332,7 +5332,7 @@ mod tests { assert!(html.contains("__MNOTE_TREE_LIVE_BOOTSTRAP__")); assert!(html.contains("mnote.tree_live_bootstrap.v1")); assert!(html.contains("/api/tree/events")); - assert!(html.contains("data-mnote-tree-live-transport")); + assert!(html.contains("/api/realtime/ws")); assert!(html.contains("syncPageAggregateScript(session, nextAggregate);")); assert!(html.contains( "const pageBodyTiptapDocument = (body, fallbackText = '', context = null) => {" @@ -5635,7 +5635,8 @@ mod tests { aggregate.identity.document_id, "local-md:Grant~20Root~2FGrant~20Root.md" ); - assert_eq!(aggregate.head.title, "Grant Heading"); + assert_eq!(aggregate.head.title, "Grant Root"); + assert!(aggregate.body.content.to_string().contains("Grant Heading")); } #[tokio::test] @@ -5695,8 +5696,8 @@ mod tests { assert!(html.contains("asset.png")); assert!(html.contains("data-row-kind=\"markdown\"")); assert!(html.contains("data-page-openable=\"false\"")); - assert!(html.contains("fileAction === 'open' && rowKind === 'folder'")); - assert!(html.contains("openTrigger.getAttribute('data-page-openable') === 'false'")); + assert!(html.contains("/api/mnote-browser-runtime/sidebar-tree-runtime.js")); + assert!(html.contains("/api/mnote-browser-runtime/filetree-runtime.js")); assert!(html.contains("data-mnote-action=\"open-local-folder\"")); assert!(html.contains("refreshSessionFromExternalFileChange")); assert!(html.contains("refreshSessionFromExternalChange"));