//! SSH management command integration tests. //! //! Tests repo CRUD (via DB functions used by the SSH handlers), //! SSH key delete-by-fingerprint, and issue counts after repo operations. use crate::harness::TestHarness; use makenotwork::db; // ── Repo CRUD ── #[tokio::test] async fn ssh_repo_list_and_info() { let mut h = TestHarness::new().await; h.signup("alice", "alice@example.com", "password123").await; let user = db::users::get_user_by_username(&h.db, &db::Username::from_trusted("alice".into())) .await .unwrap() .unwrap(); // No repos initially let repos = db::git_repos::get_repos_by_user(&h.db, user.id).await.unwrap(); assert!(repos.is_empty()); // Create a repo let repo = db::git_repos::create_repo(&h.db, user.id, "myproject").await.unwrap(); assert_eq!(repo.name, "myproject"); assert_eq!(repo.visibility, "public"); // List should show 1 repo let repos = db::git_repos::get_repos_by_user(&h.db, user.id).await.unwrap(); assert_eq!(repos.len(), 1); // Info: issue counts should be zero let (open, closed) = db::issues::get_issue_counts(&h.db, repo.id).await.unwrap(); assert_eq!(open, 0); assert_eq!(closed, 0); } #[tokio::test] async fn ssh_repo_set_visibility() { let mut h = TestHarness::new().await; h.signup("bob", "bob@example.com", "password123").await; let user = db::users::get_user_by_username(&h.db, &db::Username::from_trusted("bob".into())) .await .unwrap() .unwrap(); let repo = db::git_repos::create_repo(&h.db, user.id, "secret").await.unwrap(); assert_eq!(repo.visibility, db::Visibility::Public); // Update visibility db::git_repos::update_visibility(&h.db, repo.id, db::Visibility::Private).await.unwrap(); let updated = db::git_repos::get_repo_by_id(&h.db, repo.id).await.unwrap().unwrap(); assert_eq!(updated.visibility, db::Visibility::Private); // Also test unlisted db::git_repos::update_visibility(&h.db, repo.id, db::Visibility::Unlisted).await.unwrap(); let updated = db::git_repos::get_repo_by_id(&h.db, repo.id).await.unwrap().unwrap(); assert_eq!(updated.visibility, db::Visibility::Unlisted); } #[tokio::test] async fn ssh_repo_set_description() { let mut h = TestHarness::new().await; h.signup("carol", "carol@example.com", "password123").await; let user = db::users::get_user_by_username(&h.db, &db::Username::from_trusted("carol".into())) .await .unwrap() .unwrap(); let repo = db::git_repos::create_repo(&h.db, user.id, "docengine").await.unwrap(); assert!(repo.description.is_empty()); db::git_repos::update_repo_settings(&h.db, repo.id, "Markdown rendering engine", repo.visibility) .await .unwrap(); let updated = db::git_repos::get_repo_by_id(&h.db, repo.id).await.unwrap().unwrap(); assert_eq!(updated.description, "Markdown rendering engine"); } #[tokio::test] async fn ssh_repo_delete_cascades_issues() { let tmp = tempfile::TempDir::new().unwrap(); let mut h = TestHarness::with_git_repos(tmp.path().to_str().unwrap().to_string()).await; h.signup("dave", "dave@example.com", "password123").await; h.login("dave", "password123").await; let user = db::users::get_user_by_username(&h.db, &db::Username::from_trusted("dave".into())) .await .unwrap() .unwrap(); // Create a bare repo on disk + DB entry let bare_path = tmp.path().join("dave").join("deleteme.git"); std::fs::create_dir_all(&bare_path).unwrap(); git2::Repository::init_bare(&bare_path).unwrap(); // Make a commit so the repo page works { let bare = git2::Repository::open(&bare_path).unwrap(); let sig = git2::Signature::now("Test", "test@example.com").unwrap(); let blob = bare.blob(b"# Delete Me\n").unwrap(); let mut tb = bare.treebuilder(None).unwrap(); tb.insert("README.md", blob, 0o100644).unwrap(); let tree_oid = tb.write().unwrap(); let tree = bare.find_tree(tree_oid).unwrap(); bare.commit(Some("refs/heads/main"), &sig, &sig, "init", &tree, &[]).unwrap(); bare.set_head("refs/heads/main").unwrap(); } // Visit to auto-register let resp = h.client.get("/git/dave/deleteme").await; assert!(resp.status.is_success()); let repo = db::git_repos::get_repo_by_user_and_name(&h.db, user.id, "deleteme") .await .unwrap() .unwrap(); // Create an issue via direct DB insert (issues are email-only, no web write path) sqlx::query( "INSERT INTO issues (repo_id, number, author_user_id, title, body_markdown, body_html) VALUES ($1, 1, $2, 'TestIssue', 'body', '

