sync from monorepo @ 2452e92e
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
//! Integration tests for the git module.
|
||||
//!
|
||||
//! These tests create real temporary git repos and exercise GitRunner
|
||||
//! and compute_git_state against them. Marked `#[ignore]` by default
|
||||
//! since they require `git` to be installed.
|
||||
|
||||
use dirigent_projects::git::{compute_git_state, GitRunner};
|
||||
use std::path::Path;
|
||||
use tokio::process::Command;
|
||||
|
||||
/// Helper: initialize a git repo in the given directory with an initial commit.
|
||||
async fn init_repo(dir: &Path) {
|
||||
run(dir, &["git", "init"]).await;
|
||||
run(dir, &["git", "config", "user.email", "test@test.com"]).await;
|
||||
run(dir, &["git", "config", "user.name", "Test"]).await;
|
||||
// Create an initial commit so HEAD exists
|
||||
let file = dir.join("README.md");
|
||||
tokio::fs::write(&file, "# Test\n").await.unwrap();
|
||||
run(dir, &["git", "add", "."]).await;
|
||||
run(dir, &["git", "commit", "-m", "Initial commit"]).await;
|
||||
}
|
||||
|
||||
async fn run(dir: &Path, args: &[&str]) {
|
||||
let status = Command::new(args[0])
|
||||
.args(&args[1..])
|
||||
.current_dir(dir)
|
||||
.output()
|
||||
.await
|
||||
.unwrap_or_else(|e| panic!("Failed to run {:?}: {e}", args));
|
||||
assert!(
|
||||
status.status.success(),
|
||||
"{:?} failed: {}",
|
||||
args,
|
||||
String::from_utf8_lossy(&status.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires git"]
|
||||
async fn test_current_branch() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
init_repo(dir.path()).await;
|
||||
|
||||
let runner = GitRunner::new(dir.path());
|
||||
let branch = runner.current_branch().await.unwrap();
|
||||
// Default branch may be "main" or "master" depending on git config
|
||||
assert!(
|
||||
branch == "main" || branch == "master",
|
||||
"unexpected branch: {branch}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires git"]
|
||||
async fn test_status_clean() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
init_repo(dir.path()).await;
|
||||
|
||||
let runner = GitRunner::new(dir.path());
|
||||
let status = runner.status().await.unwrap();
|
||||
assert!(!status.is_dirty);
|
||||
assert_eq!(status.ahead, 0);
|
||||
assert_eq!(status.behind, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires git"]
|
||||
async fn test_status_dirty() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
init_repo(dir.path()).await;
|
||||
|
||||
// Create an untracked file
|
||||
tokio::fs::write(dir.path().join("dirty.txt"), "dirty")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let runner = GitRunner::new(dir.path());
|
||||
let status = runner.status().await.unwrap();
|
||||
assert!(status.is_dirty);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires git"]
|
||||
async fn test_remotes_empty() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
init_repo(dir.path()).await;
|
||||
|
||||
let runner = GitRunner::new(dir.path());
|
||||
let remotes = runner.remotes().await.unwrap();
|
||||
assert!(remotes.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires git"]
|
||||
async fn test_worktree_list_single() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
init_repo(dir.path()).await;
|
||||
|
||||
let runner = GitRunner::new(dir.path());
|
||||
let worktrees = runner.worktree_list().await.unwrap();
|
||||
// A non-bare repo always has at least the main worktree
|
||||
assert_eq!(worktrees.len(), 1);
|
||||
assert!(worktrees[0].branch.is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires git"]
|
||||
async fn test_worktree_add_and_list() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
init_repo(dir.path()).await;
|
||||
|
||||
let wt_path = dir.path().join("wt-feature");
|
||||
// Create a branch first
|
||||
run(dir.path(), &["git", "branch", "feature"]).await;
|
||||
|
||||
let runner = GitRunner::new(dir.path());
|
||||
runner.worktree_add(&wt_path, "feature").await.unwrap();
|
||||
|
||||
let worktrees = runner.worktree_list().await.unwrap();
|
||||
assert_eq!(worktrees.len(), 2);
|
||||
|
||||
// Find the feature worktree by branch name (paths may differ due to symlink canonicalization)
|
||||
let feature_wt = worktrees
|
||||
.iter()
|
||||
.find(|w| w.branch.as_deref() == Some("feature"))
|
||||
.expect("should find worktree with branch 'feature'");
|
||||
assert!(!feature_wt.is_detached);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires git"]
|
||||
async fn test_compute_git_state() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
init_repo(dir.path()).await;
|
||||
|
||||
// Make it dirty
|
||||
tokio::fs::write(dir.path().join("new.txt"), "content")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let runner = GitRunner::new(dir.path());
|
||||
let state = compute_git_state(&runner).await;
|
||||
|
||||
assert!(!state.branch.is_empty());
|
||||
assert!(state.is_dirty);
|
||||
assert!(
|
||||
state.unexpected.is_empty(),
|
||||
"unexpected warnings: {:?}",
|
||||
state.unexpected
|
||||
);
|
||||
// Should have at least the main worktree
|
||||
assert!(!state.worktrees.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires git"]
|
||||
async fn test_graceful_degradation_not_a_repo() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
// Don't init — not a git repo
|
||||
|
||||
let runner = GitRunner::new(dir.path());
|
||||
let state = compute_git_state(&runner).await;
|
||||
|
||||
// Should have warnings, not panic
|
||||
assert!(!state.unexpected.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires git"]
|
||||
async fn test_commit_returns_hash() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
init_repo(dir.path()).await;
|
||||
|
||||
let file = dir.path().join("commit_test.txt");
|
||||
tokio::fs::write(&file, "data").await.unwrap();
|
||||
run(dir.path(), &["git", "add", "."]).await;
|
||||
|
||||
let runner = GitRunner::new(dir.path());
|
||||
let hash = runner.commit("test commit").await.unwrap();
|
||||
|
||||
// SHA-1 hash is 40 hex chars
|
||||
assert_eq!(hash.len(), 40, "unexpected hash: {hash}");
|
||||
assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
//! Integration tests for project CRUD lifecycle.
|
||||
|
||||
use dirigent_projects::{
|
||||
CreateProjectParams, FileBasedProjectStore, ProjectFilter, ProjectStore, ProjectUpdate,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
async fn make_store() -> FileBasedProjectStore {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
FileBasedProjectStore::new(dir.into_path()).await.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_and_get_project() {
|
||||
let store = make_store().await;
|
||||
let owner = Uuid::now_v7();
|
||||
|
||||
let project = store
|
||||
.create_project(CreateProjectParams {
|
||||
name: "Test Project".to_string(),
|
||||
description: "A test".to_string(),
|
||||
icon: Some("🚀".to_string()),
|
||||
owner,
|
||||
tags: vec!["rust".to_string()],
|
||||
languages: vec!["Rust".to_string()],
|
||||
metadata: serde_json::json!({}),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(project.name, "Test Project");
|
||||
assert_eq!(project.owner, owner);
|
||||
|
||||
let fetched = store.get_project(&project.id).await.unwrap();
|
||||
assert_eq!(fetched.id, project.id);
|
||||
assert_eq!(fetched.name, "Test Project");
|
||||
assert_eq!(fetched.icon, Some("🚀".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_list_projects_empty() {
|
||||
let store = make_store().await;
|
||||
let projects = store.list_projects(ProjectFilter::default()).await.unwrap();
|
||||
assert!(projects.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_list_projects_with_filter() {
|
||||
let store = make_store().await;
|
||||
let owner1 = Uuid::now_v7();
|
||||
let owner2 = Uuid::now_v7();
|
||||
|
||||
store
|
||||
.create_project(CreateProjectParams {
|
||||
name: "Alpha".to_string(),
|
||||
owner: owner1,
|
||||
tags: vec!["web".to_string()],
|
||||
..default_params()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
store
|
||||
.create_project(CreateProjectParams {
|
||||
name: "Beta".to_string(),
|
||||
owner: owner2,
|
||||
tags: vec!["cli".to_string()],
|
||||
..default_params()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Filter by owner
|
||||
let filtered = store
|
||||
.list_projects(ProjectFilter {
|
||||
owner: Some(owner1),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(filtered.len(), 1);
|
||||
assert_eq!(filtered[0].name, "Alpha");
|
||||
|
||||
// Filter by name
|
||||
let filtered = store
|
||||
.list_projects(ProjectFilter {
|
||||
name_contains: Some("bet".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(filtered.len(), 1);
|
||||
assert_eq!(filtered[0].name, "Beta");
|
||||
|
||||
// Filter by tag
|
||||
let filtered = store
|
||||
.list_projects(ProjectFilter {
|
||||
tags: vec!["web".to_string()],
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(filtered.len(), 1);
|
||||
assert_eq!(filtered[0].name, "Alpha");
|
||||
|
||||
// No filter returns all, sorted by name
|
||||
let all = store.list_projects(ProjectFilter::default()).await.unwrap();
|
||||
assert_eq!(all.len(), 2);
|
||||
assert_eq!(all[0].name, "Alpha");
|
||||
assert_eq!(all[1].name, "Beta");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_project() {
|
||||
let store = make_store().await;
|
||||
|
||||
let project = store
|
||||
.create_project(CreateProjectParams {
|
||||
name: "Original".to_string(),
|
||||
..default_params()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let updated = store
|
||||
.update_project(
|
||||
&project.id,
|
||||
ProjectUpdate {
|
||||
name: Some("Renamed".to_string()),
|
||||
description: Some("New description".to_string()),
|
||||
tags: Some(vec!["new-tag".to_string()]),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(updated.name, "Renamed");
|
||||
assert_eq!(updated.description, "New description");
|
||||
assert_eq!(updated.tags, vec!["new-tag"]);
|
||||
assert!(updated.updated_at > project.created_at);
|
||||
|
||||
// Verify persistence
|
||||
let fetched = store.get_project(&project.id).await.unwrap();
|
||||
assert_eq!(fetched.name, "Renamed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_delete_project() {
|
||||
let store = make_store().await;
|
||||
|
||||
let project = store
|
||||
.create_project(CreateProjectParams {
|
||||
name: "ToDelete".to_string(),
|
||||
..default_params()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
store.delete_project(&project.id).await.unwrap();
|
||||
|
||||
let err = store.get_project(&project.id).await.unwrap_err();
|
||||
assert!(matches!(err, dirigent_projects::ProjectError::NotFound(_)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_nonexistent_project() {
|
||||
let store = make_store().await;
|
||||
let err = store.get_project(&Uuid::now_v7()).await.unwrap_err();
|
||||
assert!(matches!(err, dirigent_projects::ProjectError::NotFound(_)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_empty_name_fails() {
|
||||
let store = make_store().await;
|
||||
let err = store
|
||||
.create_project(CreateProjectParams {
|
||||
name: " ".to_string(),
|
||||
..default_params()
|
||||
})
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(matches!(
|
||||
err,
|
||||
dirigent_projects::ProjectError::Validation(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_empty_name_fails() {
|
||||
let store = make_store().await;
|
||||
let project = store
|
||||
.create_project(CreateProjectParams {
|
||||
name: "Valid".to_string(),
|
||||
..default_params()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let err = store
|
||||
.update_project(
|
||||
&project.id,
|
||||
ProjectUpdate {
|
||||
name: Some("".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(matches!(
|
||||
err,
|
||||
dirigent_projects::ProjectError::Validation(_)
|
||||
));
|
||||
}
|
||||
|
||||
fn default_params() -> CreateProjectParams {
|
||||
CreateProjectParams {
|
||||
name: String::new(),
|
||||
description: String::new(),
|
||||
icon: None,
|
||||
owner: Uuid::now_v7(),
|
||||
tags: vec![],
|
||||
languages: vec![],
|
||||
metadata: serde_json::json!({}),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
//! Integration tests for repository and binding CRUD, plus working directory resolution.
|
||||
|
||||
use dirigent_projects::{
|
||||
AddRepositoryParams, BindParams, CreateProjectParams, FileBasedProjectStore, ProjectStore,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use uuid::Uuid;
|
||||
|
||||
async fn make_store() -> FileBasedProjectStore {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
FileBasedProjectStore::new(dir.into_path()).await.unwrap()
|
||||
}
|
||||
|
||||
fn default_params() -> CreateProjectParams {
|
||||
CreateProjectParams {
|
||||
name: "Test Project".to_string(),
|
||||
description: String::new(),
|
||||
icon: None,
|
||||
owner: Uuid::now_v7(),
|
||||
tags: vec![],
|
||||
languages: vec![],
|
||||
metadata: serde_json::json!({}),
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Repository Tests
|
||||
// ============================================================================
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_add_and_list_repositories() {
|
||||
let store = make_store().await;
|
||||
let project = store.create_project(default_params()).await.unwrap();
|
||||
|
||||
let repo = store
|
||||
.add_repository(AddRepositoryParams {
|
||||
project_id: project.id,
|
||||
path: PathBuf::from("/home/user/project"),
|
||||
is_primary: false,
|
||||
label: Some("main".to_string()),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(repo.project_id, project.id);
|
||||
assert_eq!(repo.path, PathBuf::from("/home/user/project"));
|
||||
assert!(!repo.is_primary);
|
||||
assert_eq!(repo.label, Some("main".to_string()));
|
||||
|
||||
let repos = store.list_repositories(&project.id).await.unwrap();
|
||||
assert_eq!(repos.len(), 1);
|
||||
assert_eq!(repos[0].id, repo.id);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_add_primary_repository_unsets_others() {
|
||||
let store = make_store().await;
|
||||
let project = store.create_project(default_params()).await.unwrap();
|
||||
|
||||
let repo1 = store
|
||||
.add_repository(AddRepositoryParams {
|
||||
project_id: project.id,
|
||||
path: PathBuf::from("/repo1"),
|
||||
is_primary: true,
|
||||
label: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(repo1.is_primary);
|
||||
|
||||
// Adding a second primary should unset the first
|
||||
let repo2 = store
|
||||
.add_repository(AddRepositoryParams {
|
||||
project_id: project.id,
|
||||
path: PathBuf::from("/repo2"),
|
||||
is_primary: true,
|
||||
label: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(repo2.is_primary);
|
||||
|
||||
let repos = store.list_repositories(&project.id).await.unwrap();
|
||||
assert_eq!(repos.len(), 2);
|
||||
|
||||
let first = repos.iter().find(|r| r.id == repo1.id).unwrap();
|
||||
let second = repos.iter().find(|r| r.id == repo2.id).unwrap();
|
||||
assert!(!first.is_primary);
|
||||
assert!(second.is_primary);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_remove_repository() {
|
||||
let store = make_store().await;
|
||||
let project = store.create_project(default_params()).await.unwrap();
|
||||
|
||||
let repo = store
|
||||
.add_repository(AddRepositoryParams {
|
||||
project_id: project.id,
|
||||
path: PathBuf::from("/repo"),
|
||||
is_primary: false,
|
||||
label: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
store.remove_repository(&repo.id).await.unwrap();
|
||||
let repos = store.list_repositories(&project.id).await.unwrap();
|
||||
assert!(repos.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_remove_nonexistent_repository() {
|
||||
let store = make_store().await;
|
||||
let err = store.remove_repository(&Uuid::now_v7()).await.unwrap_err();
|
||||
assert!(matches!(
|
||||
err,
|
||||
dirigent_projects::ProjectError::RepositoryNotFound(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_set_primary_repository() {
|
||||
let store = make_store().await;
|
||||
let project = store.create_project(default_params()).await.unwrap();
|
||||
|
||||
let repo1 = store
|
||||
.add_repository(AddRepositoryParams {
|
||||
project_id: project.id,
|
||||
path: PathBuf::from("/repo1"),
|
||||
is_primary: true,
|
||||
label: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let repo2 = store
|
||||
.add_repository(AddRepositoryParams {
|
||||
project_id: project.id,
|
||||
path: PathBuf::from("/repo2"),
|
||||
is_primary: false,
|
||||
label: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Switch primary to repo2
|
||||
store
|
||||
.set_primary_repository(&project.id, &repo2.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let repos = store.list_repositories(&project.id).await.unwrap();
|
||||
let first = repos.iter().find(|r| r.id == repo1.id).unwrap();
|
||||
let second = repos.iter().find(|r| r.id == repo2.id).unwrap();
|
||||
assert!(!first.is_primary);
|
||||
assert!(second.is_primary);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_set_primary_nonexistent_repo() {
|
||||
let store = make_store().await;
|
||||
let project = store.create_project(default_params()).await.unwrap();
|
||||
|
||||
let err = store
|
||||
.set_primary_repository(&project.id, &Uuid::now_v7())
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(matches!(
|
||||
err,
|
||||
dirigent_projects::ProjectError::RepositoryNotFound(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_add_repo_to_nonexistent_project() {
|
||||
let store = make_store().await;
|
||||
let err = store
|
||||
.add_repository(AddRepositoryParams {
|
||||
project_id: Uuid::now_v7(),
|
||||
path: PathBuf::from("/repo"),
|
||||
is_primary: false,
|
||||
label: None,
|
||||
})
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, dirigent_projects::ProjectError::NotFound(_)));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Working Directory Resolution Tests
|
||||
// ============================================================================
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_working_dir_specific_repo() {
|
||||
let store = make_store().await;
|
||||
let project = store.create_project(default_params()).await.unwrap();
|
||||
|
||||
let repo = store
|
||||
.add_repository(AddRepositoryParams {
|
||||
project_id: project.id,
|
||||
path: PathBuf::from("/specific/repo"),
|
||||
is_primary: false,
|
||||
label: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let resolved = store
|
||||
.resolve_working_dir(&project.id, Some(&repo.id))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resolved, PathBuf::from("/specific/repo"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_working_dir_primary_repo() {
|
||||
let store = make_store().await;
|
||||
let project = store.create_project(default_params()).await.unwrap();
|
||||
|
||||
store
|
||||
.add_repository(AddRepositoryParams {
|
||||
project_id: project.id,
|
||||
path: PathBuf::from("/secondary"),
|
||||
is_primary: false,
|
||||
label: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
store
|
||||
.add_repository(AddRepositoryParams {
|
||||
project_id: project.id,
|
||||
path: PathBuf::from("/primary"),
|
||||
is_primary: true,
|
||||
label: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let resolved = store.resolve_working_dir(&project.id, None).await.unwrap();
|
||||
assert_eq!(resolved, PathBuf::from("/primary"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_working_dir_first_repo_fallback() {
|
||||
let store = make_store().await;
|
||||
let project = store.create_project(default_params()).await.unwrap();
|
||||
|
||||
store
|
||||
.add_repository(AddRepositoryParams {
|
||||
project_id: project.id,
|
||||
path: PathBuf::from("/only-repo"),
|
||||
is_primary: false,
|
||||
label: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let resolved = store.resolve_working_dir(&project.id, None).await.unwrap();
|
||||
assert_eq!(resolved, PathBuf::from("/only-repo"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_working_dir_no_repos_errors() {
|
||||
let store = make_store().await;
|
||||
let project = store.create_project(default_params()).await.unwrap();
|
||||
|
||||
let err = store
|
||||
.resolve_working_dir(&project.id, None)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(matches!(
|
||||
err,
|
||||
dirigent_projects::ProjectError::Validation(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_working_dir_nonexistent_repo_id() {
|
||||
let store = make_store().await;
|
||||
let project = store.create_project(default_params()).await.unwrap();
|
||||
|
||||
store
|
||||
.add_repository(AddRepositoryParams {
|
||||
project_id: project.id,
|
||||
path: PathBuf::from("/repo"),
|
||||
is_primary: true,
|
||||
label: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let err = store
|
||||
.resolve_working_dir(&project.id, Some(&Uuid::now_v7()))
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(matches!(
|
||||
err,
|
||||
dirigent_projects::ProjectError::RepositoryNotFound(_)
|
||||
));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Binding Tests
|
||||
// ============================================================================
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_bind_and_list_bindings() {
|
||||
let store = make_store().await;
|
||||
let project = store.create_project(default_params()).await.unwrap();
|
||||
|
||||
let binding = store
|
||||
.bind(BindParams {
|
||||
project_id: project.id,
|
||||
connector_id: Some("opencode-1".to_string()),
|
||||
session_id: None,
|
||||
working_dir: Some(PathBuf::from("/custom/dir")),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(binding.project_id, project.id);
|
||||
assert_eq!(binding.connector_id, Some("opencode-1".to_string()));
|
||||
assert!(binding.session_id.is_none());
|
||||
assert_eq!(binding.working_dir, Some(PathBuf::from("/custom/dir")));
|
||||
|
||||
let bindings = store.list_bindings(&project.id).await.unwrap();
|
||||
assert_eq!(bindings.len(), 1);
|
||||
assert_eq!(bindings[0].id, binding.id);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_unbind() {
|
||||
let store = make_store().await;
|
||||
let project = store.create_project(default_params()).await.unwrap();
|
||||
|
||||
let binding = store
|
||||
.bind(BindParams {
|
||||
project_id: project.id,
|
||||
connector_id: Some("conn-1".to_string()),
|
||||
session_id: None,
|
||||
working_dir: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
store.unbind(&binding.id).await.unwrap();
|
||||
let bindings = store.list_bindings(&project.id).await.unwrap();
|
||||
assert!(bindings.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_unbind_nonexistent() {
|
||||
let store = make_store().await;
|
||||
let err = store.unbind(&Uuid::now_v7()).await.unwrap_err();
|
||||
assert!(matches!(
|
||||
err,
|
||||
dirigent_projects::ProjectError::BindingNotFound(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_bind_to_nonexistent_project() {
|
||||
let store = make_store().await;
|
||||
let err = store
|
||||
.bind(BindParams {
|
||||
project_id: Uuid::now_v7(),
|
||||
connector_id: Some("conn".to_string()),
|
||||
session_id: None,
|
||||
working_dir: None,
|
||||
})
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, dirigent_projects::ProjectError::NotFound(_)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_bind_with_session_id() {
|
||||
let store = make_store().await;
|
||||
let project = store.create_project(default_params()).await.unwrap();
|
||||
let session_id = Uuid::now_v7();
|
||||
|
||||
let binding = store
|
||||
.bind(BindParams {
|
||||
project_id: project.id,
|
||||
connector_id: Some("conn-1".to_string()),
|
||||
session_id: Some(session_id),
|
||||
working_dir: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(binding.session_id, Some(session_id));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_multiple_bindings_per_project() {
|
||||
let store = make_store().await;
|
||||
let project = store.create_project(default_params()).await.unwrap();
|
||||
|
||||
store
|
||||
.bind(BindParams {
|
||||
project_id: project.id,
|
||||
connector_id: Some("conn-1".to_string()),
|
||||
session_id: None,
|
||||
working_dir: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
store
|
||||
.bind(BindParams {
|
||||
project_id: project.id,
|
||||
connector_id: Some("conn-2".to_string()),
|
||||
session_id: None,
|
||||
working_dir: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let bindings = store.list_bindings(&project.id).await.unwrap();
|
||||
assert_eq!(bindings.len(), 2);
|
||||
}
|
||||
Reference in New Issue
Block a user