Database System 0.1.0
Advanced C++20 Database System with Multi-Backend Support
Loading...
Searching...
No Matches
test_unified_database_system.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2025, šŸ€ā˜€šŸŒ•šŸŒ„ 🌊
3// See the LICENSE file in the project root for full license information.
4
17
18#include <iostream>
19#include <cassert>
20#include <chrono>
21#include <thread>
22
23using namespace database::integrated;
24
25// Test counters
26static int tests_passed = 0;
27static int tests_failed = 0;
28
29// Test helpers
30#define TEST_START(name) \
31 std::cout << "\n[TEST] " << name << "...\n"
32
33#define ASSERT_TRUE(condition, message) \
34 do { \
35 if (!(condition)) { \
36 std::cout << " āŒ FAILED: " << message << "\n"; \
37 std::cout << " at " << __FILE__ << ":" << __LINE__ << "\n"; \
38 tests_failed++; \
39 return false; \
40 } \
41 } while(0)
42
43#define ASSERT_FALSE(condition, message) \
44 ASSERT_TRUE(!(condition), message)
45
46#define TEST_END() \
47 do { \
48 std::cout << " āœ… PASSED\n"; \
49 tests_passed++; \
50 return true; \
51 } while(0)
52
53//==============================================================================
54// Stub Backend for Mock Testing
55//==============================================================================
56
57namespace {
58
67class stub_backend : public ::database::core::database_backend {
68public:
69 stub_backend() = default;
70
71 static std::unique_ptr<::database::core::database_backend> create() {
72 return std::make_unique<stub_backend>();
73 }
74
75 ::database::database_types type() const override {
76 return ::database::database_types::sqlite;
77 }
78
79 kcenon::common::VoidResult initialize(
80 const ::database::core::connection_config& /*config*/) override {
81 initialized_ = true;
82 return kcenon::common::VoidResult(std::monostate{});
83 }
84
85 kcenon::common::VoidResult shutdown() override {
86 initialized_ = false;
87 in_tx_ = false;
88 return kcenon::common::VoidResult(std::monostate{});
89 }
90
91 bool is_initialized() const override { return initialized_; }
92
93 kcenon::common::Result<::database::core::database_result> select_query(
94 const std::string& /*query_string*/) override {
95 ::database::core::database_result result;
96 ::database::core::database_row row;
97 row["id"] = int64_t{1};
98 row["name"] = std::string("test_user");
99 result.push_back(row);
100 return result;
101 }
102
103 kcenon::common::VoidResult execute_query(
104 const std::string& /*query_string*/) override {
105 return kcenon::common::VoidResult(std::monostate{});
106 }
107
108 kcenon::common::VoidResult begin_transaction() override {
109 in_tx_ = true;
110 return kcenon::common::VoidResult(std::monostate{});
111 }
112
113 kcenon::common::VoidResult commit_transaction() override {
114 in_tx_ = false;
115 return kcenon::common::VoidResult(std::monostate{});
116 }
117
118 kcenon::common::VoidResult rollback_transaction() override {
119 in_tx_ = false;
120 return kcenon::common::VoidResult(std::monostate{});
121 }
122
123 bool in_transaction() const override { return in_tx_; }
124
125 std::string last_error() const override { return ""; }
126
127 std::map<std::string, std::string> connection_info() const override {
128 return {{"backend", "stub"}, {"version", "1.0"}};
129 }
130
131private:
132 bool initialized_ = false;
133 bool in_tx_ = false;
134};
135
136// Register stub as "sqlite" backend for test use
137void register_stub_backend() {
138 auto& registry = ::database::core::backend_registry::instance();
139 if (!registry.has_backend("sqlite")) {
140 registry.register_backend("sqlite", &stub_backend::create);
141 }
142}
143
144void unregister_stub_backend() {
145 ::database::core::backend_registry::instance().unregister_backend("sqlite");
146}
147
148} // anonymous namespace
149
150//==============================================================================
151// Test 1: Builder Pattern - Default Configuration
152//==============================================================================
153
155 TEST_START("Builder Pattern - Default Configuration");
156
158 auto db_result = builder.build();
159
160 ASSERT_TRUE(db_result.is_ok(), "Builder should succeed");
161 auto db = std::move(db_result.value());
162 ASSERT_TRUE(db != nullptr, "Builder should create database instance");
163
164 TEST_END();
165}
166
167//==============================================================================
168// Test 2: Builder Pattern - Custom Configuration
169//==============================================================================
170
172 TEST_START("Builder Pattern - Custom Configuration");
173
175 .set_backend(backend_type::postgres)
176 .set_connection_string("host=localhost dbname=test")
177 .set_pool_size(5, 20)
178 .enable_logging(db_log_level::debug, "./test_logs")
179 .enable_monitoring(true)
180 .enable_async(8)
181 .set_slow_query_threshold(std::chrono::milliseconds(500))
182 .build();
183
184 if (db_result.is_err()) {
185 // If PostgreSQL is not available or not compiled in, that's acceptable
186 // This test is just verifying the builder API works
187 std::cout << " Note: Database connection not available: " << db_result.error().message << "\n";
188 std::cout << " Builder API test passed (connection test skipped)\n";
189 } else {
190 ASSERT_TRUE(db_result.value() != nullptr, "Builder with custom config should create instance");
191 }
192
193 TEST_END();
194}
195
196//==============================================================================
197// Test 3: Zero-Config Construction
198//==============================================================================
199
201 TEST_START("Zero-Config Construction");
202
203 // Should create with smart defaults
205
206 // Should not be connected yet
207 ASSERT_FALSE(db.is_connected(), "Should not be connected without connect()");
208
209 TEST_END();
210}
211
212//==============================================================================
213// Test 4: Configuration-Based Construction
214//==============================================================================
215
217 TEST_START("Configuration-Based Construction");
218
219 unified_db_config config;
220 config.database.type = backend_type::postgres;
223 config.logger.enable_query_logging = true;
224 config.monitoring.enable_metrics = true;
225
226 unified_database_system db(config);
227
228 ASSERT_FALSE(db.is_connected(), "Should not be connected without connect()");
229
230 TEST_END();
231}
232
233//==============================================================================
234// Test 5: Move Semantics
235//==============================================================================
236
238 TEST_START("Move Semantics");
239
241 .set_backend(backend_type::postgres)
242 .build();
243
244 ASSERT_TRUE(db1_result.is_ok(), "Builder should succeed");
245 auto db1 = std::move(db1_result.value());
246 ASSERT_TRUE(db1 != nullptr, "Original instance should be valid");
247
248 // Move construction
249 auto db2 = std::move(db1);
250 ASSERT_TRUE(db2 != nullptr, "Moved instance should be valid");
251
252 // Move assignment
254 ASSERT_TRUE(db3_result.is_ok(), "Builder should succeed");
255 auto db3 = std::move(db3_result.value());
256 db3 = std::move(db2);
257 ASSERT_TRUE(db3 != nullptr, "Move-assigned instance should be valid");
258
259 TEST_END();
260}
261
262//==============================================================================
263// Test 6: Connection State Management (Without Real DB)
264//==============================================================================
265
267 TEST_START("Connection State Management API");
268
270
271 // Initial state
272 ASSERT_FALSE(db.is_connected(), "Should start disconnected");
273
274 // Note: We can't actually test connect() without a real database
275 // Integration tests will cover actual connections
276
277 // Test that disconnect can be called safely when not connected
278 auto result = db.disconnect();
279 // Should either succeed (no-op) or return a specific error
280 // Either is acceptable behavior
281
282 TEST_END();
283}
284
285//==============================================================================
286// Test 7: Health Check API Availability
287//==============================================================================
288
290 TEST_START("Health Check API Availability");
291
293
294 // Should be able to call health check even when not connected
295 auto health = db.check_health();
296
297 // Health check should return a valid structure
298 // When not connected, status should indicate this
300 health.status == health_status::failed ||
301 health.status == health_status::critical,
302 "Health check should show non-healthy status when disconnected"
303 );
304
305 ASSERT_FALSE(health.is_connected, "Health check should show not connected");
306
307 TEST_END();
308}
309
310//==============================================================================
311// Test 8: Metrics API Availability
312//==============================================================================
313
315 TEST_START("Metrics API Availability");
316
318
319 // Should be able to retrieve metrics even when not connected
320 auto metrics = db.get_metrics();
321
322 // Initial metrics should be zero
323 ASSERT_TRUE(metrics.total_queries == 0, "Initial query count should be 0");
324 ASSERT_TRUE(metrics.successful_queries == 0, "Initial success count should be 0");
325 ASSERT_TRUE(metrics.failed_queries == 0, "Initial failure count should be 0");
326 ASSERT_TRUE(metrics.active_connections == 0, "Initial connections should be 0");
327
328 TEST_END();
329}
330
331//==============================================================================
332// Test 9: Query Result Structure
333//==============================================================================
334
336 TEST_START("Query Result Structure");
337
338 // Create an empty query result
339 query_result result;
340
341 ASSERT_TRUE(result.empty(), "Empty result should report as empty");
342 ASSERT_TRUE(result.size() == 0, "Empty result size should be 0");
343 ASSERT_TRUE(result.affected_rows == 0, "Initial affected rows should be 0");
344
345 // Add some test data
346 result.rows.push_back({{"id", "1"}, {"name", "test"}});
347 result.affected_rows = 1;
348
349 ASSERT_FALSE(result.empty(), "Result with data should not be empty");
350 ASSERT_TRUE(result.size() == 1, "Result size should match row count");
351 ASSERT_TRUE(result.affected_rows == 1, "Affected rows should match");
352
353 // Test row access
354 auto& row = result[0];
355 ASSERT_TRUE(row.at("id") == "1", "Row data should be accessible");
356 ASSERT_TRUE(row.at("name") == "test", "Row data should be correct");
357
358 // Test iteration
359 size_t count = 0;
360 for (const auto& r : result) {
361 count++;
362 (void)r; // Suppress unused warning
363 }
364 ASSERT_TRUE(count == 1, "Should iterate over all rows");
365
366 TEST_END();
367}
368
369//==============================================================================
370// Test 10: Query Parameter Construction
371//==============================================================================
372
374 TEST_START("Query Parameter Construction");
375
376 // Test various parameter types
377 std::vector<query_param> params;
378
379 params.push_back(query_param("string value"));
380 params.push_back(query_param(42));
381 params.push_back(query_param(3.14));
382 params.push_back(query_param(true));
383 params.push_back(query_param(false));
384
385 ASSERT_TRUE(params.size() == 5, "Should accept various parameter types");
386 ASSERT_TRUE(params[0].get_value() == "string value", "String param should work");
387 ASSERT_TRUE(params[1].get_value() == "42", "Int param should convert to string");
388 ASSERT_TRUE(params[3].get_value() == "true", "Bool true should convert correctly");
389 ASSERT_TRUE(params[4].get_value() == "false", "Bool false should convert correctly");
390
391 // Verify non-null parameters
392 ASSERT_FALSE(params[0].is_null(), "String param should not be null");
393 ASSERT_FALSE(params[1].is_null(), "Int param should not be null");
394
395 TEST_END();
396}
397
398//==============================================================================
399// Test 10a: Query Parameter Null Safety
400//==============================================================================
401
403 TEST_START("Query Parameter Null Safety");
404
405 // Test explicit nullptr
406 query_param null_param(nullptr);
407 ASSERT_TRUE(null_param.is_null(), "nullptr should create null param");
408 ASSERT_TRUE(null_param.get_value().empty(), "Null param get_value should return empty string");
409 ASSERT_TRUE(null_param.to_sql_string() == "NULL", "Null param SQL string should be NULL");
410
411 // Test null const char*
412 const char* null_str = nullptr;
413 query_param null_char_param(null_str);
414 ASSERT_TRUE(null_char_param.is_null(), "null const char* should create null param");
415
416 // Test valid const char*
417 const char* valid_str = "test";
418 query_param valid_char_param(valid_str);
419 ASSERT_FALSE(valid_char_param.is_null(), "valid const char* should not be null");
420 ASSERT_TRUE(valid_char_param.get_value() == "test", "valid const char* should preserve value");
421
422 // Test empty string vs null
423 query_param empty_param("");
424 ASSERT_FALSE(empty_param.is_null(), "Empty string should not be null");
425 ASSERT_TRUE(empty_param.get_value().empty(), "Empty string value should be empty");
426 ASSERT_TRUE(empty_param.to_sql_string().empty(), "Empty string SQL should be empty");
427
428 // Test move semantics
429 std::string str = "moved value";
430 query_param moved_param(std::move(str));
431 ASSERT_FALSE(moved_param.is_null(), "Moved string should not be null");
432 ASSERT_TRUE(moved_param.get_value() == "moved value", "Moved value should be preserved");
433
434 // Test additional integer types
435 query_param ll_param(static_cast<long long>(123456789012345LL));
436 ASSERT_FALSE(ll_param.is_null(), "long long should not be null");
437
438 query_param ull_param(static_cast<unsigned long long>(18446744073709551615ULL));
439 ASSERT_FALSE(ull_param.is_null(), "unsigned long long should not be null");
440
441 // Test float
442 query_param float_param(3.14f);
443 ASSERT_FALSE(float_param.is_null(), "float should not be null");
444
445 TEST_END();
446}
447
448//==============================================================================
449// Test 11: Database Metrics Structure
450//==============================================================================
451
453 TEST_START("Database Metrics Structure");
454
455 database_metrics metrics;
456
457 // Test default values
458 ASSERT_TRUE(metrics.total_queries == 0, "Default total_queries is 0");
459 ASSERT_TRUE(metrics.queries_per_second == 0.0, "Default QPS is 0");
460 ASSERT_TRUE(metrics.pool_size == 0, "Default pool_size is 0");
461 ASSERT_TRUE(metrics.transactions_started == 0, "Default transactions is 0");
462
463 // Test assignment
464 metrics.total_queries = 100;
465 metrics.successful_queries = 95;
466 metrics.failed_queries = 5;
467 metrics.queries_per_second = 10.5;
468
469 ASSERT_TRUE(metrics.total_queries == 100, "Can set total queries");
470 ASSERT_TRUE(metrics.successful_queries == 95, "Can set successful queries");
471 ASSERT_TRUE(metrics.queries_per_second == 10.5, "Can set QPS");
472
473 TEST_END();
474}
475
476//==============================================================================
477// Test 12: Health Check Structure
478//==============================================================================
479
481 TEST_START("Health Check Structure");
482
483 health_check health;
484
485 // Test default values
486 ASSERT_TRUE(health.status == health_status::healthy, "Default status is healthy");
487 ASSERT_FALSE(health.is_connected, "Default is not connected");
488 ASSERT_TRUE(health.issues.empty(), "Default has no issues");
489
490 // Test assignment
491 health.status = health_status::degraded;
492 health.is_connected = true;
493 health.logger_healthy = true;
494 health.monitor_healthy = true;
495 health.thread_pool_healthy = true;
496 health.connection_pool_utilization = 0.75;
497 health.issues.push_back("Test issue");
498
499 ASSERT_TRUE(health.status == health_status::degraded, "Can set status");
500 ASSERT_TRUE(health.is_connected, "Can set connected state");
501 ASSERT_TRUE(health.connection_pool_utilization == 0.75, "Can set utilization");
502 ASSERT_TRUE(health.issues.size() == 1, "Can add issues");
503
504 TEST_END();
505}
506
507//==============================================================================
508// Test 13: Thread Safety - Concurrent Health Checks
509//==============================================================================
510
512 TEST_START("Thread Safety - Concurrent Health Checks");
513
515
516 // Launch multiple threads checking health concurrently
517 std::vector<std::thread> threads;
518 std::atomic<size_t> checks_completed{0};
519
520 for (int i = 0; i < 10; ++i) {
521 threads.emplace_back([&db, &checks_completed]() {
522 for (int j = 0; j < 100; ++j) {
523 auto health = db.check_health();
524 (void)health; // Suppress unused warning
525 }
526 checks_completed++;
527 });
528 }
529
530 // Wait for all threads
531 for (auto& t : threads) {
532 t.join();
533 }
534
535 ASSERT_TRUE(checks_completed == 10, "All threads should complete");
536
537 TEST_END();
538}
539
540//==============================================================================
541// Test 14: Thread Safety - Concurrent Metrics Retrieval
542//==============================================================================
543
545 TEST_START("Thread Safety - Concurrent Metrics Retrieval");
546
548
549 std::vector<std::thread> threads;
550 std::atomic<size_t> retrievals_completed{0};
551
552 for (int i = 0; i < 10; ++i) {
553 threads.emplace_back([&db, &retrievals_completed]() {
554 for (int j = 0; j < 100; ++j) {
555 auto metrics = db.get_metrics();
556 (void)metrics; // Suppress unused warning
557 }
558 retrievals_completed++;
559 });
560 }
561
562 for (auto& t : threads) {
563 t.join();
564 }
565
566 ASSERT_TRUE(retrievals_completed == 10, "All threads should complete");
567
568 TEST_END();
569}
570
571//==============================================================================
572// Test 15: Error Handling - Query Without Connection
573//==============================================================================
574
576 TEST_START("Error Handling - Query Without Connection");
577
579
580 // Try to execute a query without connecting
581 auto result = db.execute("SELECT 1");
582
583#if defined(USE_COMMON_SYSTEM)
584 ASSERT_TRUE(result.is_err(), "Query without connection should fail");
585#else
586 ASSERT_TRUE(result.is_error(), "Query without connection should fail");
587#endif
588
589 TEST_END();
590}
591
592//==============================================================================
593// Test 16: Connect with Mock Backend
594//==============================================================================
595
597 TEST_START("Connect with Mock Backend (SQLite stub)");
598
599 register_stub_backend();
600
602 auto result = db.connect(backend_type::sqlite, ":memory:");
603 ASSERT_TRUE(result.is_ok(), "Connect with stub backend should succeed");
604 ASSERT_TRUE(db.is_connected(), "Should be connected after successful connect");
605
606 auto disconnect_result = db.disconnect();
607 ASSERT_TRUE(disconnect_result.is_ok(), "Disconnect should succeed");
608 ASSERT_FALSE(db.is_connected(), "Should not be connected after disconnect");
609
610 TEST_END();
611}
612
613//==============================================================================
614// Test 17: Connect with Typed Backend
615//==============================================================================
616
618 TEST_START("Connect with Typed Backend");
619
620 register_stub_backend();
621
623 auto result = db.connect(backend_type::sqlite, ":memory:");
624 ASSERT_TRUE(result.is_ok(), "Typed connect with stub should succeed");
625 ASSERT_TRUE(db.is_connected(), "Should be connected");
626
627 TEST_END();
628}
629
630//==============================================================================
631// Test 18: Connect Unsupported Backend
632//==============================================================================
633
635 TEST_START("Connect Unsupported Backend");
636
638 auto result = db.connect(backend_type::mongodb, "localhost:27017");
639
640#if defined(USE_COMMON_SYSTEM)
641 ASSERT_TRUE(result.is_err(), "Connect with unsupported backend should fail");
642#else
643 ASSERT_TRUE(result.is_error(), "Connect with unsupported backend should fail");
644#endif
645
646 ASSERT_FALSE(db.is_connected(), "Should not be connected on failure");
647
648 TEST_END();
649}
650
651//==============================================================================
652// Test 19: Execute Success Path
653//==============================================================================
654
656 TEST_START("Execute Success Path");
657
658 register_stub_backend();
659
661 auto conn = db.connect(backend_type::sqlite, ":memory:");
662 ASSERT_TRUE(conn.is_ok(), "Connect should succeed");
663
664 auto result = db.execute("SELECT * FROM users WHERE id = 1");
665 ASSERT_TRUE(result.is_ok(), "Execute on connected db should succeed");
666
667 auto& qr = result.value();
668 ASSERT_TRUE(qr.size() == 1, "Should return one row from stub");
669 ASSERT_TRUE(qr[0].at("name") == "test_user", "Row should contain stub data");
670
671 TEST_END();
672}
673
674//==============================================================================
675// Test 20: Select Success Path
676//==============================================================================
677
679 TEST_START("Select Success Path");
680
681 register_stub_backend();
682
684 db.connect(backend_type::sqlite, ":memory:");
685
686 auto result = db.select("SELECT id, name FROM users");
687 ASSERT_TRUE(result.is_ok(), "Select should succeed");
688
689 auto& qr = result.value();
690 ASSERT_FALSE(qr.empty(), "Select should return rows");
691 ASSERT_TRUE(qr[0].count("id") > 0, "Row should have 'id' column");
692
693 TEST_END();
694}
695
696//==============================================================================
697// Test 21: Select Failure Path (Not Connected)
698//==============================================================================
699
701 TEST_START("Select Failure Path - Not Connected");
702
704 auto result = db.select("SELECT 1");
705
706#if defined(USE_COMMON_SYSTEM)
707 ASSERT_TRUE(result.is_err(), "Select without connection should fail");
708#else
709 ASSERT_TRUE(result.is_error(), "Select without connection should fail");
710#endif
711
712 TEST_END();
713}
714
715//==============================================================================
716// Test 22: Insert Success Path
717//==============================================================================
718
720 TEST_START("Insert Success Path");
721
722 register_stub_backend();
723
725 db.connect(backend_type::sqlite, ":memory:");
726
727 auto result = db.insert("INSERT INTO users (name) VALUES ('Alice')");
728 ASSERT_TRUE(result.is_ok(), "Insert should succeed");
729
730 auto affected = result.value();
731 ASSERT_TRUE(affected > 0, "Insert should affect at least one row");
732
733 TEST_END();
734}
735
736//==============================================================================
737// Test 23: Insert Failure Path (Not Connected)
738//==============================================================================
739
741 TEST_START("Insert Failure Path - Not Connected");
742
744 auto result = db.insert("INSERT INTO users (name) VALUES ('Bob')");
745
746#if defined(USE_COMMON_SYSTEM)
747 ASSERT_TRUE(result.is_err(), "Insert without connection should fail");
748#else
749 ASSERT_TRUE(result.is_error(), "Insert without connection should fail");
750#endif
751
752 TEST_END();
753}
754
755//==============================================================================
756// Test 24: Update Success Path
757//==============================================================================
758
760 TEST_START("Update Success Path");
761
762 register_stub_backend();
763
765 db.connect(backend_type::sqlite, ":memory:");
766
767 auto result = db.update("UPDATE users SET name = 'Bob' WHERE id = 1");
768 ASSERT_TRUE(result.is_ok(), "Update should succeed");
769
770 auto affected = result.value();
771 ASSERT_TRUE(affected > 0, "Update should affect at least one row");
772
773 TEST_END();
774}
775
776//==============================================================================
777// Test 25: Update Failure Path (Not Connected)
778//==============================================================================
779
781 TEST_START("Update Failure Path - Not Connected");
782
784 auto result = db.update("UPDATE users SET name = 'Bob'");
785
786#if defined(USE_COMMON_SYSTEM)
787 ASSERT_TRUE(result.is_err(), "Update without connection should fail");
788#else
789 ASSERT_TRUE(result.is_error(), "Update without connection should fail");
790#endif
791
792 TEST_END();
793}
794
795//==============================================================================
796// Test 26: Remove Success Path
797//==============================================================================
798
800 TEST_START("Remove Success Path");
801
802 register_stub_backend();
803
805 db.connect(backend_type::sqlite, ":memory:");
806
807 auto result = db.remove("DELETE FROM users WHERE id = 1");
808 ASSERT_TRUE(result.is_ok(), "Remove should succeed");
809
810 auto affected = result.value();
811 ASSERT_TRUE(affected > 0, "Remove should affect at least one row");
812
813 TEST_END();
814}
815
816//==============================================================================
817// Test 27: Remove Failure Path (Not Connected)
818//==============================================================================
819
821 TEST_START("Remove Failure Path - Not Connected");
822
824 auto result = db.remove("DELETE FROM users WHERE id = 1");
825
826#if defined(USE_COMMON_SYSTEM)
827 ASSERT_TRUE(result.is_err(), "Remove without connection should fail");
828#else
829 ASSERT_TRUE(result.is_error(), "Remove without connection should fail");
830#endif
831
832 TEST_END();
833}
834
835//==============================================================================
836// Test 28: Begin Transaction Success Path
837//==============================================================================
838
840 TEST_START("Begin Transaction Success Path");
841
842 register_stub_backend();
843
845 db.connect(backend_type::sqlite, ":memory:");
846
847 auto tx_result = db.begin_transaction();
848 ASSERT_TRUE(tx_result.is_ok(), "Begin transaction should succeed when connected");
849
850 auto& tx = tx_result.value();
851 ASSERT_TRUE(tx != nullptr, "Transaction pointer should be valid");
852 ASSERT_TRUE(tx->is_active(), "Transaction should be active after begin");
853
854 TEST_END();
855}
856
857//==============================================================================
858// Test 29: Begin Transaction Failure Path (Not Connected)
859//==============================================================================
860
862 TEST_START("Begin Transaction Failure Path - Not Connected");
863
865 auto tx_result = db.begin_transaction();
866
867#if defined(USE_COMMON_SYSTEM)
868 ASSERT_TRUE(tx_result.is_err(), "Begin transaction without connection should fail");
869#else
870 ASSERT_TRUE(tx_result.is_error(), "Begin transaction without connection should fail");
871#endif
872
873 TEST_END();
874}
875
876//==============================================================================
877// Test 30: Transaction Execute
878//==============================================================================
879
881 TEST_START("Transaction Execute");
882
883 register_stub_backend();
884
886 db.connect(backend_type::sqlite, ":memory:");
887
888 auto tx_result = db.begin_transaction();
889 ASSERT_TRUE(tx_result.is_ok(), "Begin transaction should succeed");
890
891 auto& tx = tx_result.value();
892 auto exec_result = tx->execute("INSERT INTO users (name) VALUES ('Alice')");
893 ASSERT_TRUE(exec_result.is_ok(), "Transaction execute should succeed");
894
895 TEST_END();
896}
897
898//==============================================================================
899// Test 31: Transaction Commit
900//==============================================================================
901
903 TEST_START("Transaction Commit");
904
905 register_stub_backend();
906
908 db.connect(backend_type::sqlite, ":memory:");
909
910 auto tx_result = db.begin_transaction();
911 ASSERT_TRUE(tx_result.is_ok(), "Begin transaction should succeed");
912
913 auto& tx = tx_result.value();
914 ASSERT_TRUE(tx->is_active(), "Transaction should be active before commit");
915
916 auto commit_result = tx->commit();
917 ASSERT_TRUE(commit_result.is_ok(), "Commit should succeed");
918 ASSERT_FALSE(tx->is_active(), "Transaction should not be active after commit");
919
920 TEST_END();
921}
922
923//==============================================================================
924// Test 32: Transaction Rollback
925//==============================================================================
926
928 TEST_START("Transaction Rollback");
929
930 register_stub_backend();
931
933 db.connect(backend_type::sqlite, ":memory:");
934
935 auto tx_result = db.begin_transaction();
936 ASSERT_TRUE(tx_result.is_ok(), "Begin transaction should succeed");
937
938 auto& tx = tx_result.value();
939 ASSERT_TRUE(tx->is_active(), "Transaction should be active before rollback");
940
941 auto rollback_result = tx->rollback();
942 ASSERT_TRUE(rollback_result.is_ok(), "Rollback should succeed");
943 ASSERT_FALSE(tx->is_active(), "Transaction should not be active after rollback");
944
945 TEST_END();
946}
947
948//==============================================================================
949// Test 33: Transaction is_active State Tracking
950//==============================================================================
951
953 TEST_START("Transaction is_active State Tracking");
954
955 register_stub_backend();
956
958 db.connect(backend_type::sqlite, ":memory:");
959
960 auto tx_result = db.begin_transaction();
961 ASSERT_TRUE(tx_result.is_ok(), "Begin transaction should succeed");
962
963 auto& tx = tx_result.value();
964
965 // Active after begin
966 ASSERT_TRUE(tx->is_active(), "Should be active after begin");
967
968 // Execute should not change active state
969 tx->execute("SELECT 1");
970 ASSERT_TRUE(tx->is_active(), "Should remain active after execute");
971
972 // Inactive after commit
973 tx->commit();
974 ASSERT_FALSE(tx->is_active(), "Should be inactive after commit");
975
976 // Double commit should fail
977 auto double_commit = tx->commit();
978#if defined(USE_COMMON_SYSTEM)
979 ASSERT_TRUE(double_commit.is_err(), "Double commit should fail");
980#else
981 ASSERT_TRUE(double_commit.is_error(), "Double commit should fail");
982#endif
983
984 TEST_END();
985}
986
987//==============================================================================
988// Test 34: Transaction RAII Cleanup (Rollback on Scope Exit)
989//==============================================================================
990
992 TEST_START("Transaction RAII Cleanup");
993
994 register_stub_backend();
995
997 db.connect(backend_type::sqlite, ":memory:");
998
999 {
1000 auto tx_result = db.begin_transaction();
1001 ASSERT_TRUE(tx_result.is_ok(), "Begin transaction should succeed");
1002
1003 auto& tx = tx_result.value();
1004 tx->execute("INSERT INTO users (name) VALUES ('Alice')");
1005
1006 // Do not commit - transaction goes out of scope
1007 // Destructor should auto-rollback
1008 }
1009
1010 // If we reach here without crash, RAII cleanup worked
1011 ASSERT_TRUE(true, "Transaction destructor should auto-rollback without crash");
1012
1013 TEST_END();
1014}
1015
1016//==============================================================================
1017// Test 35: Execute Transaction (Batch)
1018//==============================================================================
1019
1021 TEST_START("Execute Transaction - Batch Success");
1022
1023 register_stub_backend();
1024
1026 db.connect(backend_type::sqlite, ":memory:");
1027
1028 std::vector<std::string> queries = {
1029 "INSERT INTO users (name) VALUES ('Alice')",
1030 "INSERT INTO users (name) VALUES ('Bob')",
1031 "UPDATE users SET name = 'Charlie' WHERE id = 1"
1032 };
1033
1034 auto result = db.execute_transaction(queries);
1035 ASSERT_TRUE(result.is_ok(), "Batch transaction should succeed");
1036
1037 TEST_END();
1038}
1039
1040//==============================================================================
1041// Test 36: Execute Transaction Failure (Not Connected)
1042//==============================================================================
1043
1045 TEST_START("Execute Transaction - Not Connected");
1046
1048
1049 std::vector<std::string> queries = {"INSERT INTO users (name) VALUES ('Alice')"};
1050 auto result = db.execute_transaction(queries);
1051
1052#if defined(USE_COMMON_SYSTEM)
1053 ASSERT_TRUE(result.is_err(), "Batch transaction without connection should fail");
1054#else
1055 ASSERT_TRUE(result.is_error(), "Batch transaction without connection should fail");
1056#endif
1057
1058 TEST_END();
1059}
1060
1061//==============================================================================
1062// Test 37: get_config Accessor
1063//==============================================================================
1064
1066 TEST_START("get_config Accessor");
1067
1068 unified_db_config config;
1069 config.database.type = backend_type::sqlite;
1071 config.connection_pool.max_connections = 15;
1072
1073 unified_database_system db(config);
1074
1075 const auto& retrieved = db.get_config();
1076 ASSERT_TRUE(retrieved.database.type == backend_type::sqlite,
1077 "Config should reflect configured backend type");
1078 ASSERT_TRUE(retrieved.connection_pool.min_connections == 3,
1079 "Config should reflect configured min connections");
1080 ASSERT_TRUE(retrieved.connection_pool.max_connections == 15,
1081 "Config should reflect configured max connections");
1082
1083 TEST_END();
1084}
1085
1086//==============================================================================
1087// Test 38: get_backend_type Accessor
1088//==============================================================================
1089
1091 TEST_START("get_backend_type Accessor");
1092
1094 // Default backend type should be postgres (from impl constructor)
1096 ASSERT_TRUE(bt == backend_type::postgres,
1097 "Default backend type should be postgres");
1098
1099 TEST_END();
1100}
1101
1102//==============================================================================
1103// Test 39: get_pool_stats Accessor
1104//==============================================================================
1105
1107 TEST_START("get_pool_stats Accessor");
1108
1110
1111 // When not connected
1112 auto stats = db.get_pool_stats();
1113 ASSERT_TRUE(stats.total_connections == 0,
1114 "Not connected: total connections should be 0");
1115 ASSERT_TRUE(stats.active_connections == 0,
1116 "Not connected: active connections should be 0");
1117
1118 // When connected
1119 register_stub_backend();
1120 db.connect(backend_type::sqlite, ":memory:");
1121
1122 auto connected_stats = db.get_pool_stats();
1123 ASSERT_TRUE(connected_stats.total_connections == 1,
1124 "Connected: total connections should be 1");
1125 ASSERT_TRUE(connected_stats.active_connections == 1,
1126 "Connected: active connections should be 1");
1127
1128 TEST_END();
1129}
1130
1131//==============================================================================
1132// Test 40: reset_metrics
1133//==============================================================================
1134
1136 TEST_START("reset_metrics");
1137
1138 register_stub_backend();
1139
1141 db.connect(backend_type::sqlite, ":memory:");
1142
1143 // Execute some queries to accumulate metrics
1144 db.execute("SELECT 1");
1145 db.execute("SELECT 2");
1146
1147 auto metrics_before = db.get_metrics();
1148 ASSERT_TRUE(metrics_before.total_queries >= 2,
1149 "Should have at least 2 queries recorded");
1150
1151 // Reset
1152 db.reset_metrics();
1153
1154 auto metrics_after = db.get_metrics();
1155 ASSERT_TRUE(metrics_after.total_queries == 0,
1156 "After reset, total queries should be 0");
1157 ASSERT_TRUE(metrics_after.successful_queries == 0,
1158 "After reset, successful queries should be 0");
1159 ASSERT_TRUE(metrics_after.failed_queries == 0,
1160 "After reset, failed queries should be 0");
1161
1162 TEST_END();
1163}
1164
1165//==============================================================================
1166// Test 41: create_query_builder
1167//==============================================================================
1168
1170 TEST_START("create_query_builder");
1171
1173 auto builder = db.create_query_builder();
1174
1175 // Just verify it returns a valid query_builder without crashing
1176 ASSERT_TRUE(true, "create_query_builder should return without error");
1177
1178 TEST_END();
1179}
1180
1181//==============================================================================
1182// Test 42: Metrics Update on Successful Query
1183//==============================================================================
1184
1186 TEST_START("Metrics Update on Successful Query");
1187
1188 register_stub_backend();
1189
1191 db.connect(backend_type::sqlite, ":memory:");
1192 db.reset_metrics();
1193
1194 // Execute queries
1195 db.execute("SELECT 1");
1196 db.execute("SELECT 2");
1197 db.execute("SELECT 3");
1198
1199 auto metrics = db.get_metrics();
1200 ASSERT_TRUE(metrics.total_queries == 3, "Should record 3 queries");
1201 ASSERT_TRUE(metrics.successful_queries == 3, "All 3 should be successful");
1202 ASSERT_TRUE(metrics.failed_queries == 0, "No failures expected");
1203
1204 TEST_END();
1205}
1206
1207//==============================================================================
1208// Test 43: Transaction Metrics
1209//==============================================================================
1210
1212 TEST_START("Transaction Metrics");
1213
1214 register_stub_backend();
1215
1217 db.connect(backend_type::sqlite, ":memory:");
1218 db.reset_metrics();
1219
1220 // Start and commit a transaction
1221 {
1222 auto tx_result = db.begin_transaction();
1223 ASSERT_TRUE(tx_result.is_ok(), "Begin should succeed");
1224 auto& tx = tx_result.value();
1225 tx->commit();
1226 }
1227
1228 auto metrics = db.get_metrics();
1229 ASSERT_TRUE(metrics.transactions_started >= 1,
1230 "Should record at least 1 transaction started");
1231
1232 TEST_END();
1233}
1234
1235//==============================================================================
1236// Main Test Runner
1237//==============================================================================
1238
1239int main() {
1240 std::cout << "\n";
1241 std::cout << "========================================\n";
1242 std::cout << "Phase 6: Unified Database System Tests\n";
1243 std::cout << "========================================\n";
1244
1245 // Run all tests
1246
1247 // Phase 6 original tests (builder, config, state, structures)
1264
1265 // CRUD and connection tests (with stub backend)
1278
1279 // Transaction tests
1289
1290 // Accessor tests
1296
1297 // Metrics verification tests
1300
1301 // Cleanup stub backend registration
1302 unregister_stub_backend();
1303
1304 // Print summary
1305 std::cout << "\n";
1306 std::cout << "========================================\n";
1307 std::cout << "Test Summary\n";
1308 std::cout << "========================================\n";
1309 std::cout << "Passed: " << tests_passed << "\n";
1310 std::cout << "Failed: " << tests_failed << "\n";
1311
1312 if (tests_failed == 0) {
1313 std::cout << "\nāœ… All tests passed!\n\n";
1314 return 0;
1315 } else {
1316 std::cout << "\nāŒ Some tests failed!\n\n";
1317 return 1;
1318 }
1319}
Registry for database backend plugins.
builder & enable_monitoring(bool enable=true)
Enable monitoring and metrics collection.
builder & set_connection_string(const std::string &conn_str)
Set the connection string.
builder & set_pool_size(size_t min_size, size_t max_size)
Set connection pool size.
builder & enable_async(size_t worker_threads=4)
Enable async operations.
builder & set_slow_query_threshold(std::chrono::milliseconds threshold)
Set slow query threshold.
kcenon::common::Result< std::unique_ptr< unified_database_system > > build()
Build and return the configured database system.
builder & enable_logging(db_log_level level, const std::string &log_dir="./logs")
Enable logging.
builder & set_backend(backend_type type)
Set the database backend type.
const unified_db_config & get_config() const
Get current configuration.
kcenon::common::Result< std::unique_ptr< transaction > > begin_transaction()
backend_type get_backend_type() const
Get database backend type.
database_metrics get_metrics() const
Get current performance metrics.
kcenon::common::Result< size_t > update(const std::string &query, const std::vector< query_param > &params={})
Execute an UPDATE query.
kcenon::common::Result< query_result > execute(const std::string &query, const std::vector< query_param > &params={})
health_check check_health() const
Perform health check.
kcenon::common::VoidResult execute_transaction(const std::vector< std::string > &queries)
Execute multiple queries in a transaction.
kcenon::common::VoidResult connect(const std::string &connection_string)
Connect to database.
static builder create_builder()
Create a builder for custom configuration.
kcenon::common::Result< size_t > insert(const std::string &query, const std::vector< query_param > &params={})
Execute an INSERT query.
kcenon::common::Result< query_result > select(const std::string &query, const std::vector< query_param > &params={})
Execute a SELECT query.
kcenon::common::Result< size_t > remove(const std::string &query, const std::vector< query_param > &params={})
Execute a DELETE query.
kcenon::common::VoidResult disconnect()
Disconnect from database.
bool is_connected() const
Check if connected to database.
Abstract interface for database backends.
backend_type
Database backend type enumeration.
backend_type type
Database backend type.
bool enable_query_logging
Log all SQL queries executed.
bool enable_metrics
Enable metrics collection.
std::size_t min_connections
Minimum number of connections to maintain.
std::size_t max_connections
Maximum number of connections allowed.
std::string to_sql_string() const
Get the value for SQL generation.
bool is_null() const noexcept
Check if this parameter represents a NULL value.
const std::string & get_value() const noexcept
Get the string value (returns empty string for null)
db_logger_config logger
Logger configuration.
pool_config connection_pool
Connection pool configuration.
db_monitoring_config monitoring
Monitoring configuration.
database_config database
Database connection configuration.
bool test_remove_success()
bool test_select_success()
bool test_connect_unsupported_backend()
bool test_execute_success()
bool test_begin_transaction_failure()
bool test_transaction_commit()
bool test_transaction_metrics()
bool test_move_semantics()
bool test_query_parameters()
bool test_transaction_is_active()
bool test_select_failure_not_connected()
bool test_thread_safety_metrics()
bool test_begin_transaction_success()
bool test_health_check_structure()
#define ASSERT_TRUE(condition, message)
bool test_update_success()
bool test_insert_failure_not_connected()
bool test_get_backend_type()
bool test_connection_state_api()
bool test_create_query_builder()
bool test_transaction_rollback()
bool test_thread_safety_health_checks()
bool test_zero_config_construction()
bool test_transaction_execute()
#define TEST_END()
#define ASSERT_FALSE(condition, message)
bool test_builder_custom()
bool test_metrics_update_on_query()
bool test_insert_success()
bool test_health_check_api()
bool test_builder_default()
bool test_remove_failure_not_connected()
static int tests_passed
bool test_query_param_null_safety()
static int tests_failed
bool test_connect_typed_backend()
bool test_error_handling_no_connection()
bool test_execute_transaction_success()
bool test_metrics_structure()
bool test_query_result_structure()
bool test_execute_transaction_failure()
bool test_update_failure_not_connected()
bool test_transaction_raii_cleanup()
bool test_config_construction()
#define TEST_START(name)
bool test_connect_mock_backend()
Zero-configuration database system with integrated adapters (Phase 6)