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