优化: 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:
@@ -5,9 +5,16 @@
|
||||
//! 2. soft_delete / list_active / list_deleted / restore — 回收站生命周期。
|
||||
//! 3. allowed_columns_for 按表隔离 — update_field 误传跨表列名在白名单阶段被拒。
|
||||
|
||||
use df_storage::crud::{BranchRepo, ProjectRepo, ReleaseRepo, TaskRepo};
|
||||
use df_storage::crud::{
|
||||
BranchRepo, ModuleDependencyRepo, NodeExecutionRepo, ProjectEventRepo, ProjectModuleRepo,
|
||||
ProjectRepo, ProjectServiceRepo, ReleaseRepo, TaskLinkRepo, TaskRepo, WorkflowRepo,
|
||||
};
|
||||
use df_storage::db::Database;
|
||||
use df_storage::models::{BranchRecord, ProjectRecord, ReleaseRecord, TaskRecord};
|
||||
use df_storage::models::{
|
||||
BranchRecord, ModuleDependencyRecord, NodeExecutionRecord, ProjectEventRecord,
|
||||
ProjectModuleRecord, ProjectRecord, ProjectServiceRecord, ReleaseRecord, TaskRecord,
|
||||
WorkflowRecord,
|
||||
};
|
||||
use df_types::types::{ProjectStatus, TaskStatus};
|
||||
|
||||
// ---------- fixtures ----------
|
||||
@@ -153,6 +160,195 @@ async fn purge_with_descendants_is_atomic_on_missing_child_table_row() {
|
||||
assert!(!projects.purge_with_descendants("p1").await.unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn purge_with_descendants_cascades_v29_v35_new_tables() {
|
||||
// G1.2:purge 级联补全 V29-V35 新增表(project_modules/module_dependencies/
|
||||
// project_events/project_services/task_links)+ 经 workflow_executions 间接到项目的
|
||||
// node_executions。此前 purge 只删 branches/releases/tasks/projects 四表,带
|
||||
// 模块/事件/服务的工程在 PRAGMA foreign_keys=ON(无 ON DELETE CASCADE)下必 FK
|
||||
// 违例回滚(latent bug)。本测试断言全部关联子表按 FK 拓扑 最深子表→父 级联清空。
|
||||
let db = Database::open_in_memory().await.expect("open_in_memory");
|
||||
let projects = ProjectRepo::new(&db);
|
||||
let tasks = TaskRepo::new(&db);
|
||||
let releases = ReleaseRepo::new(&db);
|
||||
let branches = BranchRepo::new(&db);
|
||||
let workflows = WorkflowRepo::new(&db);
|
||||
let node_execs = NodeExecutionRepo::new(&db);
|
||||
let events = ProjectEventRepo::new(&db);
|
||||
let services = ProjectServiceRepo::new(&db);
|
||||
let modules = ProjectModuleRepo::new(&db);
|
||||
let deps = ModuleDependencyRepo::new(&db);
|
||||
let links = TaskLinkRepo::new(&db);
|
||||
|
||||
// p1 全谱系子记录(V1 四表 + V29-V35 新表)
|
||||
projects.insert(project("p1")).await.unwrap();
|
||||
tasks.insert(task("t1", "p1")).await.unwrap();
|
||||
tasks.insert(task("t2", "p1")).await.unwrap();
|
||||
releases.insert(release("r1", "p1")).await.unwrap();
|
||||
branches.insert(branch("b1", "p1")).await.unwrap();
|
||||
workflows
|
||||
.insert(WorkflowRecord {
|
||||
id: "w1".into(),
|
||||
name: "wf".into(),
|
||||
dag_json: "{}".into(),
|
||||
status: "pending".into(),
|
||||
triggered_by: None,
|
||||
project_id: Some("p1".into()),
|
||||
task_id: None,
|
||||
created_at: now_ts(),
|
||||
completed_at: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
node_execs
|
||||
.insert(NodeExecutionRecord {
|
||||
id: "n1".into(),
|
||||
workflow_id: "w1".into(),
|
||||
node_id: "node1".into(),
|
||||
node_type: "ai".into(),
|
||||
status: "pending".into(),
|
||||
input_json: None,
|
||||
output_json: None,
|
||||
error_message: None,
|
||||
started_at: None,
|
||||
completed_at: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
events
|
||||
.insert(ProjectEventRecord {
|
||||
id: "e1".into(),
|
||||
project_id: "p1".into(),
|
||||
event_type: "task_created".into(),
|
||||
entity_type: Some("task".into()),
|
||||
entity_id: Some("t1".into()),
|
||||
from_state: None,
|
||||
to_state: None,
|
||||
context_json: None,
|
||||
source: Some("human".into()),
|
||||
conversation_id: None,
|
||||
created_at: now_ts(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
services
|
||||
.insert_validated(ProjectServiceRecord {
|
||||
id: "s1".into(),
|
||||
project_id: "p1".into(),
|
||||
name: "db".into(),
|
||||
service_type: "mysql".into(),
|
||||
endpoint: Some("localhost:3306".into()),
|
||||
config_json: None,
|
||||
environment: "development".into(),
|
||||
remark: None,
|
||||
created_at: now_ts(),
|
||||
updated_at: now_ts(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
modules
|
||||
.insert(ProjectModuleRecord {
|
||||
id: "m1".into(),
|
||||
project_id: "p1".into(),
|
||||
name: "mod-1".into(),
|
||||
path: "C:/p1/mod1".into(),
|
||||
git_url: None,
|
||||
stack: None,
|
||||
auto_detected: false,
|
||||
sort_order: 0,
|
||||
created_at: now_ts(),
|
||||
updated_at: now_ts(),
|
||||
description: None,
|
||||
status: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
modules
|
||||
.insert(ProjectModuleRecord {
|
||||
id: "m2".into(),
|
||||
project_id: "p1".into(),
|
||||
name: "mod-2".into(),
|
||||
path: "C:/p1/mod2".into(),
|
||||
git_url: None,
|
||||
stack: None,
|
||||
auto_detected: false,
|
||||
sort_order: 1,
|
||||
created_at: now_ts(),
|
||||
updated_at: now_ts(),
|
||||
description: None,
|
||||
status: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
deps
|
||||
.insert(ModuleDependencyRecord {
|
||||
id: "d1".into(),
|
||||
project_id: "p1".into(),
|
||||
from_module_id: "m1".into(),
|
||||
to_module_id: "m2".into(),
|
||||
dep_type: "library".into(),
|
||||
label: None,
|
||||
created_at: now_ts(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
links
|
||||
.create_link("l1", "t1", "t2", "relates_to", None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 邻居项目 p2 只插一个 module,验证隔离(不波及)
|
||||
projects.insert(project("p2")).await.unwrap();
|
||||
modules
|
||||
.insert(ProjectModuleRecord {
|
||||
id: "mp2".into(),
|
||||
project_id: "p2".into(),
|
||||
name: "mod-p2".into(),
|
||||
path: "C:/p2/mod".into(),
|
||||
git_url: None,
|
||||
stack: None,
|
||||
auto_detected: false,
|
||||
sort_order: 0,
|
||||
created_at: now_ts(),
|
||||
updated_at: now_ts(),
|
||||
description: None,
|
||||
status: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let affected = projects.purge_with_descendants("p1").await.unwrap();
|
||||
assert!(affected, "应命中 p1");
|
||||
|
||||
// p1 全谱系子记录消失(最深子表→父逐层清空)
|
||||
assert!(projects.get_by_id("p1").await.unwrap().is_none());
|
||||
assert!(tasks.get_by_id("t1").await.unwrap().is_none());
|
||||
assert!(tasks.get_by_id("t2").await.unwrap().is_none());
|
||||
assert!(releases.get_by_id("r1").await.unwrap().is_none());
|
||||
assert!(branches.get_by_id("b1").await.unwrap().is_none());
|
||||
assert!(workflows.get_by_id("w1").await.unwrap().is_none());
|
||||
assert!(node_execs.get_by_id("n1").await.unwrap().is_none());
|
||||
assert!(links.get_by_id("l1").await.unwrap().is_none(), "task_links 应级联清空");
|
||||
assert!(modules.get_by_id("m1").await.unwrap().is_none());
|
||||
assert!(modules.get_by_id("m2").await.unwrap().is_none());
|
||||
assert!(
|
||||
deps.list_by_field("project_id", "p1").await.unwrap().is_empty(),
|
||||
"module_dependencies 应级联清空"
|
||||
);
|
||||
assert!(
|
||||
events.get_by_project("p1", 200).await.unwrap().is_empty(),
|
||||
"project_events 应级联清空"
|
||||
);
|
||||
assert!(
|
||||
services.list_by_project("p1").await.unwrap().is_empty(),
|
||||
"project_services 应级联清空"
|
||||
);
|
||||
|
||||
// 邻居 p2 完好(其 module 未被波及)
|
||||
assert!(projects.get_by_id("p2").await.unwrap().is_some());
|
||||
assert!(modules.get_by_id("mp2").await.unwrap().is_some());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 2. soft_delete / list_active / list_deleted / restore
|
||||
// ============================================================
|
||||
|
||||
Reference in New Issue
Block a user