body

')" ) .bind(repo.id) .bind(user.id) .execute(&h.db) .await .unwrap(); let (open, _) = db::issues::get_issue_counts(&h.db, repo.id).await.unwrap(); assert_eq!(open, 1); // Delete the repo — issues should cascade db::git_repos::delete_repo(&h.db, repo.id).await.unwrap(); // Verify repo is gone let gone = db::git_repos::get_repo_by_id(&h.db, repo.id).await.unwrap(); assert!(gone.is_none()); } // ── SSH key delete by fingerprint ── #[tokio::test] async fn ssh_key_delete_by_fingerprint() { let mut h = TestHarness::new().await; h.signup("eve", "eve@example.com", "password123").await; h.login("eve", "password123").await; // Add a key via the API let test_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGrJSsFMsNzFqLOsNjMoVMtQ3fMM4JhPmLPWVOmBsBzq test@example.com"; let body = format!("public_key={}&label=laptop", urlencoding::encode(test_key)); let resp = h.client.post_form("/api/users/me/ssh-keys", &body).await; assert!(resp.status.is_success(), "Add key failed: {}", resp.text); let json: serde_json::Value = resp.json(); let fingerprint = json["fingerprint"].as_str().unwrap().to_string(); let user = db::users::get_user_by_username(&h.db, &db::Username::from_trusted("eve".into())) .await .unwrap() .unwrap(); // Delete by fingerprint let deleted = db::ssh_keys::delete_key_by_fingerprint(&h.db, user.id, &fingerprint) .await .unwrap(); assert!(deleted); // Should not be found again let deleted_again = db::ssh_keys::delete_key_by_fingerprint(&h.db, user.id, &fingerprint) .await .unwrap(); assert!(!deleted_again); // Verify key list is empty let keys = db::ssh_keys::list_keys_by_user(&h.db, user.id).await.unwrap(); assert!(keys.is_empty()); } #[tokio::test] async fn ssh_key_delete_by_fingerprint_wrong_user() { let mut h = TestHarness::new().await; h.signup("frank", "frank@example.com", "password123").await; h.login("frank", "password123").await; let test_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGrJSsFMsNzFqLOsNjMoVMtQ3fMM4JhPmLPWVOmBsBzq test@example.com"; let body = format!("public_key={}&label=mykey", urlencoding::encode(test_key)); let resp = h.client.post_form("/api/users/me/ssh-keys", &body).await; assert!(resp.status.is_success()); let json: serde_json::Value = resp.json(); let fingerprint = json["fingerprint"].as_str().unwrap().to_string(); // Create another user h.client.post_form("/logout", "").await; h.signup("grace", "grace@example.com", "password123").await; let other_user = db::users::get_user_by_username(&h.db, &db::Username::from_trusted("grace".into())) .await .unwrap() .unwrap(); // Other user can't delete Frank's key let deleted = db::ssh_keys::delete_key_by_fingerprint(&h.db, other_user.id, &fingerprint) .await .unwrap(); assert!(!deleted); }