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 begin_transaction<'a>(
287 &'a mut self,
288 ) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
289 Box::pin(async move {
290 if self.in_transaction {
291 return Err(DbError::Internal("MockConnection: 已在事务中".to_string()));
292 }
293 self.in_transaction = true;
294 self.committed = false;
295 self.rolled_back = false;
296 Ok(())
297 })
298 }
299
300 fn commit<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
301 Box::pin(async move {
302 if !self.in_transaction {
303 return Err(DbError::Internal("MockConnection: 未开启事务".to_string()));
304 }
305 self.in_transaction = false;
306 self.committed = true;
307 Ok(())
308 })
309 }
310
311 fn rollback<'a>(
312 &'a mut self,
313 ) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
314 Box::pin(async move {
315 if !self.in_transaction {
316 return Err(DbError::Internal("MockConnection: 未开启事务".to_string()));
317 }
318 self.in_transaction = false;
319 self.rolled_back = true;
320 Ok(())
321 })
322 }
323
324 fn is_connected(&self) -> bool {
325 true
326 }
327
328 fn ping<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
329 Box::pin(async move { true })
330 }
331
332 fn close<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
333 Box::pin(async move { Ok(()) })
334 }
335}
336
337#[cfg(test)]
342mod tests {
343 use super::*;
344 use crate::value::Value;
345
346 #[tokio::test]
347 async fn test_mock_connection_basic_query() {
348 let mut mock = MockConnection::new();
349 mock.expect_query("SELECT * FROM users")
350 .with_rows(vec![vec![
351 ("id", Value::from(1i64)),
352 ("name", Value::from("Alice")),
353 ]]);
354
355 let rows = mock.query("SELECT * FROM users").await.unwrap();
356 assert_eq!(rows.len(), 1);
357 let row = rows.first().unwrap();
358 assert_eq!(row.get("id"), Some(&Value::from(1i64)));
359 assert_eq!(row.get("name"), Some(&Value::from("Alice")));
360 }
361
362 #[tokio::test]
363 async fn test_mock_connection_execute() {
364 let mut mock = MockConnection::new();
365 mock.expect_execute("INSERT INTO users (name) VALUES ('Bob')", 1);
366
367 let affected = mock
368 .execute("INSERT INTO users (name) VALUES ('Bob')")
369 .await
370 .unwrap();
371 assert_eq!(affected, 1);
372 }
373
374 #[tokio::test]
375 async fn test_mock_connection_fallback_empty() {
376 let mut mock = MockConnection::new();
377 let rows = mock.query("SELECT * FROM anything").await.unwrap();
379 assert_eq!(rows.len(), 0);
380 }
381
382 #[tokio::test]
383 async fn test_mock_connection_fallback_error() {
384 let mut mock = MockConnection::new().with_fallback(FallbackBehavior::Error);
385 let result = mock.query("SELECT * FROM anything").await;
386 assert!(result.is_err());
387 }
388
389 #[tokio::test]
390 async fn test_mock_connection_transaction_commit() {
391 let mut mock = MockConnection::new();
392 assert!(!mock.in_transaction());
393
394 mock.begin_transaction().await.unwrap();
395 assert!(mock.in_transaction());
396 assert!(!mock.was_committed());
397
398 mock.commit().await.unwrap();
399 assert!(!mock.in_transaction());
400 assert!(mock.was_committed());
401 assert!(!mock.was_rolled_back());
402 }
403
404 #[tokio::test]
405 async fn test_mock_connection_transaction_rollback() {
406 let mut mock = MockConnection::new();
407 mock.begin_transaction().await.unwrap();
408 mock.rollback().await.unwrap();
409 assert!(!mock.in_transaction());
410 assert!(!mock.was_committed());
411 assert!(mock.was_rolled_back());
412 }
413
414 #[tokio::test]
415 async fn test_mock_connection_double_begin_errors() {
416 let mut mock = MockConnection::new();
417 mock.begin_transaction().await.unwrap();
418 let result = mock.begin_transaction().await;
419 assert!(result.is_err());
420 }
421
422 #[tokio::test]
423 async fn test_mock_connection_commit_without_transaction_errors() {
424 let mut mock = MockConnection::new();
425 let result = mock.commit().await;
426 assert!(result.is_err());
427 }
428
429 #[tokio::test]
430 async fn test_mock_connection_executed_sql_tracking() {
431 let mut mock = MockConnection::new();
432 mock.expect_query("SELECT 1").with_rows(vec![]);
433 mock.query("SELECT 1").await.unwrap();
434 mock.query("SELECT 2").await.unwrap();
435
436 assert_eq!(mock.executed_sql().len(), 2);
437 assert_eq!(mock.executed_sql()[0], "SELECT 1");
438 assert_eq!(mock.executed_sql()[1], "SELECT 2");
439 }
440
441 #[tokio::test]
442 async fn test_mock_connection_assert_executed_count() {
443 let mut mock = MockConnection::new();
444 mock.expect_query("SELECT x").with_rows(vec![]);
445 mock.query("SELECT x").await.unwrap();
446 mock.query("SELECT x").await.unwrap();
447 mock.query("SELECT y").await.unwrap();
448
449 mock.assert_executed_count("SELECT x", 2);
450 mock.assert_executed_count("SELECT y", 1);
451 }
452
453 #[tokio::test]
454 async fn test_mock_connection_expect_any_fallback() {
455 let mut mock = MockConnection::new();
456 mock.expectations.push_back(QueryExpectation {
458 sql: None,
459 rows: vec![vec![("a", Value::from(42i64))]],
460 rows_affected: 0,
461 });
462
463 let rows = mock.query("ANY RANDOM SQL").await.unwrap();
464 assert_eq!(rows.len(), 1);
465 }
466
467 #[tokio::test]
468 async fn test_mock_connection_is_connected() {
469 let mock = MockConnection::new();
470 assert!(mock.is_connected());
471 }
472
473 #[tokio::test]
474 async fn test_mock_connection_ping() {
475 let mut mock = MockConnection::new();
476 assert!(mock.ping().await);
477 }
478
479 #[tokio::test]
480 async fn test_mock_connection_close() {
481 let mut mock = MockConnection::new();
482 assert!(mock.close().await.is_ok());
483 }
484
485 #[tokio::test]
486 async fn test_mock_connection_multiple_rows() {
487 let mut mock = MockConnection::new();
488 mock.expect_query("SELECT * FROM products").with_rows(vec![
489 vec![("id", Value::from(1i64)), ("name", Value::from("Widget"))],
490 vec![("id", Value::from(2i64)), ("name", Value::from("Gadget"))],
491 vec![
492 ("id", Value::from(3i64)),
493 ("name", Value::from("Doohickey")),
494 ],
495 ]);
496
497 let rows = mock.query("SELECT * FROM products").await.unwrap();
498 assert_eq!(rows.len(), 3);
499 assert_eq!(
500 rows.first().unwrap().get("name"),
501 Some(&Value::from("Widget"))
502 );
503 assert_eq!(
504 rows.get(2).unwrap().get("name"),
505 Some(&Value::from("Doohickey"))
506 );
507 }
508
509 #[tokio::test]
510 async fn test_mock_connection_exact_match_before_fallback() {
511 let mut mock = MockConnection::new();
512 mock.expectations.push_back(QueryExpectation {
514 sql: None,
515 rows: vec![vec![("source", Value::from("fallback"))]],
516 rows_affected: 0,
517 });
518 mock.expect_query("SELECT exact")
520 .with_rows(vec![vec![("source", Value::from("exact"))]]);
521
522 let rows = mock.query("SELECT exact").await.unwrap();
523 assert_eq!(
524 rows.first().unwrap().get("source"),
525 Some(&Value::from("exact"))
526 );
527 }
528}