use anyhow::{Context, Result, anyhow};
use oxigdal_cli::util::vector::{AttributeFilter, FilterOp, convert_vector};
use std::io::Write as _;
fn write_temp_geojson(name: &str, json: &str) -> Result<std::path::PathBuf> {
let mut path = std::env::temp_dir();
path.push(format!("oxigdal_vct_{name}.geojson"));
let mut f = std::fs::File::create(&path)
.with_context(|| format!("create temp geojson: {}", path.display()))?;
f.write_all(json.as_bytes()).context("write temp geojson")?;
Ok(path)
}
fn temp_path(name: &str, ext: &str) -> std::path::PathBuf {
let mut path = std::env::temp_dir();
path.push(format!("oxigdal_vct_{name}.{ext}"));
path
}
const GEOJSON_3F: &str = r#"{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [10.0, 20.0] },
"properties": { "name": "Alpha", "kind": "city", "pop": 1000 }
},
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [11.0, 21.0] },
"properties": { "name": "Beta", "kind": "town", "pop": 500 }
},
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [12.0, 22.0] },
"properties": { "name": "Gamma", "kind": "city", "pop": 2000 }
}
]
}"#;
const GEOJSON_5F: &str = r#"{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [1.0, 2.0] },
"properties": { "label": "apple", "score": 10 }
},
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [2.0, 3.0] },
"properties": { "label": "banana", "score": 20 }
},
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [3.0, 4.0] },
"properties": { "label": "apricot", "score": 30 }
},
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [4.0, 5.0] },
"properties": { "label": "cherry", "score": 40 }
},
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [5.0, 6.0] },
"properties": { "label": "blueberry", "score": 50 }
}
]
}"#;
#[test]
fn test_geojson_to_geojson() -> Result<()> {
let input = write_temp_geojson("gj2gj_in", GEOJSON_3F)?;
let output = temp_path("gj2gj_out", "geojson");
let count = convert_vector(&input, &output, None)?;
assert_eq!(count, 3, "expected 3 features written");
assert!(output.exists(), "output file should exist");
let content = std::fs::read_to_string(&output)?;
let parsed: serde_json::Value = serde_json::from_str(&content)?;
let features = parsed["features"]
.as_array()
.ok_or_else(|| anyhow!("expected features array in output"))?;
assert_eq!(features.len(), 3);
let _ = std::fs::remove_file(&input);
let _ = std::fs::remove_file(&output);
Ok(())
}
#[test]
fn test_geojson_to_shapefile() -> Result<()> {
let input = write_temp_geojson("gj2shp_in", GEOJSON_3F)?;
let output = temp_path("gj2shp_out", "shp");
let count = convert_vector(&input, &output, None)?;
assert_eq!(count, 3, "expected 3 features written");
assert!(output.exists(), ".shp file should exist");
let dbf = output.with_extension("dbf");
assert!(dbf.exists(), ".dbf file should exist");
let base = output.with_extension("");
let reader = oxigdal_shapefile::ShapefileReader::open(&base)?;
let features = reader.read_features()?;
assert_eq!(features.len(), 3);
let _ = std::fs::remove_file(&input);
for ext in &["shp", "dbf", "shx"] {
let _ = std::fs::remove_file(output.with_extension(ext));
}
Ok(())
}
#[test]
fn test_shapefile_to_geojson() -> Result<()> {
let gj_input = write_temp_geojson("shp2gj_setup", GEOJSON_3F)?;
let shp_intermediate = temp_path("shp2gj_inter", "shp");
convert_vector(&gj_input, &shp_intermediate, None)?;
let gj_output = temp_path("shp2gj_out", "geojson");
let count = convert_vector(&shp_intermediate, &gj_output, None)?;
assert_eq!(count, 3, "expected 3 features in GeoJSON output");
assert!(gj_output.exists(), "output GeoJSON should exist");
let content = std::fs::read_to_string(&gj_output)?;
let parsed: serde_json::Value = serde_json::from_str(&content)?;
let features = parsed["features"]
.as_array()
.ok_or_else(|| anyhow!("expected features array in output"))?;
assert_eq!(features.len(), 3);
let _ = std::fs::remove_file(&gj_input);
let _ = std::fs::remove_file(&gj_output);
for ext in &["shp", "dbf", "shx"] {
let _ = std::fs::remove_file(shp_intermediate.with_extension(ext));
}
Ok(())
}
#[test]
fn test_attribute_filter_eq() -> Result<()> {
let input = write_temp_geojson("filt_eq_in", GEOJSON_3F)?;
let output = temp_path("filt_eq_out", "geojson");
let filter = AttributeFilter {
field: "kind".to_string(),
op: FilterOp::Eq,
value: "city".to_string(),
};
let count = convert_vector(&input, &output, Some(&filter))?;
assert_eq!(count, 2, "eq filter should match 2 features");
let content = std::fs::read_to_string(&output)?;
let parsed: serde_json::Value = serde_json::from_str(&content)?;
let features = parsed["features"]
.as_array()
.ok_or_else(|| anyhow!("expected features array"))?;
assert_eq!(features.len(), 2);
for f in features {
let kind = f["properties"]["kind"]
.as_str()
.ok_or_else(|| anyhow!("expected kind field to be a string"))?;
assert_eq!(kind, "city");
}
let _ = std::fs::remove_file(&input);
let _ = std::fs::remove_file(&output);
Ok(())
}
#[test]
fn test_attribute_filter_contains() -> Result<()> {
let input = write_temp_geojson("filt_contains_in", GEOJSON_5F)?;
let output = temp_path("filt_contains_out", "geojson");
let filter = AttributeFilter {
field: "label".to_string(),
op: FilterOp::Contains,
value: "ap".to_string(), };
let count = convert_vector(&input, &output, Some(&filter))?;
assert_eq!(
count, 2,
"contains filter should match 2 features ('apple', 'apricot')"
);
let content = std::fs::read_to_string(&output)?;
let parsed: serde_json::Value = serde_json::from_str(&content)?;
let features = parsed["features"]
.as_array()
.ok_or_else(|| anyhow!("expected features array"))?;
assert_eq!(features.len(), 2);
for f in features {
let label = f["properties"]["label"]
.as_str()
.ok_or_else(|| anyhow!("expected label field to be a string"))?;
assert!(label.contains("ap"), "label '{label}' should contain 'ap'");
}
let _ = std::fs::remove_file(&input);
let _ = std::fs::remove_file(&output);
Ok(())
}
#[test]
fn test_unknown_output_format_error() -> Result<()> {
let input = write_temp_geojson("unk_fmt_in", GEOJSON_3F)?;
let output = temp_path("unk_fmt_out", "xyz");
let result = convert_vector(&input, &output, None);
assert!(result.is_err(), "should return error for unknown extension");
if let Err(e) = result {
let err_msg = e.to_string();
assert!(
err_msg.contains("Cannot determine output format") || err_msg.contains("Unknown"),
"error message should mention unknown format, got: {err_msg}"
);
}
let _ = std::fs::remove_file(&input);
Ok(())
}
#[test]
fn test_attribute_filter_ne() -> Result<()> {
let input = write_temp_geojson("filt_ne_in", GEOJSON_3F)?;
let output = temp_path("filt_ne_out", "geojson");
let filter = AttributeFilter {
field: "kind".to_string(),
op: FilterOp::Ne,
value: "city".to_string(),
};
let count = convert_vector(&input, &output, Some(&filter))?;
assert_eq!(count, 1, "ne filter should match 1 feature");
let content = std::fs::read_to_string(&output)?;
let parsed: serde_json::Value = serde_json::from_str(&content)?;
let features = parsed["features"]
.as_array()
.ok_or_else(|| anyhow!("expected features array"))?;
assert_eq!(features.len(), 1);
let name = features[0]["properties"]["name"]
.as_str()
.ok_or_else(|| anyhow!("expected name field"))?;
assert_eq!(name, "Beta");
let _ = std::fs::remove_file(&input);
let _ = std::fs::remove_file(&output);
Ok(())
}