修正 platform-image mock 请求体读取竞态

- nanobanana 生成用例的 mock server 改为复用同文件 read_http_request,按 Content-Length 读满请求体再断言,消除「只读到 \r\n\r\n 就断言头后 body」的竞态
- pitfalls 新增「测试内 mock HTTP server 必须按 Content-Length 读满请求体再断言」记录现象、原因与变异验证方式
This commit is contained in:
2026-09-22 11:46:43 +08:00
parent 97b0a4b9b8
commit 71a17d6096
2 changed files with 9 additions and 14 deletions
@@ -7,6 +7,14 @@
- 处理:客户端画布图标生成只 trim 描述并以单项 `iconDescriptions` 原样提交,保留内部换行;面板与原生入口按 `200` 个 Unicode 码点校验并拒绝空白或超限输入,不静默截断、不机械拆条。服务端共用的提示词流程负责规范图、背景与排布要求;其它图片生成的 `32000` 字符上限保持不变。
- 验证:检查实际请求体与 trim 后的原文一致,并覆盖 `200/201` 码点、补充平面字符、换行和空白输入;不能只断言“请求长度未超限”。完整合同见 [画板图标素材生成入口设计](../../【编辑器】画板图标素材生成入口设计-2026-06-15.md)。
## 测试内 mock HTTP server 必须按 Content-Length 读满请求体再断言
- 现象:`platform-image``nanobanana_generate_content_posts_native_body_and_reads_inline_data` 在本机(Windows)稳定红、在 CI 上存在偶发红,断言 `request_text.contains("\"imageSize\":\"512\"")` 失败,看起来像实现没有发送请求体。
- 原因:该 mock 用例自带一版「读到第一个 `\r\n\r\n` 就 break」的读循环,而 `\r\n\r\n` 只代表请求头结束;reqwest 写完头再写 bodyTCP 分片下 body 常常还没到。断言实际只看到头和半截 body,是测试脚手架的竞态,与平台 / 网络栈无关,被测代码本身没有问题。
- 处理:mock server 一律复用同文件已有的 `read_http_request`,先解析 `Content-Length`,读满 `header_end + content_length` 再断言;同文件的 `image_edit_retries_send_timeout_once_and_succeeds``image_provider_deadline_clips_stalled_attempt_and_prevents_retry` 早已使用该 helper。
- 验证:把断言期望值临时改成错误值,用例必须失败(证明读到了真实 body);改回后期望连续重跑 8 次全绿,并跑 `cargo test -p platform-image` 全量(22 + 64 + 14 passed)。
- 关联:`server-rs/crates/platform-image/tests/image_provider.rs`
## 2026-09-21 不同渠道的包体在同一台设备安装会互相顶掉
- **现象**:在一台已经装了某个渠道 AGC 客户端的设备上安装另一个渠道的安装包,装完后旧客户端直接消失(安装目录被覆盖、卸载项被接管),更新端点、平台服务器与本地登录态一起换成新渠道的;两个渠道的客户端无法共存。
@@ -443,20 +443,7 @@ async fn nanobanana_generate_content_posts_native_body_and_reads_inline_data() {
let Ok((mut stream, _)) = listener.accept().await else {
return;
};
let mut request = Vec::new();
let mut buffer = [0_u8; 4096];
loop {
let Ok(read) = stream.read(&mut buffer).await else {
return;
};
if read == 0 {
return;
}
request.extend_from_slice(&buffer[..read]);
if request.windows(4).any(|window| window == b"\r\n\r\n") {
break;
}
}
let request = read_http_request(&mut stream).await;
let request_text = String::from_utf8_lossy(request.as_slice());
assert!(
request_text.contains("/v1beta/models/gemini-3.1-flash-image-preview:generateContent")