Database System 0.1.0
Advanced C++20 Database System with Multi-Backend Support
Loading...
Searching...
No Matches
backend_contract_test.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
19#include <atomic>
20#include <gtest/gtest.h>
21#include <memory>
22#include <string>
23#include <thread>
24#include <vector>
25
27#include "mocks/mock_backend.h"
28
29using namespace database;
30using namespace database::core;
31using namespace database::testing;
32
33// =============================================================================
34// Test Fixture
35// =============================================================================
36
37class BackendContractTest : public ::testing::Test {
38protected:
41
42 void SetUp() override
43 {
44 test_config_.host = "localhost";
45 test_config_.port = 5432;
46 test_config_.database = "test_db";
47 test_config_.username = "user";
48 test_config_.password = "pass";
49
50 // Initialize mock for most tests
51 backend_.set_database_type(database_types::postgres);
52 }
53};
54
55// =============================================================================
56// Initialization Contract Tests
57// =============================================================================
58
59TEST_F(BackendContractTest, InitializeSucceeds)
60{
61 auto result = backend_.initialize(test_config_);
62 EXPECT_TRUE(result.is_ok());
63 EXPECT_TRUE(backend_.is_initialized());
64}
65
66TEST_F(BackendContractTest, InitializeStoresConnectionString)
67{
68 backend_.initialize(test_config_);
69 auto conn_str = backend_.get_connection_string();
70 EXPECT_FALSE(conn_str.empty());
71 EXPECT_NE(conn_str.find("localhost"), std::string::npos);
72 EXPECT_NE(conn_str.find("test_db"), std::string::npos);
73}
74
75TEST_F(BackendContractTest, InitializeFailureSimulation)
76{
77 backend_.simulate_initialization_failure("Custom error");
78 auto result = backend_.initialize(test_config_);
79 EXPECT_FALSE(result.is_ok());
80 EXPECT_FALSE(backend_.is_initialized());
81}
82
83TEST_F(BackendContractTest, InitializeFailureDefaultMessage)
84{
85 backend_.simulate_initialization_failure();
86 auto result = backend_.initialize(test_config_);
87 EXPECT_FALSE(result.is_ok());
88 EXPECT_EQ(backend_.last_error(), "Mock initialization failed");
89}
90
91TEST_F(BackendContractTest, ShutdownClearsState)
92{
93 backend_.initialize(test_config_);
94 EXPECT_TRUE(backend_.is_initialized());
95
96 auto result = backend_.shutdown();
97 EXPECT_TRUE(result.is_ok());
98 EXPECT_FALSE(backend_.is_initialized());
99}
100
101TEST_F(BackendContractTest, SimulateShutdown)
102{
103 backend_.initialize(test_config_);
104 backend_.simulate_shutdown();
105 EXPECT_FALSE(backend_.is_initialized());
106}
107
108// =============================================================================
109// Type Contract Tests
110// =============================================================================
111
112TEST_F(BackendContractTest, TypeReturnsConfiguredType)
113{
114 backend_.set_database_type(database_types::sqlite);
115 EXPECT_EQ(backend_.type(), database_types::sqlite);
116}
117
118TEST_F(BackendContractTest, DefaultTypeIsNone)
119{
120 mock_backend fresh_backend;
121 EXPECT_EQ(fresh_backend.type(), database_types::none);
122}
123
124// =============================================================================
125// Execute Query Contract Tests (DML operations)
126// =============================================================================
127
128TEST_F(BackendContractTest, ExecuteInsertQuerySucceeds)
129{
130 auto result = backend_.execute_query("INSERT INTO users (name) VALUES ('John')");
131 ASSERT_TRUE(result.is_ok());
132}
133
134TEST_F(BackendContractTest, ExecuteInsertQueryWithError)
135{
136 backend_.expect_query("INSERT INTO users (name) VALUES ('John')")
137 .will_fail("Duplicate key violation");
138
139 auto result = backend_.execute_query("INSERT INTO users (name) VALUES ('John')");
140 EXPECT_FALSE(result.is_ok());
141}
142
143TEST_F(BackendContractTest, ExecuteUpdateQuerySucceeds)
144{
145 auto result = backend_.execute_query("UPDATE users SET name = 'Jane' WHERE id = 1");
146 ASSERT_TRUE(result.is_ok());
147}
148
149TEST_F(BackendContractTest, ExecuteDeleteQuerySucceeds)
150{
151 auto result = backend_.execute_query("DELETE FROM users WHERE id = 1");
152 ASSERT_TRUE(result.is_ok());
153}
154
155// =============================================================================
156// Select Query Contract Tests
157// =============================================================================
158
159TEST_F(BackendContractTest, SelectQueryReturnsResults)
160{
161 database_result expected_data = {
162 {{"id", int64_t(1)}, {"name", std::string("Alice")}},
163 {{"id", int64_t(2)}, {"name", std::string("Bob")}}
164 };
165
166 backend_.expect_query("SELECT * FROM users")
167 .will_return(expected_data);
168
169 auto result = backend_.select_query("SELECT * FROM users");
170 ASSERT_TRUE(result.is_ok());
171 EXPECT_EQ(result.value().size(), 2u);
172
173 auto& row0 = result.value()[0];
174 EXPECT_EQ(std::get<int64_t>(row0.at("id")), 1);
175 EXPECT_EQ(std::get<std::string>(row0.at("name")), "Alice");
176}
177
178TEST_F(BackendContractTest, SelectQueryReturnsEmptyResult)
179{
180 backend_.expect_query("SELECT * FROM empty_table")
181 .will_return(database_result{});
182
183 auto result = backend_.select_query("SELECT * FROM empty_table");
184 ASSERT_TRUE(result.is_ok());
185 EXPECT_TRUE(result.value().empty());
186}
187
188TEST_F(BackendContractTest, SelectQueryWithDefaultResult)
189{
190 database_result default_data = {
191 {{"count", int64_t(42)}}
192 };
193 backend_.set_default_select_result(default_data);
194
195 auto result = backend_.select_query("SELECT COUNT(*) FROM anything");
196 ASSERT_TRUE(result.is_ok());
197 EXPECT_EQ(result.value().size(), 1u);
198 EXPECT_EQ(std::get<int64_t>(result.value()[0].at("count")), 42);
199}
200
201// =============================================================================
202// Execute Query Contract Tests
203// =============================================================================
204
205TEST_F(BackendContractTest, ExecuteQuerySucceeds)
206{
207 auto result = backend_.execute_query("CREATE TABLE test (id INTEGER)");
208 EXPECT_TRUE(result.is_ok());
209}
210
211TEST_F(BackendContractTest, ExecuteQueryWithExpectedFailure)
212{
213 backend_.expect_query("DROP TABLE nonexistent")
214 .will_fail("Table does not exist");
215
216 auto result = backend_.execute_query("DROP TABLE nonexistent");
217 EXPECT_FALSE(result.is_ok());
218}
219
220// =============================================================================
221// Transaction State Machine Tests
222// =============================================================================
223
224TEST_F(BackendContractTest, TransactionInitiallyInactive)
225{
226 EXPECT_FALSE(backend_.in_transaction());
227}
228
229TEST_F(BackendContractTest, BeginTransactionActivates)
230{
231 EXPECT_TRUE(backend_.begin_transaction().is_ok());
232 EXPECT_TRUE(backend_.in_transaction());
233}
234
235TEST_F(BackendContractTest, CommitTransactionDeactivates)
236{
237 backend_.begin_transaction();
238 EXPECT_TRUE(backend_.commit_transaction().is_ok());
239 EXPECT_FALSE(backend_.in_transaction());
240}
241
242TEST_F(BackendContractTest, RollbackTransactionDeactivates)
243{
244 backend_.begin_transaction();
245 EXPECT_TRUE(backend_.rollback_transaction().is_ok());
246 EXPECT_FALSE(backend_.in_transaction());
247}
248
249TEST_F(BackendContractTest, NestedBeginTransactionFails)
250{
251 backend_.begin_transaction();
252 auto result = backend_.begin_transaction();
253 EXPECT_FALSE(result.is_ok());
254 // Should still be in transaction
255 EXPECT_TRUE(backend_.in_transaction());
256}
257
258TEST_F(BackendContractTest, CommitWithoutTransactionFails)
259{
260 auto result = backend_.commit_transaction();
261 EXPECT_FALSE(result.is_ok());
262}
263
264TEST_F(BackendContractTest, RollbackWithoutTransactionFails)
265{
266 auto result = backend_.rollback_transaction();
267 EXPECT_FALSE(result.is_ok());
268}
269
270TEST_F(BackendContractTest, TransactionCycleBeginCommitBeginRollback)
271{
272 // First cycle: begin -> commit
273 EXPECT_TRUE(backend_.begin_transaction().is_ok());
274 EXPECT_TRUE(backend_.in_transaction());
275 EXPECT_TRUE(backend_.commit_transaction().is_ok());
276 EXPECT_FALSE(backend_.in_transaction());
277
278 // Second cycle: begin -> rollback
279 EXPECT_TRUE(backend_.begin_transaction().is_ok());
280 EXPECT_TRUE(backend_.in_transaction());
281 EXPECT_TRUE(backend_.rollback_transaction().is_ok());
282 EXPECT_FALSE(backend_.in_transaction());
283}
284
285TEST_F(BackendContractTest, ShutdownClearsTransactionState)
286{
287 backend_.initialize(test_config_);
288 backend_.begin_transaction();
289 EXPECT_TRUE(backend_.in_transaction());
290
291 backend_.shutdown();
292 EXPECT_FALSE(backend_.in_transaction());
293}
294
295// =============================================================================
296// Pattern Matching Tests
297// =============================================================================
298
299TEST_F(BackendContractTest, PatternMatchingWithRegex)
300{
301 database_result expected = {
302 {{"id", int64_t(1)}, {"name", std::string("Test")}}
303 };
304
305 backend_.expect_pattern("SELECT.*FROM users.*")
306 .will_return(expected);
307
308 auto result = backend_.select_query("SELECT id, name FROM users WHERE id = 1");
309 ASSERT_TRUE(result.is_ok());
310 EXPECT_EQ(result.value().size(), 1u);
311}
312
313TEST_F(BackendContractTest, AnyMatcherMatchesAll)
314{
315 auto r1 = backend_.execute_query("INSERT INTO a VALUES (1)");
316 auto r2 = backend_.execute_query("UPDATE b SET x = 1");
317 auto r3 = backend_.execute_query("DELETE FROM c WHERE id = 1");
318
319 EXPECT_TRUE(r1.is_ok());
320 EXPECT_TRUE(r2.is_ok());
321 EXPECT_TRUE(r3.is_ok());
322}
323
324// =============================================================================
325// Query Recording Tests
326// =============================================================================
327
328TEST_F(BackendContractTest, RecordsExecutedQueries)
329{
330 backend_.execute_query("INSERT INTO t1 VALUES (1)");
331 backend_.select_query("SELECT * FROM t1");
332 backend_.execute_query("UPDATE t1 SET x = 2");
333
334 auto queries = backend_.get_executed_queries();
335 EXPECT_EQ(queries.size(), 3u);
336 EXPECT_EQ(queries[0], "INSERT INTO t1 VALUES (1)");
337 EXPECT_EQ(queries[1], "SELECT * FROM t1");
338 EXPECT_EQ(queries[2], "UPDATE t1 SET x = 2");
339}
340
341TEST_F(BackendContractTest, GetQueryCountTotal)
342{
343 backend_.execute_query("q1");
344 backend_.execute_query("q2");
345 backend_.select_query("q3");
346
347 EXPECT_EQ(backend_.get_query_count(), 3u);
348}
349
350TEST_F(BackendContractTest, GetQueryCountByPattern)
351{
352 backend_.execute_query("INSERT INTO users VALUES (1)");
353 backend_.execute_query("INSERT INTO orders VALUES (1)");
354 backend_.select_query("SELECT * FROM users");
355
356 EXPECT_EQ(backend_.get_query_count("INSERT"), 2u);
357 EXPECT_EQ(backend_.get_query_count("users"), 2u);
358 EXPECT_EQ(backend_.get_query_count("orders"), 1u);
359}
360
362{
363 backend_.execute_query("q1");
364 backend_.clear_history();
365 EXPECT_EQ(backend_.get_query_count(), 0u);
366 EXPECT_TRUE(backend_.get_executed_queries().empty());
367}
368
369TEST_F(BackendContractTest, ClearExpectations)
370{
371 backend_.expect_query("q1").will_return_rows(10);
372 backend_.clear_expectations();
373
374 // After clearing, should use default behavior (success)
375 auto result = backend_.execute_query("q1");
376 ASSERT_TRUE(result.is_ok());
377}
378
379TEST_F(BackendContractTest, ResetClearsEverything)
380{
381 backend_.initialize(test_config_);
382 backend_.begin_transaction();
383 backend_.execute_query("q1");
384 backend_.expect_query("q2").will_return_rows(5);
385
386 backend_.reset();
387
388 EXPECT_FALSE(backend_.is_initialized());
389 EXPECT_FALSE(backend_.in_transaction());
390 EXPECT_EQ(backend_.get_query_count(), 0u);
391 EXPECT_TRUE(backend_.get_connection_string().empty());
392}
393
394// =============================================================================
395// Expectation Verification Tests
396// =============================================================================
397
398TEST_F(BackendContractTest, VerifyAllExpectationsWhenAllMatched)
399{
400 backend_.expect_query("q1").will_return_rows(1).once();
401 backend_.execute_query("q1");
402 EXPECT_TRUE(backend_.verify_all_expectations());
403}
404
405TEST_F(BackendContractTest, VerifyExpectationsFailsWhenUnmatched)
406{
407 backend_.expect_query("q1").will_return_rows(1).once();
408 // Don't execute q1
409 EXPECT_FALSE(backend_.verify_all_expectations());
410}
411
412// =============================================================================
413// Connection Info Tests
414// =============================================================================
415
416TEST_F(BackendContractTest, ConnectionInfoReturnsMap)
417{
418 backend_.initialize(test_config_);
419 auto info = backend_.connection_info();
420
421 EXPECT_FALSE(info.empty());
422 EXPECT_EQ(info.at("type"), "mock");
423 EXPECT_EQ(info.at("initialized"), "true");
424}
425
426TEST_F(BackendContractTest, LastErrorInitiallyEmpty)
427{
428 EXPECT_TRUE(backend_.last_error().empty());
429}
430
431// =============================================================================
432// Mock Builder Preset Tests
433// =============================================================================
434
435TEST_F(BackendContractTest, EmptyDatabasePreset)
436{
438 auto result = db.select_query("SELECT * FROM anything");
439 ASSERT_TRUE(result.is_ok());
440 EXPECT_TRUE(result.value().empty());
441}
442
444{
445 database_result test_data = {
446 {{"id", int64_t(1)}, {"name", std::string("Test")}}
447 };
448 auto db = mock_backend_builder::with_data("users", test_data);
449
450 auto result = db.select_query("SELECT * FROM users WHERE id = 1");
451 ASSERT_TRUE(result.is_ok());
452 EXPECT_EQ(result.value().size(), 1u);
453}
454
455TEST_F(BackendContractTest, FailingDatabasePreset)
456{
457 auto db = mock_backend_builder::failing_database("DB is down");
458 auto result = db.select_query("SELECT * FROM users");
459 EXPECT_FALSE(result.is_ok());
460}
461
462// =============================================================================
463// Data Type Tests (database_value variants)
464// =============================================================================
465
466TEST_F(BackendContractTest, AllDatabaseValueTypesInResult)
467{
468 database_result data = {{
469 {"string_col", std::string("hello")},
470 {"int_col", int64_t(42)},
471 {"double_col", double(3.14)},
472 {"bool_col", true},
473 {"null_col", nullptr}
474 }};
475
476 backend_.expect_query("SELECT *").will_return(data);
477 auto result = backend_.select_query("SELECT *");
478 ASSERT_TRUE(result.is_ok());
479 ASSERT_EQ(result.value().size(), 1u);
480
481 auto& row = result.value()[0];
482 EXPECT_EQ(std::get<std::string>(row.at("string_col")), "hello");
483 EXPECT_EQ(std::get<int64_t>(row.at("int_col")), 42);
484 EXPECT_DOUBLE_EQ(std::get<double>(row.at("double_col")), 3.14);
485 EXPECT_EQ(std::get<bool>(row.at("bool_col")), true);
486 EXPECT_EQ(std::get<std::nullptr_t>(row.at("null_col")), nullptr);
487}
488
489// =============================================================================
490// Concurrent Access Tests
491// =============================================================================
492
493TEST_F(BackendContractTest, ConcurrentQueryExecution)
494{
495 constexpr int num_threads = 10;
496 constexpr int queries_per_thread = 50;
497 std::vector<std::thread> threads;
498
499 for (int t = 0; t < num_threads; ++t) {
500 threads.emplace_back([this, t]() {
501 for (int q = 0; q < queries_per_thread; ++q) {
502 std::string query = "INSERT INTO t" + std::to_string(t) +
503 " VALUES (" + std::to_string(q) + ")";
504 backend_.execute_query(query);
505 }
506 });
507 }
508
509 for (auto& t : threads) {
510 t.join();
511 }
512
513 EXPECT_EQ(backend_.get_query_count(),
514 static_cast<size_t>(num_threads * queries_per_thread));
515}
516
517// =============================================================================
518// Move Semantics Tests
519// =============================================================================
520
522{
523 backend_.initialize(test_config_);
524 backend_.set_database_type(database_types::sqlite);
525 backend_.execute_query("q1");
526
527 mock_backend moved(std::move(backend_));
528
529 EXPECT_TRUE(moved.is_initialized());
530 EXPECT_EQ(moved.type(), database_types::sqlite);
531 EXPECT_EQ(moved.get_query_count(), 1u);
532}
533
535{
536 backend_.initialize(test_config_);
537 backend_.execute_query("q1");
538
539 mock_backend target;
540 target = std::move(backend_);
541
542 EXPECT_TRUE(target.is_initialized());
543 EXPECT_EQ(target.get_query_count(), 1u);
544}
545
546int main(int argc, char** argv)
547{
548 ::testing::InitGoogleTest(&argc, argv);
549 return RUN_ALL_TESTS();
550}
TEST_F(BackendContractTest, InitializeSucceeds)
connection_config test_config_
static mock_backend failing_database(const std::string &error="Mock database error")
static mock_backend with_data(const std::string &table_name, const core::database_result &data)
Configurable mock for database_backend interface.
bool is_initialized() const override
Check if backend is initialized and ready.
kcenon::common::VoidResult initialize(const core::connection_config &config) override
Initialize the database backend.
mock_backend & set_database_type(database_types type)
database_types type() const override
Get the database type of this backend.
Abstract interface for database backends.
std::vector< database_row > database_result
Configuration for database connection.
#define ASSERT_EQ(expected, actual, message)
#define ASSERT_TRUE(condition, message)