diff --git a/Cargo.lock b/Cargo.lock index e757151ae..7b9251ce8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1710,6 +1710,7 @@ dependencies = [ "flate2", "futures", "indoc", + "insta", "itertools 0.11.0", "json-patch", "log", diff --git a/Cargo.toml b/Cargo.toml index ad632147f..a7ce09837 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ env_logger = "0.10" flate2 = "1" futures = "0.3" indoc = "2" -insta = { version = "1", features = ["toml"] } +insta = "1" itertools = "0.11" json-patch = "1.2" log = "0.4" diff --git a/justfile b/justfile index 2a78b4952..774a88746 100644 --- a/justfile +++ b/justfile @@ -2,6 +2,7 @@ set shell := ["bash", "-c"] +#export DATABASE_URL="postgres://postgres:postgres@localhost:5411/db" export PGPORT := "5411" export DATABASE_URL := "postgres://postgres:postgres@localhost:" + PGPORT + "/db" export CARGO_TERM_COLOR := "always" @@ -138,7 +139,7 @@ test-int: clean-test install-sqlx fi # Run integration tests and save its output as the new expected output -bless: restart clean-test bless-insta +bless: restart clean-test bless-insta-martin bless-insta-mbtiles rm -rf tests/temp cargo test -p martin --features bless-tests tests/test.sh @@ -146,10 +147,14 @@ bless: restart clean-test bless-insta mv tests/output tests/expected # Run integration tests and save its output as the new expected output -bless-insta *ARGS: (cargo-install "insta" "cargo-insta") +bless-insta-mbtiles *ARGS: (cargo-install "insta" "cargo-insta") #rm -rf martin-mbtiles/tests/snapshots cargo insta test --accept --unreferenced=auto -p martin-mbtiles {{ ARGS }} +# Run integration tests and save its output as the new expected output +bless-insta-martin *ARGS: (cargo-install "insta" "cargo-insta") + cargo insta test --accept --unreferenced=auto -p martin {{ ARGS }} + # Build and open mdbook documentation book: (cargo-install "mdbook") mdbook serve docs --open --port 8321 diff --git a/martin-mbtiles/Cargo.toml b/martin-mbtiles/Cargo.toml index 957618f4d..ef100d7fa 100644 --- a/martin-mbtiles/Cargo.toml +++ b/martin-mbtiles/Cargo.toml @@ -37,7 +37,7 @@ tokio = { workspace = true, features = ["rt-multi-thread"], optional = true } actix-rt.workspace = true ctor.workspace = true env_logger.workspace = true -insta.workspace = true +insta = { workspace = true, features = ["toml"] } pretty_assertions.workspace = true rstest.workspace = true diff --git a/martin/Cargo.toml b/martin/Cargo.toml index c1ca06366..38681e00b 100644 --- a/martin/Cargo.toml +++ b/martin/Cargo.toml @@ -92,6 +92,7 @@ cargo-husky.workspace = true criterion.workspace = true ctor.workspace = true indoc.workspace = true +insta = { workspace = true, features = ["yaml"] } #test-log = "0.2" [[bench]] diff --git a/martin/src/srv/mod.rs b/martin/src/srv/mod.rs index c637ca755..6b8e739e6 100644 --- a/martin/src/srv/mod.rs +++ b/martin/src/srv/mod.rs @@ -2,6 +2,6 @@ mod config; mod server; pub use config::{SrvConfig, KEEP_ALIVE_DEFAULT, LISTEN_ADDRESSES_DEFAULT}; -pub use server::{new_server, router, RESERVED_KEYWORDS}; +pub use server::{new_server, router, Catalog, RESERVED_KEYWORDS}; pub use crate::source::CatalogSourceEntry; diff --git a/martin/src/srv/server.rs b/martin/src/srv/server.rs index f058a72c0..7dc7ee99f 100755 --- a/martin/src/srv/server.rs +++ b/martin/src/srv/server.rs @@ -29,7 +29,7 @@ use crate::sprites::{SpriteCatalog, SpriteError, SpriteSources}; use crate::srv::config::{SrvConfig, KEEP_ALIVE_DEFAULT, LISTEN_ADDRESSES_DEFAULT}; use crate::utils::{decode_brotli, decode_gzip, encode_brotli, encode_gzip}; use crate::Error::BindingError; -use crate::Xyz; +use crate::{Error, Xyz}; /// List of keywords that cannot be used as source IDs. Some of these are reserved for future use. /// Reserved keywords must never end in a "dot number" (e.g. ".1"). @@ -51,6 +51,15 @@ pub struct Catalog { pub sprites: SpriteCatalog, } +impl Catalog { + pub fn new(state: &ServerState) -> Result { + Ok(Self { + tiles: state.tiles.get_catalog(), + sprites: state.sprites.get_catalog()?, + }) + } +} + #[derive(Deserialize)] struct TileJsonRequest { source_ids: String, @@ -421,26 +430,22 @@ pub fn router(cfg: &mut web::ServiceConfig) { } /// Create a new initialized Actix `App` instance together with the listening address. -pub fn new_server(config: SrvConfig, all_sources: ServerState) -> crate::Result<(Server, String)> { +pub fn new_server(config: SrvConfig, state: ServerState) -> crate::Result<(Server, String)> { + let catalog = Catalog::new(&state)?; let keep_alive = Duration::from_secs(config.keep_alive.unwrap_or(KEEP_ALIVE_DEFAULT)); let worker_processes = config.worker_processes.unwrap_or_else(num_cpus::get); let listen_addresses = config .listen_addresses .unwrap_or_else(|| LISTEN_ADDRESSES_DEFAULT.to_owned()); - let catalog = Catalog { - tiles: all_sources.tiles.get_catalog(), - sprites: all_sources.sprites.get_catalog()?, - }; - let server = HttpServer::new(move || { let cors_middleware = Cors::default() .allow_any_origin() .allowed_methods(vec!["GET"]); App::new() - .app_data(Data::new(all_sources.tiles.clone())) - .app_data(Data::new(all_sources.sprites.clone())) + .app_data(Data::new(state.tiles.clone())) + .app_data(Data::new(state.sprites.clone())) .app_data(Data::new(catalog.clone())) .wrap(cors_middleware) .wrap(middleware::NormalizePath::new(TrailingSlash::MergeOnly)) diff --git a/tests/mb_server_test.rs b/martin/tests/mb_server_test.rs similarity index 82% rename from tests/mb_server_test.rs rename to martin/tests/mb_server_test.rs index 94e1d1dff..31f3177ef 100644 --- a/tests/mb_server_test.rs +++ b/martin/tests/mb_server_test.rs @@ -2,8 +2,8 @@ use actix_web::http::header::{ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_TYPE}; use actix_web::test::{call_service, read_body, read_body_json, TestRequest}; use ctor::ctor; use indoc::indoc; +use insta::assert_yaml_snapshot; use martin::decode_gzip; -use martin::srv::IndexEntry; use tilejson::TileJSON; pub mod utils; @@ -16,11 +16,13 @@ fn init() { macro_rules! create_app { ($sources:expr) => {{ - let sources = mock_sources(mock_cfg($sources)).await.0; - let state = crate::utils::mock_app_data(sources).await; + let state = mock_sources(mock_cfg($sources)).await.0; ::actix_web::test::init_service( ::actix_web::App::new() - .app_data(state) + .app_data(actix_web::web::Data::new( + ::martin::srv::Catalog::new(&state).unwrap(), + )) + .app_data(actix_web::web::Data::new(state.tiles)) .configure(::martin::srv::router), ) .await @@ -34,10 +36,10 @@ fn test_get(path: &str) -> TestRequest { const CONFIG: &str = indoc! {" mbtiles: sources: - m_json: tests/fixtures/mbtiles/json.mbtiles - m_mvt: tests/fixtures/mbtiles/world_cities.mbtiles - m_raw_mvt: tests/fixtures/mbtiles/uncompressed_mvt.mbtiles - m_webp: tests/fixtures/mbtiles/webp.mbtiles + m_json: ../tests/fixtures/mbtiles/json.mbtiles + m_mvt: ../tests/fixtures/mbtiles/world_cities.mbtiles + m_raw_mvt: ../tests/fixtures/mbtiles/uncompressed_mvt.mbtiles + m_webp: ../tests/fixtures/mbtiles/webp.mbtiles "}; #[actix_rt::test] @@ -47,11 +49,27 @@ async fn mbt_get_catalog() { let req = test_get("/catalog").to_request(); let response = call_service(&app, req).await; assert!(response.status().is_success()); - let body = read_body(response).await; - let sources: Vec = serde_json::from_slice(&body).unwrap(); - assert_eq!(sources.iter().filter(|v| v.id == "m_mvt").count(), 1); - assert_eq!(sources.iter().filter(|v| v.id == "m_webp").count(), 1); - assert_eq!(sources.iter().filter(|v| v.id == "m_raw_mvt").count(), 1); + let body: serde_json::Value = read_body_json(response).await; + assert_yaml_snapshot!(body, @r###" + --- + tiles: + m_json: + content_type: application/json + name: Dummy json data + m_mvt: + content_type: application/x-protobuf + content_encoding: gzip + name: Major cities from Natural Earth data + description: Major cities from Natural Earth data + m_raw_mvt: + content_type: application/x-protobuf + name: Major cities from Natural Earth data + description: Major cities from Natural Earth data + m_webp: + content_type: image/webp + name: ne2sr + sprites: {} + "###); } #[actix_rt::test] @@ -62,10 +80,27 @@ async fn mbt_get_catalog_gzip() { let response = call_service(&app, req).await; assert!(response.status().is_success()); let body = decode_gzip(&read_body(response).await).unwrap(); - let sources: Vec = serde_json::from_slice(&body).unwrap(); - assert_eq!(sources.iter().filter(|v| v.id == "m_mvt").count(), 1); - assert_eq!(sources.iter().filter(|v| v.id == "m_webp").count(), 1); - assert_eq!(sources.iter().filter(|v| v.id == "m_raw_mvt").count(), 1); + let body: serde_json::Value = serde_json::from_slice(&body).unwrap(); + assert_yaml_snapshot!(body, @r###" + --- + tiles: + m_json: + content_type: application/json + name: Dummy json data + m_mvt: + content_type: application/x-protobuf + content_encoding: gzip + name: Major cities from Natural Earth data + description: Major cities from Natural Earth data + m_raw_mvt: + content_type: application/x-protobuf + name: Major cities from Natural Earth data + description: Major cities from Natural Earth data + m_webp: + content_type: image/webp + name: ne2sr + sprites: {} + "###); } #[actix_rt::test] diff --git a/tests/pg_function_source_test.rs b/martin/tests/pg_function_source_test.rs similarity index 68% rename from tests/pg_function_source_test.rs rename to martin/tests/pg_function_source_test.rs index 2db3a3791..b0940a109 100644 --- a/tests/pg_function_source_test.rs +++ b/martin/tests/pg_function_source_test.rs @@ -1,6 +1,6 @@ use ctor::ctor; use indoc::indoc; -use itertools::Itertools; +use insta::assert_yaml_snapshot; use martin::Xyz; pub mod utils; @@ -14,18 +14,15 @@ fn init() { #[actix_rt::test] async fn function_source_tilejson() { let mock = mock_sources(mock_pgcfg("connection_string: $DATABASE_URL")).await; - assert_eq!( - source(&mock, "function_zxy_query").get_tilejson(), - serde_json::from_str(indoc! {r#" -{ - "name": "function_zxy_query", - "description": "public.function_zxy_query", - "tilejson": "3.0.0", - "tiles": [] -} - "#}) - .unwrap() - ); + let tj = source(&mock, "function_zxy_query").get_tilejson(); + assert_yaml_snapshot!(tj, @r###" + --- + tilejson: 3.0.0 + tiles: [] + name: function_zxy_query + foo: + bar: foo + "###); } #[actix_rt::test] @@ -55,9 +52,11 @@ async fn function_source_schemas() { functions: from_schemas: MixedCase "}); - let sources = mock_sources(cfg).await.0; - assert_eq!( - sources.keys().sorted().collect::>(), - vec!["function_Mixed_Name"], - ); + let sources = mock_sources(cfg).await.0.tiles; + assert_yaml_snapshot!(sources.get_catalog(), @r###" + --- + function_Mixed_Name: + content_type: application/x-protobuf + description: a function source with MixedCase name + "###); } diff --git a/tests/pg_server_test.rs b/martin/tests/pg_server_test.rs similarity index 85% rename from tests/pg_server_test.rs rename to martin/tests/pg_server_test.rs index 7f4037a15..c814f554d 100644 --- a/tests/pg_server_test.rs +++ b/martin/tests/pg_server_test.rs @@ -3,7 +3,7 @@ use actix_web::http::StatusCode; use actix_web::test::{call_and_read_body_json, call_service, read_body, TestRequest}; use ctor::ctor; use indoc::indoc; -use martin::srv::IndexEntry; +use insta::assert_yaml_snapshot; use martin::OneOrMany; use tilejson::TileJSON; @@ -18,11 +18,13 @@ fn init() { macro_rules! create_app { ($sources:expr) => {{ let cfg = mock_cfg(indoc::indoc!($sources)); - let sources = mock_sources(cfg).await.0; - let state = crate::utils::mock_app_data(sources).await; + let state = mock_sources(cfg).await.0; ::actix_web::test::init_service( ::actix_web::App::new() - .app_data(state) + .app_data(actix_web::web::Data::new( + ::martin::srv::Catalog::new(&state).unwrap(), + )) + .app_data(actix_web::web::Data::new(state.tiles)) .configure(::martin::srv::router), ) .await @@ -44,16 +46,76 @@ postgres: let response = call_service(&app, req).await; assert!(response.status().is_success()); let body = read_body(response).await; - let sources: Vec = serde_json::from_slice(&body).unwrap(); - - let expected = "table_source"; - assert_eq!(sources.iter().filter(|v| v.id == expected).count(), 1); - - let expected = "function_zxy_query"; - assert_eq!(sources.iter().filter(|v| v.id == expected).count(), 1); - - let expected = "function_zxy_query_jsonb"; - assert_eq!(sources.iter().filter(|v| v.id == expected).count(), 1); + let body: serde_json::Value = serde_json::from_slice(&body).unwrap(); + assert_yaml_snapshot!(body, @r###" + --- + tiles: + MixPoints: + content_type: application/x-protobuf + description: a description from comment on table + auto_table: + content_type: application/x-protobuf + description: autodetect.auto_table.geom + bigint_table: + content_type: application/x-protobuf + description: autodetect.bigint_table.geom + function_Mixed_Name: + content_type: application/x-protobuf + description: a function source with MixedCase name + function_null: + content_type: application/x-protobuf + description: public.function_null + function_null_row: + content_type: application/x-protobuf + description: public.function_null_row + function_null_row2: + content_type: application/x-protobuf + description: public.function_null_row2 + function_zoom_xy: + content_type: application/x-protobuf + description: public.function_zoom_xy + function_zxy: + content_type: application/x-protobuf + description: public.function_zxy + function_zxy2: + content_type: application/x-protobuf + description: public.function_zxy2 + function_zxy_query: + content_type: application/x-protobuf + function_zxy_query_jsonb: + content_type: application/x-protobuf + description: public.function_zxy_query_jsonb + function_zxy_query_test: + content_type: application/x-protobuf + description: public.function_zxy_query_test + function_zxy_row: + content_type: application/x-protobuf + description: public.function_zxy_row + function_zxy_row_key: + content_type: application/x-protobuf + description: public.function_zxy_row_key + points1: + content_type: application/x-protobuf + description: public.points1.geom + points1_vw: + content_type: application/x-protobuf + description: public.points1_vw.geom + points2: + content_type: application/x-protobuf + description: public.points2.geom + points3857: + content_type: application/x-protobuf + description: public.points3857.geom + table_source: + content_type: application/x-protobuf + table_source_multiple_geom: + content_type: application/x-protobuf + description: public.table_source_multiple_geom.geom1 + table_source_multiple_geom.1: + content_type: application/x-protobuf + description: public.table_source_multiple_geom.geom2 + sprites: {} + "###); } #[actix_rt::test] @@ -947,44 +1009,85 @@ tables: let src = table(&mock, "no_id"); assert_eq!(src.id_column, None); assert!(matches!(&src.properties, Some(v) if v.len() == 1)); - assert_eq!( - source(&mock, "no_id").get_tilejson(), - serde_json::from_str(indoc! {r#" -{ - "name": "no_id", - "description": "MixedCase.MixPoints.Geom", - "tilejson": "3.0.0", - "tiles": [], - "vector_layers": [ - { - "id": "no_id", - "fields": {"TABLE": "text"} - } - ], - "bounds": [-180.0, -90.0, 180.0, 90.0] -} - "#}) - .unwrap() - ); - - let src = table(&mock, "id_only"); - assert_eq!(src.id_column, some("giD")); - assert!(matches!(&src.properties, Some(v) if v.len() == 1)); + let tj = source(&mock, "no_id").get_tilejson(); + assert_yaml_snapshot!(tj, @r###" + --- + tilejson: 3.0.0 + tiles: [] + vector_layers: + - id: no_id + fields: + TABLE: text + bounds: + - -180 + - -90 + - 180 + - 90 + description: MixedCase.MixPoints.Geom + name: no_id + "###); + + assert_yaml_snapshot!(table(&mock, "id_only"), @r###" + --- + schema: MixedCase + table: MixPoints + srid: 4326 + geometry_column: Geom + id_column: giD + bounds: + - -180 + - -90 + - 180 + - 90 + geometry_type: POINT + properties: + TABLE: text + "###); - let src = table(&mock, "id_and_prop"); - assert_eq!(src.id_column, some("giD")); - assert!(matches!(&src.properties, Some(v) if v.len() == 2)); + assert_yaml_snapshot!(table(&mock, "id_and_prop"), @r###" + --- + schema: MixedCase + table: MixPoints + srid: 4326 + geometry_column: Geom + id_column: giD + bounds: + - -180 + - -90 + - 180 + - 90 + geometry_type: POINT + properties: + TABLE: text + giD: int4 + "###); - let src = table(&mock, "prop_only"); - assert_eq!(src.id_column, None); - assert!(matches!(&src.properties, Some(v) if v.len() == 2)); + assert_yaml_snapshot!(table(&mock, "prop_only"), @r###" + --- + schema: MixedCase + table: MixPoints + srid: 4326 + geometry_column: Geom + bounds: + - -180 + - -90 + - 180 + - 90 + geometry_type: POINT + properties: + TABLE: text + giD: int4 + "###); // -------------------------------------------- - let state = mock_app_data(mock.0).await; + let state = mock_sources(cfg.clone()).await.0; let app = ::actix_web::test::init_service( ::actix_web::App::new() - .app_data(state) + .app_data(actix_web::web::Data::new( + ::martin::srv::Catalog::new(&state).unwrap(), + )) + .app_data(actix_web::web::Data::new(state.tiles)) .configure(::martin::srv::router), ) .await; diff --git a/martin/tests/pg_table_source_test.rs b/martin/tests/pg_table_source_test.rs new file mode 100644 index 000000000..6b1f28a14 --- /dev/null +++ b/martin/tests/pg_table_source_test.rs @@ -0,0 +1,185 @@ +use ctor::ctor; +use indoc::indoc; +use insta::assert_yaml_snapshot; +use martin::Xyz; + +pub mod utils; +pub use utils::*; + +#[ctor] +fn init() { + let _ = env_logger::builder().is_test(true).try_init(); +} + +#[actix_rt::test] +async fn table_source() { + let mock = mock_sources(mock_pgcfg("connection_string: $DATABASE_URL")).await; + assert_yaml_snapshot!(mock.0.tiles.get_catalog(), @r###" + --- + MixPoints: + content_type: application/x-protobuf + description: a description from comment on table + auto_table: + content_type: application/x-protobuf + description: autodetect.auto_table.geom + bigint_table: + content_type: application/x-protobuf + description: autodetect.bigint_table.geom + function_Mixed_Name: + content_type: application/x-protobuf + description: a function source with MixedCase name + function_null: + content_type: application/x-protobuf + description: public.function_null + function_null_row: + content_type: application/x-protobuf + description: public.function_null_row + function_null_row2: + content_type: application/x-protobuf + description: public.function_null_row2 + function_zoom_xy: + content_type: application/x-protobuf + description: public.function_zoom_xy + function_zxy: + content_type: application/x-protobuf + description: public.function_zxy + function_zxy2: + content_type: application/x-protobuf + description: public.function_zxy2 + function_zxy_query: + content_type: application/x-protobuf + function_zxy_query_jsonb: + content_type: application/x-protobuf + description: public.function_zxy_query_jsonb + function_zxy_query_test: + content_type: application/x-protobuf + description: public.function_zxy_query_test + function_zxy_row: + content_type: application/x-protobuf + description: public.function_zxy_row + function_zxy_row_key: + content_type: application/x-protobuf + description: public.function_zxy_row_key + points1: + content_type: application/x-protobuf + description: public.points1.geom + points1_vw: + content_type: application/x-protobuf + description: public.points1_vw.geom + points2: + content_type: application/x-protobuf + description: public.points2.geom + points3857: + content_type: application/x-protobuf + description: public.points3857.geom + table_source: + content_type: application/x-protobuf + table_source_multiple_geom: + content_type: application/x-protobuf + description: public.table_source_multiple_geom.geom1 + table_source_multiple_geom.1: + content_type: application/x-protobuf + description: public.table_source_multiple_geom.geom2 + "###); + + let source = table(&mock, "table_source"); + assert_yaml_snapshot!(source, @r###" + --- + schema: public + table: table_source + srid: 4326 + geometry_column: geom + bounds: + - -2 + - -1 + - 142.84131509869133 + - 45 + geometry_type: GEOMETRY + properties: + gid: int4 + "###); +} + +#[actix_rt::test] +async fn tables_tilejson() { + let mock = mock_sources(mock_pgcfg("connection_string: $DATABASE_URL")).await; + let tj = source(&mock, "table_source").get_tilejson(); + assert_yaml_snapshot!(tj, @r###" + --- + tilejson: 3.0.0 + tiles: [] + vector_layers: + - id: table_source + fields: + gid: int4 + bounds: + - -2 + - -1 + - 142.84131509869133 + - 45 + name: table_source + foo: + bar: foo + "###); +} + +#[actix_rt::test] +async fn tables_tile_ok() { + let mock = mock_sources(mock_pgcfg("connection_string: $DATABASE_URL")).await; + let tile = source(&mock, "table_source") + .get_tile(&Xyz { z: 0, x: 0, y: 0 }, &None) + .await + .unwrap(); + + assert!(!tile.is_empty()); +} + +#[actix_rt::test] +async fn tables_srid_ok() { + let mock = mock_sources(mock_pgcfg(indoc! {" + connection_string: $DATABASE_URL + default_srid: 900913 + "})) + .await; + + let source = table(&mock, "points1"); + assert_eq!(source.srid, 4326); + + let source = table(&mock, "points2"); + assert_eq!(source.srid, 4326); + + let source = table(&mock, "points3857"); + assert_eq!(source.srid, 3857); + + let source = table(&mock, "points_empty_srid"); + assert_eq!(source.srid, 900_913); +} + +#[actix_rt::test] +async fn tables_multiple_geom_ok() { + let mock = mock_sources(mock_pgcfg("connection_string: $DATABASE_URL")).await; + + let source = table(&mock, "table_source_multiple_geom"); + assert_eq!(source.geometry_column, "geom1"); + + let source = table(&mock, "table_source_multiple_geom.1"); + assert_eq!(source.geometry_column, "geom2"); +} + +#[actix_rt::test] +async fn table_source_schemas() { + let cfg = mock_pgcfg(indoc! {" + connection_string: $DATABASE_URL + auto_publish: + tables: + from_schemas: MixedCase + functions: false + "}); + let sources = mock_sources(cfg).await.0; + assert_yaml_snapshot!(sources.tiles.get_catalog(), @r###" + --- + MixPoints: + content_type: application/x-protobuf + description: a description from comment on table + "###); +} diff --git a/tests/pmt_server_test.rs b/martin/tests/pmt_server_test.rs similarity index 79% rename from tests/pmt_server_test.rs rename to martin/tests/pmt_server_test.rs index 916601a87..3ed778313 100644 --- a/tests/pmt_server_test.rs +++ b/martin/tests/pmt_server_test.rs @@ -2,8 +2,8 @@ use actix_web::http::header::{ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_TYPE}; use actix_web::test::{call_service, read_body, read_body_json, TestRequest}; use ctor::ctor; use indoc::indoc; +use insta::assert_yaml_snapshot; use martin::decode_gzip; -use martin::srv::IndexEntry; use tilejson::TileJSON; pub mod utils; @@ -16,11 +16,13 @@ fn init() { macro_rules! create_app { ($sources:expr) => {{ - let sources = mock_sources(mock_cfg($sources)).await.0; - let state = crate::utils::mock_app_data(sources).await; + let state = mock_sources(mock_cfg($sources)).await.0; ::actix_web::test::init_service( ::actix_web::App::new() - .app_data(state) + .app_data(actix_web::web::Data::new( + ::martin::srv::Catalog::new(&state).unwrap(), + )) + .app_data(actix_web::web::Data::new(state.tiles)) .configure(::martin::srv::router), ) .await @@ -34,22 +36,25 @@ fn test_get(path: &str) -> TestRequest { const CONFIG: &str = indoc! {" pmtiles: sources: - p_png: tests/fixtures/pmtiles/stamen_toner__raster_CC-BY+ODbL_z3.pmtiles + p_png: ../tests/fixtures/pmtiles/stamen_toner__raster_CC-BY+ODbL_z3.pmtiles "}; #[actix_rt::test] async fn pmt_get_catalog() { - let path = "pmtiles: tests/fixtures/pmtiles/stamen_toner__raster_CC-BY+ODbL_z3.pmtiles"; + let path = "pmtiles: ../tests/fixtures/pmtiles/stamen_toner__raster_CC-BY+ODbL_z3.pmtiles"; let app = create_app! { path }; let req = test_get("/catalog").to_request(); let response = call_service(&app, req).await; assert!(response.status().is_success()); - let body = read_body(response).await; - let sources: Vec = serde_json::from_slice(&body).unwrap(); - - let expected = "stamen_toner__raster_CC-BY-ODbL_z3"; - assert_eq!(sources.iter().filter(|v| v.id == expected).count(), 1); + let body: serde_json::Value = read_body_json(response).await; + assert_yaml_snapshot!(body, @r###" + --- + tiles: + stamen_toner__raster_CC-BY-ODbL_z3: + content_type: image/png + sprites: {} + "###); } #[actix_rt::test] @@ -60,8 +65,14 @@ async fn pmt_get_catalog_gzip() { let response = call_service(&app, req).await; assert!(response.status().is_success()); let body = decode_gzip(&read_body(response).await).unwrap(); - let sources: Vec = serde_json::from_slice(&body).unwrap(); - assert_eq!(sources.iter().filter(|v| v.id == "p_png").count(), 1); + let body: serde_json::Value = serde_json::from_slice(&body).unwrap(); + assert_yaml_snapshot!(body, @r###" + --- + tiles: + p_png: + content_type: image/png + sprites: {} + "###); } #[actix_rt::test] diff --git a/tests/utils/mod.rs b/martin/tests/utils/mod.rs similarity index 81% rename from tests/utils/mod.rs rename to martin/tests/utils/mod.rs index d15588ea7..e61dae3d0 100644 --- a/tests/utils/mod.rs +++ b/martin/tests/utils/mod.rs @@ -4,10 +4,8 @@ mod pg_utils; -use actix_web::web::Data; use log::warn; -use martin::srv::AppState; -use martin::{Config, Sources}; +use martin::Config; pub use pg_utils::*; #[path = "../../src/utils/test_utils.rs"] @@ -15,10 +13,6 @@ mod test_utils; #[allow(clippy::wildcard_imports)] pub use test_utils::*; -pub async fn mock_app_data(sources: Sources) -> Data { - Data::new(sources) -} - #[must_use] pub fn mock_cfg(yaml: &str) -> Config { let env = if let Ok(db_url) = std::env::var("DATABASE_URL") { diff --git a/tests/utils/pg_utils.rs b/martin/tests/utils/pg_utils.rs similarity index 89% rename from tests/utils/pg_utils.rs rename to martin/tests/utils/pg_utils.rs index b5c729d34..da5da7d3d 100644 --- a/tests/utils/pg_utils.rs +++ b/martin/tests/utils/pg_utils.rs @@ -1,7 +1,7 @@ use indoc::formatdoc; pub use martin::args::Env; use martin::pg::TableInfo; -use martin::{Config, IdResolver, Source, Sources}; +use martin::{Config, IdResolver, ServerState, Source}; use crate::mock_cfg; @@ -10,7 +10,7 @@ use crate::mock_cfg; // Each function should allow dead_code as they might not be used by a specific test file. // -pub type MockSource = (Sources, Config); +pub type MockSource = (ServerState, Config); #[allow(dead_code)] #[must_use] @@ -48,5 +48,5 @@ pub fn table<'a>(mock: &'a MockSource, name: &str) -> &'a TableInfo { #[must_use] pub fn source<'a>(mock: &'a MockSource, name: &str) -> &'a dyn Source { let (sources, _) = mock; - sources.get(name).unwrap().as_ref() + sources.tiles.get_source(name).unwrap() } diff --git a/tests/pg_table_source_test.rs b/tests/pg_table_source_test.rs deleted file mode 100644 index 5bba0b85b..000000000 --- a/tests/pg_table_source_test.rs +++ /dev/null @@ -1,117 +0,0 @@ -use std::collections::HashMap; - -use ctor::ctor; -use indoc::indoc; -use martin::Xyz; - -pub mod utils; -pub use utils::*; - -#[ctor] -fn init() { - let _ = env_logger::builder().is_test(true).try_init(); -} - -#[actix_rt::test] -async fn table_source() { - let mock = mock_sources(mock_pgcfg("connection_string: $DATABASE_URL")).await; - assert!(!mock.0.is_empty()); - - let source = table(&mock, "table_source"); - assert_eq!(source.schema, "public"); - assert_eq!(source.table, "table_source"); - assert_eq!(source.srid, 4326); - assert_eq!(source.geometry_column, "geom"); - assert_eq!(source.id_column, None); - assert_eq!(source.minzoom, None); - assert_eq!(source.maxzoom, None); - assert!(source.bounds.is_some()); - assert_eq!(source.extent, Some(4096)); - assert_eq!(source.buffer, Some(64)); - assert_eq!(source.clip_geom, Some(true)); - assert_eq!(source.geometry_type, some("GEOMETRY")); - - let mut properties = HashMap::new(); - properties.insert("gid".to_owned(), "int4".to_owned()); - assert_eq!(source.properties, Some(properties)); -} - -#[actix_rt::test] -async fn tables_tilejson() { - let mock = mock_sources(mock_pgcfg("connection_string: $DATABASE_URL")).await; - assert_eq!( - source(&mock, "table_source").get_tilejson(), - serde_json::from_str(indoc! {r#" -{ - "name": "table_source", - "description": "public.table_source.geom", - "tilejson": "3.0.0", - "tiles": [], - "vector_layers": [ - { - "id": "table_source", - "fields": {"gid": "int4"} - } - ], - "bounds": [-2.0, -1.0, 142.84131509869133, 45.0] -} - "#}) - .unwrap() - ); -} - -#[actix_rt::test] -async fn tables_tile_ok() { - let mock = mock_sources(mock_pgcfg("connection_string: $DATABASE_URL")).await; - let tile = source(&mock, "table_source") - .get_tile(&Xyz { z: 0, x: 0, y: 0 }, &None) - .await - .unwrap(); - - assert!(!tile.is_empty()); -} - -#[actix_rt::test] -async fn tables_srid_ok() { - let mock = mock_sources(mock_pgcfg(indoc! {" - connection_string: $DATABASE_URL - default_srid: 900913 - "})) - .await; - - let source = table(&mock, "points1"); - assert_eq!(source.srid, 4326); - - let source = table(&mock, "points2"); - assert_eq!(source.srid, 4326); - - let source = table(&mock, "points3857"); - assert_eq!(source.srid, 3857); - - let source = table(&mock, "points_empty_srid"); - assert_eq!(source.srid, 900_913); -} - -#[actix_rt::test] -async fn tables_multiple_geom_ok() { - let mock = mock_sources(mock_pgcfg("connection_string: $DATABASE_URL")).await; - - let source = table(&mock, "table_source_multiple_geom"); - assert_eq!(source.geometry_column, "geom1"); - - let source = table(&mock, "table_source_multiple_geom.1"); - assert_eq!(source.geometry_column, "geom2"); -} - -#[actix_rt::test] -async fn table_source_schemas() { - let cfg = mock_pgcfg(indoc! {" - connection_string: $DATABASE_URL - auto_publish: - tables: - from_schemas: MixedCase - functions: false - "}); - let sources = mock_sources(cfg).await.0; - assert_eq!(sources.keys().collect::>(), vec!["MixPoints"]); -}