use std::{
io::{Read, Write},
net::{TcpListener, TcpStream},
path::Path,
sync::{Arc, Mutex},
};
use super::{geofabrik, list_local, resolve_geofabrik_url};
fn start_json_server(body: String) -> String {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
let url = format!("http://127.0.0.1:{port}/index.json");
let body = Arc::new(body);
std::thread::spawn(move || {
for stream in listener.incoming().flatten() {
let body = Arc::clone(&body);
std::thread::spawn(move || serve_json(stream, &body));
}
});
url
}
fn start_counting_json_server(body: String) -> (String, Arc<Mutex<u32>>) {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
let url = format!("http://127.0.0.1:{port}/index.json");
let body = Arc::new(body);
let count: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
let count_srv = count.clone();
std::thread::spawn(move || {
for stream in listener.incoming().flatten() {
let body = Arc::clone(&body);
let count = Arc::clone(&count_srv);
std::thread::spawn(move || {
*count.lock().unwrap() += 1;
serve_json(stream, &body);
});
}
});
(url, count)
}
fn serve_json(mut stream: TcpStream, body: &str) {
let mut buf = [0u8; 4096];
let _ = stream.read(&mut buf);
let response = format!(
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
body.len(),
body
);
let _ = stream.write_all(response.as_bytes());
}
fn tmp(name: &str) -> std::path::PathBuf {
let p = std::env::temp_dir().join(name);
let _ = std::fs::remove_dir_all(&p);
std::fs::create_dir_all(&p).unwrap();
p
}
fn sqlite(name: &str) -> String {
tmp(name).join("test.sqlite3").to_str().unwrap().to_string()
}
#[test]
fn _00_fetches_and_caches_when_no_cache_exists() {
let db = sqlite("ls_t00");
let json = r#"{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":"europe/germany","name":"Germany","urls":{"pbf":"http://example.com/germany.osm.pbf"}}},{"type":"Feature","properties":{"id":"europe/france","name":"France","urls":{"pbf":"http://example.com/france.osm.pbf"}}}]}"#.to_string();
let url = start_json_server(json);
let items = geofabrik(&db, false, &url);
assert_eq!(items.len(), 2);
assert_eq!(items[0].id, "europe/france");
assert_eq!(items[0].url, "http://example.com/france.osm.pbf");
assert_eq!(items[1].id, "europe/germany");
assert!(Path::new(&db).exists());
}
#[test]
fn _01_returns_cached_without_refetching() {
let db = sqlite("ls_t01");
let json = r#"{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":"asia/japan","name":"Japan","urls":{"pbf":"http://example.com/japan.osm.pbf"}}}]}"#.to_string();
let (url, request_count) = start_counting_json_server(json);
geofabrik(&db, false, &url);
let after_first = *request_count.lock().unwrap();
geofabrik(&db, false, &url);
assert_eq!(*request_count.lock().unwrap(), after_first);
}
#[test]
fn _02_recreate_cache_forces_refetch() {
let db = sqlite("ls_t02");
let json = r#"{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":"africa/kenya","name":"Kenya","urls":{"pbf":"http://example.com/kenya.osm.pbf"}}}]}"#.to_string();
let (url, request_count) = start_counting_json_server(json);
geofabrik(&db, false, &url);
let after_first = *request_count.lock().unwrap();
geofabrik(&db, true, &url);
assert!(*request_count.lock().unwrap() > after_first);
}
#[test]
fn _03_feature_without_pbf_url_has_dash_url() {
let db = sqlite("ls_t03");
let json = r#"{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":"oceania/australia","name":"Australia"}}]}"#.to_string();
let url = start_json_server(json);
let items = geofabrik(&db, false, &url);
assert_eq!(items.len(), 1);
assert_eq!(items[0].url, "-");
}
#[test]
fn _04_resolve_geofabrik_url_returns_url_for_known_id() {
let db = sqlite("ls_t04");
let json = r#"{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":"south-america/brazil","name":"Brazil","urls":{"pbf":"http://example.com/brazil.osm.pbf"}}}]}"#.to_string();
let url = start_json_server(json);
let result = resolve_geofabrik_url(&db, "south-america/brazil", &url);
assert_eq!(result.as_deref(), Some("http://example.com/brazil.osm.pbf"));
}
#[test]
fn _05_resolve_geofabrik_url_returns_none_for_unknown_id() {
let db = sqlite("ls_t05");
let json = r#"{"type":"FeatureCollection","features":[]}"#.to_string();
let url = start_json_server(json);
let result = resolve_geofabrik_url(&db, "nonexistent/region", &url);
assert!(result.is_none());
}
#[test]
fn _06_resolve_geofabrik_url_returns_none_for_entry_without_url() {
let db = sqlite("ls_t06");
let json = r#"{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":"oceania/australia","name":"Australia"}}]}"#.to_string();
let url = start_json_server(json);
let result = resolve_geofabrik_url(&db, "oceania/australia", &url);
assert!(result.is_none());
}
#[test]
fn _07_list_local_returns_empty_when_no_pbf_dir() {
let dir = tmp("ls_t07");
let items = list_local(dir.to_str().unwrap());
assert!(items.is_empty());
}
#[test]
fn _08_list_local_returns_sorted_pbf_files() {
let dir = tmp("ls_t08");
std::fs::write(dir.join("brazil.osm.pbf"), b"fake").unwrap();
std::fs::write(dir.join("andorra.osm.pbf"), b"fake content longer").unwrap();
std::fs::write(dir.join("readme.txt"), b"ignored").unwrap();
let items = list_local(dir.to_str().unwrap());
assert_eq!(items.len(), 2);
assert_eq!(items[0].path.file_name().unwrap(), "andorra.osm.pbf");
assert_eq!(items[1].path.file_name().unwrap(), "brazil.osm.pbf");
assert_eq!(items[0].size_bytes, 19);
assert!(Path::new(&items[0].path).exists());
}
#[test]
fn _09_resolve_geofabrik_url_uses_cache_without_refetching() {
let db = sqlite("ls_t09");
let json = r#"{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":"europe/spain","name":"Spain","urls":{"pbf":"http://example.com/spain.osm.pbf"}}}]}"#.to_string();
let (url, request_count) = start_counting_json_server(json);
geofabrik(&db, false, &url);
let after_geofabrik = *request_count.lock().unwrap();
let result = resolve_geofabrik_url(&db, "europe/spain", &url);
assert_eq!(result.as_deref(), Some("http://example.com/spain.osm.pbf"));
assert_eq!(*request_count.lock().unwrap(), after_geofabrik);
}
#[test]
fn _10_list_local_returns_empty_for_nonexistent_path() {
let dir = tmp("ls_t10");
let missing = dir.join("does_not_exist");
let items = list_local(missing.to_str().unwrap());
assert!(items.is_empty());
}