1919
2020#include " iceberg/catalog/rest/rest_catalog.h"
2121
22+ #include < unistd.h>
23+
24+ #include < chrono>
25+ #include < memory>
26+ #include < print>
2227#include < string>
28+ #include < thread>
2329#include < unordered_map>
2430
31+ #include < arpa/inet.h>
2532#include < gmock/gmock.h>
2633#include < gtest/gtest.h>
34+ #include < netinet/in.h>
35+ #include < sys/socket.h>
2736
2837#include " iceberg/catalog/rest/catalog_properties.h"
38+ #include " iceberg/result.h"
2939#include " iceberg/table_identifier.h"
3040#include " iceberg/test/matchers.h"
41+ #include " iceberg/test/util/docker_compose_util.h"
3142
3243namespace iceberg ::rest {
3344
34- // Test fixture for REST catalog tests, This assumes you have a local REST catalog service
35- // running Default configuration: http://localhost:8181.
36- class RestCatalogTest : public ::testing::Test {
45+ namespace {
46+
47+ constexpr uint16_t kRestCatalogPort = 8181 ;
48+ constexpr int kMaxRetries = 60 ; // Wait up to 60 seconds
49+ constexpr int kRetryDelayMs = 1000 ;
50+
51+ constexpr std::string_view kDockerProjectName = " iceberg-rest-catalog-service" ;
52+ constexpr std::string_view kCatalogName = " test_catalog" ;
53+ constexpr std::string_view kWarehouseName = " default" ;
54+ constexpr std::string_view kLocalhostUri = " http://localhost" ;
55+
56+ // / \brief Check if a localhost port is ready to accept connections
57+ // / \param port Port number to check
58+ // / \return true if the port is accessible on localhost, false otherwise
59+ bool CheckServiceReady (uint16_t port) {
60+ int sock = socket (AF_INET, SOCK_STREAM, 0 );
61+ if (sock < 0 ) {
62+ return false ;
63+ }
64+
65+ struct timeval timeout{
66+ .tv_sec = 1 ,
67+ .tv_usec = 0 ,
68+ };
69+ setsockopt (sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof (timeout));
70+
71+ sockaddr_in addr{
72+ .sin_family = AF_INET,
73+ .sin_port = htons (port),
74+ .sin_addr = {.s_addr = htonl (INADDR_LOOPBACK)} // 127.0.0.1
75+ };
76+ bool result =
77+ (connect (sock, reinterpret_cast <struct sockaddr *>(&addr), sizeof (addr)) == 0 );
78+ close (sock);
79+ return result;
80+ }
81+
82+ } // namespace
83+
84+ // / \brief Integration test fixture for REST catalog with automatic Docker Compose setup。
85+ class RestCatalogIntegrationTest : public ::testing::Test {
3786 protected:
38- void SetUp () override {
39- // Default configuration for local testing
40- // You can override this with environment variables if needed
41- const char * uri_env = std::getenv (" ICEBERG_REST_URI" );
42- const char * warehouse_env = std::getenv (" ICEBERG_REST_WAREHOUSE" );
87+ static void SetUpTestSuite () {
88+ std::string project_name{kDockerProjectName };
89+ std::filesystem::path resources_dir =
90+ std::filesystem::path (__FILE__).parent_path () / " resources" ;
91+
92+ // Create and start DockerCompose
93+ docker_compose_ = std::make_unique<DockerCompose>(project_name, resources_dir);
94+ docker_compose_->Up ();
95+
96+ // Wait for REST catalog to be ready on localhost
97+ std::println (" [INFO] Waiting for REST catalog to be ready at localhost:{}..." ,
98+ kRestCatalogPort );
99+ for (int i = 0 ; i < kMaxRetries ; ++i) {
100+ if (CheckServiceReady (kRestCatalogPort )) {
101+ std::println (" [INFO] REST catalog is ready!" );
102+ return ;
103+ }
104+ std::println (
105+ " [INFO] Waiting for 1s for REST catalog to be ready... (attempt {}/{})" , i + 1 ,
106+ kMaxRetries );
107+ std::this_thread::sleep_for (std::chrono::milliseconds (kRetryDelayMs ));
108+ }
109+ throw RestError (" REST catalog failed to start within {} seconds" , kMaxRetries );
110+ }
43111
44- std::string uri = uri_env ? uri_env : " http://localhost:8181" ;
45- std::string warehouse = warehouse_env ? warehouse_env : " default" ;
112+ static void TearDownTestSuite () { docker_compose_.reset (); }
46113
114+ void SetUp () override {
47115 config_ = RestCatalogProperties::default_properties ();
48- config_->Set (RestCatalogProperties::kUri , uri)
49- .Set (RestCatalogProperties::kName , std::string (" test_catalog" ))
50- .Set (RestCatalogProperties::kWarehouse , warehouse);
116+ config_
117+ ->Set (RestCatalogProperties::kUri ,
118+ std::format (" {}:{}" , kLocalhostUri , kRestCatalogPort ))
119+ .Set (RestCatalogProperties::kName , std::string (kCatalogName ))
120+ .Set (RestCatalogProperties::kWarehouse , std::string (kWarehouseName ));
51121 }
52122
53123 void TearDown () override {}
54124
125+ // / \brief Helper function to create a REST catalog instance
126+ Result<std::unique_ptr<RestCatalog>> CreateCatalog () {
127+ return RestCatalog::Make (*config_);
128+ }
129+
130+ static inline std::unique_ptr<DockerCompose> docker_compose_;
55131 std::unique_ptr<RestCatalogProperties> config_;
56132};
57133
58- TEST_F (RestCatalogTest, DISABLED_MakeCatalogSuccess ) {
59- auto catalog_result = RestCatalog::Make (*config_ );
60- EXPECT_THAT (catalog_result, IsOk ());
134+ TEST_F (RestCatalogIntegrationTest, MakeCatalogSuccess ) {
135+ auto catalog_result = CreateCatalog ( );
136+ ASSERT_THAT (catalog_result, IsOk ());
61137
62- if (catalog_result.has_value ()) {
63- auto & catalog = catalog_result.value ();
64- EXPECT_EQ (catalog->name (), " test_catalog" );
65- }
138+ auto & catalog = catalog_result.value ();
139+ EXPECT_EQ (catalog->name (), kCatalogName );
66140}
67141
68- TEST_F (RestCatalogTest, DISABLED_MakeCatalogEmptyUri) {
69- auto invalid_config = RestCatalogProperties::default_properties ();
70- invalid_config->Set (RestCatalogProperties::kUri , std::string (" " ));
142+ TEST_F (RestCatalogIntegrationTest, ListNamespaces) {
143+ auto catalog_result = CreateCatalog ();
144+ ASSERT_THAT (catalog_result, IsOk ());
145+ auto & catalog = catalog_result.value ();
71146
72- auto catalog_result = RestCatalog::Make (*invalid_config) ;
73- EXPECT_THAT (catalog_result, IsError (ErrorKind:: kInvalidArgument ) );
74- EXPECT_THAT (catalog_result, HasErrorMessage ( " uri " ));
147+ Namespace root{. levels = {}} ;
148+ auto result = catalog-> ListNamespaces (root );
149+ EXPECT_THAT (result, IsOk ( ));
75150}
76151
77- TEST_F (RestCatalogTest, DISABLED_MakeCatalogWithCustomProperties) {
78- auto custom_config = RestCatalogProperties::default_properties ();
79- custom_config
80- ->Set (RestCatalogProperties::kUri , config_->Get (RestCatalogProperties::kUri ))
81- .Set (RestCatalogProperties::kName , config_->Get (RestCatalogProperties::kName ))
82- .Set (RestCatalogProperties::kWarehouse ,
83- config_->Get (RestCatalogProperties::kWarehouse ))
84- .Set (RestCatalogProperties::Entry<std::string>{" custom_prop" , " " },
85- std::string (" custom_value" ))
86- .Set (RestCatalogProperties::Entry<std::string>{" timeout" , " " },
87- std::string (" 30000" ));
88-
89- auto catalog_result = RestCatalog::Make (*custom_config);
90- EXPECT_THAT (catalog_result, IsOk ());
152+ TEST_F (RestCatalogIntegrationTest, DISABLED_GetNonExistentNamespace) {
153+ auto catalog_result = CreateCatalog ();
154+ ASSERT_THAT (catalog_result, IsOk ());
155+ auto & catalog = catalog_result.value ();
156+
157+ Namespace ns{.levels = {" test_get_non_existent_namespace" }};
158+ auto result = catalog->GetNamespaceProperties (ns);
159+
160+ EXPECT_THAT (result, HasErrorMessage (" does not exist" ));
91161}
92162
93- TEST_F (RestCatalogTest, DISABLED_ListNamespaces ) {
94- auto catalog_result = RestCatalog::Make (*config_ );
163+ TEST_F (RestCatalogIntegrationTest, DISABLED_CreateAndDropNamespace ) {
164+ auto catalog_result = CreateCatalog ( );
95165 ASSERT_THAT (catalog_result, IsOk ());
96- auto & catalog = catalog_result.value ();
166+ auto catalog = std::move ( catalog_result.value () );
97167
98- Namespace ns{.levels = {}};
99- auto result = catalog->ListNamespaces (ns);
100- EXPECT_THAT (result, IsOk ());
101- EXPECT_FALSE (result->empty ());
102- EXPECT_EQ (result->front ().levels , (std::vector<std::string>{" my_namespace_test2" }));
168+ // Create a namespace
169+ Namespace test_ns{.levels = {" test_create_drop_ns" }};
170+ std::unordered_map<std::string, std::string> props = {{" owner" , " test_user" }};
171+
172+ auto create_result = catalog->CreateNamespace (test_ns, props);
173+ ASSERT_THAT (create_result, IsOk ());
174+
175+ // Verify it exists
176+ auto exists_result = catalog->NamespaceExists (test_ns);
177+ EXPECT_THAT (exists_result, HasValue (::testing::Eq (true )));
178+
179+ // Drop it
180+ auto drop_result = catalog->DropNamespace (test_ns);
181+ EXPECT_THAT (drop_result, IsOk ());
182+
183+ // Verify it no longer exists
184+ auto exists_result2 = catalog->NamespaceExists (test_ns);
185+ EXPECT_THAT (exists_result2, HasValue (::testing::Eq (false )));
103186}
104187
105- TEST_F (RestCatalogTest, DISABLED_CreateNamespaceNotImplemented ) {
106- auto catalog_result = RestCatalog::Make (*config_ );
188+ TEST_F (RestCatalogIntegrationTest, DISABLED_UpdateNamespaceProperties ) {
189+ auto catalog_result = CreateCatalog ( );
107190 ASSERT_THAT (catalog_result, IsOk ());
108191 auto catalog = std::move (catalog_result.value ());
109192
110- Namespace ns{.levels = {" test_namespace" }};
111- std::unordered_map<std::string, std::string> props = {{" owner" , " test" }};
193+ // Create a namespace
194+ Namespace test_ns{.levels = {" test_update_props_ns" }};
195+ std::unordered_map<std::string, std::string> initial_props = {{" owner" , " alice" },
196+ {" team" , " data_eng" }};
197+
198+ auto create_result = catalog->CreateNamespace (test_ns, initial_props);
199+ ASSERT_THAT (create_result, IsOk ());
200+
201+ // Update properties
202+ std::unordered_map<std::string, std::string> updates = {
203+ {" owner" , " bob" }, {" description" , " test namespace" }};
204+ std::unordered_set<std::string> removals = {" team" };
205+
206+ auto update_result = catalog->UpdateNamespaceProperties (test_ns, updates, removals);
207+ EXPECT_THAT (update_result, IsOk ());
208+
209+ // Verify updated properties
210+ auto props_result = catalog->GetNamespaceProperties (test_ns);
211+ ASSERT_THAT (props_result, IsOk ());
212+ EXPECT_EQ ((*props_result)[" owner" ], " bob" );
213+ EXPECT_EQ ((*props_result)[" description" ], " test namespace" );
214+ EXPECT_EQ (props_result->count (" team" ), 0 ); // Should be removed
112215
113- auto result = catalog-> CreateNamespace (ns, props);
114- EXPECT_THAT (result, IsError (ErrorKind:: kNotImplemented ) );
216+ // Cleanup
217+ catalog-> DropNamespace (test_ns );
115218}
116219
117- TEST_F (RestCatalogTest, DISABLED_IntegrationTestFullNamespaceWorkflow ) {
118- auto catalog_result = RestCatalog::Make (*config_ );
220+ TEST_F (RestCatalogIntegrationTest, DISABLED_FullNamespaceWorkflow ) {
221+ auto catalog_result = CreateCatalog ( );
119222 ASSERT_THAT (catalog_result, IsOk ());
120223 auto catalog = std::move (catalog_result.value ());
121224
@@ -126,11 +229,11 @@ TEST_F(RestCatalogTest, DISABLED_IntegrationTestFullNamespaceWorkflow) {
126229 size_t initial_count = list_result1->size ();
127230
128231 // 2. Create a new namespace
129- Namespace test_ns{.levels = {" integration_test_ns " }};
232+ Namespace test_ns{.levels = {" integration_test_workflow " }};
130233 std::unordered_map<std::string, std::string> props = {
131- {" owner" , " test" }, {" created_by" , " rest_catalog_test " }};
234+ {" owner" , " test" }, {" created_by" , " rest_catalog_integration_test " }};
132235 auto create_result = catalog->CreateNamespace (test_ns, props);
133- EXPECT_THAT (create_result, IsOk ());
236+ ASSERT_THAT (create_result, IsOk ());
134237
135238 // 3. Verify namespace exists
136239 auto exists_result = catalog->NamespaceExists (test_ns);
@@ -148,15 +251,15 @@ TEST_F(RestCatalogTest, DISABLED_IntegrationTestFullNamespaceWorkflow) {
148251
149252 // 6. Update properties
150253 std::unordered_map<std::string, std::string> updates = {
151- {" description" , " test namespace" }};
254+ {" description" , " integration test namespace" }};
152255 std::unordered_set<std::string> removals = {};
153256 auto update_result = catalog->UpdateNamespaceProperties (test_ns, updates, removals);
154257 EXPECT_THAT (update_result, IsOk ());
155258
156259 // 7. Verify updated properties
157260 auto props_result2 = catalog->GetNamespaceProperties (test_ns);
158261 ASSERT_THAT (props_result2, IsOk ());
159- EXPECT_EQ ((*props_result2)[" description" ], " test namespace" );
262+ EXPECT_EQ ((*props_result2)[" description" ], " integration test namespace" );
160263
161264 // 8. Drop the namespace (cleanup)
162265 auto drop_result = catalog->DropNamespace (test_ns);
0 commit comments