Files
MyAiDesk/nomifun-tauri/crates/backend/nomifun-team/tests/mailbox_integration.rs
T
freedak f7a720204a Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
2026-07-04 19:20:46 +08:00

230 lines
7.3 KiB
Rust

//! Black-box integration tests for `Mailbox` service.
//!
//! Exercises the service layer against a real SQLite database.
//!
//! Covers test-plan items:
//! - MW-1..MW-3 (write messages: text, idle_notification, shutdown_request)
//! - MR-1..MR-4 (atomic read + mark, re-read empty, no messages)
//! - MH-1..MH-3 (history query with/without limit)
//! - MD-1..MD-2 (delete by team, isolation)
use std::sync::Arc;
use nomifun_db::{ITeamRepository, SqliteTeamRepository, init_database_memory};
use nomifun_team::{Mailbox, MailboxMessageType};
/// Returns the `Mailbox` service plus the repo (so cascade-delete tests can
/// drop a team) and the owning `Database`. Seeds the `users` row and the two
/// teams (`t1`, `t2`) the tests write into — the `mailbox.team_id` FK
/// (CASCADE, spec §5.4) now requires the parent team to exist.
async fn setup() -> (Mailbox, Arc<dyn ITeamRepository>, nomifun_db::Database) {
let db = init_database_memory().await.unwrap();
sqlx::query(
"INSERT INTO users (id, username, password_hash, created_at, updated_at) \
VALUES ('user_1', 'tester', 'hash', 0, 0)",
)
.execute(db.pool())
.await
.unwrap();
for team_id in ["t1", "t2"] {
sqlx::query(
"INSERT INTO teams (id, user_id, name, workspace, workspace_mode, agents_version, created_at, updated_at) \
VALUES (?, 'user_1', 'Test Team', '/tmp/ws', 'shared', '1.0.0', 0, 0)",
)
.bind(team_id)
.execute(db.pool())
.await
.unwrap();
}
let repo = Arc::new(SqliteTeamRepository::new(db.pool().clone())) as Arc<dyn ITeamRepository>;
(Mailbox::new(repo.clone()), repo, db)
}
// -- MW: Write messages -------------------------------------------------------
#[tokio::test]
async fn mw1_write_text_message() {
let (mailbox, _repo, _db) = setup().await;
let msg = mailbox
.write("t1", "a1", "user", MailboxMessageType::Message, "hello", None)
.await
.unwrap();
assert_eq!(msg.msg_type, MailboxMessageType::Message);
assert_eq!(msg.content, "hello");
assert!(!msg.read);
// `mailbox.id` is now an autoincrement i64 (spec §2 row 19) returned by
// `write_message` via `last_insert_rowid()`.
assert!(msg.id > 0);
}
#[tokio::test]
async fn mw2_write_idle_notification_with_summary() {
let (mailbox, _repo, _db) = setup().await;
let msg = mailbox
.write(
"t1",
"lead",
"a1",
MailboxMessageType::IdleNotification,
"done",
Some("Task finished"),
)
.await
.unwrap();
assert_eq!(msg.msg_type, MailboxMessageType::IdleNotification);
assert_eq!(msg.summary.as_deref(), Some("Task finished"));
}
#[tokio::test]
async fn mw3_write_shutdown_request() {
let (mailbox, _repo, _db) = setup().await;
let msg = mailbox
.write(
"t1",
"a1",
"lead",
MailboxMessageType::ShutdownRequest,
"cleanup done",
None,
)
.await
.unwrap();
assert_eq!(msg.msg_type, MailboxMessageType::ShutdownRequest);
}
// -- MR: Atomic read + mark ---------------------------------------------------
#[tokio::test]
async fn mr1_read_unread_returns_all_and_marks() {
let (mailbox, _repo, _db) = setup().await;
for i in 0..3 {
mailbox
.write(
"t1",
"a1",
"user",
MailboxMessageType::Message,
&format!("msg-{i}"),
None,
)
.await
.unwrap();
}
let unread = mailbox.read_unread("t1", "a1").await.unwrap();
assert_eq!(unread.len(), 3);
}
#[tokio::test]
async fn mr2_second_read_returns_empty() {
let (mailbox, _repo, _db) = setup().await;
mailbox
.write("t1", "a1", "user", MailboxMessageType::Message, "x", None)
.await
.unwrap();
mailbox.read_unread("t1", "a1").await.unwrap();
let second = mailbox.read_unread("t1", "a1").await.unwrap();
assert!(second.is_empty());
}
#[tokio::test]
async fn mr4_no_unread_messages() {
let (mailbox, _repo, _db) = setup().await;
let unread = mailbox.read_unread("t1", "a1").await.unwrap();
assert!(unread.is_empty());
}
// -- MH: History query --------------------------------------------------------
#[tokio::test]
async fn mh1_get_history_no_limit() {
let (mailbox, _repo, _db) = setup().await;
for i in 0..5 {
mailbox
.write("t1", "a1", "user", MailboxMessageType::Message, &format!("m{i}"), None)
.await
.unwrap();
}
mailbox.read_unread("t1", "a1").await.unwrap();
let history = mailbox.get_history("t1", "a1", None).await.unwrap();
assert_eq!(history.len(), 5);
}
#[tokio::test]
async fn mh2_get_history_with_limit() {
let (mailbox, _repo, _db) = setup().await;
for i in 0..10 {
mailbox
.write("t1", "a1", "user", MailboxMessageType::Message, &format!("m{i}"), None)
.await
.unwrap();
}
let history = mailbox.get_history("t1", "a1", Some(5)).await.unwrap();
assert_eq!(history.len(), 5);
}
#[tokio::test]
async fn mh3_empty_history() {
let (mailbox, _repo, _db) = setup().await;
let history = mailbox.get_history("t1", "a1", None).await.unwrap();
assert!(history.is_empty());
}
// -- MD: Delete team cascades to its mailbox ----------------------------------
#[tokio::test]
async fn md1_delete_team_removes_all_messages() {
let (mailbox, repo, _db) = setup().await;
mailbox
.write("t1", "a1", "user", MailboxMessageType::Message, "x", None)
.await
.unwrap();
mailbox
.write("t1", "a2", "user", MailboxMessageType::Message, "y", None)
.await
.unwrap();
// The mailbox is purged via FK ON DELETE CASCADE when the team goes away
// (spec §5.4) — there is no manual delete_mailbox_by_team.
repo.delete_team("t1").await.unwrap();
let h1 = mailbox.get_history("t1", "a1", None).await.unwrap();
let h2 = mailbox.get_history("t1", "a2", None).await.unwrap();
assert!(h1.is_empty());
assert!(h2.is_empty());
}
#[tokio::test]
async fn md2_delete_team_does_not_affect_other_teams() {
let (mailbox, repo, _db) = setup().await;
mailbox
.write("t1", "a1", "user", MailboxMessageType::Message, "x", None)
.await
.unwrap();
mailbox
.write("t2", "a1", "user", MailboxMessageType::Message, "y", None)
.await
.unwrap();
repo.delete_team("t1").await.unwrap();
let h2 = mailbox.get_history("t2", "a1", None).await.unwrap();
assert_eq!(h2.len(), 1);
}
// -- Agent scope isolation ----------------------------------------------------
#[tokio::test]
async fn read_unread_scoped_to_target_agent() {
let (mailbox, _repo, _db) = setup().await;
mailbox
.write("t1", "a1", "user", MailboxMessageType::Message, "for-a1", None)
.await
.unwrap();
mailbox
.write("t1", "a2", "user", MailboxMessageType::Message, "for-a2", None)
.await
.unwrap();
let a1_msgs = mailbox.read_unread("t1", "a1").await.unwrap();
assert_eq!(a1_msgs.len(), 1);
assert_eq!(a1_msgs[0].content, "for-a1");
let a2_msgs = mailbox.read_unread("t1", "a2").await.unwrap();
assert_eq!(a2_msgs.len(), 1);
assert_eq!(a2_msgs[0].content, "for-a2");
}