优化: UI/UX遗留批(审计/AI命令层/前端组件,会话前基线收尾)
- AuditLog +298(筛选/详情/i18n)+ audit 后端 record/mod - AI 命令层:generate_image +81 / fetch_url / fetch_search / skills / tool_registry / tools/file / provider / conversation - 前端组件:AiChat/TopBar/ConversationSidebar/GitChanges/ApprovalPopup/Dashboard/ProjectDetail 等 30+ + composables + i18n - 诊断文档: aichat历史会话实证诊断-2026-08-04 + project_soft_delete 测试
This commit is contained in:
@@ -475,6 +475,7 @@ fn body_snippet(v: &Value) -> String {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// ── build_images_url:端点拼接规则 ──
|
||||
|
||||
@@ -641,8 +642,6 @@ mod tests {
|
||||
// 边界:估算值 = MAX_IMAGE_BYTES + 1 → 应被拒
|
||||
// 反推 b64 长度:要使 len*3/4 == MAX+1,len = (MAX+1)*4/3
|
||||
let target = MAX_IMAGE_BYTES + 1;
|
||||
let b64_len = (target as u128 * 4 / 3) as usize;
|
||||
let estimated = estimate_decoded_bytes(b64_len);
|
||||
// 因整数除法,估算可能略小于 target,但应保证 > MAX_IMAGE_BYTES
|
||||
// 用更精确的 b64_len:target*4/3 + 4(向上补一个 base64 块),保证估算严格 > MAX
|
||||
let b64_len_safe = ((target as u128 * 4 + 2) / 3) as usize;
|
||||
@@ -795,4 +794,82 @@ mod tests {
|
||||
msg
|
||||
);
|
||||
}
|
||||
|
||||
// ── 真实 SenseNova U1-Fast 图像生成验证(#[ignore],手动跑) ──
|
||||
//
|
||||
// 端到端验证:用真实 db 副本(SenseNova provider 配置)+ OS keyring 解析 api_key +
|
||||
// 真实调 https://token.sensenova.cn/v1/images/generations 生成一张测试图并下载落地。
|
||||
// 覆盖 generate_image 全链路:provider 选择(host 匹配)/ build_images_url 端点拼接 /
|
||||
// 请求构造 / 响应解析 / SSRF 防护下载落地。
|
||||
//
|
||||
// 运行方式:
|
||||
// set DEVFLOW_LIVE_DB=<真实 devflow.db 路径(含 wal)>
|
||||
// cargo test -p devflow --lib -- --ignored live_sensenova_u1_fast --nocapture
|
||||
//
|
||||
// 需真实 API 凭证(付费),默认 #[ignore] 不随 CI 跑。落盘到 <cwd>/tmp_generated/ 便于清理。
|
||||
|
||||
/// 构造带持久授权目录的 AllowedDirs,指向项目内 tmp_generated/ 输出目录。
|
||||
fn mk_allowed_with_generated_dir() -> (AllowedDirs, PathBuf) {
|
||||
let out_dir = std::env::current_dir()
|
||||
.expect("读 cwd")
|
||||
.join("tmp_generated");
|
||||
let mut persistent = std::collections::HashSet::new();
|
||||
persistent.insert(out_dir.clone());
|
||||
(AllowedDirs { persistent, session: Default::default(), once: Default::default() }, out_dir)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // 真实 API 调用(付费),需 DEVFLOW_LIVE_DB 环境变量,手动跑
|
||||
async fn live_sensenova_u1_fast() {
|
||||
let db_path = std::env::var("DEVFLOW_LIVE_DB")
|
||||
.map(PathBuf::from)
|
||||
.expect("请设置 DEVFLOW_LIVE_DB=<真实 devflow.db 路径> 以跑真实 API 验证");
|
||||
assert!(
|
||||
db_path.exists(),
|
||||
"DEVFLOW_LIVE_DB 指向的 db 不存在: {}",
|
||||
db_path.display()
|
||||
);
|
||||
|
||||
// 打开真实 db(会跑迁移,用真实路径;读 SenseNova provider + keyring 解析 key)
|
||||
let db = Arc::new(Database::open(&db_path).await.expect("打开真实 db"));
|
||||
let (allowed, out_dir) = mk_allowed_with_generated_dir();
|
||||
|
||||
// 走 execute_generate_image 全链路:model 默认 sensenova-u1-fast → host 匹配 SenseNova provider
|
||||
let args = json!({
|
||||
"prompt": "一张极简科技风信息图,主题「DevFlow AI 工作流」,顶部大标题,三个步骤图标(规划→执行→核查),浅蓝灰配色",
|
||||
"size": "1536x2752",
|
||||
"n": 1
|
||||
});
|
||||
let result = execute_generate_image(args, &db, &Arc::new(RwLock::new(allowed)))
|
||||
.await
|
||||
.expect("execute_generate_image 应成功生成并下载图片");
|
||||
|
||||
let path = result["path"].as_str().expect("result 应有 path");
|
||||
let bytes = result["bytes_written"].as_u64().expect("result 应有 bytes_written");
|
||||
let model = result["model"].as_str().unwrap_or("");
|
||||
let provider_id = result["provider_id"].as_str().unwrap_or("");
|
||||
println!("✅ 生成成功:");
|
||||
println!(" model : {}", model);
|
||||
println!(" provider : {}", provider_id);
|
||||
println!(" 本地路径 : {}", path);
|
||||
println!(" 字节数 : {}", bytes);
|
||||
|
||||
// 断言:文件真实落盘 + 非空 + 是图片(PNG 魔数)
|
||||
assert!(
|
||||
std::path::Path::new(path).exists(),
|
||||
"图片应真实落盘: {}",
|
||||
path
|
||||
);
|
||||
assert!(bytes > 1000, "图片应 > 1KB,实际 {} bytes", bytes);
|
||||
let head = std::fs::read(path).expect("读图片头").into_iter().take(8).collect::<Vec<_>>();
|
||||
assert!(
|
||||
head.starts_with(&[0x89, b'P', b'N', b'G']) || head.starts_with(&[0xFF, 0xD8, 0xFF]),
|
||||
"应为 PNG/JPEG 魔数,实际头字节: {:?}",
|
||||
head
|
||||
);
|
||||
|
||||
// 清理输出目录(测后不留临时图)
|
||||
let _ = std::fs::remove_dir_all(&out_dir);
|
||||
println!(" (输出目录已清理:{})", out_dir.display());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user