Database System 0.1.0
Advanced C++20 Database System with Multi-Backend Support
Loading...
Searching...
No Matches
backend_base_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
26
27using namespace database;
28using namespace database::core;
29
30// =============================================================================
31// Test Backend Implementations
32// =============================================================================
33
34namespace {
35
39class succeeding_backend
40 : public backend_base<succeeding_backend, database_types::sqlite>
41{
42public:
43 static constexpr const char* backend_name() { return "succeeding_backend"; }
44
45 int init_call_count = 0;
46 int shutdown_call_count = 0;
47
48 kcenon::common::Result<database_result> select_query(const std::string&) override
49 {
50 return kcenon::common::Result<database_result>::ok(database_result{});
51 }
52
53 kcenon::common::VoidResult execute_query(const std::string&) override
54 {
55 return kcenon::common::ok();
56 }
57
58 kcenon::common::VoidResult begin_transaction() override
59 {
60 return kcenon::common::ok();
61 }
62
63 kcenon::common::VoidResult commit_transaction() override
64 {
65 return kcenon::common::ok();
66 }
67
68 kcenon::common::VoidResult rollback_transaction() override
69 {
70 return kcenon::common::ok();
71 }
72
73 bool in_transaction() const override { return false; }
74 std::string last_error() const override { return ""; }
75 std::map<std::string, std::string> connection_info() const override { return {}; }
76
77protected:
78 friend class backend_base<succeeding_backend, database_types::sqlite>;
79
80 kcenon::common::VoidResult do_initialize(const connection_config&)
81 {
82 init_call_count++;
83 return kcenon::common::ok();
84 }
85
86 kcenon::common::VoidResult do_shutdown()
87 {
88 shutdown_call_count++;
89 return kcenon::common::ok();
90 }
91};
92
96class failing_backend
97 : public backend_base<failing_backend, database_types::postgres>
98{
99public:
100 static constexpr const char* backend_name() { return "failing_backend"; }
101
102 int init_call_count = 0;
103
104 kcenon::common::Result<database_result> select_query(const std::string&) override
105 {
106 return kcenon::common::error_info{-1, "Not initialized"};
107 }
108
109 kcenon::common::VoidResult execute_query(const std::string&) override
110 {
111 return kcenon::common::error_info{-1, "Not initialized"};
112 }
113
114 kcenon::common::VoidResult begin_transaction() override
115 {
116 return kcenon::common::error_info{-1, "Not initialized"};
117 }
118
119 kcenon::common::VoidResult commit_transaction() override
120 {
121 return kcenon::common::error_info{-1, "Not initialized"};
122 }
123
124 kcenon::common::VoidResult rollback_transaction() override
125 {
126 return kcenon::common::error_info{-1, "Not initialized"};
127 }
128
129 bool in_transaction() const override { return false; }
130 std::string last_error() const override { return "Connection failed"; }
131 std::map<std::string, std::string> connection_info() const override { return {}; }
132
133protected:
134 friend class backend_base<failing_backend, database_types::postgres>;
135
136 kcenon::common::VoidResult do_initialize(const connection_config&)
137 {
138 init_call_count++;
139 return kcenon::common::error_info{
141 "Simulated connection failure",
142 "failing_backend"
143 };
144 }
145
146 kcenon::common::VoidResult do_shutdown()
147 {
148 return kcenon::common::ok();
149 }
150};
151
152} // anonymous namespace
153
154// =============================================================================
155// Test Fixture
156// =============================================================================
157
158class BackendBaseTest : public ::testing::Test {
159protected:
161
162 void SetUp() override
163 {
164 test_config_.host = "localhost";
165 test_config_.port = 5432;
166 test_config_.database = "test_db";
167 test_config_.username = "test_user";
168 test_config_.password = "test_pass";
169 }
170};
171
172// =============================================================================
173// Type Tests
174// =============================================================================
175
176TEST_F(BackendBaseTest, TypeReturnsCorrectDatabaseType)
177{
178 succeeding_backend backend;
179 EXPECT_EQ(backend.type(), database_types::sqlite);
180}
181
182TEST_F(BackendBaseTest, TypeReturnsCorrectForAlternateBackend)
183{
184 failing_backend backend;
185 EXPECT_EQ(backend.type(), database_types::postgres);
186}
187
188// =============================================================================
189// Initialization Tests
190// =============================================================================
191
192TEST_F(BackendBaseTest, InitialStateIsNotInitialized)
193{
194 succeeding_backend backend;
195 EXPECT_FALSE(backend.is_initialized());
196}
197
198TEST_F(BackendBaseTest, SuccessfulInitializationSetsState)
199{
200 succeeding_backend backend;
201 auto result = backend.initialize(test_config_);
202 EXPECT_TRUE(result.is_ok());
203 EXPECT_TRUE(backend.is_initialized());
204}
205
206TEST_F(BackendBaseTest, FailedInitializationDoesNotSetState)
207{
208 failing_backend backend;
209 auto result = backend.initialize(test_config_);
210 EXPECT_FALSE(result.is_ok());
211 EXPECT_FALSE(backend.is_initialized());
212}
213
214TEST_F(BackendBaseTest, DoubleInitializationIsRejected)
215{
216 succeeding_backend backend;
217 EXPECT_TRUE(backend.initialize(test_config_).is_ok());
218
219 // Second initialization should fail with error
220 auto result = backend.initialize(test_config_);
221 EXPECT_FALSE(result.is_ok());
222 EXPECT_TRUE(backend.is_initialized()); // State unchanged
223}
224
225TEST_F(BackendBaseTest, DoubleInitializationDoesNotCallDoInitialize)
226{
227 succeeding_backend backend;
228 backend.initialize(test_config_);
229 backend.initialize(test_config_); // Should be rejected
230
231 // do_initialize should only be called once
232 EXPECT_EQ(backend.init_call_count, 1);
233}
234
235TEST_F(BackendBaseTest, InitializeAfterShutdownSucceeds)
236{
237 succeeding_backend backend;
238 EXPECT_TRUE(backend.initialize(test_config_).is_ok());
239 EXPECT_TRUE(backend.shutdown().is_ok());
240 EXPECT_FALSE(backend.is_initialized());
241
242 // Re-initialization should succeed
243 auto result = backend.initialize(test_config_);
244 EXPECT_TRUE(result.is_ok());
245 EXPECT_TRUE(backend.is_initialized());
246 EXPECT_EQ(backend.init_call_count, 2);
247}
248
249// =============================================================================
250// Shutdown Tests
251// =============================================================================
252
253TEST_F(BackendBaseTest, ShutdownWithoutInitIsNoOp)
254{
255 succeeding_backend backend;
256 auto result = backend.shutdown();
257 EXPECT_TRUE(result.is_ok());
258 EXPECT_FALSE(backend.is_initialized());
259 // do_shutdown should not be called since not initialized
260 EXPECT_EQ(backend.shutdown_call_count, 0);
261}
262
263TEST_F(BackendBaseTest, ShutdownAfterInitClearsState)
264{
265 succeeding_backend backend;
266 backend.initialize(test_config_);
267 auto result = backend.shutdown();
268 EXPECT_TRUE(result.is_ok());
269 EXPECT_FALSE(backend.is_initialized());
270 EXPECT_EQ(backend.shutdown_call_count, 1);
271}
272
273TEST_F(BackendBaseTest, DoubleShutdownIsIdempotent)
274{
275 succeeding_backend backend;
276 backend.initialize(test_config_);
277 backend.shutdown();
278 auto result = backend.shutdown(); // Second call is no-op
279 EXPECT_TRUE(result.is_ok());
280 EXPECT_EQ(backend.shutdown_call_count, 1);
281}
282
283TEST_F(BackendBaseTest, ShutdownSetsInitializedToFalseEvenOnError)
284{
285 // backend_base always sets initialized_ = false after do_shutdown,
286 // regardless of whether do_shutdown returns error
287 succeeding_backend backend;
288 backend.initialize(test_config_);
289 backend.shutdown();
290 EXPECT_FALSE(backend.is_initialized());
291}
292
293// =============================================================================
294// Factory Method Tests
295// =============================================================================
296
297TEST_F(BackendBaseTest, CreateReturnsUniquePointer)
298{
299 auto backend = succeeding_backend::create();
300 ASSERT_NE(backend, nullptr);
301 EXPECT_EQ(backend->type(), database_types::sqlite);
302 EXPECT_FALSE(backend->is_initialized());
303}
304
305TEST_F(BackendBaseTest, CreateReturnsDistinctInstances)
306{
307 auto b1 = succeeding_backend::create();
308 auto b2 = succeeding_backend::create();
309 EXPECT_NE(b1.get(), b2.get());
310}
311
312// =============================================================================
313// RAII Tests (Destructor calls shutdown)
314// =============================================================================
315
316TEST_F(BackendBaseTest, DestructorCallsShutdown)
317{
318 int shutdown_count = 0;
319 {
320 succeeding_backend backend;
321 backend.initialize(test_config_);
322 shutdown_count = backend.shutdown_call_count;
323 EXPECT_EQ(shutdown_count, 0);
324 // Destructor should call shutdown
325 }
326 // After destruction, we cannot check shutdown_call_count directly,
327 // but we can verify the pattern works without crashes
328 SUCCEED();
329}
330
331TEST_F(BackendBaseTest, DestructorSafeWithoutInit)
332{
333 // Backend destroyed without ever being initialized - should not crash
334 { succeeding_backend backend; }
335 SUCCEED();
336}
337
338// =============================================================================
339// Lifecycle Cycle Tests
340// =============================================================================
341
342TEST_F(BackendBaseTest, FullLifecycleCycle)
343{
344 succeeding_backend backend;
345
346 // Initial state
347 EXPECT_FALSE(backend.is_initialized());
348
349 // Initialize
350 EXPECT_TRUE(backend.initialize(test_config_).is_ok());
351 EXPECT_TRUE(backend.is_initialized());
352
353 // Use (simple operation)
354 auto result = backend.execute_query("INSERT INTO test VALUES (1)");
355 EXPECT_TRUE(result.is_ok());
356
357 // Shutdown
358 EXPECT_TRUE(backend.shutdown().is_ok());
359 EXPECT_FALSE(backend.is_initialized());
360}
361
362TEST_F(BackendBaseTest, MultipleLifecycleCycles)
363{
364 succeeding_backend backend;
365
366 for (int i = 0; i < 5; ++i) {
367 EXPECT_TRUE(backend.initialize(test_config_).is_ok());
368 EXPECT_TRUE(backend.is_initialized());
369 EXPECT_TRUE(backend.shutdown().is_ok());
370 EXPECT_FALSE(backend.is_initialized());
371 }
372
373 EXPECT_EQ(backend.init_call_count, 5);
374 EXPECT_EQ(backend.shutdown_call_count, 5);
375}
376
377// =============================================================================
378// Non-Copyable / Non-Moveable Tests
379// =============================================================================
380
381TEST_F(BackendBaseTest, BackendIsNotCopyable)
382{
383 EXPECT_FALSE(std::is_copy_constructible_v<succeeding_backend>);
384 EXPECT_FALSE(std::is_copy_assignable_v<succeeding_backend>);
385}
386
387TEST_F(BackendBaseTest, BackendIsNotMoveable)
388{
389 EXPECT_FALSE(std::is_move_constructible_v<succeeding_backend>);
390 EXPECT_FALSE(std::is_move_assignable_v<succeeding_backend>);
391}
392
393int main(int argc, char** argv)
394{
395 ::testing::InitGoogleTest(&argc, argv);
396 return RUN_ALL_TESTS();
397}
CRTP template base class for database backends.
TEST_F(BackendBaseTest, TypeReturnsCorrectDatabaseType)
void SetUp() override
connection_config test_config_
CRTP template base class for database backends.
Abstract interface for database backends.
std::vector< database_row > database_result
database_types
Represents various database backends or modes.
@ sqlite
Indicates a SQLite database.
@ postgres
Indicates a PostgreSQL database.
Configuration for database connection.