1use gen_core::errors::{ConfigError, ConnectionError, StrandError};
2use thiserror::Error;
3
4#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
5pub enum QueryError {
6 #[error("Results not found: {0}")]
7 ResultsNotFound(String),
8}
9
10#[derive(Debug, Error, PartialEq)]
11pub enum SampleError {
12 #[error("Query Error: {0}")]
13 QueryError(#[from] QueryError),
14 #[error("SQLite Error: {0}")]
15 SqliteError(#[from] rusqlite::Error),
16}
17
18#[derive(Debug, Error, PartialEq)]
19pub enum ChangeError {
20 #[error("Operation Error: {0}")]
21 OutOfBounds(String),
22}
23
24#[derive(Debug, Error, PartialEq)]
25pub enum ChangesetError {
26 #[error("Strand Error: {0}")]
27 StrandError(#[from] StrandError),
28 #[error("Missing Model: {0}")]
29 MissingModel(String),
30 #[error("SQLite Error: {0}")]
31 SqliteError(#[from] rusqlite::Error),
32}
33
34#[derive(Debug, PartialEq, Error)]
35pub enum OperationError {
36 #[error("Changeset Error: {0}")]
37 ChangesetError(#[from] ChangesetError),
38 #[error("Changeset Error: {0}")]
39 ConnectionError(#[from] ConnectionError),
40 #[error("No Changes")]
41 NoChanges,
42 #[error("Operation Already Exists")]
43 OperationExists,
44 #[error("Operation {0} Does Not Exist")]
45 NoOperation(String),
46 #[error("SQL Error: {0}")]
47 SQLError(String),
48 #[error("SQLite Error: {0}")]
49 SqliteError(#[from] rusqlite::Error),
50 #[error("Config Error: {0}")]
51 ConfigError(#[from] ConfigError),
52 #[error("Error storing data file")]
53 IOError,
54}
55
56#[derive(Debug, PartialEq, Error)]
57pub enum BranchError {
58 #[error("Cannot delete branch: {0}")]
59 CannotDelete(String),
60 #[error("SQL Error: {0}")]
61 SQLError(String),
62 #[error("SQLite Error: {0}")]
63 SqliteError(#[from] rusqlite::Error),
64}
65
66#[derive(Debug, Error, PartialEq)]
67pub enum RemoteError {
68 #[error("Remote '{0}' already exists")]
69 RemoteAlreadyExists(String),
70
71 #[error("Remote '{0}' not found")]
72 RemoteNotFound(String),
73
74 #[error("Invalid remote URL: {0}")]
75 InvalidUrl(String),
76
77 #[error("Invalid remote name: {0}")]
78 InvalidName(String),
79
80 #[error("Remote name cannot be empty")]
81 EmptyName,
82
83 #[error("Remote URL cannot be empty")]
84 EmptyUrl,
85
86 #[error("Remote name can only contain letters, numbers, hyphens, and underscores")]
87 InvalidNameCharacters,
88
89 #[error("URL must use HTTP, HTTPS, SSH protocol, or be a valid file path")]
90 UnsupportedUrlScheme,
91
92 #[error("Database error: {0}")]
93 DatabaseError(#[from] rusqlite::Error),
94
95 #[error("Cannot delete remote '{0}' as it is set as the default remote")]
96 CannotDeleteDefaultRemote(String),
97
98 #[error("Cannot set non-existent remote '{0}' as default")]
99 DefaultRemoteNotFound(String),
100}
101
102#[derive(Debug, Error)]
103pub enum FileAdditionError {
104 #[error("Failed to read file: {0}")]
105 FileReadError(#[from] std::io::Error),
106
107 #[error("File not found: {0}")]
108 FileNotFound(String),
109
110 #[error("Permission denied accessing file: {0}")]
111 FilePermissionDenied(String),
112
113 #[error("Failed to calculate checksum: {0}")]
114 ChecksumError(String),
115
116 #[error("Database error: {0}")]
117 DatabaseError(#[from] rusqlite::Error),
118
119 #[error("HashId generation failed: {0}")]
120 HashIdError(String),
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 mod remote_error_tests {
128 use super::*;
129 use crate::{
130 db::OperationsConnection,
131 operations::{Branch, Defaults, Remote},
132 test_helpers::get_operation_connection,
133 };
134
135 fn setup_test_db() -> OperationsConnection {
136 get_operation_connection(None).unwrap()
137 }
138
139 #[test]
140 fn test_remote_validation_empty_name() {
141 let result = Remote::validate_name("");
142 assert_eq!(result, Err(RemoteError::EmptyName));
143 }
144
145 #[test]
146 fn test_remote_validation_invalid_name_characters() {
147 assert_eq!(
149 Remote::validate_name("remote with spaces"),
150 Err(RemoteError::InvalidNameCharacters)
151 );
152 assert_eq!(
153 Remote::validate_name("remote@special"),
154 Err(RemoteError::InvalidNameCharacters)
155 );
156 assert_eq!(
157 Remote::validate_name("remote.dot"),
158 Err(RemoteError::InvalidNameCharacters)
159 );
160 assert_eq!(
161 Remote::validate_name("remote/slash"),
162 Err(RemoteError::InvalidNameCharacters)
163 );
164 assert_eq!(
165 Remote::validate_name("remote\\backslash"),
166 Err(RemoteError::InvalidNameCharacters)
167 );
168 assert_eq!(
169 Remote::validate_name("remote:colon"),
170 Err(RemoteError::InvalidNameCharacters)
171 );
172 assert_eq!(
173 Remote::validate_name("remote;semicolon"),
174 Err(RemoteError::InvalidNameCharacters)
175 );
176 assert_eq!(
177 Remote::validate_name("remote|pipe"),
178 Err(RemoteError::InvalidNameCharacters)
179 );
180 assert_eq!(
181 Remote::validate_name("remote<bracket"),
182 Err(RemoteError::InvalidNameCharacters)
183 );
184 assert_eq!(
185 Remote::validate_name("remote>bracket"),
186 Err(RemoteError::InvalidNameCharacters)
187 );
188 assert_eq!(
189 Remote::validate_name("remote?question"),
190 Err(RemoteError::InvalidNameCharacters)
191 );
192 assert_eq!(
193 Remote::validate_name("remote*asterisk"),
194 Err(RemoteError::InvalidNameCharacters)
195 );
196 assert_eq!(
197 Remote::validate_name("remote+plus"),
198 Err(RemoteError::InvalidNameCharacters)
199 );
200 assert_eq!(
201 Remote::validate_name("remote=equals"),
202 Err(RemoteError::InvalidNameCharacters)
203 );
204 assert_eq!(
205 Remote::validate_name("remote[bracket"),
206 Err(RemoteError::InvalidNameCharacters)
207 );
208 assert_eq!(
209 Remote::validate_name("remote]bracket"),
210 Err(RemoteError::InvalidNameCharacters)
211 );
212 assert_eq!(
213 Remote::validate_name("remote{brace"),
214 Err(RemoteError::InvalidNameCharacters)
215 );
216 assert_eq!(
217 Remote::validate_name("remote}brace"),
218 Err(RemoteError::InvalidNameCharacters)
219 );
220 assert_eq!(
221 Remote::validate_name("remote(paren"),
222 Err(RemoteError::InvalidNameCharacters)
223 );
224 assert_eq!(
225 Remote::validate_name("remote)paren"),
226 Err(RemoteError::InvalidNameCharacters)
227 );
228 assert_eq!(
229 Remote::validate_name("remote\"quote"),
230 Err(RemoteError::InvalidNameCharacters)
231 );
232 assert_eq!(
233 Remote::validate_name("remote'quote"),
234 Err(RemoteError::InvalidNameCharacters)
235 );
236 assert_eq!(
237 Remote::validate_name("remote`backtick"),
238 Err(RemoteError::InvalidNameCharacters)
239 );
240 assert_eq!(
241 Remote::validate_name("remote~tilde"),
242 Err(RemoteError::InvalidNameCharacters)
243 );
244 assert_eq!(
245 Remote::validate_name("remote!exclamation"),
246 Err(RemoteError::InvalidNameCharacters)
247 );
248 assert_eq!(
249 Remote::validate_name("remote#hash"),
250 Err(RemoteError::InvalidNameCharacters)
251 );
252 assert_eq!(
253 Remote::validate_name("remote$dollar"),
254 Err(RemoteError::InvalidNameCharacters)
255 );
256 assert_eq!(
257 Remote::validate_name("remote%percent"),
258 Err(RemoteError::InvalidNameCharacters)
259 );
260 assert_eq!(
261 Remote::validate_name("remote^caret"),
262 Err(RemoteError::InvalidNameCharacters)
263 );
264 assert_eq!(
265 Remote::validate_name("remote&ersand"),
266 Err(RemoteError::InvalidNameCharacters)
267 );
268 }
269
270 #[test]
271 fn test_remote_validation_valid_names() {
272 assert!(Remote::validate_name("origin").is_ok());
274 assert!(Remote::validate_name("my-remote").is_ok());
275 assert!(Remote::validate_name("remote_1").is_ok());
276 assert!(Remote::validate_name("test123").is_ok());
277 assert!(Remote::validate_name("UPPERCASE").is_ok());
278 assert!(Remote::validate_name("MixedCase").is_ok());
279 assert!(Remote::validate_name("remote-with-many-hyphens").is_ok());
280 assert!(Remote::validate_name("remote_with_many_underscores").is_ok());
281 assert!(Remote::validate_name("remote123with456numbers").is_ok());
282 assert!(Remote::validate_name("a").is_ok()); assert!(Remote::validate_name("a1").is_ok());
284 assert!(Remote::validate_name("1a").is_ok());
285 assert!(Remote::validate_name("123").is_ok()); assert!(Remote::validate_name("remote-123_test").is_ok()); }
288
289 #[test]
290 fn test_url_validation_empty_url() {
291 let result = Remote::validate_url("");
292 assert_eq!(result, Err(RemoteError::EmptyUrl));
293 }
294
295 #[test]
296 fn test_url_validation_valid_urls() {
297 assert!(Remote::validate_url("https://genhub.bio/user/repo.gen").is_ok());
299 assert!(Remote::validate_url("http://example.com/repo").is_ok());
300 assert!(Remote::validate_url("https://gitlab.com/user/repo").is_ok());
301 assert!(Remote::validate_url("http://localhost:8080/repo").is_ok());
302
303 assert!(Remote::validate_url("ssh://git@genhub.bio/user/repo.gen").is_ok());
305 assert!(Remote::validate_url("ssh://user@host.com:2222/path/to/repo").is_ok());
306
307 assert!(Remote::validate_url("/path/to/local/repo").is_ok());
309 assert!(Remote::validate_url("/home/user/repos/myrepo").is_ok());
310 assert!(Remote::validate_url("/tmp/test").is_ok());
311 assert!(Remote::validate_url("file:///tmp/test").is_ok());
312
313 assert!(Remote::validate_url("user@host:path/to/repo").is_ok());
315 assert!(Remote::validate_url("git@genhub.bio:user/repo.gen").is_ok());
316 assert!(Remote::validate_url("user@server.com:~/repos/project").is_ok());
317 }
318
319 #[test]
320 fn test_url_validation_invalid_urls() {
321 assert_eq!(
323 Remote::validate_url("ftp://invalid-protocol.com"),
324 Err(RemoteError::UnsupportedUrlScheme)
325 );
326 assert_eq!(
327 Remote::validate_url("smb://network/share"),
328 Err(RemoteError::UnsupportedUrlScheme)
329 );
330
331 assert_eq!(
333 Remote::validate_url("not-a-url"),
334 Err(RemoteError::UnsupportedUrlScheme)
335 );
336 assert_eq!(
337 Remote::validate_url("just-text"),
338 Err(RemoteError::UnsupportedUrlScheme)
339 );
340 assert_eq!(
341 Remote::validate_url("no-scheme-or-path"),
342 Err(RemoteError::UnsupportedUrlScheme)
343 );
344
345 assert!(matches!(
347 Remote::validate_url("https://"),
348 Err(RemoteError::InvalidUrl(_))
349 ));
350 assert!(matches!(
351 Remote::validate_url("http://[invalid"),
352 Err(RemoteError::InvalidUrl(_))
353 ));
354 }
355
356 #[test]
357 fn test_remote_create_success() {
358 let conn = setup_test_db();
359
360 let result = Remote::create(&conn, "origin", "https://genhub.bio/user/repo.gen");
361 assert!(result.is_ok());
362
363 let remote = result.unwrap();
364 assert_eq!(remote.name, "origin");
365 assert_eq!(remote.url, "https://genhub.bio/user/repo.gen");
366 }
367
368 #[test]
369 fn test_remote_create_duplicate_name() {
370 let conn = setup_test_db();
371
372 Remote::create(&conn, "origin", "https://genhub.bio/user/repo1.gen").unwrap();
374
375 let result = Remote::create(&conn, "origin", "https://genhub.bio/user/repo2.gen");
377 assert_eq!(
378 result,
379 Err(RemoteError::RemoteAlreadyExists("origin".to_string()))
380 );
381 }
382
383 #[test]
384 fn test_remote_create_invalid_name() {
385 let conn = setup_test_db();
386
387 let result = Remote::create(&conn, "", "https://genhub.bio/user/repo.gen");
388 assert_eq!(result, Err(RemoteError::EmptyName));
389
390 let result = Remote::create(&conn, "invalid name", "https://genhub.bio/user/repo.gen");
391 assert_eq!(result, Err(RemoteError::InvalidNameCharacters));
392 }
393
394 #[test]
395 fn test_remote_create_invalid_url() {
396 let conn = setup_test_db();
397
398 let result = Remote::create(&conn, "origin", "");
399 assert_eq!(result, Err(RemoteError::EmptyUrl));
400
401 let result = Remote::create(&conn, "origin", "ftp://invalid.com");
402 assert_eq!(result, Err(RemoteError::UnsupportedUrlScheme));
403 }
404
405 #[test]
406 fn test_remote_get_by_name_success() {
407 let conn = setup_test_db();
408
409 Remote::create(&conn, "origin", "https://genhub.bio/user/repo.gen").unwrap();
410
411 let result = Remote::get_by_name(&conn, "origin");
412 assert!(result.is_ok());
413
414 let remote = result.unwrap();
415 assert_eq!(remote.name, "origin");
416 assert_eq!(remote.url, "https://genhub.bio/user/repo.gen");
417 }
418
419 #[test]
420 fn test_remote_get_by_name_not_found() {
421 let conn = setup_test_db();
422
423 let result = Remote::get_by_name(&conn, "nonexistent");
424 assert_eq!(
425 result,
426 Err(RemoteError::RemoteNotFound("nonexistent".to_string()))
427 );
428 }
429
430 #[test]
431 fn test_remote_delete_success() {
432 let conn = setup_test_db();
433
434 Remote::create(&conn, "origin", "https://genhub.bio/user/repo.gen").unwrap();
435
436 let result = Remote::delete(&conn, "origin");
437 assert!(result.is_ok());
438
439 let get_result = Remote::get_by_name(&conn, "origin");
441 assert_eq!(
442 get_result,
443 Err(RemoteError::RemoteNotFound("origin".to_string()))
444 );
445 }
446
447 #[test]
448 fn test_remote_delete_not_found() {
449 let conn = setup_test_db();
450
451 let result = Remote::delete(&conn, "nonexistent");
452 assert_eq!(
453 result,
454 Err(RemoteError::RemoteNotFound("nonexistent".to_string()))
455 );
456 }
457
458 #[test]
459 fn test_remote_exists() {
460 let conn = setup_test_db();
461
462 assert!(!Remote::exists(&conn, "origin"));
463
464 Remote::create(&conn, "origin", "https://genhub.bio/user/repo.gen").unwrap();
465 assert!(Remote::exists(&conn, "origin"));
466
467 Remote::delete(&conn, "origin").unwrap();
468 assert!(!Remote::exists(&conn, "origin"));
469 }
470
471 #[test]
472 fn test_defaults_set_default_remote_success() {
473 let conn = setup_test_db();
474
475 Remote::create(&conn, "origin", "https://genhub.bio/user/repo.gen").unwrap();
476
477 let result = Defaults::set_default_remote(&conn, Some("origin"));
478 assert!(result.is_ok());
479
480 let default = Defaults::get_default_remote(&conn);
481 assert_eq!(default, Some("origin".to_string()));
482 }
483
484 #[test]
485 fn test_defaults_set_default_remote_not_found() {
486 let conn = setup_test_db();
487
488 let result = Defaults::set_default_remote(&conn, Some("nonexistent"));
489 assert_eq!(
490 result,
491 Err(RemoteError::RemoteNotFound("nonexistent".to_string()))
492 );
493 }
494
495 #[test]
496 fn test_defaults_set_default_remote_clear() {
497 let conn = setup_test_db();
498
499 Remote::create(&conn, "origin", "https://genhub.bio/user/repo.gen").unwrap();
500 Defaults::set_default_remote(&conn, Some("origin")).unwrap();
501
502 let result = Defaults::set_default_remote(&conn, None);
503 assert!(result.is_ok());
504
505 let default = Defaults::get_default_remote(&conn);
506 assert_eq!(default, None);
507 }
508
509 #[test]
510 fn test_defaults_get_default_remote_url() {
511 let conn = setup_test_db();
512
513 assert_eq!(Defaults::get_default_remote_url(&conn), None);
515
516 Remote::create(&conn, "origin", "https://genhub.bio/user/repo.gen").unwrap();
518 Defaults::set_default_remote(&conn, Some("origin")).unwrap();
519
520 let url = Defaults::get_default_remote_url(&conn);
521 assert_eq!(url, Some("https://genhub.bio/user/repo.gen".to_string()));
522
523 Remote::delete(&conn, "origin").unwrap();
525 let url = Defaults::get_default_remote_url(&conn);
526 assert_eq!(url, None);
527 }
528
529 #[test]
530 fn test_branch_set_remote_validated_success() {
531 let conn = setup_test_db();
532
533 Remote::create(&conn, "origin", "https://genhub.bio/user/repo.gen").unwrap();
535
536 let branch_id = 1i64;
539
540 let result = Branch::set_remote_validated(&conn, branch_id, Some("origin"));
541 assert!(result.is_ok());
542 }
543
544 #[test]
545 fn test_branch_set_remote_validated_remote_not_found() {
546 let conn = setup_test_db();
547
548 let branch_id = 1i64;
549
550 let result = Branch::set_remote_validated(&conn, branch_id, Some("nonexistent"));
551 assert_eq!(
552 result,
553 Err(RemoteError::RemoteNotFound("nonexistent".to_string()))
554 );
555 }
556
557 #[test]
558 fn test_branch_set_remote_validated_clear() {
559 let conn = setup_test_db();
560
561 let branch_id = 1i64;
562
563 let result = Branch::set_remote_validated(&conn, branch_id, None);
564 assert!(result.is_ok());
565 }
566
567 #[test]
568 fn test_error_display_messages() {
569 assert_eq!(
571 RemoteError::EmptyName.to_string(),
572 "Remote name cannot be empty"
573 );
574 assert_eq!(
575 RemoteError::EmptyUrl.to_string(),
576 "Remote URL cannot be empty"
577 );
578 assert_eq!(
579 RemoteError::InvalidNameCharacters.to_string(),
580 "Remote name can only contain letters, numbers, hyphens, and underscores"
581 );
582 assert_eq!(
583 RemoteError::UnsupportedUrlScheme.to_string(),
584 "URL must use HTTP, HTTPS, SSH protocol, or be a valid file path"
585 );
586 assert_eq!(
587 RemoteError::RemoteAlreadyExists("origin".to_string()).to_string(),
588 "Remote 'origin' already exists"
589 );
590 assert_eq!(
591 RemoteError::RemoteNotFound("origin".to_string()).to_string(),
592 "Remote 'origin' not found"
593 );
594 assert_eq!(
595 RemoteError::InvalidUrl("test".to_string()).to_string(),
596 "Invalid remote URL: test"
597 );
598 assert_eq!(
599 RemoteError::InvalidName("test".to_string()).to_string(),
600 "Invalid remote name: test"
601 );
602 assert_eq!(
603 RemoteError::CannotDeleteDefaultRemote("origin".to_string()).to_string(),
604 "Cannot delete remote 'origin' as it is set as the default remote"
605 );
606 assert_eq!(
607 RemoteError::DefaultRemoteNotFound("origin".to_string()).to_string(),
608 "Cannot set non-existent remote 'origin' as default"
609 );
610 }
611
612 #[test]
613 fn test_error_equality() {
614 assert_eq!(RemoteError::EmptyName, RemoteError::EmptyName);
616 assert_eq!(RemoteError::EmptyUrl, RemoteError::EmptyUrl);
617 assert_eq!(
618 RemoteError::InvalidNameCharacters,
619 RemoteError::InvalidNameCharacters
620 );
621 assert_eq!(
622 RemoteError::UnsupportedUrlScheme,
623 RemoteError::UnsupportedUrlScheme
624 );
625 assert_eq!(
626 RemoteError::RemoteAlreadyExists("test".to_string()),
627 RemoteError::RemoteAlreadyExists("test".to_string())
628 );
629 assert_eq!(
630 RemoteError::RemoteNotFound("test".to_string()),
631 RemoteError::RemoteNotFound("test".to_string())
632 );
633
634 assert_ne!(RemoteError::EmptyName, RemoteError::EmptyUrl);
636 assert_ne!(
637 RemoteError::RemoteAlreadyExists("test1".to_string()),
638 RemoteError::RemoteAlreadyExists("test2".to_string())
639 );
640 }
641
642 #[test]
643 fn test_comprehensive_validation_edge_cases() {
644 assert!(Remote::validate_name("a").is_ok()); assert!(Remote::validate_name("1").is_ok()); assert!(Remote::validate_name("-").is_ok()); assert!(Remote::validate_name("_").is_ok()); let long_name = "a".repeat(1000);
652 assert!(Remote::validate_name(&long_name).is_ok());
653
654 let long_name_with_valid_chars = "a-b_c".repeat(200);
655 assert!(Remote::validate_name(&long_name_with_valid_chars).is_ok());
656
657 assert!(Remote::validate_url("https://a.b").is_ok()); assert!(Remote::validate_url("/a").is_ok()); assert!(Remote::validate_url("a:b").is_ok()); }
662 }
663}