Skip to content

Commit e770ac5

Browse files
committed
test(catalog-rest): cover endpoint config parsing + fixture integration test
Unit tests in types.rs for CatalogConfig endpoint parsing: a valid advertised list parses, a missing field deserializes to None, and a single malformed endpoint string fails the whole parse (matching the Java reference's ConfigResponseParser, which rejects rather than silently dropping). Integration test in crates/integration_tests exercising RestCatalog::supports_endpoint against the real iceberg-rest-fixture GET /v1/config response.
1 parent 47518b8 commit e770ac5

2 files changed

Lines changed: 88 additions & 0 deletions

File tree

crates/catalog/rest/src/types.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,4 +480,32 @@ mod tests {
480480
assert_eq!(request.stage_create, None);
481481
assert!(request.properties.is_empty());
482482
}
483+
484+
#[test]
485+
fn config_parses_advertised_endpoints() {
486+
let json = r#"{"overrides":{},"defaults":{},
487+
"endpoints":["GET /v1/{prefix}/namespaces","POST /v1/{prefix}/namespaces/{namespace}/tables"]}"#;
488+
let config: CatalogConfig = serde_json::from_str(json).unwrap();
489+
let endpoints = config.endpoints.expect("endpoints should be present");
490+
assert_eq!(endpoints.len(), 2);
491+
assert!(endpoints.contains(&"GET /v1/{prefix}/namespaces".parse().unwrap()));
492+
}
493+
494+
#[test]
495+
fn config_without_endpoints_field_deserializes_to_none() {
496+
// `Option<Vec<Endpoint>>` defaults to `None` for a missing field without
497+
// an explicit `#[serde(default)]`.
498+
let config: CatalogConfig =
499+
serde_json::from_str(r#"{"overrides":{},"defaults":{}}"#).unwrap();
500+
assert!(config.endpoints.is_none());
501+
}
502+
503+
#[test]
504+
fn malformed_endpoint_fails_config_parse() {
505+
// A single malformed endpoint string fails the whole config parse,
506+
// matching the Java reference (`ConfigResponseParser` rejects it rather
507+
// than silently dropping it).
508+
let json = r#"{"overrides":{},"defaults":{},"endpoints":["GET_v1/namespaces"]}"#;
509+
assert!(serde_json::from_str::<CatalogConfig>(json).is_err());
510+
}
483511
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//! Integration test for REST endpoint capability negotiation, run against the
19+
//! `iceberg-rest-fixture` started by `dev/docker-compose.yaml`.
20+
21+
use std::collections::HashMap;
22+
23+
use iceberg::CatalogBuilder;
24+
use iceberg_catalog_rest::{Endpoint, REST_CATALOG_PROP_URI, RestCatalogBuilder};
25+
use iceberg_test_utils::{get_rest_catalog_endpoint, set_up};
26+
27+
#[tokio::test]
28+
async fn test_supports_endpoint_from_server_config() {
29+
set_up();
30+
31+
// Endpoint negotiation only reads `GET /v1/config`, so the catalog needs
32+
// just the REST URI — no storage configuration is involved.
33+
let config = HashMap::from([(
34+
REST_CATALOG_PROP_URI.to_string(),
35+
get_rest_catalog_endpoint(),
36+
)]);
37+
let catalog = RestCatalogBuilder::default()
38+
.load("rest", config)
39+
.await
40+
.unwrap();
41+
42+
// `HEAD .../tables/{table}` (table-exists) is advertised by the fixture but
43+
// is NOT part of `DEFAULT_ENDPOINTS`. Asserting it is supported confirms the
44+
// server's advertised list — not the default fallback — is what gets
45+
// negotiated (the fallback would report it unsupported).
46+
let table_exists: Endpoint = "HEAD /v1/{prefix}/namespaces/{namespace}/tables/{table}"
47+
.parse()
48+
.unwrap();
49+
assert!(catalog.supports_endpoint(&table_exists).await.unwrap());
50+
51+
// A base operation is advertised and supported too.
52+
let load_table: Endpoint = "GET /v1/{prefix}/namespaces/{namespace}/tables/{table}"
53+
.parse()
54+
.unwrap();
55+
assert!(catalog.supports_endpoint(&load_table).await.unwrap());
56+
57+
// A path the fixture does not advertise is reported unsupported.
58+
let bogus: Endpoint = "GET /v1/{prefix}/does-not-exist".parse().unwrap();
59+
assert!(!catalog.supports_endpoint(&bogus).await.unwrap());
60+
}

0 commit comments

Comments
 (0)