1use std::collections::VecDeque;
31use std::future::Future;
32use std::pin::Pin;
33
34use crate::pool::Connection;
35use crate::pool::QueryRows;
36use crate::value::Value;
37use crate::DbError;
38
39pub type MockRow = Vec<(&'static str, Value)>;
43
44#[derive(Clone)]
46struct QueryExpectation {
47 sql: Option<String>,
49 rows: Vec<MockRow>,
51 rows_affected: u64,
53}
54
55pub struct MockConnection {
64 expectations: VecDeque<QueryExpectation>,
66 executed_sql: Vec<String>,
68 in_transaction: bool,
70 committed: bool,
71 rolled_back: bool,
72 fallback_behavior: FallbackBehavior,
74}
75
76#[derive(Clone, Copy, Debug, Default)]
78pub enum FallbackBehavior {
79 #[default]
81 Empty,
82 Error,
84}
85
86impl MockConnection {
87 pub fn new() -> Self {
89 Self {
90 expectations: VecDeque::new(),
91 executed_sql: Vec::new(),
92 in_transaction: false,
93 committed: false,
94 rolled_back: false,
95 fallback_behavior: FallbackBehavior::Empty,
96 }
97 }
98
99 pub fn with_fallback(mut self, behavior: FallbackBehavior) -> Self {
101 self.fallback_behavior = behavior;
102 self
103 }
104
105 pub fn expect_query(&mut self, sql: impl Into<String>) -> QueryExpectationBuilder<'_> {
116 QueryExpectationBuilder {
117 mock: self,
118 sql: Some(sql.into()),
119 rows: vec![],
120 rows_affected: 0,
121 }
122 }
123
124 pub fn expect_any(&mut self) -> QueryExpectationBuilder<'_> {
126 QueryExpectationBuilder {
127 mock: self,
128 sql: None,
129 rows: vec![],
130 rows_affected: 0,
131 }
132 }
133
134 pub fn expect_execute(&mut self, sql: impl Into<String>, rows_affected: u64) {
136 self.expectations.push_back(QueryExpectation {
137 sql: Some(sql.into()),
138 rows: vec![],
139 rows_affected,
140 });
141 }
142
143 pub fn in_transaction(&self) -> bool {
145 self.in_transaction
146 }
147
148 pub fn was_committed(&self) -> bool {
150 self.committed
151 }
152
153 pub fn was_rolled_back(&self) -> bool {
155 self.rolled_back
156 }
157
158 pub fn executed_sql(&self) -> &[String] {
160 &self.executed_sql
161 }
162
163 pub fn assert_executed_count(&self, sql: &str, count: usize) {
165 let actual = self.executed_sql.iter().filter(|s| *s == sql).count();
166 assert!(
167 actual == count,
168 "SQL `{}` 预期执行 {} 次,实际 {} 次",
169 sql,
170 count,
171 actual
172 );
173 }
174
175 fn find_expectation(&mut self, sql: &str) -> Option<QueryExpectation> {
177 if let Some(pos) = self
179 .expectations
180 .iter()
181 .position(|e| e.sql.as_deref() == Some(sql))
182 {
183 return Some(self.expectations.remove(pos).unwrap()); }
185 if let Some(pos) = self.expectations.iter().position(|e| e.sql.is_none()) {
187 return Some(self.expectations.remove(pos).unwrap()); }
189 None
190 }
191
192 fn handle_query(&mut self, sql: &str) -> Result<QueryRows, DbError> {
193 self.executed_sql.push(sql.to_string());
194 match self.find_expectation(sql) {
195 Some(exp) => Ok(exp
196 .rows
197 .into_iter()
198 .map(|row| row.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
199 .collect()),
200 None => match self.fallback_behavior {
201 FallbackBehavior::Empty => Ok(Vec::new()),
202 FallbackBehavior::Error => Err(DbError::Internal(format!(
203 "MockConnection: 未预设的查询 `{}`",
204 sql
205 ))),
206 },
207 }
208 }
209
210 fn handle_execute(&mut self, sql: &str) -> Result<u64, DbError> {
211 self.executed_sql.push(sql.to_string());
212 match self.find_expectation(sql) {
213 Some(exp) => Ok(exp.rows_affected),
214 None => match self.fallback_behavior {
215 FallbackBehavior::Empty => Ok(0),
216 FallbackBehavior::Error => Err(DbError::Internal(format!(
217 "MockConnection: 未预设的 execute `{}`",
218 sql
219 ))),
220 },
221 }
222 }
223}
224
225impl Default for MockConnection {
226 fn default() -> Self {
227 Self::new()
228 }
229}
230
231pub struct QueryExpectationBuilder<'a> {
233 mock: &'a mut MockConnection,
234 sql: Option<String>,
235 rows: Vec<MockRow>,
236 rows_affected: u64,
237}
238
239impl<'a> QueryExpectationBuilder<'a> {
240 pub fn with_rows(mut self, rows: Vec<MockRow>) -> &'a mut MockConnection {
242 self.mock.expectations.push_back(QueryExpectation {
243 sql: self.sql.take(),
244 rows,
245 rows_affected: self.rows_affected,
246 });
247 self.mock
248 }
249
250 pub fn with_rows_affected(mut self, n: u64) -> &'a mut MockConnection {
252 self.rows_affected = n;
253 let rows = std::mem::take(&mut self.rows);
254 self.with_rows(rows)
255 }
256}
257
258impl Connection for MockConnection {
263 fn execute<'a>(
264 &'a mut self,
265 sql: &'a str,
266 ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
267 Box::pin(async move { self.handle_execute(sql) })
268 }
269
270 fn query<'a>(
271 &'a mut self,
272 sql: &'a str,
273 ) -> Pin<Box<dyn Future<Output = Result<QueryRows, DbError>> + Send + 'a>> {
274 Box::pin(async move { self.handle_query(sql) })
275 }
276
277 fn query_with_params<'a>(
278 &'a mut self,
279 sql: &'a str,
280 _params: &'a [crate::value::Value],
281 ) -> Pin<Box<dyn Future<Output = Result<QueryRows, DbError>> + Send + 'a>> {
282 Box::pin(async move { self.handle_query(sql) })
284 }
285
286 fn execute_with_params<'a>(
287 &'a mut self,
288 sql: &'a str,
289 _params: &'a [crate::value::Value],
290 ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
291 Box::pin(async move { self.handle_execute(sql) })
293 }
294
295 fn begin_transaction<'a>(
296 &'a mut self,
297 ) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
298 Box::pin(async move {
299 if self.in_transaction {
300 return Err(DbError::Internal("MockConnection: 已在事务中".to_string()));
301 }
302 self.in_transaction = true;
303 self.committed = false;
304 self.rolled_back = false;
305 Ok(())
306 })
307 }
308
309 fn commit<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
310 Box::pin(async move {
311 if !self.in_transaction {
312 return Err(DbError::Internal("MockConnection: 未开启事务".to_string()));
313 }
314 self.in_transaction = false;
315 self.committed = true;
316 Ok(())
317 })
318 }
319
320 fn rollback<'a>(
321 &'a mut self,
322 ) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
323 Box::pin(async move {
324 if !self.in_transaction {
325 return Err(DbError::Internal("MockConnection: 未开启事务".to_string()));
326 }
327 self.in_transaction = false;
328 self.rolled_back = true;
329 Ok(())
330 })
331 }
332
333 fn is_connected(&self) -> bool {
334 true
335 }
336
337 fn ping<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
338 Box::pin(async move { true })
339 }
340
341 fn close<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
342 Box::pin(async move { Ok(()) })
343 }
344}
345
346#[cfg(test)]
351mod tests {
352 use super::*;
353 use crate::value::Value;
354
355 #[tokio::test]
356 async fn test_mock_connection_basic_query() {
357 let mut mock = MockConnection::new();
358 mock.expect_query("SELECT * FROM users")
359 .with_rows(vec![vec![
360 ("id", Value::from(1i64)),
361 ("name", Value::from("Alice")),
362 ]]);
363
364 let rows = mock.query("SELECT * FROM users").await.unwrap();
365 assert_eq!(rows.len(), 1);
366 let row = rows.first().unwrap();
367 assert_eq!(row.get("id"), Some(&Value::from(1i64)));
368 assert_eq!(row.get("name"), Some(&Value::from("Alice")));
369 }
370
371 #[tokio::test]
372 async fn test_mock_connection_execute() {
373 let mut mock = MockConnection::new();
374 mock.expect_execute("INSERT INTO users (name) VALUES ('Bob')", 1);
375
376 let affected = mock
377 .execute("INSERT INTO users (name) VALUES ('Bob')")
378 .await
379 .unwrap();
380 assert_eq!(affected, 1);
381 }
382
383 #[tokio::test]
384 async fn test_mock_connection_fallback_empty() {
385 let mut mock = MockConnection::new();
386 let rows = mock.query("SELECT * FROM anything").await.unwrap();
388 assert_eq!(rows.len(), 0);
389 }
390
391 #[tokio::test]
392 async fn test_mock_connection_fallback_error() {
393 let mut mock = MockConnection::new().with_fallback(FallbackBehavior::Error);
394 let result = mock.query("SELECT * FROM anything").await;
395 assert!(result.is_err());
396 }
397
398 #[tokio::test]
399 async fn test_mock_connection_transaction_commit() {
400 let mut mock = MockConnection::new();
401 assert!(!mock.in_transaction());
402
403 mock.begin_transaction().await.unwrap();
404 assert!(mock.in_transaction());
405 assert!(!mock.was_committed());
406
407 mock.commit().await.unwrap();
408 assert!(!mock.in_transaction());
409 assert!(mock.was_committed());
410 assert!(!mock.was_rolled_back());
411 }
412
413 #[tokio::test]
414 async fn test_mock_connection_transaction_rollback() {
415 let mut mock = MockConnection::new();
416 mock.begin_transaction().await.unwrap();
417 mock.rollback().await.unwrap();
418 assert!(!mock.in_transaction());
419 assert!(!mock.was_committed());
420 assert!(mock.was_rolled_back());
421 }
422
423 #[tokio::test]
424 async fn test_mock_connection_double_begin_errors() {
425 let mut mock = MockConnection::new();
426 mock.begin_transaction().await.unwrap();
427 let result = mock.begin_transaction().await;
428 assert!(result.is_err());
429 }
430
431 #[tokio::test]
432 async fn test_mock_connection_commit_without_transaction_errors() {
433 let mut mock = MockConnection::new();
434 let result = mock.commit().await;
435 assert!(result.is_err());
436 }
437
438 #[tokio::test]
439 async fn test_mock_connection_executed_sql_tracking() {
440 let mut mock = MockConnection::new();
441 mock.expect_query("SELECT 1").with_rows(vec![]);
442 mock.query("SELECT 1").await.unwrap();
443 mock.query("SELECT 2").await.unwrap();
444
445 assert_eq!(mock.executed_sql().len(), 2);
446 assert_eq!(mock.executed_sql()[0], "SELECT 1");
447 assert_eq!(mock.executed_sql()[1], "SELECT 2");
448 }
449
450 #[tokio::test]
451 async fn test_mock_connection_assert_executed_count() {
452 let mut mock = MockConnection::new();
453 mock.expect_query("SELECT x").with_rows(vec![]);
454 mock.query("SELECT x").await.unwrap();
455 mock.query("SELECT x").await.unwrap();
456 mock.query("SELECT y").await.unwrap();
457
458 mock.assert_executed_count("SELECT x", 2);
459 mock.assert_executed_count("SELECT y", 1);
460 }
461
462 #[tokio::test]
463 async fn test_mock_connection_expect_any_fallback() {
464 let mut mock = MockConnection::new();
465 mock.expectations.push_back(QueryExpectation {
467 sql: None,
468 rows: vec![vec![("a", Value::from(42i64))]],
469 rows_affected: 0,
470 });
471
472 let rows = mock.query("ANY RANDOM SQL").await.unwrap();
473 assert_eq!(rows.len(), 1);
474 }
475
476 #[tokio::test]
477 async fn test_mock_connection_is_connected() {
478 let mock = MockConnection::new();
479 assert!(mock.is_connected());
480 }
481
482 #[tokio::test]
483 async fn test_mock_connection_ping() {
484 let mut mock = MockConnection::new();
485 assert!(mock.ping().await);
486 }
487
488 #[tokio::test]
489 async fn test_mock_connection_close() {
490 let mut mock = MockConnection::new();
491 assert!(mock.close().await.is_ok());
492 }
493
494 #[tokio::test]
495 async fn test_mock_connection_multiple_rows() {
496 let mut mock = MockConnection::new();
497 mock.expect_query("SELECT * FROM products").with_rows(vec![
498 vec![("id", Value::from(1i64)), ("name", Value::from("Widget"))],
499 vec![("id", Value::from(2i64)), ("name", Value::from("Gadget"))],
500 vec![
501 ("id", Value::from(3i64)),
502 ("name", Value::from("Doohickey")),
503 ],
504 ]);
505
506 let rows = mock.query("SELECT * FROM products").await.unwrap();
507 assert_eq!(rows.len(), 3);
508 assert_eq!(
509 rows.first().unwrap().get("name"),
510 Some(&Value::from("Widget"))
511 );
512 assert_eq!(
513 rows.get(2).unwrap().get("name"),
514 Some(&Value::from("Doohickey"))
515 );
516 }
517
518 #[tokio::test]
519 async fn test_mock_connection_exact_match_before_fallback() {
520 let mut mock = MockConnection::new();
521 mock.expectations.push_back(QueryExpectation {
523 sql: None,
524 rows: vec![vec![("source", Value::from("fallback"))]],
525 rows_affected: 0,
526 });
527 mock.expect_query("SELECT exact")
529 .with_rows(vec![vec![("source", Value::from("exact"))]]);
530
531 let rows = mock.query("SELECT exact").await.unwrap();
532 assert_eq!(
533 rows.first().unwrap().get("source"),
534 Some(&Value::from("exact"))
535 );
536 }
537}