1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
//! Tests for database integration
//!
//! This module contains unit tests for the database integration layer.
//! Integration tests that require a real database are in examples/tests.
#[cfg(feature = "database")]
#[cfg(test)]
mod error_tests {
use crate::database::DatabaseError;
#[test]
fn test_database_error_display() {
let err = DatabaseError::Connection("test error".to_string());
assert_eq!(err.to_string(), "Database connection error: test error");
let err = DatabaseError::Query("query failed".to_string());
assert_eq!(err.to_string(), "Database query error: query failed");
let err = DatabaseError::NotConfigured;
assert!(err.to_string().contains("not configured"));
}
#[test]
fn test_database_error_conversion() {
let db_err = DatabaseError::Connection("test".to_string());
let ultimo_err: crate::UltimoError = db_err.into();
match ultimo_err {
crate::UltimoError::Internal(msg) => {
assert!(msg.contains("connection error"));
}
_ => panic!("Expected Internal error"),
}
}
#[test]
fn test_database_error_types() {
// Test all error variants
let errors = vec![
DatabaseError::Connection("conn".to_string()),
DatabaseError::Query("query".to_string()),
DatabaseError::Migration("migration".to_string()),
DatabaseError::Pool("pool".to_string()),
DatabaseError::Transaction("tx".to_string()),
DatabaseError::NotConfigured,
];
for err in errors {
// Each error should have a meaningful display message
let msg = err.to_string();
assert!(!msg.is_empty(), "Error message should not be empty");
// Each error should convert to UltimoError
let _ultimo_err: crate::UltimoError = err.into();
}
}
}
// Unit tests for database module structure (no connection required)
#[cfg(feature = "database")]
#[cfg(test)]
mod database_module_tests {
use crate::database::Database;
#[test]
fn test_database_enum_size() {
// Ensure Database enum is reasonably sized
use std::mem::size_of;
let size = size_of::<Database>();
// Should be pointer-sized (Arc<T>) - 8 bytes on 64-bit, 16 bytes with tag
assert!(
size <= 32,
"Database enum should be small (Arc-based): {} bytes",
size
);
}
#[test]
fn test_database_error_is_send_sync() {
// Ensure errors can be sent between threads
use crate::database::DatabaseError;
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<DatabaseError>();
assert_sync::<DatabaseError>();
}
#[test]
fn test_database_enum_is_send_sync() {
// Ensure Database enum can be used across threads
use crate::database::Database;
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<Database>();
assert_sync::<Database>();
}
}
// Tests for database configuration and types
#[cfg(all(feature = "database", any(feature = "sqlx", feature = "diesel")))]
#[cfg(test)]
mod database_config_tests {
#[test]
fn test_connection_string_parsing() {
// Test that we can parse various connection string formats
let valid_strings = vec![
"postgres://localhost/mydb",
"postgresql://user:pass@localhost:5432/db",
"mysql://localhost/mydb",
"sqlite::memory:",
"sqlite:./test.db",
];
for conn_str in valid_strings {
// Just verify the string formats are reasonable
assert!(!conn_str.is_empty());
assert!(conn_str.contains("://") || conn_str.starts_with("sqlite:"));
}
}
#[test]
fn test_database_url_validation() {
// Test URL validation logic
let invalid_strings = vec!["", " ", "not-a-url", "http://wrong-protocol"];
for invalid in invalid_strings {
// These should be rejected by actual pool creation
assert!(invalid.is_empty() || !invalid.contains("postgres://"));
}
}
}
#[cfg(feature = "sqlx-postgres")]
#[cfg(test)]
mod sqlx_tests {
use crate::database::sqlx::SqlxPool;
use crate::database::Database;
#[tokio::test]
async fn test_sqlx_pool_type_safety() {
// Test that SqlxPool enforces type safety at compile time
// This ensures we can't mix different database types
// Type check - this should compile
let _pg_pool: Option<SqlxPool<sqlx::Postgres>> = None;
#[cfg(feature = "sqlx-mysql")]
let _mysql_pool: Option<SqlxPool<sqlx::MySql>> = None;
#[cfg(feature = "sqlx-sqlite")]
let _sqlite_pool: Option<SqlxPool<sqlx::Sqlite>> = None;
}
#[test]
fn test_sqlx_database_enum_creation() {
// Test that we can create Database enum variants
// This validates the API design
use std::sync::Arc;
// Mock pool for testing (we can't create real pool without connection)
// In real usage, this would be: Database::from_sqlx(actual_pool)
let mock_pool = Arc::new(42); // Just for type checking
let _db = Database::Sqlx(mock_pool);
}
#[test]
fn test_sqlx_error_conversion() {
use crate::database::DatabaseError;
// Test that SQLx errors convert properly to our error types
let errors = vec![
DatabaseError::Pool("connection timeout".to_string()),
DatabaseError::Query("invalid SQL syntax".to_string()),
DatabaseError::Transaction("deadlock detected".to_string()),
];
for err in errors {
let msg = err.to_string();
assert!(!msg.is_empty());
// Should convert to UltimoError
let ultimo_err: crate::UltimoError = err.into();
match ultimo_err {
crate::UltimoError::Internal(_) => {}
_ => panic!("Expected Internal error"),
}
}
}
}
#[cfg(feature = "diesel-postgres")]
#[cfg(test)]
mod diesel_tests {
use crate::database::diesel::DieselPool;
use crate::database::Database;
#[test]
fn test_diesel_pool_type_safety() {
// Test that DieselPool enforces type safety at compile time
// Type check - this should compile
let _pg_pool: Option<DieselPool<diesel::PgConnection>> = None;
#[cfg(feature = "diesel-mysql")]
let _mysql_pool: Option<DieselPool<diesel::MysqlConnection>> = None;
#[cfg(feature = "diesel-sqlite")]
let _sqlite_pool: Option<DieselPool<diesel::SqliteConnection>> = None;
}
#[test]
fn test_diesel_database_enum_creation() {
// Test Database enum variant for Diesel
use std::sync::Arc;
let mock_pool = Arc::new(42); // Just for type checking
let _db = Database::Diesel(mock_pool);
}
#[test]
fn test_diesel_pool_config_validation() {
// Test that pool configuration makes sense
// Max pool size should be positive
let max_size = 10;
assert!(max_size > 0, "Pool size must be positive");
// Min pool size should not exceed max
let min_size = 5;
assert!(min_size <= max_size, "Min size cannot exceed max size");
}
#[test]
fn test_diesel_error_conversion() {
use crate::database::DatabaseError;
let errors = vec![
DatabaseError::Connection("failed to connect".to_string()),
DatabaseError::Query("table not found".to_string()),
DatabaseError::Transaction("constraint violation".to_string()),
];
for err in errors {
let msg = err.to_string();
assert!(!msg.is_empty());
let ultimo_err: crate::UltimoError = err.into();
match ultimo_err {
crate::UltimoError::Internal(_) => {}
_ => panic!("Expected Internal error"),
}
}
}
}
#[cfg(test)]
mod context_tests {
// Test that Context methods work as expected
#[test]
fn test_database_attachment_api() {
// This tests the API design - that we have the right methods
// Actual integration tested in examples
// Just verify the API compiles
// `_ctx` is only referenced under the postgres backend features; the
// underscore keeps it warning-free under any other feature combo.
fn _check_context_api(_ctx: &crate::Context) {
#[cfg(feature = "sqlx-postgres")]
{
let _result: Result<&sqlx::Pool<sqlx::Postgres>, crate::UltimoError> =
_ctx.sqlx::<sqlx::Postgres>();
}
#[cfg(feature = "diesel-postgres")]
{
use diesel::r2d2::{ConnectionManager, PooledConnection};
let _result: Result<
PooledConnection<ConnectionManager<diesel::PgConnection>>,
crate::UltimoError,
> = _ctx.diesel::<diesel::PgConnection>();
}
}
}
}
#[cfg(all(test, feature = "database"))]
mod integration_hints {
//! This module documents how to write integration tests
//! See examples/database-sqlx/tests and examples/database-diesel/tests
/// Example of how to write an integration test with SQLx
///
/// ```rust,ignore
/// #[tokio::test]
/// async fn test_sqlx_crud_operations() {
/// // 1. Create test database connection
/// let pool = SqlxPool::connect("postgres://localhost/test_db").await.unwrap();
///
/// // 2. Run migrations
/// sqlx::query("CREATE TABLE IF NOT EXISTS users (id SERIAL, name TEXT)")
/// .execute(pool.pool())
/// .await
/// .unwrap();
///
/// // 3. Test CRUD operations
/// let user = sqlx::query_as::<_, User>("INSERT INTO users (name) VALUES ($1) RETURNING *")
/// .bind("Alice")
/// .fetch_one(pool.pool())
/// .await
/// .unwrap();
///
/// assert_eq!(user.name, "Alice");
///
/// // 4. Cleanup
/// sqlx::query("DROP TABLE users")
/// .execute(pool.pool())
/// .await
/// .unwrap();
/// }
/// ```
#[allow(dead_code)]
fn example_sqlx_test() {}
/// Example of how to write an integration test with Diesel
///
/// ```rust,ignore
/// #[test]
/// fn test_diesel_crud_operations() {
/// use diesel::prelude::*;
///
/// // 1. Create test database connection
/// let pool = DieselPool::<PgConnection>::new("postgres://localhost/test_db").unwrap();
/// let mut conn = pool.get().unwrap();
///
/// // 2. Run migrations
/// diesel::sql_query("CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name TEXT)")
/// .execute(&mut conn)
/// .unwrap();
///
/// // 3. Test CRUD operations
/// #[derive(Queryable)]
/// struct User { id: i32, name: String }
///
/// let user = diesel::insert_into(users::table)
/// .values(users::name.eq("Alice"))
/// .get_result::<User>(&mut conn)
/// .unwrap();
///
/// assert_eq!(user.name, "Alice");
///
/// // 4. Cleanup
/// diesel::sql_query("DROP TABLE users").execute(&mut conn).unwrap();
/// }
/// ```
#[allow(dead_code)]
fn example_diesel_test() {}
}