Skip to main content

max / makenotwork

7.8 KB · 237 lines History Blame Raw
1 //! SSH management command integration tests.
2 //!
3 //! Tests repo CRUD (via DB functions used by the SSH handlers),
4 //! SSH key delete-by-fingerprint, and issue counts after repo operations.
5
6 use crate::harness::TestHarness;
7 use makenotwork::db;
8
9 // ── Repo CRUD ──
10
11 #[tokio::test]
12 async fn ssh_repo_list_and_info() {
13 let mut h = TestHarness::new().await;
14 h.signup("alice", "alice@example.com", "password123").await;
15 let user = db::users::get_user_by_username(&h.db, &db::Username::from_trusted("alice".into()))
16 .await
17 .unwrap()
18 .unwrap();
19
20 // No repos initially
21 let repos = db::git_repos::get_repos_by_user(&h.db, user.id)
22 .await
23 .unwrap();
24 assert!(repos.is_empty());
25
26 let repo = db::git_repos::create_repo(&h.db, user.id, "myproject")
27 .await
28 .unwrap();
29 assert_eq!(repo.name, "myproject");
30 assert_eq!(repo.visibility, "public");
31
32 // List should show 1 repo
33 let repos = db::git_repos::get_repos_by_user(&h.db, user.id)
34 .await
35 .unwrap();
36 assert_eq!(repos.len(), 1);
37
38 // Info: issue counts should be zero
39 let (open, closed) = db::issues::get_issue_counts(&h.db, repo.id).await.unwrap();
40 assert_eq!(open, 0);
41 assert_eq!(closed, 0);
42 }
43
44 #[tokio::test]
45 async fn ssh_repo_set_visibility() {
46 let mut h = TestHarness::new().await;
47 h.signup("bob", "bob@example.com", "password123").await;
48 let user = db::users::get_user_by_username(&h.db, &db::Username::from_trusted("bob".into()))
49 .await
50 .unwrap()
51 .unwrap();
52
53 let repo = db::git_repos::create_repo(&h.db, user.id, "secret")
54 .await
55 .unwrap();
56 assert_eq!(repo.visibility, db::Visibility::Public);
57
58 db::git_repos::update_visibility(&h.db, repo.id, db::Visibility::Private)
59 .await
60 .unwrap();
61
62 let updated = db::git_repos::get_repo_by_id(&h.db, repo.id)
63 .await
64 .unwrap()
65 .unwrap();
66 assert_eq!(updated.visibility, db::Visibility::Private);
67
68 // Also test unlisted
69 db::git_repos::update_visibility(&h.db, repo.id, db::Visibility::Unlisted)
70 .await
71 .unwrap();
72 let updated = db::git_repos::get_repo_by_id(&h.db, repo.id)
73 .await
74 .unwrap()
75 .unwrap();
76 assert_eq!(updated.visibility, db::Visibility::Unlisted);
77 }
78
79 #[tokio::test]
80 async fn ssh_repo_set_description() {
81 let mut h = TestHarness::new().await;
82 h.signup("carol", "carol@example.com", "password123").await;
83 let user = db::users::get_user_by_username(&h.db, &db::Username::from_trusted("carol".into()))
84 .await
85 .unwrap()
86 .unwrap();
87
88 let repo = db::git_repos::create_repo(&h.db, user.id, "docengine")
89 .await
90 .unwrap();
91 assert!(repo.description.is_empty());
92
93 db::git_repos::update_repo_settings(
94 &h.db,
95 repo.id,
96 "Markdown rendering engine",
97 repo.visibility,
98 )
99 .await
100 .unwrap();
101
102 let updated = db::git_repos::get_repo_by_id(&h.db, repo.id)
103 .await
104 .unwrap()
105 .unwrap();
106 assert_eq!(updated.description, "Markdown rendering engine");
107 }
108
109 #[tokio::test]
110 async fn ssh_repo_delete_cascades_issues() {
111 let tmp = tempfile::TempDir::new().unwrap();
112 let mut h = TestHarness::with_git_repos(tmp.path().to_str().unwrap().to_string()).await;
113 h.signup("dave", "dave@example.com", "password123").await;
114 h.login("dave", "password123").await;
115
116 let user = db::users::get_user_by_username(&h.db, &db::Username::from_trusted("dave".into()))
117 .await
118 .unwrap()
119 .unwrap();
120
121 // Create a bare repo on disk + DB entry
122 let bare_path = tmp.path().join("dave").join("deleteme.git");
123 std::fs::create_dir_all(&bare_path).unwrap();
124 git2::Repository::init_bare(&bare_path).unwrap();
125
126 // Make a commit so the repo page works
127 {
128 let bare = git2::Repository::open(&bare_path).unwrap();
129 let sig = git2::Signature::now("Test", "test@example.com").unwrap();
130 let blob = bare.blob(b"# Delete Me\n").unwrap();
131 let mut tb = bare.treebuilder(None).unwrap();
132 tb.insert("README.md", blob, 0o100_644).unwrap();
133 let tree_oid = tb.write().unwrap();
134 let tree = bare.find_tree(tree_oid).unwrap();
135 bare.commit(Some("refs/heads/main"), &sig, &sig, "init", &tree, &[])
136 .unwrap();
137 bare.set_head("refs/heads/main").unwrap();
138 }
139
140 // Visit to auto-register
141 let resp = h.client.get("/git/dave/deleteme").await;
142 assert!(resp.status.is_success());
143
144 let repo = db::git_repos::get_repo_by_user_and_name(&h.db, user.id, "deleteme")
145 .await
146 .unwrap()
147 .unwrap();
148
149 // Create an issue via direct DB insert (issues are email-only, no web write path)
150 sqlx::query(
151 "INSERT INTO issues (repo_id, number, author_user_id, title, body_markdown, body_html) VALUES ($1, 1, $2, 'TestIssue', 'body', '<p>body</p>')"
152 )
153 .bind(repo.id)
154 .bind(user.id)
155 .execute(&h.db)
156 .await
157 .unwrap();
158
159 let (open, _) = db::issues::get_issue_counts(&h.db, repo.id).await.unwrap();
160 assert_eq!(open, 1);
161
162 // Delete the repo, issues should cascade
163 db::git_repos::delete_repo(&h.db, repo.id).await.unwrap();
164
165 // Verify repo is gone
166 let gone = db::git_repos::get_repo_by_id(&h.db, repo.id).await.unwrap();
167 assert!(gone.is_none());
168 }
169
170 // ── SSH key delete by fingerprint ──
171
172 #[tokio::test]
173 async fn ssh_key_delete_by_fingerprint() {
174 let mut h = TestHarness::new().await;
175 h.signup("eve", "eve@example.com", "password123").await;
176 h.login("eve", "password123").await;
177
178 // Add a key via the API
179 let test_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGrJSsFMsNzFqLOsNjMoVMtQ3fMM4JhPmLPWVOmBsBzq test@example.com";
180 let body = format!("public_key={}&label=laptop", urlencoding::encode(test_key));
181 let resp = h.client.post_form("/api/users/me/ssh-keys", &body).await;
182 assert!(resp.status.is_success(), "Add key failed: {}", resp.text);
183 let json: serde_json::Value = resp.json();
184 let fingerprint = json["fingerprint"].as_str().unwrap().to_string();
185
186 let user = db::users::get_user_by_username(&h.db, &db::Username::from_trusted("eve".into()))
187 .await
188 .unwrap()
189 .unwrap();
190
191 let deleted = db::ssh_keys::delete_key_by_fingerprint(&h.db, user.id, &fingerprint)
192 .await
193 .unwrap();
194 assert!(deleted);
195
196 // Should not be found again
197 let deleted_again = db::ssh_keys::delete_key_by_fingerprint(&h.db, user.id, &fingerprint)
198 .await
199 .unwrap();
200 assert!(!deleted_again);
201
202 // Verify key list is empty
203 let keys = db::ssh_keys::list_keys_by_user(&h.db, user.id)
204 .await
205 .unwrap();
206 assert!(keys.is_empty());
207 }
208
209 #[tokio::test]
210 async fn ssh_key_delete_by_fingerprint_wrong_user() {
211 let mut h = TestHarness::new().await;
212 h.signup("frank", "frank@example.com", "password123").await;
213 h.login("frank", "password123").await;
214
215 let test_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGrJSsFMsNzFqLOsNjMoVMtQ3fMM4JhPmLPWVOmBsBzq test@example.com";
216 let body = format!("public_key={}&label=mykey", urlencoding::encode(test_key));
217 let resp = h.client.post_form("/api/users/me/ssh-keys", &body).await;
218 assert!(resp.status.is_success());
219 let json: serde_json::Value = resp.json();
220 let fingerprint = json["fingerprint"].as_str().unwrap().to_string();
221
222 // Create another user
223 h.client.post_form("/logout", "").await;
224 h.signup("grace", "grace@example.com", "password123").await;
225 let other_user =
226 db::users::get_user_by_username(&h.db, &db::Username::from_trusted("grace".into()))
227 .await
228 .unwrap()
229 .unwrap();
230
231 // Other user can't delete Frank's key
232 let deleted = db::ssh_keys::delete_key_by_fingerprint(&h.db, other_user.id, &fingerprint)
233 .await
234 .unwrap();
235 assert!(!deleted);
236 }
237