use hyperdb_mcp::engine::Engine;
use hyperdb_mcp::error::ErrorCode;
use hyperdb_mcp::saved_queries::{SavedQuery, SavedQueryStore, SessionStore, WorkspaceStore};
use hyperdb_mcp::server::HyperMcpServer;
use tempfile::TempDir;
fn mk_query(name: &str, sql: &str) -> SavedQuery {
SavedQuery {
name: name.into(),
sql: sql.into(),
description: Some(format!("Test query {name}")),
created_at: chrono::Utc::now(),
}
}
#[test]
fn session_save_then_get_roundtrips() {
let store = SessionStore::new();
let q = mk_query(
"revenue_by_region",
"SELECT region, SUM(r) FROM s GROUP BY region",
);
store.save(None, q.clone()).unwrap();
let fetched = store.get(None, "revenue_by_region").unwrap().unwrap();
assert_eq!(fetched.name, q.name);
assert_eq!(fetched.sql, q.sql);
assert_eq!(fetched.description, q.description);
}
#[test]
fn session_get_unknown_returns_none() {
let store = SessionStore::new();
assert!(store.get(None, "no-such-query").unwrap().is_none());
}
#[test]
fn session_list_returns_sorted_names() {
let store = SessionStore::new();
store.save(None, mk_query("zeta", "SELECT 1")).unwrap();
store.save(None, mk_query("alpha", "SELECT 2")).unwrap();
store.save(None, mk_query("mu", "SELECT 3")).unwrap();
let names: Vec<String> = store
.list(None)
.unwrap()
.into_iter()
.map(|q| q.name)
.collect();
assert_eq!(names, vec!["alpha", "mu", "zeta"]);
}
#[test]
fn session_duplicate_name_errors_with_invalid_argument() {
let store = SessionStore::new();
store.save(None, mk_query("dup", "SELECT 1")).unwrap();
let err = store.save(None, mk_query("dup", "SELECT 2")).unwrap_err();
assert_eq!(err.code, ErrorCode::InvalidArgument);
assert!(err.message.contains("dup"));
assert!(err.message.to_lowercase().contains("delete_query"));
}
#[test]
fn session_delete_returns_true_when_present_false_otherwise() {
let store = SessionStore::new();
store.save(None, mk_query("foo", "SELECT 1")).unwrap();
assert!(store.delete(None, "foo").unwrap());
assert!(!store.delete(None, "foo").unwrap(), "second delete no-ops");
assert!(store.get(None, "foo").unwrap().is_none());
}
fn workspace_engine() -> (Engine, TempDir) {
let dir = TempDir::new().unwrap();
let path = dir.path().join("ws.hyper");
let engine = Engine::new_no_daemon(Some(path.to_str().unwrap().into())).unwrap();
(engine, dir)
}
#[test]
fn workspace_save_then_get_roundtrips() {
let (engine, _dir) = workspace_engine();
let store = WorkspaceStore::new();
let q = mk_query(
"top_products",
"SELECT product, SUM(qty) FROM orders GROUP BY product",
);
store.save(Some(&engine), q.clone()).unwrap();
let fetched = store.get(Some(&engine), "top_products").unwrap().unwrap();
assert_eq!(fetched.name, q.name);
assert_eq!(fetched.sql, q.sql);
assert_eq!(fetched.description, q.description);
}
#[test]
fn workspace_list_returns_sorted_names() {
let (engine, _dir) = workspace_engine();
let store = WorkspaceStore::new();
store
.save(Some(&engine), mk_query("zeta", "SELECT 1"))
.unwrap();
store
.save(Some(&engine), mk_query("alpha", "SELECT 2"))
.unwrap();
store
.save(Some(&engine), mk_query("mu", "SELECT 3"))
.unwrap();
let names: Vec<String> = store
.list(Some(&engine))
.unwrap()
.into_iter()
.map(|q| q.name)
.collect();
assert_eq!(names, vec!["alpha", "mu", "zeta"]);
}
#[test]
fn workspace_duplicate_name_errors_with_invalid_argument() {
let (engine, _dir) = workspace_engine();
let store = WorkspaceStore::new();
store
.save(Some(&engine), mk_query("dup", "SELECT 1"))
.unwrap();
let err = store
.save(Some(&engine), mk_query("dup", "SELECT 2"))
.unwrap_err();
assert_eq!(err.code, ErrorCode::InvalidArgument);
assert!(err.message.contains("dup"));
}
#[test]
fn workspace_delete_returns_true_when_present_false_otherwise() {
let (engine, _dir) = workspace_engine();
let store = WorkspaceStore::new();
store
.save(Some(&engine), mk_query("foo", "SELECT 1"))
.unwrap();
assert!(store.delete(Some(&engine), "foo").unwrap());
assert!(!store.delete(Some(&engine), "foo").unwrap());
assert!(store.get(Some(&engine), "foo").unwrap().is_none());
}
#[test]
fn workspace_handles_quoted_sql() {
let (engine, _dir) = workspace_engine();
let store = WorkspaceStore::new();
let sql = "SELECT * FROM orders WHERE status = 'ship''d' AND notes LIKE '%user''s%'";
store.save(Some(&engine), mk_query("quoted", sql)).unwrap();
let fetched = store.get(Some(&engine), "quoted").unwrap().unwrap();
assert_eq!(fetched.sql, sql);
}
#[test]
fn workspace_persists_across_restarts() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("ws.hyper");
let path_str = path.to_str().unwrap().to_string();
{
let engine = Engine::new_no_daemon(Some(path_str.clone())).unwrap();
let store = WorkspaceStore::new();
store
.save(Some(&engine), mk_query("persisted", "SELECT 42"))
.unwrap();
drop(engine);
}
let engine = Engine::new_no_daemon(Some(path_str)).unwrap();
let store = WorkspaceStore::new();
let fetched = store.get(Some(&engine), "persisted").unwrap().unwrap();
assert_eq!(fetched.sql, "SELECT 42");
let listed = store.list(Some(&engine)).unwrap();
assert_eq!(listed.len(), 1);
assert_eq!(listed[0].name, "persisted");
}
#[test]
fn server_ephemeral_session_store_exposes_query_resources() {
let server = HyperMcpServer::with_no_daemon(None, false, true);
let store = hyperdb_mcp::saved_queries::build_store(None);
store
.save(None, mk_query("top5", "SELECT * FROM t LIMIT 5"))
.unwrap();
drop(store);
let result = server.resource_body_for_uri("hyper://queries/top5/definition");
assert!(result.is_err());
assert_eq!(
result.unwrap_err().code,
ErrorCode::TableNotFound,
"unknown saved query should surface as TableNotFound"
);
}
#[test]
fn server_workspace_store_exposes_saved_queries_via_resources() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("ws.hyper");
let path_str = path.to_str().unwrap().to_string();
{
let engine = Engine::new_no_daemon(Some(path_str.clone())).unwrap();
let store = WorkspaceStore::new();
store
.save(
Some(&engine),
mk_query("recent_orders", "SELECT * FROM orders LIMIT 10"),
)
.unwrap();
}
let server = HyperMcpServer::with_no_daemon(Some(path_str), false, true);
let uris = server.list_resource_uris();
assert!(uris.contains(&"hyper://queries/recent_orders/definition".to_string()));
assert!(uris.contains(&"hyper://queries/recent_orders/result".to_string()));
assert!(!uris
.iter()
.any(|u| u.contains(hyperdb_mcp::saved_queries::SAVED_QUERIES_TABLE)));
let body = server
.resource_body_for_uri("hyper://queries/recent_orders/definition")
.unwrap()
.expect("definition resource should exist");
let json = body.as_json().unwrap();
assert_eq!(json["name"], "recent_orders");
assert_eq!(json["sql"], "SELECT * FROM orders LIMIT 10");
}