use crate::brain::tools::rename_session::RenameSessionTool;
use crate::brain::tools::{Tool, ToolExecutionContext};
use crate::db::Database;
use crate::services::{ServiceContext, SessionService};
use serde_json::json;
async fn setup() -> (Database, ServiceContext, uuid::Uuid) {
let db = Database::connect_in_memory()
.await
.expect("connect_in_memory");
db.run_migrations().await.expect("migrations");
let svc_ctx = ServiceContext::new(db.pool().clone());
let session = SessionService::new(svc_ctx.clone())
.create_session(Some("Initial title".to_string()))
.await
.expect("create_session");
(db, svc_ctx, session.id)
}
#[tokio::test]
async fn rename_session_updates_title() {
let (_db, svc_ctx, sid) = setup().await;
let mut ctx = ToolExecutionContext::new(sid);
ctx.service_context = Some(svc_ctx.clone());
let result = RenameSessionTool
.execute(json!({ "title": "Property matching audit" }), &ctx)
.await
.expect("execute");
assert!(result.success, "rename should succeed: {:?}", result.error);
assert!(
result.output.contains("Property matching audit"),
"output should echo the new title, got: {}",
result.output
);
let session = SessionService::new(svc_ctx)
.get_session(sid)
.await
.expect("get_session")
.expect("session exists");
assert_eq!(session.title.as_deref(), Some("Property matching audit"));
}
#[tokio::test]
async fn rename_session_rejects_empty_string() {
let (_db, svc_ctx, sid) = setup().await;
let mut ctx = ToolExecutionContext::new(sid);
ctx.service_context = Some(svc_ctx.clone());
let result = RenameSessionTool
.execute(json!({ "title": "" }), &ctx)
.await
.expect("execute");
assert!(!result.success, "empty title must be rejected");
assert!(
result
.error
.as_deref()
.unwrap_or_default()
.to_lowercase()
.contains("empty"),
"error must mention empty/whitespace, got: {:?}",
result.error
);
let session = SessionService::new(svc_ctx)
.get_session(sid)
.await
.expect("get_session")
.expect("session exists");
assert_eq!(
session.title.as_deref(),
Some("Initial title"),
"title must be untouched after rejected empty rename"
);
}
#[tokio::test]
async fn rename_session_rejects_whitespace_only() {
let (_db, svc_ctx, sid) = setup().await;
let mut ctx = ToolExecutionContext::new(sid);
ctx.service_context = Some(svc_ctx.clone());
let result = RenameSessionTool
.execute(json!({ "title": " \t \n " }), &ctx)
.await
.expect("execute");
assert!(!result.success, "whitespace-only must be rejected");
let session = SessionService::new(svc_ctx)
.get_session(sid)
.await
.expect("get_session")
.expect("session exists");
assert_eq!(
session.title.as_deref(),
Some("Initial title"),
"title must be untouched"
);
}
#[tokio::test]
async fn rename_session_trims_whitespace() {
let (_db, svc_ctx, sid) = setup().await;
let mut ctx = ToolExecutionContext::new(sid);
ctx.service_context = Some(svc_ctx.clone());
RenameSessionTool
.execute(json!({ "title": " spaced out " }), &ctx)
.await
.expect("execute");
let session = SessionService::new(svc_ctx)
.get_session(sid)
.await
.expect("get_session")
.expect("session exists");
assert_eq!(session.title.as_deref(), Some("spaced out"));
}
#[tokio::test]
async fn rename_session_rejects_oversized_title() {
let (_db, svc_ctx, sid) = setup().await;
let mut ctx = ToolExecutionContext::new(sid);
ctx.service_context = Some(svc_ctx.clone());
let huge = "a".repeat(250);
let result = RenameSessionTool
.execute(json!({ "title": huge }), &ctx)
.await
.expect("execute");
assert!(!result.success, "oversized title must be rejected");
let err = result.error.unwrap_or_default();
assert!(
err.contains("too long"),
"error should mention the size limit, got: {err}"
);
let session = SessionService::new(svc_ctx)
.get_session(sid)
.await
.expect("get_session")
.expect("session exists");
assert_eq!(
session.title.as_deref(),
Some("Initial title"),
"original title must survive rejection"
);
}
#[tokio::test]
async fn rename_session_errors_when_service_context_missing() {
let sid = uuid::Uuid::new_v4();
let ctx = ToolExecutionContext::new(sid);
let result = RenameSessionTool
.execute(json!({ "title": "won't work" }), &ctx)
.await
.expect("execute");
assert!(!result.success);
let err = result.error.unwrap_or_default();
assert!(
err.to_lowercase().contains("service context"),
"error must mention missing service context, got: {err}"
);
}
#[test]
fn rename_session_metadata() {
let tool = RenameSessionTool;
assert_eq!(tool.name(), "rename_session");
assert!(
!tool.requires_approval(),
"metadata-only update should not require approval"
);
assert!(
tool.capabilities().is_empty(),
"rename_session has no filesystem/shell/network capabilities"
);
let schema = tool.input_schema();
let props = schema
.get("properties")
.and_then(|p| p.as_object())
.expect("schema has properties");
assert!(props.contains_key("title"), "schema must declare 'title'");
}