use agentstategraph::session::CreateSessionParams;
use agentstategraph::{CommitOptions, RepoError, Repository};
use agentstategraph_core::{IntentCategory, Namespace, Object};
use agentstategraph_storage::SqliteStorage;
fn repo_in_ns(ns: &str) -> Repository {
let storage = SqliteStorage::in_memory().expect("in-memory sqlite");
let namespace = Namespace::new(ns).expect("valid namespace");
let repo = Repository::new(Box::new(storage)).with_namespace(namespace);
repo.create_namespace(ns).expect("create namespace");
repo.init().expect("init");
repo
}
fn default_repo() -> Repository {
let storage = SqliteStorage::in_memory().expect("in-memory sqlite");
let repo = Repository::new(Box::new(storage));
repo.init().expect("init");
repo
}
fn checkpoint(desc: &str) -> CommitOptions {
CommitOptions::new("agent/test", IntentCategory::Checkpoint, desc)
}
#[test]
fn create_and_list_namespaces() {
let repo = default_repo();
repo.create_namespace("alpha").unwrap();
repo.create_namespace("beta").unwrap();
let names: Vec<String> = repo
.list_namespaces()
.unwrap()
.into_iter()
.map(|n| n.as_str().to_string())
.collect();
assert!(names.contains(&"default".to_string()));
assert!(names.contains(&"alpha".to_string()));
assert!(names.contains(&"beta".to_string()));
}
#[test]
fn create_namespace_is_idempotent() {
let repo = default_repo();
repo.create_namespace("gamma").unwrap();
repo.create_namespace("gamma").unwrap(); let names = repo.list_namespaces().unwrap();
let count = names.iter().filter(|n| n.as_str() == "gamma").count();
assert_eq!(count, 1, "gamma should appear exactly once");
}
#[test]
fn create_namespace_rejects_invalid_name() {
let repo = default_repo();
let err = repo.create_namespace("has space").unwrap_err();
assert!(
matches!(err, RepoError::InvalidOperation(_)),
"expected InvalidOperation, got {:?}",
err
);
}
#[test]
fn refs_are_isolated_between_namespaces() {
let repo_a = repo_in_ns("ns-a");
let repo_b = repo_in_ns("ns-b");
repo_a
.set(
"main",
"/color",
&Object::Atom(agentstategraph_core::Atom::String("red".into())),
checkpoint("set color in ns-a"),
)
.unwrap();
let val_b = repo_b.get("main", "/color");
assert!(
val_b.is_err(),
"ns-b should not see ns-a's writes; got {:?}",
val_b
);
}
#[test]
fn branch_in_one_namespace_invisible_to_another() {
let repo_a = repo_in_ns("ns-c");
let repo_b = repo_in_ns("ns-d");
repo_a.branch("feature", "main").unwrap();
let branches_b = repo_b.list_branches(None).unwrap();
let names: Vec<&str> = branches_b.iter().map(|(n, _)| n.as_str()).collect();
assert!(
!names.contains(&"feature"),
"feature branch from ns-c leaked into ns-d: {:?}",
names
);
}
#[test]
fn repository_namespace_reflects_configured_value() {
let storage = SqliteStorage::in_memory().expect("in-memory sqlite");
let ns = Namespace::new("my-project").unwrap();
let repo = Repository::new(Box::new(storage)).with_namespace(ns.clone());
assert_eq!(repo.namespace(), &ns);
}
#[test]
fn default_repository_uses_default_namespace() {
let storage = SqliteStorage::in_memory().expect("in-memory sqlite");
let repo = Repository::new(Box::new(storage));
assert_eq!(repo.namespace().as_str(), "default");
}
#[test]
fn cross_namespace_merge_same_namespace_succeeds() {
let repo = repo_in_ns("same-ns");
repo.branch("feature", "main").unwrap();
repo.set(
"feature",
"/x",
&Object::Atom(agentstategraph_core::Atom::String("hello".into())),
checkpoint("write on feature"),
)
.unwrap();
let result = repo.cross_namespace_merge(
"same-ns",
"feature",
"main",
checkpoint("cross-ns merge (same)"),
);
assert!(
result.is_ok(),
"same-ns cross_namespace_merge failed: {:?}",
result
);
let val = repo.get("main", "/x").unwrap();
assert_eq!(
val,
Object::Atom(agentstategraph_core::Atom::String("hello".into()))
);
}
#[test]
fn cross_namespace_merge_across_namespaces_denied_without_policy() {
let repo_src = repo_in_ns("src-ns");
let repo_dst = repo_in_ns("dst-ns");
repo_src
.set(
"main",
"/msg",
&Object::Atom(agentstategraph_core::Atom::String("secret".into())),
checkpoint("write in src-ns"),
)
.unwrap();
let result = repo_dst.cross_namespace_merge(
"src-ns", "main",
"main",
checkpoint("cross-ns attempt"),
);
assert!(
matches!(result, Err(RepoError::CrossNamespaceAccessDenied)),
"expected CrossNamespaceAccessDenied, got {:?}",
result
);
}
#[test]
fn session_scope_namespace_overrides_repo_namespace() {
let storage = SqliteStorage::in_memory().expect("in-memory sqlite");
let repo = Repository::new(Box::new(storage));
repo.create_namespace("default").unwrap();
repo.create_namespace("project-x").unwrap();
repo.init().unwrap();
let mgr = repo.sessions();
let head = repo.head("main").unwrap();
let session = mgr
.create(
"agent/test",
"main",
head,
CreateSessionParams {
scope_namespace: Some(Namespace::new("project-x").unwrap()),
..Default::default()
},
)
.unwrap();
assert_eq!(
session.scope_namespace.as_ref().map(|n| n.as_str()),
Some("project-x")
);
let namespaces = repo.list_namespaces().unwrap();
let names: Vec<&str> = namespaces.iter().map(|n| n.as_str()).collect();
assert!(names.contains(&"project-x"), "project-x should be listed");
}
#[test]
fn init_auto_creates_namespace() {
let storage = SqliteStorage::in_memory().expect("in-memory sqlite");
let ns = Namespace::new("fresh-ns").unwrap();
let repo = Repository::new(Box::new(storage)).with_namespace(ns);
repo.init()
.expect("init() should auto-create the namespace");
}
#[test]
fn delete_namespace_removes_refs_and_namespace() {
let repo = default_repo();
repo.create_namespace("to-delete").unwrap();
let storage2 = SqliteStorage::in_memory().expect("in-memory sqlite");
let ns = Namespace::new("to-delete").unwrap();
let repo2 = Repository::new(Box::new(storage2)).with_namespace(ns);
repo2.init().unwrap();
assert!(repo.delete_namespace("to-delete").unwrap());
assert!(!repo.delete_namespace("to-delete").unwrap());
let names: Vec<String> = repo
.list_namespaces()
.unwrap()
.into_iter()
.map(|n| n.as_str().to_string())
.collect();
assert!(
!names.contains(&"to-delete".to_string()),
"deleted namespace must not appear in listing"
);
}
#[test]
fn delete_default_namespace_is_rejected() {
let repo = default_repo();
let err = repo.delete_namespace("default").unwrap_err();
assert!(
matches!(err, RepoError::Storage(_)),
"expected Storage error wrapping InvalidOperation, got {:?}",
err
);
}