use std::fs;
use std::path::Path;
use agileplus_domain::intent_graph::IntentGraph;
use agileplus_sqlite::Storage;
use tempfile::TempDir;
fn sample_graph_json(name: &str) -> String {
format!(
r#"{{
"nodes": [
{{
"id": "Feature#{name}",
"node_type": "Feature",
"dag_stage": "feature",
"title": "Test {name}",
"status": "draft",
"meta": {{
"source": "test",
"timestamp": "2024-01-01T00:00:00Z"
}}
}}
],
"edges": [],
"metadata": {{
"version": "1.0",
"schema_uri": "https://agileplus.dev/schema/intent-graph.json",
"created_at": "2024-01-01T00:00:00Z"
}}
}}"#
)
}
fn write_sample_graphs(dir: &Path) -> Vec<String> {
let names = ["auth", "dashboard", "api"];
for name in &names {
let json = sample_graph_json(name);
let path = dir.join(format!("{name}.json"));
fs::write(&path, &json).expect("failed to write sample graph");
}
names.iter().map(|s| s.to_string()).collect()
}
#[test]
fn bulk_import_and_export_roundtrip() {
let input_dir = TempDir::new().expect("failed to create input temp dir");
let expected_ids = write_sample_graphs(input_dir.path());
let storage = Storage::in_memory().expect("failed to create in-memory storage");
let mut imported_ids: Vec<String> = Vec::new();
for entry in fs::read_dir(input_dir.path()).expect("failed to read input dir") {
let entry = entry.expect("failed to read entry");
let path = entry.path();
if path.extension().map_or(true, |e| e != "json") {
continue;
}
let id = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap()
.to_string();
let content = fs::read_to_string(&path).expect("failed to read sample graph");
let graph: IntentGraph =
serde_json::from_str(&content).expect("failed to parse sample graph");
storage
.save_graph(&id, &graph)
.expect("failed to save graph to storage");
imported_ids.push(id);
}
imported_ids.sort();
let mut sorted_expected = expected_ids.clone();
sorted_expected.sort();
assert_eq!(imported_ids, sorted_expected, "imported IDs should match expected");
let stored_ids = storage.list_graphs().expect("failed to list graphs");
let mut stored_ids_sorted = stored_ids.clone();
stored_ids_sorted.sort();
assert_eq!(stored_ids_sorted, sorted_expected, "stored IDs should match expected");
let output_dir = TempDir::new().expect("failed to create output temp dir");
for id in &stored_ids {
let graph = storage
.load_graph(id)
.expect("failed to load graph")
.unwrap_or_else(|| panic!("graph {id} not found in storage"));
let json = serde_json::to_string_pretty(&graph)
.unwrap_or_else(|_| panic!("failed to serialize graph {id}"));
let out_path = output_dir.path().join(format!("{id}.json"));
fs::write(&out_path, &json).unwrap_or_else(|_| panic!("failed to write {id}.json"));
}
let output_entries: Vec<_> = fs::read_dir(output_dir.path())
.expect("failed to read output dir")
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().map_or(false, |e| e == "json"))
.collect();
assert_eq!(
output_entries.len(),
expected_ids.len(),
"output dir should have the same number of JSON files"
);
for id in &expected_ids {
let original_path = input_dir.path().join(format!("{id}.json"));
let exported_path = output_dir.path().join(format!("{id}.json"));
let original_content = fs::read_to_string(&original_path)
.unwrap_or_else(|_| panic!("failed to read original {id}.json"));
let exported_content = fs::read_to_string(&exported_path)
.unwrap_or_else(|_| panic!("failed to read exported {id}.json"));
let original_graph: IntentGraph =
serde_json::from_str(&original_content).expect("failed to parse original graph");
let exported_graph: IntentGraph =
serde_json::from_str(&exported_content).expect("failed to parse exported graph");
assert_eq!(
original_graph, exported_graph,
"round-tripped graph {id} should match original"
);
}
}
#[test]
fn bulk_import_handles_empty_dir() {
let input_dir = TempDir::new().expect("failed to create temp dir");
let storage = Storage::in_memory().expect("failed to create in-memory storage");
for entry in fs::read_dir(input_dir.path()).expect("failed to read empty dir") {
let entry = entry.expect("failed to read entry");
let path = entry.path();
if path.extension().map_or(true, |e| e != "json") {
continue;
}
let id = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap()
.to_string();
let content = fs::read_to_string(&path).expect("failed to read");
let graph: IntentGraph =
serde_json::from_str(&content).expect("failed to parse graph");
storage
.save_graph(&id, &graph)
.expect("failed to save graph");
}
let ids = storage.list_graphs().expect("failed to list graphs");
assert!(ids.is_empty(), "should have no graphs after importing empty dir");
}
#[test]
fn bulk_export_empty_db() {
let storage = Storage::in_memory().expect("failed to create in-memory storage");
let output_dir = TempDir::new().expect("failed to create output temp dir");
let ids = storage.list_graphs().expect("failed to list graphs");
assert!(ids.is_empty(), "fresh storage should be empty");
for id in &ids {
let graph = storage.load_graph(id).expect("failed to load graph");
if let Some(g) = graph {
let json = serde_json::to_string_pretty(&g).expect("failed to serialize");
let path = output_dir.path().join(format!("{id}.json"));
fs::write(&path, &json).expect("failed to write");
}
}
let output_count = fs::read_dir(output_dir.path())
.expect("failed to read output dir")
.count();
assert_eq!(output_count, 0, "export from empty db should produce no files");
}
#[test]
fn bulk_import_skips_non_json_files() {
let input_dir = TempDir::new().expect("failed to create temp dir");
let storage = Storage::in_memory().expect("failed to create in-memory storage");
let json_content = sample_graph_json("only-json");
fs::write(input_dir.path().join("valid.json"), &json_content)
.expect("failed to write valid.json");
fs::write(input_dir.path().join("readme.txt"), "not a graph")
.expect("failed to write readme.txt");
fs::write(input_dir.path().join("data.md"), "# Markdown")
.expect("failed to write data.md");
let mut imported = 0;
for entry in fs::read_dir(input_dir.path()).expect("failed to read dir") {
let entry = entry.expect("failed to read entry");
let path = entry.path();
if path.extension().map_or(true, |e| e != "json") {
continue;
}
let id = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap()
.to_string();
let content = fs::read_to_string(&path).expect("failed to read");
let graph: IntentGraph =
serde_json::from_str(&content).expect("failed to parse graph");
storage
.save_graph(&id, &graph)
.expect("failed to save graph");
imported += 1;
}
assert_eq!(imported, 1, "only the .json file should be imported");
let ids = storage.list_graphs().expect("failed to list graphs");
assert_eq!(ids.len(), 1, "storage should contain exactly one graph");
assert_eq!(ids[0], "valid", "imported graph should have correct id");
}