Files
mixvideo-v2/apps/desktop/src-tauri/src/tests/project_template_binding_tests.rs
imeepos 730402aba0 feat: 实现项目-模板绑定和素材-模特绑定管理功能
新功能:
- 项目-模板绑定管理系统
  - 支持主要/次要模板绑定类型
  - 绑定优先级和状态管理
  - 批量绑定操作
  - 绑定关系的CRUD操作

- 素材-模特绑定管理系统
  - 素材与模特的关联管理
  - 批量绑定/解绑操作
  - 绑定统计和分析
  - 素材编辑对话框

 架构改进:
- 新增项目-模板绑定数据模型和仓库层
- 新增素材-模特绑定业务服务层
- 完善的API命令层实现
- 响应式前端界面设计

 用户体验优化:
- 统一的通知系统
- 增强的加载状态组件
- 流畅的交互动画
- 优雅的确认对话框

 测试覆盖:
- 单元测试和集成测试
- 业务逻辑验证
- API接口测试

 技术栈:
- 后端: Rust + Tauri + SQLite
- 前端: React + TypeScript + TailwindCSS
- 状态管理: Zustand
- 测试: Vitest + Rust测试框架

 配置更新:
- 更新数据库迁移脚本
- 完善测试配置
- 优化构建流程
2025-07-15 12:50:30 +08:00

380 lines
14 KiB
Rust

