1pub mod error;
2pub mod trait_def;
3pub mod schema;
4
5#[cfg(feature = "sqlite")]
6pub mod sqlite;
7
8#[cfg(feature = "sqlite")]
9pub mod migration;
10
11#[cfg(test)]
12#[cfg(feature = "sqlite")]
13mod integration_tests;
14
15pub use error::{StorageError, StorageOp, StorageResult};
17pub use trait_def::{BatchOp, GraphStorage, NodeFilter};
18pub use schema::SCHEMA_SQL;
19
20#[cfg(feature = "sqlite")]
21pub use sqlite::{Direction, SqliteStorage};
22
23#[cfg(feature = "sqlite")]
24pub use migration::{migrate, MigrationConfig, MigrationReport, MigrationError, MigrationStatus, ValidationLevel};
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum StorageBackend {
33 Yaml,
35 Sqlite,
37}
38
39impl std::fmt::Display for StorageBackend {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 match self {
42 StorageBackend::Yaml => write!(f, "yaml"),
43 StorageBackend::Sqlite => write!(f, "sqlite"),
44 }
45 }
46}
47
48pub fn detect_backend(gid_dir: &std::path::Path) -> StorageBackend {
57 let db_path = gid_dir.join("graph.db");
58 let yaml_path = gid_dir.join("graph.yml");
59
60 if db_path.exists() {
61 StorageBackend::Sqlite
62 } else if yaml_path.exists() {
63 StorageBackend::Yaml
64 } else {
65 StorageBackend::Sqlite
67 }
68}
69
70pub fn resolve_backend(
74 explicit: Option<StorageBackend>,
75 gid_dir: &std::path::Path,
76) -> StorageBackend {
77 explicit.unwrap_or_else(|| detect_backend(gid_dir))
78}
79
80impl std::str::FromStr for StorageBackend {
81 type Err = String;
82
83 fn from_str(s: &str) -> Result<Self, Self::Err> {
84 match s.to_lowercase().as_str() {
85 "yaml" | "yml" => Ok(StorageBackend::Yaml),
86 "sqlite" | "db" | "sql" => Ok(StorageBackend::Sqlite),
87 _ => Err(format!("unknown backend '{}': expected 'yaml' or 'sqlite'", s)),
88 }
89 }
90}
91
92#[cfg(feature = "sqlite")]
101pub fn load_graph_from_sqlite(db_path: &std::path::Path) -> Result<crate::Graph, StorageError> {
102 let storage = SqliteStorage::open(db_path)?;
103
104 let project = storage.get_project_meta()?;
106
107 let ids = storage.get_all_node_ids()?;
109 let mut nodes = Vec::with_capacity(ids.len());
110 for id in &ids {
111 if let Some(node) = storage.get_node(id)? {
112 nodes.push(node);
113 }
114 }
115
116 let mut seen_edges = std::collections::HashSet::new();
118 let mut edges = Vec::new();
119 for id in &ids {
120 for edge in storage.get_edges(id)? {
121 let key = (edge.from.clone(), edge.to.clone(), edge.relation.clone());
122 if seen_edges.insert(key) {
123 edges.push(edge);
124 }
125 }
126 }
127
128 Ok(crate::Graph {
129 project,
130 nodes,
131 edges,
132 })
133}
134
135#[cfg(feature = "sqlite")]
143pub fn save_graph_to_sqlite(graph: &crate::Graph, db_path: &std::path::Path) -> Result<(), StorageError> {
144 let storage = SqliteStorage::open(db_path)?;
145
146 let mut ops = Vec::new();
148
149 let existing_ids = storage.get_all_node_ids()?;
151 for id in existing_ids {
152 ops.push(BatchOp::DeleteNode(id));
153 }
154
155 for node in &graph.nodes {
157 ops.push(BatchOp::PutNode(node.clone()));
158 }
159
160 for edge in &graph.edges {
162 ops.push(BatchOp::AddEdge(edge.clone()));
163 }
164
165 storage.execute_migration_batch(&ops)?;
167
168 if let Some(ref meta) = graph.project {
170 storage.set_project_meta(meta)?;
171 }
172
173 Ok(())
174}
175
176pub fn load_graph_auto(
189 gid_dir: &std::path::Path,
190 explicit_backend: Option<StorageBackend>,
191) -> Result<crate::Graph, Box<dyn std::error::Error + Send + Sync>> {
192 let backend = resolve_backend(explicit_backend, gid_dir);
193 match backend {
194 StorageBackend::Yaml => {
195 let yaml_path = gid_dir.join("graph.yml");
196 if yaml_path.exists() {
197 crate::load_graph(&yaml_path).map_err(|e| e.into())
198 } else {
199 Ok(crate::Graph::default())
200 }
201 }
202 #[cfg(feature = "sqlite")]
203 StorageBackend::Sqlite => {
204 let db_path = gid_dir.join("graph.db");
205 if db_path.exists() {
206 load_graph_from_sqlite(&db_path).map_err(|e| e.into())
207 } else {
208 Ok(crate::Graph::default())
209 }
210 }
211 #[cfg(not(feature = "sqlite"))]
212 StorageBackend::Sqlite => {
213 Err("SQLite backend not available (compile with --features sqlite)".into())
214 }
215 }
216}
217
218pub fn save_graph_auto(
223 graph: &crate::Graph,
224 gid_dir: &std::path::Path,
225 explicit_backend: Option<StorageBackend>,
226) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
227 let backend = resolve_backend(explicit_backend, gid_dir);
228 match backend {
229 StorageBackend::Yaml => {
230 let yaml_path = gid_dir.join("graph.yml");
231 crate::save_graph(graph, &yaml_path).map_err(|e| e.into())
232 }
233 #[cfg(feature = "sqlite")]
234 StorageBackend::Sqlite => {
235 let db_path = gid_dir.join("graph.db");
236 save_graph_to_sqlite(graph, &db_path).map_err(|e| e.into())
237 }
238 #[cfg(not(feature = "sqlite"))]
239 StorageBackend::Sqlite => {
240 Err("SQLite backend not available (compile with --features sqlite)".into())
241 }
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use tempfile::TempDir;
249 use std::fs;
250
251 #[test]
252 fn test_detect_yaml_when_graph_yml_exists() {
253 let tmp = TempDir::new().unwrap();
254 fs::write(tmp.path().join("graph.yml"), "nodes: []\nedges: []\n").unwrap();
255 assert_eq!(detect_backend(tmp.path()), StorageBackend::Yaml);
256 }
257
258 #[test]
259 fn test_detect_sqlite_when_graph_db_exists() {
260 let tmp = TempDir::new().unwrap();
261 fs::write(tmp.path().join("graph.db"), "").unwrap(); assert_eq!(detect_backend(tmp.path()), StorageBackend::Sqlite);
263 }
264
265 #[test]
266 fn test_detect_sqlite_takes_priority_over_yaml() {
267 let tmp = TempDir::new().unwrap();
268 fs::write(tmp.path().join("graph.yml"), "nodes: []\n").unwrap();
269 fs::write(tmp.path().join("graph.db"), "").unwrap();
270 assert_eq!(detect_backend(tmp.path()), StorageBackend::Sqlite);
272 }
273
274 #[test]
275 fn test_detect_defaults_to_sqlite_empty_dir() {
276 let tmp = TempDir::new().unwrap();
277 assert_eq!(detect_backend(tmp.path()), StorageBackend::Sqlite);
278 }
279
280 #[test]
281 fn test_resolve_explicit_overrides_detection() {
282 let tmp = TempDir::new().unwrap();
283 fs::write(tmp.path().join("graph.db"), "").unwrap();
284 assert_eq!(
286 resolve_backend(Some(StorageBackend::Yaml), tmp.path()),
287 StorageBackend::Yaml
288 );
289 }
290
291 #[test]
292 fn test_resolve_none_delegates_to_detection() {
293 let tmp = TempDir::new().unwrap();
294 fs::write(tmp.path().join("graph.yml"), "").unwrap();
295 assert_eq!(
296 resolve_backend(None, tmp.path()),
297 StorageBackend::Yaml
298 );
299 }
300
301 #[test]
302 fn test_backend_display() {
303 assert_eq!(StorageBackend::Yaml.to_string(), "yaml");
304 assert_eq!(StorageBackend::Sqlite.to_string(), "sqlite");
305 }
306
307 #[test]
308 fn test_backend_equality() {
309 assert_eq!(StorageBackend::Yaml, StorageBackend::Yaml);
310 assert_eq!(StorageBackend::Sqlite, StorageBackend::Sqlite);
311 assert_ne!(StorageBackend::Yaml, StorageBackend::Sqlite);
312 }
313
314 #[test]
315 fn test_backend_from_str() {
316 assert_eq!("yaml".parse::<StorageBackend>().unwrap(), StorageBackend::Yaml);
317 assert_eq!("yml".parse::<StorageBackend>().unwrap(), StorageBackend::Yaml);
318 assert_eq!("sqlite".parse::<StorageBackend>().unwrap(), StorageBackend::Sqlite);
319 assert_eq!("db".parse::<StorageBackend>().unwrap(), StorageBackend::Sqlite);
320 assert_eq!("sql".parse::<StorageBackend>().unwrap(), StorageBackend::Sqlite);
321 assert!("unknown".parse::<StorageBackend>().is_err());
322 }
323
324 #[test]
325 fn test_backend_from_str_case_insensitive() {
326 assert_eq!("YAML".parse::<StorageBackend>().unwrap(), StorageBackend::Yaml);
327 assert_eq!("SQLite".parse::<StorageBackend>().unwrap(), StorageBackend::Sqlite);
328 assert_eq!("DB".parse::<StorageBackend>().unwrap(), StorageBackend::Sqlite);
329 }
330
331 #[test]
334 fn test_load_graph_auto_yaml() {
335 let tmp = TempDir::new().unwrap();
336 let yaml = "project:\n name: test\nnodes:\n - id: n1\n title: Node 1\nedges: []\n";
337 fs::write(tmp.path().join("graph.yml"), yaml).unwrap();
338
339 let graph = load_graph_auto(tmp.path(), None).unwrap();
340 assert_eq!(graph.project.as_ref().unwrap().name, "test");
341 assert_eq!(graph.nodes.len(), 1);
342 assert_eq!(graph.nodes[0].id, "n1");
343 }
344
345 #[test]
346 fn test_load_graph_auto_empty_dir_returns_default() {
347 let tmp = TempDir::new().unwrap();
348 let graph = load_graph_auto(tmp.path(), None).unwrap();
349 assert!(graph.nodes.is_empty());
350 assert!(graph.edges.is_empty());
351 }
352
353 #[test]
354 fn test_load_graph_auto_explicit_yaml_override() {
355 let tmp = TempDir::new().unwrap();
356 let yaml = "nodes:\n - id: y1\n title: YAML node\nedges: []\n";
357 fs::write(tmp.path().join("graph.yml"), yaml).unwrap();
358 let graph = load_graph_auto(tmp.path(), Some(StorageBackend::Yaml)).unwrap();
362 assert_eq!(graph.nodes.len(), 1);
363 assert_eq!(graph.nodes[0].id, "y1");
364 }
365
366 #[test]
369 fn test_save_graph_auto_yaml() {
370 let tmp = TempDir::new().unwrap();
371 let graph = crate::Graph {
372 project: Some(crate::graph::ProjectMeta {
373 name: "test-save".into(),
374 description: None,
375 }),
376 nodes: vec![crate::graph::Node::new("s1", "Saved 1")],
377 edges: vec![],
378 };
379
380 save_graph_auto(&graph, tmp.path(), Some(StorageBackend::Yaml)).unwrap();
381 assert!(tmp.path().join("graph.yml").exists());
382
383 let loaded = load_graph_auto(tmp.path(), Some(StorageBackend::Yaml)).unwrap();
385 assert_eq!(loaded.nodes.len(), 1);
386 assert_eq!(loaded.nodes[0].id, "s1");
387 assert_eq!(loaded.project.unwrap().name, "test-save");
388 }
389
390 #[cfg(feature = "sqlite")]
393 mod sqlite_bridge {
394 use super::*;
395 use crate::graph::{Node, Edge, NodeStatus, ProjectMeta};
396 use crate::task_graph_knowledge::KnowledgeNode;
397 use std::collections::HashMap;
398
399 #[test]
400 fn test_save_and_load_roundtrip() {
401 let tmp = TempDir::new().unwrap();
402 let db_path = tmp.path().join("graph.db");
403
404 let graph = crate::Graph {
405 project: Some(ProjectMeta {
406 name: "roundtrip-test".into(),
407 description: Some("Testing roundtrip".into()),
408 }),
409 nodes: vec![
410 Node::new("a", "Alpha"),
411 Node::new("b", "Beta"),
412 ],
413 edges: vec![
414 Edge::new("a", "b", "depends_on"),
415 ],
416 };
417
418 save_graph_to_sqlite(&graph, &db_path).unwrap();
419 let loaded = load_graph_from_sqlite(&db_path).unwrap();
420
421 assert_eq!(loaded.project.as_ref().unwrap().name, "roundtrip-test");
422 assert_eq!(loaded.project.as_ref().unwrap().description.as_deref(), Some("Testing roundtrip"));
423 assert_eq!(loaded.nodes.len(), 2);
424 assert_eq!(loaded.edges.len(), 1);
425 assert_eq!(loaded.edges[0].from, "a");
426 assert_eq!(loaded.edges[0].to, "b");
427 assert_eq!(loaded.edges[0].relation, "depends_on");
428 }
429
430 #[test]
431 fn test_roundtrip_with_tags_and_metadata() {
432 let tmp = TempDir::new().unwrap();
433 let db_path = tmp.path().join("graph.db");
434
435 let mut node = Node::new("tagged", "Tagged Node");
436 node.tags = vec!["urgent".into(), "backend".into()];
437 node.metadata.insert("priority".into(), serde_json::json!("high"));
438 node.metadata.insert("count".into(), serde_json::json!(42));
439
440 let graph = crate::Graph {
441 project: None,
442 nodes: vec![node],
443 edges: vec![],
444 };
445
446 save_graph_to_sqlite(&graph, &db_path).unwrap();
447 let loaded = load_graph_from_sqlite(&db_path).unwrap();
448
449 let n = &loaded.nodes[0];
450 assert_eq!(n.tags.len(), 2);
451 assert!(n.tags.contains(&"urgent".into()));
452 assert!(n.tags.contains(&"backend".into()));
453 assert_eq!(n.metadata.get("priority"), Some(&serde_json::json!("high")));
454 assert_eq!(n.metadata.get("count"), Some(&serde_json::json!(42)));
455 }
456
457 #[test]
458 fn test_roundtrip_with_knowledge() {
459 let tmp = TempDir::new().unwrap();
460 let db_path = tmp.path().join("graph.db");
461
462 let mut node = Node::new("knowledgeable", "Smart Node");
463 node.knowledge = KnowledgeNode {
464 findings: HashMap::from([
465 ("FINDING-1".into(), "Bug found in parser".into()),
466 ]),
467 file_cache: HashMap::from([
468 ("src/main.rs".into(), "fn main() {}".into()),
469 ]),
470 tool_history: vec![],
471 };
472
473 let graph = crate::Graph {
474 project: None,
475 nodes: vec![node],
476 edges: vec![],
477 };
478
479 save_graph_to_sqlite(&graph, &db_path).unwrap();
480 let loaded = load_graph_from_sqlite(&db_path).unwrap();
481
482 let n = &loaded.nodes[0];
483 assert_eq!(n.knowledge.findings.get("FINDING-1").unwrap(), "Bug found in parser");
484 assert_eq!(n.knowledge.file_cache.get("src/main.rs").unwrap(), "fn main() {}");
485 }
486
487 #[test]
488 fn test_roundtrip_node_statuses() {
489 let tmp = TempDir::new().unwrap();
490 let db_path = tmp.path().join("graph.db");
491
492 let mut todo = Node::new("t1", "Todo");
493 todo.status = NodeStatus::Todo;
494 let mut done = Node::new("t2", "Done");
495 done.status = NodeStatus::Done;
496 let mut ip = Node::new("t3", "InProgress");
497 ip.status = NodeStatus::InProgress;
498
499 let graph = crate::Graph {
500 project: None,
501 nodes: vec![todo, done, ip],
502 edges: vec![],
503 };
504
505 save_graph_to_sqlite(&graph, &db_path).unwrap();
506 let loaded = load_graph_from_sqlite(&db_path).unwrap();
507
508 let find = |id: &str| loaded.nodes.iter().find(|n| n.id == id).unwrap();
509 assert_eq!(find("t1").status, NodeStatus::Todo);
510 assert_eq!(find("t2").status, NodeStatus::Done);
511 assert_eq!(find("t3").status, NodeStatus::InProgress);
512 }
513
514 #[test]
515 fn test_roundtrip_edge_metadata() {
516 let tmp = TempDir::new().unwrap();
517 let db_path = tmp.path().join("graph.db");
518
519 let mut edge = Edge::new("a", "b", "calls");
520 edge.weight = Some(0.8);
521 edge.confidence = Some(0.95);
522
523 let graph = crate::Graph {
524 project: None,
525 nodes: vec![Node::new("a", "A"), Node::new("b", "B")],
526 edges: vec![edge],
527 };
528
529 save_graph_to_sqlite(&graph, &db_path).unwrap();
530 let loaded = load_graph_from_sqlite(&db_path).unwrap();
531
532 assert_eq!(loaded.edges.len(), 1);
533 assert!((loaded.edges[0].weight.unwrap() - 0.8).abs() < 0.001);
534 assert!((loaded.edges[0].confidence.unwrap() - 0.95).abs() < 0.001);
535 }
536
537 #[test]
538 fn test_roundtrip_empty_graph() {
539 let tmp = TempDir::new().unwrap();
540 let db_path = tmp.path().join("graph.db");
541
542 let graph = crate::Graph::default();
543 save_graph_to_sqlite(&graph, &db_path).unwrap();
544 let loaded = load_graph_from_sqlite(&db_path).unwrap();
545
546 assert!(loaded.nodes.is_empty());
547 assert!(loaded.edges.is_empty());
548 }
549
550 #[test]
551 fn test_save_overwrites_existing_data() {
552 let tmp = TempDir::new().unwrap();
553 let db_path = tmp.path().join("graph.db");
554
555 let graph1 = crate::Graph {
557 project: Some(ProjectMeta { name: "v1".into(), description: None }),
558 nodes: vec![Node::new("old", "Old Node")],
559 edges: vec![],
560 };
561 save_graph_to_sqlite(&graph1, &db_path).unwrap();
562
563 let graph2 = crate::Graph {
565 project: Some(ProjectMeta { name: "v2".into(), description: None }),
566 nodes: vec![Node::new("new", "New Node")],
567 edges: vec![],
568 };
569 save_graph_to_sqlite(&graph2, &db_path).unwrap();
570
571 let loaded = load_graph_from_sqlite(&db_path).unwrap();
572 assert_eq!(loaded.project.unwrap().name, "v2");
573 assert_eq!(loaded.nodes.len(), 1);
574 assert_eq!(loaded.nodes[0].id, "new");
575 }
576
577 #[test]
578 fn test_load_graph_auto_detects_sqlite() {
579 let tmp = TempDir::new().unwrap();
580 let db_path = tmp.path().join("graph.db");
581
582 let graph = crate::Graph {
583 project: Some(ProjectMeta { name: "auto-sqlite".into(), description: None }),
584 nodes: vec![Node::new("x", "X")],
585 edges: vec![],
586 };
587 save_graph_to_sqlite(&graph, &db_path).unwrap();
588
589 let loaded = load_graph_auto(tmp.path(), None).unwrap();
591 assert_eq!(loaded.project.unwrap().name, "auto-sqlite");
592 assert_eq!(loaded.nodes.len(), 1);
593 }
594
595 #[test]
596 fn test_save_graph_auto_sqlite() {
597 let tmp = TempDir::new().unwrap();
598
599 let graph = crate::Graph {
600 project: Some(ProjectMeta { name: "auto-save".into(), description: None }),
601 nodes: vec![Node::new("as1", "Auto Saved")],
602 edges: vec![],
603 };
604
605 save_graph_auto(&graph, tmp.path(), Some(StorageBackend::Sqlite)).unwrap();
606 assert!(tmp.path().join("graph.db").exists());
607
608 let loaded = load_graph_auto(tmp.path(), None).unwrap();
609 assert_eq!(loaded.project.unwrap().name, "auto-save");
610 assert_eq!(loaded.nodes[0].id, "as1");
611 }
612
613 #[test]
614 fn test_roundtrip_many_nodes_and_edges() {
615 let tmp = TempDir::new().unwrap();
616 let db_path = tmp.path().join("graph.db");
617
618 let nodes: Vec<Node> = (0..50).map(|i| {
619 let mut n = Node::new(&format!("n{}", i), &format!("Node {}", i));
620 n.tags = vec![format!("group-{}", i % 5)];
621 n
622 }).collect();
623 let edges: Vec<Edge> = (0..49).map(|i| {
624 Edge::new(&format!("n{}", i), &format!("n{}", i + 1), "depends_on")
625 }).collect();
626
627 let graph = crate::Graph {
628 project: Some(ProjectMeta { name: "big".into(), description: None }),
629 nodes,
630 edges,
631 };
632
633 save_graph_to_sqlite(&graph, &db_path).unwrap();
634 let loaded = load_graph_from_sqlite(&db_path).unwrap();
635
636 assert_eq!(loaded.nodes.len(), 50);
637 assert_eq!(loaded.edges.len(), 49);
638 let n0 = loaded.nodes.iter().find(|n| n.id == "n0").unwrap();
640 assert!(n0.tags.contains(&"group-0".into()));
641 }
642
643 #[test]
644 fn test_roundtrip_code_node_fields() {
645 let tmp = TempDir::new().unwrap();
646 let db_path = tmp.path().join("graph.db");
647
648 let mut node = Node::new("fn:main", "main");
649 node.node_type = Some("function".into());
650 node.file_path = Some("src/main.rs".into());
651 node.lang = Some("rust".into());
652 node.start_line = Some(1);
653 node.end_line = Some(10);
654 node.signature = Some("fn main() -> Result<()>".into());
655 node.visibility = Some("pub".into());
656 node.doc_comment = Some("Entry point".into());
657 node.source = Some("code_extract".into());
658 node.body_hash = Some("abc123".into());
659
660 let graph = crate::Graph {
661 project: None,
662 nodes: vec![node],
663 edges: vec![],
664 };
665
666 save_graph_to_sqlite(&graph, &db_path).unwrap();
667 let loaded = load_graph_from_sqlite(&db_path).unwrap();
668
669 let n = &loaded.nodes[0];
670 assert_eq!(n.node_type.as_deref(), Some("function"));
671 assert_eq!(n.file_path.as_deref(), Some("src/main.rs"));
672 assert_eq!(n.lang.as_deref(), Some("rust"));
673 assert_eq!(n.start_line, Some(1));
674 assert_eq!(n.end_line, Some(10));
675 assert_eq!(n.signature.as_deref(), Some("fn main() -> Result<()>"));
676 assert_eq!(n.visibility.as_deref(), Some("pub"));
677 assert_eq!(n.doc_comment.as_deref(), Some("Entry point"));
678 assert_eq!(n.source.as_deref(), Some("code_extract"));
679 }
680
681 #[test]
682 fn test_edge_deduplication_in_load() {
683 let tmp = TempDir::new().unwrap();
684 let db_path = tmp.path().join("graph.db");
685
686 let graph = crate::Graph {
688 project: None,
689 nodes: vec![
690 Node::new("a", "A"),
691 Node::new("b", "B"),
692 Node::new("c", "C"),
693 ],
694 edges: vec![
695 Edge::new("a", "b", "calls"),
696 Edge::new("b", "c", "calls"),
697 Edge::new("a", "c", "depends_on"),
698 ],
699 };
700
701 save_graph_to_sqlite(&graph, &db_path).unwrap();
702 let loaded = load_graph_from_sqlite(&db_path).unwrap();
703
704 assert_eq!(loaded.edges.len(), 3);
706 }
707
708 #[test]
709 fn test_explicit_sqlite_when_both_exist() {
710 let tmp = TempDir::new().unwrap();
711
712 let yaml = "nodes:\n - id: yaml-node\n title: From YAML\nedges: []\n";
714 fs::write(tmp.path().join("graph.yml"), yaml).unwrap();
715
716 let graph = crate::Graph {
718 project: None,
719 nodes: vec![Node::new("sqlite-node", "From SQLite")],
720 edges: vec![],
721 };
722 save_graph_to_sqlite(&graph, &tmp.path().join("graph.db")).unwrap();
723
724 let loaded_yaml = load_graph_auto(tmp.path(), Some(StorageBackend::Yaml)).unwrap();
726 assert_eq!(loaded_yaml.nodes[0].id, "yaml-node");
727
728 let loaded_sqlite = load_graph_auto(tmp.path(), Some(StorageBackend::Sqlite)).unwrap();
730 assert_eq!(loaded_sqlite.nodes[0].id, "sqlite-node");
731
732 let loaded_auto = load_graph_auto(tmp.path(), None).unwrap();
734 assert_eq!(loaded_auto.nodes[0].id, "sqlite-node");
735 }
736 }
737}