test(core): 使用隔离会话验证二十次打包冷启动

This commit is contained in:
2026-09-08 20:02:30 +08:00
parent 9e295b9a5d
commit 4ff8fdbd70
2 changed files with 65 additions and 0 deletions
@@ -4,6 +4,12 @@
**状态:实施中,未 done,未通过生产发布门禁。** 本文记录工程进度,不将单元测试或开发机运行替代[生产化计划](../architecture/第三阶段生产化工程规划与验收目标.md)中 AE 的完整验收。
## 当前状态摘要
截至 `7b69537`Host Sync capability 已开启,扩展 capability 仍关闭。笔记与 Task CRUD 已接入当前 Rust Vault;同步具备上传/拉取、冲突解决、非空初始合并、100 MiB 续传、主题和编辑器偏好、持久退避。Workspace 当前 schema 9。凭据备份/恢复及 WTS 撤销已实现,但实机锁屏验收仍缺。
最近完整测试:后端 900 项(Task 增量后)、前端 517 项、Rust desktop 52 项;后两者在重试持久化增量后复跑。以下各节为历史增量,出现的旧 capability、旧 schema 和旧测试数量描述对应当时状态,以本摘要及后续增量为准。OS 沙箱、Rust 扩展管理、剩余逻辑数据类别、完整故障/性能/发布验收仍未完成。
## 持续实施增量
- Rust 同步新增 schema 4 inbox/boundary、稳定远端 file_id、remote 来源的移动/删除日志和冲突保留。真实 HTTP 双客户端测试覆盖历史拉取、零回流、本机提交回放保护和同改冲突;文件提交前后的 inbox 重启测试通过。冲突解决和用户界面正在继续实施,仍不开放完整 Sync capability。
@@ -116,3 +122,10 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写
- 取消/切换绑定不增加失败次数,旧绑定回调不能修改新绑定;保存的错误仅允许有长度限制的机器码,不保存服务端任意响应正文。Retry-After 和指数退避保持 1–3600 秒限制。界面对停止自动重试状态不再显示误导性的倒计时。
- 连续关闭重开 20 次验证失败次数与时间保留,另验证认证停止、状态清除、取消、错误正文过滤和新旧绑定隔离。当前是绑定级轮次状态;每个上传作业的独立尝试历史、强制刷新后的 401 重试和完整服务故障矩阵仍需完成。
- 全量前端 97 文件 / 517 项、Rust desktop 全目标 52 项通过(另 1 个父测试驱动并强杀的辅助入口);TypeScript 两项目检查和 Clippy `-D warnings` 通过。日志 `.build/retry-frontend-tests.log``.build/retry-rust-tests.log`。Core 打包正在更新,不视为签名发布包。
## 增量:最新 Core 打包与 20 次冷启动
- 使用锁定 packaging 环境重新生成 PyInstaller onedir,清单包含 1523 个文件。构建日志 `.build/core-build-production.log`,产物 `.build/sidecar/dist/opennexus-core``.build/sidecar/manifest.json`
- 新增显式打包测试 `cargo test --locked --features desktop --test core_bundle packaged_core_twenty -- --ignored`,调用实际可执行文件并在每次启动前校验完整文件清单。20 个独立临时数据目录、20 个不同会话代际、带鉴权和代际头的 HTTP 健康请求全部成功,逐次退出后端口关闭。测试耗时 95.64 秒,日志 `.build/core-packaged-cold-starts.log`。初版测试遗漏代际请求头导致拒绝,修正测试后通过。
- 该测试按要求先构建后显式运行,普通源码全目标测试会跳过此入口。Clippy `-D warnings` 通过。此证据来自当前 Windows 开发机,不能替代干净 VM、MSVC 签名安装包、两台真实设备或完整 A-01 验收。
+52
View File
@@ -19,3 +19,55 @@ fn bundle_rejects_modified_missing_and_extra_files() {
std::fs::remove_file(file).unwrap();
assert!(verify_bundle(dir.path(), manifest).is_err());
}
#[test]
#[ignore = "requires scripts/build-core.py; run explicitly after building the isolated Core"]
fn packaged_core_twenty_cold_starts_use_isolated_data_and_rotate_sessions() {
use notesagent_host::core::CoreSupervisor;
use std::{
collections::HashSet,
io::{Read, Write},
path::Path,
time::Duration,
};
let output = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../.build/sidecar");
let bundle = output.join("dist/opennexus-core").canonicalize().unwrap();
let manifest = std::fs::read_to_string(output.join("manifest.json")).unwrap();
let executable = bundle.join(if cfg!(windows) {
"opennexus-core.exe"
} else {
"opennexus-core"
});
let temp = tempfile::tempdir().unwrap();
let mut generations = HashSet::new();
for attempt in 0..20 {
let mut core = CoreSupervisor::new(
executable.clone(),
vec![],
bundle.clone(),
temp.path().join(format!("run-{attempt}")),
)
.with_bundle_manifest(manifest.clone());
let request = core.request_session("/health").unwrap();
assert!(generations.insert(request.generation.clone()));
let endpoint = request
.url
.trim_start_matches("http://")
.trim_end_matches("/health");
let mut stream = std::net::TcpStream::connect(endpoint).unwrap();
stream
.set_read_timeout(Some(Duration::from_secs(20)))
.unwrap();
write!(stream, "GET /health HTTP/1.1\r\nHost: {endpoint}\r\nAuthorization: {}\r\nX-Core-Generation: {}\r\nConnection: close\r\n\r\n", request.authorization.as_str(), request.generation).unwrap();
let mut response = String::new();
stream.read_to_string(&mut response).unwrap();
assert!(
response.starts_with("HTTP/1.1 200"),
"packaged health failed on attempt {attempt}"
);
assert!(!response.contains(request.authorization.as_str()));
drop(stream);
drop(core);
assert!(std::net::TcpStream::connect(endpoint).is_err());
}
}