/**
* 项目-模板绑定功能测试
* 遵循 Tauri 开发规范的测试设计原则
*/
#[cfg(test)]
mod tests {
use super::*;
use crate::data::models::project_template_binding::{
ProjectTemplateBinding, BindingType, BindingStatus,
CreateProjectTemplateBindingRequest, UpdateProjectTemplateBindingRequest,
ProjectTemplateBindingQueryParams, BatchCreateProjectTemplateBindingRequest,
};
use crate::data::repositories::project_template_binding_repository::ProjectTemplateBindingRepository;
use crate::business::services::project_template_binding_service::ProjectTemplateBindingService;
use crate::infrastructure::database::Database;
use std::sync::{Arc, Mutex};
use uuid::Uuid;
/// 创建测试数据库连接
fn create_test_database() -> Arc<Mutex<rusqlite::Connection>> {
let conn = rusqlite::Connection::open_in_memory().unwrap();
// 创建项目-模板绑定表
conn.execute(
"CREATE TABLE project_template_bindings (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL,
template_id TEXT NOT NULL,
binding_name TEXT,
description TEXT,
priority INTEGER DEFAULT 0,
is_active INTEGER DEFAULT 1,
binding_type TEXT NOT NULL,
binding_status TEXT NOT NULL,
metadata TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE (project_id, template_id)
)",
[],
).unwrap();
Arc::new(Mutex::new(conn))
}
/// 创建测试绑定请求
fn create_test_binding_request() -> CreateProjectTemplateBindingRequest {
CreateProjectTemplateBindingRequest {
project_id: Uuid::new_v4().to_string(),
template_id: Uuid::new_v4().to_string(),
binding_name: Some("测试绑定".to_string()),
description: Some("这是一个测试绑定".to_string()),
priority: Some(1),
binding_type: BindingType::Primary,
}
}
#[test]
fn test_create_project_template_binding() {
let conn = create_test_database();
let repository = ProjectTemplateBindingRepository::new(conn);
let request = create_test_binding_request();
let result = repository.create(request.clone());
assert!(result.is_ok());
let binding = result.unwrap();
assert_eq!(binding.project_id, request.project_id);
assert_eq!(binding.template_id, request.template_id);
assert_eq!(binding.binding_name, request.binding_name);
assert_eq!(binding.description, request.description);
assert_eq!(binding.priority, request.priority.unwrap());
assert_eq!(binding.binding_type, request.binding_type);
assert_eq!(binding.binding_status, BindingStatus::Active);
assert!(binding.is_active);
}
#[test]
fn test_get_binding_by_id() {
let conn = create_test_database();
let repository = ProjectTemplateBindingRepository::new(conn);
let request = create_test_binding_request();
// 创建绑定
let created_binding = repository.create(request).unwrap();
// 获取绑定
let result = repository.get_by_id(&created_binding.id);
assert!(result.is_ok());
let binding = result.unwrap();
assert!(binding.is_some());
let binding = binding.unwrap();
assert_eq!(binding.id, created_binding.id);
assert_eq!(binding.project_id, created_binding.project_id);
assert_eq!(binding.template_id, created_binding.template_id);
}
#[test]
fn test_update_binding() {
let conn = create_test_database();
let repository = ProjectTemplateBindingRepository::new(conn);
let request = create_test_binding_request();
// 创建绑定
let created_binding = repository.create(request).unwrap();
// 更新绑定
let update_request = UpdateProjectTemplateBindingRequest {
binding_name: Some("更新后的绑定名称".to_string()),
description: Some("更新后的描述".to_string()),
priority: Some(5),
binding_type: Some(BindingType::Secondary),
binding_status: Some(BindingStatus::Paused),
is_active: Some(false),
};
let result = repository.update(&created_binding.id, update_request);
assert!(result.is_ok());
let updated_binding = result.unwrap();
assert!(updated_binding.is_some());
let updated_binding = updated_binding.unwrap();
assert_eq!(updated_binding.binding_name, Some("更新后的绑定名称".to_string()));
assert_eq!(updated_binding.description, Some("更新后的描述".to_string()));
assert_eq!(updated_binding.priority, 5);
assert_eq!(updated_binding.binding_type, BindingType::Secondary);
assert_eq!(updated_binding.binding_status, BindingStatus::Paused);
assert!(!updated_binding.is_active);
}
#[test]
fn test_delete_binding() {
let conn = create_test_database();
let repository = ProjectTemplateBindingRepository::new(conn);
let request = create_test_binding_request();
// 创建绑定
let created_binding = repository.create(request).unwrap();
// 删除绑定
let result = repository.delete(&created_binding.id);
assert!(result.is_ok());
assert!(result.unwrap());
// 验证绑定已被删除
let get_result = repository.get_by_id(&created_binding.id);
assert!(get_result.is_ok());
assert!(get_result.unwrap().is_none());
}
#[test]
fn test_list_bindings_with_filters() {
let conn = create_test_database();
let repository = ProjectTemplateBindingRepository::new(conn);
let project_id = Uuid::new_v4().to_string();
// 创建多个绑定
let requests = vec![
CreateProjectTemplateBindingRequest {
project_id: project_id.clone(),
template_id: Uuid::new_v4().to_string(),
binding_name: Some("主要绑定".to_string()),
description: None,
priority: Some(0),
binding_type: BindingType::Primary,
},
CreateProjectTemplateBindingRequest {
project_id: project_id.clone(),
template_id: Uuid::new_v4().to_string(),
binding_name: Some("次要绑定".to_string()),
description: None,
priority: Some(1),
binding_type: BindingType::Secondary,
},
CreateProjectTemplateBindingRequest {
project_id: Uuid::new_v4().to_string(), // 不同项目
template_id: Uuid::new_v4().to_string(),
binding_name: Some("其他项目绑定".to_string()),
description: None,
priority: Some(0),
binding_type: BindingType::Primary,
},
];
for request in requests {
repository.create(request).unwrap();
}
// 测试按项目ID过滤
let query_params = ProjectTemplateBindingQueryParams {
project_id: Some(project_id.clone()),
template_id: None,
binding_type: None,
binding_status: None,
is_active: None,
limit: None,
offset: None,
};
let result = repository.list(query_params);
assert!(result.is_ok());
let bindings = result.unwrap();
assert_eq!(bindings.len(), 2);
// 验证排序(按优先级排序)
assert_eq!(bindings[0].priority, 0);
assert_eq!(bindings[1].priority, 1);
// 测试按绑定类型过滤
let query_params = ProjectTemplateBindingQueryParams {
project_id: Some(project_id),
template_id: None,
binding_type: Some(BindingType::Primary),
binding_status: None,
is_active: None,
limit: None,
offset: None,
};
let result = repository.list(query_params);
assert!(result.is_ok());
let bindings = result.unwrap();
assert_eq!(bindings.len(), 1);
assert_eq!(bindings[0].binding_type, BindingType::Primary);
}
#[test]
fn test_batch_create_bindings() {
let conn = create_test_database();
let repository = ProjectTemplateBindingRepository::new(conn);
let project_id = Uuid::new_v4().to_string();
let template_ids = vec![
Uuid::new_v4().to_string(),
Uuid::new_v4().to_string(),
Uuid::new_v4().to_string(),
];
let request = BatchCreateProjectTemplateBindingRequest {
project_id: project_id.clone(),
template_ids: template_ids.clone(),
binding_type: BindingType::Secondary,
priority_start: Some(10),
};
let result = repository.batch_create(request);
assert!(result.is_ok());
let bindings = result.unwrap();
assert_eq!(bindings.len(), 3);
// 验证优先级递增
for (i, binding) in bindings.iter().enumerate() {
assert_eq!(binding.project_id, project_id);
assert_eq!(binding.template_id, template_ids[i]);
assert_eq!(binding.binding_type, BindingType::Secondary);
assert_eq!(binding.priority, 10 + i as u32);
}
}
#[test]
fn test_binding_exists() {
let conn = create_test_database();
let repository = ProjectTemplateBindingRepository::new(conn);
let request = create_test_binding_request();
// 绑定不存在时
let result = repository.exists(&request.project_id, &request.template_id);
assert!(result.is_ok());
assert!(!result.unwrap());
// 创建绑定
repository.create(request.clone()).unwrap();
// 绑定存在时
let result = repository.exists(&request.project_id, &request.template_id);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn test_unique_constraint() {
let conn = create_test_database();
let repository = ProjectTemplateBindingRepository::new(conn);
let request = create_test_binding_request();
// 第一次创建应该成功
let result1 = repository.create(request.clone());
assert!(result1.is_ok());
// 第二次创建相同的项目-模板绑定应该失败
let result2 = repository.create(request);
assert!(result2.is_err());
}
#[tokio::test]
async fn test_business_service_create_binding() {
let conn = create_test_database();
let service = ProjectTemplateBindingService::new(conn).unwrap();
let request = create_test_binding_request();
let result = service.create_binding(request.clone()).await;
assert!(result.is_ok());
let binding = result.unwrap();
assert_eq!(binding.project_id, request.project_id);
assert_eq!(binding.template_id, request.template_id);
}
#[tokio::test]
async fn test_business_service_prevent_duplicate_primary() {
let conn = create_test_database();
let service = ProjectTemplateBindingService::new(conn).unwrap();
let project_id = Uuid::new_v4().to_string();
// 创建第一个主要绑定
let request1 = CreateProjectTemplateBindingRequest {
project_id: project_id.clone(),
template_id: Uuid::new_v4().to_string(),
binding_name: Some("第一个主要绑定".to_string()),
description: None,
priority: Some(0),
binding_type: BindingType::Primary,
};
let result1 = service.create_binding(request1).await;
assert!(result1.is_ok());
// 尝试创建第二个主要绑定应该失败
let request2 = CreateProjectTemplateBindingRequest {
project_id: project_id.clone(),
template_id: Uuid::new_v4().to_string(),
binding_name: Some("第二个主要绑定".to_string()),
description: None,
priority: Some(0),
binding_type: BindingType::Primary,
};
let result2 = service.create_binding(request2).await;
assert!(result2.is_err());
}
#[test]
fn test_binding_validation() {
let mut binding = ProjectTemplateBinding::new(
"project_id".to_string(),
"template_id".to_string(),
BindingType::Primary,
);
// 有效的绑定应该通过验证
assert!(binding.validate().is_ok());
// 空的项目ID应该失败
binding.project_id = "".to_string();
assert!(binding.validate().is_err());
// 重置项目ID
binding.project_id = "project_id".to_string();
// 空的模板ID应该失败
binding.template_id = "".to_string();
assert!(binding.validate().is_err());
// 重置模板ID
binding.template_id = "template_id".to_string();
// 过长的绑定名称应该失败
binding.binding_name = Some("a".repeat(101));
assert!(binding.validate().is_err());
// 重置绑定名称
binding.binding_name = Some("正常名称".to_string());
// 过长的描述应该失败
binding.description = Some("a".repeat(501));
assert!(binding.validate().is_err());
}
}