mono-cpp/tests/unit/test_gadm_reader.cpp

164 lines
6.2 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include "gadm_reader/gadm_reader.h"
#include <cmath>
using namespace gadm;
using Catch::Matchers::WithinAbs;
using Catch::Matchers::WithinRel;
// ── Helper: fixtures path ───────────────────────────────────────────────────
// Tests are run with WORKING_DIRECTORY = CMAKE_SOURCE_DIR (server/cpp)
static const std::string CACHE_DIR = "cache/gadm";
// ── country_code ────────────────────────────────────────────────────────────
TEST_CASE("country_code: simple ISO3", "[gadm][util]") {
REQUIRE(country_code("ABW") == "ABW");
}
TEST_CASE("country_code: dotted GID", "[gadm][util]") {
REQUIRE(country_code("AFG.1.1_1") == "AFG");
REQUIRE(country_code("ESP.6.1_1") == "ESP");
}
// ── infer_level ─────────────────────────────────────────────────────────────
TEST_CASE("infer_level: level 0 (country)", "[gadm][util]") {
REQUIRE(infer_level("ABW") == 0);
REQUIRE(infer_level("AFG") == 0);
}
TEST_CASE("infer_level: level 1", "[gadm][util]") {
REQUIRE(infer_level("AFG.1_1") == 1);
}
TEST_CASE("infer_level: level 2", "[gadm][util]") {
REQUIRE(infer_level("AFG.1.1_1") == 2);
}
TEST_CASE("infer_level: level 3", "[gadm][util]") {
REQUIRE(infer_level("ESP.6.1.4_1") == 3);
}
// ── load_boundary_file: ABW level 0 ────────────────────────────────────────
TEST_CASE("Load ABW level 0: basic structure", "[gadm][file]") {
auto res = load_boundary_file(CACHE_DIR + "/boundary_ABW_0.json");
REQUIRE(res.error.empty());
REQUIRE(res.features.size() == 1);
const auto& f = res.features[0];
REQUIRE(f.gid == "ABW");
REQUIRE(f.name == "Aruba");
REQUIRE(f.level == 0);
REQUIRE(f.isOuter == true);
}
TEST_CASE("Load ABW level 0: has rings", "[gadm][file]") {
auto res = load_boundary_file(CACHE_DIR + "/boundary_ABW_0.json");
REQUIRE(res.error.empty());
const auto& f = res.features[0];
REQUIRE(f.rings.size() >= 1);
REQUIRE(f.rings[0].size() > 10); // ABW has ~55 coords
}
TEST_CASE("Load ABW level 0: GHS population data", "[gadm][file]") {
auto res = load_boundary_file(CACHE_DIR + "/boundary_ABW_0.json");
REQUIRE(res.error.empty());
const auto& f = res.features[0];
REQUIRE_THAT(f.ghsPopulation, WithinRel(104847.0, 0.01));
REQUIRE(f.ghsPopCenters.size() == 5);
// First pop center: [-70.04183, 12.53341, 104.0]
REQUIRE_THAT(f.ghsPopCenters[0][0], WithinAbs(-70.04183, 0.0001));
REQUIRE_THAT(f.ghsPopCenters[0][1], WithinAbs(12.53341, 0.0001));
REQUIRE_THAT(f.ghsPopCenters[0][2], WithinAbs(104.0, 0.1));
}
TEST_CASE("Load ABW level 0: GHS built data", "[gadm][file]") {
auto res = load_boundary_file(CACHE_DIR + "/boundary_ABW_0.json");
REQUIRE(res.error.empty());
const auto& f = res.features[0];
REQUIRE_THAT(f.ghsBuiltWeight, WithinRel(22900682.0, 0.01));
REQUIRE(f.ghsBuiltCenters.size() == 5);
REQUIRE_THAT(f.ghsBuiltCenter.lon, WithinAbs(-69.99304, 0.001));
REQUIRE_THAT(f.ghsBuiltCenter.lat, WithinAbs(12.51234, 0.001));
}
TEST_CASE("Load ABW level 0: computed bbox", "[gadm][file]") {
auto res = load_boundary_file(CACHE_DIR + "/boundary_ABW_0.json");
REQUIRE(res.error.empty());
const auto& f = res.features[0];
// ABW bbox should be roughly in the Caribbean
REQUIRE(f.bbox.minLon < -69.8);
REQUIRE(f.bbox.maxLon > -70.1);
REQUIRE(f.bbox.minLat > 12.4);
REQUIRE(f.bbox.maxLat < 12.7);
}
TEST_CASE("Load ABW level 0: computed area", "[gadm][file]") {
auto res = load_boundary_file(CACHE_DIR + "/boundary_ABW_0.json");
REQUIRE(res.error.empty());
const auto& f = res.features[0];
// Aruba is ~180 km²
REQUIRE_THAT(f.areaSqKm, WithinRel(180.0, 0.15)); // 15% tolerance
}
// ── load_boundary_file: AFG level 2 ────────────────────────────────────────
TEST_CASE("Load AFG.1.1_1 level 2: basic structure", "[gadm][file]") {
auto res = load_boundary_file(CACHE_DIR + "/boundary_AFG.1.1_1_2.json");
REQUIRE(res.error.empty());
REQUIRE(res.features.size() == 1);
const auto& f = res.features[0];
REQUIRE(f.gid == "AFG.1.1_1");
REQUIRE(f.name == "Baharak");
REQUIRE(f.level == 2);
}
TEST_CASE("Load AFG.1.1_1 level 2: has GHS data", "[gadm][file]") {
auto res = load_boundary_file(CACHE_DIR + "/boundary_AFG.1.1_1_2.json");
REQUIRE(res.error.empty());
const auto& f = res.features[0];
REQUIRE(f.ghsPopCenters.size() == 5);
REQUIRE(f.ghsBuiltCenters.size() == 5);
REQUIRE(f.ghsPopulation > 0);
}
// ── load_boundary: path resolution ──────────────────────────────────────────
TEST_CASE("load_boundary: direct GID match", "[gadm][resolve]") {
auto res = load_boundary("ABW", 0, CACHE_DIR);
REQUIRE(res.error.empty());
REQUIRE(res.features.size() == 1);
REQUIRE(res.features[0].gid == "ABW");
}
TEST_CASE("load_boundary: sub-region GID", "[gadm][resolve]") {
auto res = load_boundary("AFG.1.1_1", 2, CACHE_DIR);
REQUIRE(res.error.empty());
REQUIRE(res.features[0].gid == "AFG.1.1_1");
}
TEST_CASE("load_boundary: missing file returns error", "[gadm][resolve]") {
auto res = load_boundary("DOESNOTEXIST", 0, CACHE_DIR);
REQUIRE(!res.error.empty());
REQUIRE(res.features.empty());
}
// ── Error handling ──────────────────────────────────────────────────────────
TEST_CASE("load_boundary_file: nonexistent file", "[gadm][error]") {
auto res = load_boundary_file("nonexistent.json");
REQUIRE(!res.error.empty());
REQUIRE(res.features.empty());
}