Socle #154 : entité attachment, store, contrat DTO et routage session. Modules : domain/chat_attachment, application/chat_attachments, infrastructure/chat_attachments, app-tauri (commands/lib), backend dto.
106 lines
3.8 KiB
Rust
106 lines
3.8 KiB
Rust
use domain::ports::{
|
|
ChatAttachmentImport, ChatAttachmentStore, ChatAttachmentStoreError, IdGenerator,
|
|
};
|
|
use domain::{
|
|
AgentId, ChatAttachmentId, ChatAttachmentSourceKind, LocalPath, ProjectId, ProjectPath,
|
|
SessionId,
|
|
};
|
|
use infrastructure::{FsChatAttachmentStore, UuidGenerator};
|
|
|
|
fn temp_dir(tag: &str) -> std::path::PathBuf {
|
|
std::env::temp_dir().join(format!(
|
|
"idea-chat-attachment-{tag}-{}",
|
|
UuidGenerator::new().new_uuid()
|
|
))
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn fs_chat_attachment_store_imports_outside_file_to_project_managed_path() {
|
|
let project_root = temp_dir("project");
|
|
let outside_root = temp_dir("outside");
|
|
tokio::fs::create_dir_all(&project_root).await.unwrap();
|
|
tokio::fs::create_dir_all(&outside_root).await.unwrap();
|
|
let source = outside_root.join("picked.png");
|
|
tokio::fs::write(&source, b"png bytes").await.unwrap();
|
|
|
|
let store = FsChatAttachmentStore::new();
|
|
let project_path = ProjectPath::new(project_root.to_string_lossy().into_owned()).unwrap();
|
|
let project_id = ProjectId::new_random();
|
|
let agent_id = AgentId::new_random();
|
|
let session_id = SessionId::new_random();
|
|
let attachment_id = ChatAttachmentId::new("attach-1").unwrap();
|
|
|
|
let attachment = store
|
|
.import_from_path(
|
|
&project_path,
|
|
project_id,
|
|
agent_id,
|
|
session_id,
|
|
&LocalPath::new(source.to_string_lossy().into_owned()),
|
|
ChatAttachmentImport {
|
|
id: attachment_id,
|
|
filename: "picked.png".to_owned(),
|
|
mime: "image/png".to_owned(),
|
|
source_kind: ChatAttachmentSourceKind::LocalFile,
|
|
created_at: 42,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(attachment.project_id, project_id);
|
|
assert_eq!(attachment.agent_id, agent_id);
|
|
assert_eq!(attachment.session_id, session_id);
|
|
assert_eq!(attachment.filename, "picked.png");
|
|
assert_eq!(attachment.size_bytes, 9);
|
|
assert!(attachment
|
|
.storage_path
|
|
.starts_with(&format!("agent-chat/{session_id}/")));
|
|
assert!(attachment
|
|
.readable_path
|
|
.starts_with(&format!("{}/.ideai/attachments/", project_root.display())));
|
|
let stored_bytes = tokio::fs::read(&attachment.readable_path).await.unwrap();
|
|
assert_eq!(stored_bytes, b"png bytes");
|
|
|
|
let listed = store
|
|
.list_for_session(&project_path, session_id)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(listed, vec![attachment]);
|
|
|
|
let _ = tokio::fs::remove_dir_all(project_root).await;
|
|
let _ = tokio::fs::remove_dir_all(outside_root).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn fs_chat_attachment_store_rejects_directories() {
|
|
let project_root = temp_dir("project-dir");
|
|
let outside_root = temp_dir("outside-dir");
|
|
tokio::fs::create_dir_all(&project_root).await.unwrap();
|
|
tokio::fs::create_dir_all(&outside_root).await.unwrap();
|
|
|
|
let store = FsChatAttachmentStore::new();
|
|
let err = store
|
|
.import_from_path(
|
|
&ProjectPath::new(project_root.to_string_lossy().into_owned()).unwrap(),
|
|
ProjectId::new_random(),
|
|
AgentId::new_random(),
|
|
SessionId::new_random(),
|
|
&LocalPath::new(outside_root.to_string_lossy().into_owned()),
|
|
ChatAttachmentImport {
|
|
id: ChatAttachmentId::new("attach-2").unwrap(),
|
|
filename: "outside-dir".to_owned(),
|
|
mime: "application/octet-stream".to_owned(),
|
|
source_kind: ChatAttachmentSourceKind::LocalFile,
|
|
created_at: 42,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
|
|
assert!(matches!(err, ChatAttachmentStoreError::Invalid(_)));
|
|
|
|
let _ = tokio::fs::remove_dir_all(project_root).await;
|
|
let _ = tokio::fs::remove_dir_all(outside_root).await;
|
|
}
|