1use crate::{
2 error::*, DatabaseConnection, DbBackend, EntityTrait, ExecResult, ExecResultHolder, Iden,
3 IdenStatic, Iterable, MockDatabaseConnection, MockDatabaseTrait, ModelTrait, QueryResult,
4 QueryResultRow, SelectA, SelectB, Statement,
5};
6use sea_query::{Value, ValueType, Values};
7use std::{collections::BTreeMap, sync::Arc};
8use tracing::instrument;
9
10#[derive(Debug)]
12pub struct MockDatabase {
13 db_backend: DbBackend,
14 transaction: Option<OpenTransaction>,
15 transaction_log: Vec<Transaction>,
16 exec_results: Vec<Result<MockExecResult, DbErr>>,
17 query_results: Vec<Result<Vec<MockRow>, DbErr>>,
18}
19
20#[derive(Clone, Debug, Default)]
22pub struct MockExecResult {
23 pub last_insert_id: u64,
25 pub rows_affected: u64,
27}
28
29#[derive(Clone, Debug)]
32pub struct MockRow {
33 pub(crate) values: BTreeMap<String, Value>,
35}
36
37pub trait IntoMockRow {
39 fn into_mock_row(self) -> MockRow;
41}
42
43#[derive(Debug)]
45pub struct OpenTransaction {
46 stmts: Vec<Statement>,
47 transaction_depth: usize,
48}
49
50#[derive(Debug, Clone, PartialEq)]
52pub struct Transaction {
53 stmts: Vec<Statement>,
54}
55
56impl MockDatabase {
57 pub fn new(db_backend: DbBackend) -> Self {
60 Self {
61 db_backend,
62 transaction: None,
63 transaction_log: Vec::new(),
64 exec_results: Vec::new(),
65 query_results: Vec::new(),
66 }
67 }
68
69 pub fn into_connection(self) -> DatabaseConnection {
71 DatabaseConnection::MockDatabaseConnection(Arc::new(MockDatabaseConnection::new(self)))
72 }
73
74 pub fn append_exec_results<I>(mut self, vec: I) -> Self
76 where
77 I: IntoIterator<Item = MockExecResult>,
78 {
79 self.exec_results.extend(vec.into_iter().map(Result::Ok));
80 self
81 }
82
83 pub fn append_query_results<T, I, II>(mut self, vec: II) -> Self
85 where
86 T: IntoMockRow,
87 I: IntoIterator<Item = T>,
88 II: IntoIterator<Item = I>,
89 {
90 for row in vec.into_iter() {
91 let row = row.into_iter().map(|vec| Ok(vec.into_mock_row())).collect();
92 self.query_results.push(row);
93 }
94 self
95 }
96
97 pub fn append_exec_errors<I>(mut self, vec: I) -> Self
99 where
100 I: IntoIterator<Item = DbErr>,
101 {
102 self.exec_results.extend(vec.into_iter().map(Result::Err));
103 self
104 }
105
106 pub fn append_query_errors<I>(mut self, vec: I) -> Self
108 where
109 I: IntoIterator<Item = DbErr>,
110 {
111 self.query_results.extend(vec.into_iter().map(Result::Err));
112 self
113 }
114}
115
116impl MockDatabaseTrait for MockDatabase {
117 #[instrument(level = "trace")]
118 fn execute(&mut self, counter: usize, statement: Statement) -> Result<ExecResult, DbErr> {
119 if let Some(transaction) = &mut self.transaction {
120 transaction.push(statement);
121 } else {
122 self.transaction_log.push(Transaction::one(statement));
123 }
124 if counter < self.exec_results.len() {
125 match std::mem::replace(
126 &mut self.exec_results[counter],
127 Err(exec_err("this value has been consumed already")),
128 ) {
129 Ok(result) => Ok(ExecResult {
130 result: ExecResultHolder::Mock(result),
131 }),
132 Err(err) => Err(err),
133 }
134 } else {
135 Err(exec_err("`exec_results` buffer is empty"))
136 }
137 }
138
139 #[instrument(level = "trace")]
140 fn query(&mut self, counter: usize, statement: Statement) -> Result<Vec<QueryResult>, DbErr> {
141 if let Some(transaction) = &mut self.transaction {
142 transaction.push(statement);
143 } else {
144 self.transaction_log.push(Transaction::one(statement));
145 }
146 if counter < self.query_results.len() {
147 match std::mem::replace(
148 &mut self.query_results[counter],
149 Err(query_err("this value has been consumed already")),
150 ) {
151 Ok(result) => Ok(result
152 .into_iter()
153 .map(|row| QueryResult {
154 row: QueryResultRow::Mock(row),
155 })
156 .collect()),
157 Err(err) => Err(err),
158 }
159 } else {
160 Err(query_err("`query_results` buffer is empty."))
161 }
162 }
163
164 #[instrument(level = "trace")]
165 fn begin(&mut self) {
166 match self.transaction.as_mut() {
167 Some(transaction) => transaction.begin_nested(self.db_backend),
168 None => self.transaction = Some(OpenTransaction::init()),
169 }
170 }
171
172 #[instrument(level = "trace")]
173 fn commit(&mut self) {
174 match self.transaction.as_mut() {
175 Some(transaction) => {
176 if transaction.commit(self.db_backend) {
177 if let Some(transaction) = self.transaction.take() {
178 self.transaction_log.push(transaction.into_transaction());
179 }
180 }
181 }
182 None => panic!("There is no open transaction to commit"),
183 }
184 }
185
186 #[instrument(level = "trace")]
187 fn rollback(&mut self) {
188 match self.transaction.as_mut() {
189 Some(transaction) => {
190 if transaction.rollback(self.db_backend) {
191 if let Some(transaction) = self.transaction.take() {
192 self.transaction_log.push(transaction.into_transaction());
193 }
194 }
195 }
196 None => panic!("There is no open transaction to rollback"),
197 }
198 }
199
200 fn drain_transaction_log(&mut self) -> Vec<Transaction> {
201 std::mem::take(&mut self.transaction_log)
202 }
203
204 fn get_database_backend(&self) -> DbBackend {
205 self.db_backend
206 }
207
208 fn ping(&self) -> Result<(), DbErr> {
209 Ok(())
210 }
211}
212
213impl MockRow {
214 pub fn try_get<T, I: crate::ColIdx>(&self, index: I) -> Result<T, DbErr>
216 where
217 T: ValueType,
218 {
219 if let Some(index) = index.as_str() {
220 T::try_from(
221 self.values
222 .get(index)
223 .ok_or_else(|| query_err(format!("No column for ColIdx {index:?}")))?
224 .clone(),
225 )
226 .map_err(type_err)
227 } else if let Some(index) = index.as_usize() {
228 let (_, value) = self
229 .values
230 .iter()
231 .nth(*index)
232 .ok_or_else(|| query_err(format!("Column at index {index} not found")))?;
233 T::try_from(value.clone()).map_err(type_err)
234 } else {
235 unreachable!("Missing ColIdx implementation for MockRow");
236 }
237 }
238
239 pub fn into_column_value_tuples(self) -> impl Iterator<Item = (String, Value)> {
241 self.values.into_iter()
242 }
243}
244
245impl IntoMockRow for MockRow {
246 fn into_mock_row(self) -> MockRow {
247 self
248 }
249}
250
251impl<M> IntoMockRow for M
252where
253 M: ModelTrait,
254{
255 fn into_mock_row(self) -> MockRow {
256 let mut values = BTreeMap::new();
257 for col in <<M::Entity as EntityTrait>::Column>::iter() {
258 values.insert(col.to_string(), self.get(col));
259 }
260 MockRow { values }
261 }
262}
263
264impl<M, N> IntoMockRow for (M, N)
265where
266 M: ModelTrait,
267 N: ModelTrait,
268{
269 fn into_mock_row(self) -> MockRow {
270 let mut mapped_join = BTreeMap::new();
271
272 for column in <<M as ModelTrait>::Entity as EntityTrait>::Column::iter() {
273 mapped_join.insert(
274 format!("{}{}", SelectA.as_str(), column.as_str()),
275 self.0.get(column),
276 );
277 }
278 for column in <<N as ModelTrait>::Entity as EntityTrait>::Column::iter() {
279 mapped_join.insert(
280 format!("{}{}", SelectB.as_str(), column.as_str()),
281 self.1.get(column),
282 );
283 }
284
285 mapped_join.into_mock_row()
286 }
287}
288
289impl<M, N> IntoMockRow for (M, Option<N>)
290where
291 M: ModelTrait,
292 N: ModelTrait,
293{
294 fn into_mock_row(self) -> MockRow {
295 let mut mapped_join = BTreeMap::new();
296
297 for column in <<M as ModelTrait>::Entity as EntityTrait>::Column::iter() {
298 mapped_join.insert(
299 format!("{}{}", SelectA.as_str(), column.as_str()),
300 self.0.get(column),
301 );
302 }
303 if let Some(b_entity) = self.1 {
304 for column in <<N as ModelTrait>::Entity as EntityTrait>::Column::iter() {
305 mapped_join.insert(
306 format!("{}{}", SelectB.as_str(), column.as_str()),
307 b_entity.get(column),
308 );
309 }
310 }
311
312 mapped_join.into_mock_row()
313 }
314}
315
316impl<T> IntoMockRow for BTreeMap<T, Value>
317where
318 T: Into<String>,
319{
320 fn into_mock_row(self) -> MockRow {
321 MockRow {
322 values: self.into_iter().map(|(k, v)| (k.into(), v)).collect(),
323 }
324 }
325}
326
327impl Transaction {
328 pub fn from_sql_and_values<I, T>(db_backend: DbBackend, sql: T, values: I) -> Self
330 where
331 I: IntoIterator<Item = Value>,
332 T: Into<String>,
333 {
334 Self::one(Statement::from_string_values_tuple(
335 db_backend,
336 (sql, Values(values.into_iter().collect())),
337 ))
338 }
339
340 pub fn one(stmt: Statement) -> Self {
342 Self { stmts: vec![stmt] }
343 }
344
345 pub fn many<I>(stmts: I) -> Self
347 where
348 I: IntoIterator<Item = Statement>,
349 {
350 Self {
351 stmts: stmts.into_iter().collect(),
352 }
353 }
354
355 pub fn wrap<I>(stmts: I) -> Vec<Self>
357 where
358 I: IntoIterator<Item = Statement>,
359 {
360 stmts.into_iter().map(Self::one).collect()
361 }
362}
363
364impl OpenTransaction {
365 fn init() -> Self {
366 Self {
367 stmts: vec![Statement::from_string(DbBackend::Postgres, "BEGIN")],
368 transaction_depth: 0,
369 }
370 }
371
372 fn begin_nested(&mut self, db_backend: DbBackend) {
373 self.transaction_depth += 1;
374 self.push(Statement::from_string(
375 db_backend,
376 format!("SAVEPOINT savepoint_{}", self.transaction_depth),
377 ));
378 }
379
380 fn commit(&mut self, db_backend: DbBackend) -> bool {
381 if self.transaction_depth == 0 {
382 self.push(Statement::from_string(db_backend, "COMMIT"));
383 true
384 } else {
385 self.push(Statement::from_string(
386 db_backend,
387 format!("RELEASE SAVEPOINT savepoint_{}", self.transaction_depth),
388 ));
389 self.transaction_depth -= 1;
390 false
391 }
392 }
393
394 fn rollback(&mut self, db_backend: DbBackend) -> bool {
395 if self.transaction_depth == 0 {
396 self.push(Statement::from_string(db_backend, "ROLLBACK"));
397 true
398 } else {
399 self.push(Statement::from_string(
400 db_backend,
401 format!("ROLLBACK TO SAVEPOINT savepoint_{}", self.transaction_depth),
402 ));
403 self.transaction_depth -= 1;
404 false
405 }
406 }
407
408 fn push(&mut self, stmt: Statement) {
409 self.stmts.push(stmt);
410 }
411
412 fn into_transaction(self) -> Transaction {
413 match self.transaction_depth {
414 0 => Transaction { stmts: self.stmts },
415 _ => panic!("There is uncommitted nested transaction"),
416 }
417 }
418}
419
420#[cfg(test)]
421#[cfg(feature = "mock")]
422mod tests {
423 use crate::{
424 entity::*, error::*, tests_cfg::*, DbBackend, DbErr, IntoMockRow, MockDatabase, Statement,
425 Transaction, TransactionError, TransactionTrait,
426 };
427 use pretty_assertions::assert_eq;
428
429 #[derive(Debug, PartialEq, Eq)]
430 pub struct MyErr(String);
431
432 impl std::error::Error for MyErr {}
433
434 impl std::fmt::Display for MyErr {
435 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
436 write!(f, "{}", self.0.as_str())
437 }
438 }
439
440 #[smol_potat::test]
441 async fn test_transaction_1() {
442 let db = MockDatabase::new(DbBackend::Postgres).into_connection();
443
444 db.transaction::<_, (), DbErr>(|txn| {
445 Box::pin(async move {
446 let _1 = cake::Entity::find().one(txn).await;
447 let _2 = fruit::Entity::find().all(txn).await;
448
449 Ok(())
450 })
451 })
452 .await
453 .unwrap();
454
455 let _ = cake::Entity::find().all(&db).await;
456
457 assert_eq!(
458 db.into_transaction_log(),
459 [
460 Transaction::many([
461 Statement::from_string(DbBackend::Postgres, "BEGIN"),
462 Statement::from_sql_and_values(
463 DbBackend::Postgres,
464 r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#,
465 [1u64.into()]
466 ),
467 Statement::from_sql_and_values(
468 DbBackend::Postgres,
469 r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit""#,
470 []
471 ),
472 Statement::from_string(DbBackend::Postgres, "COMMIT"),
473 ]),
474 Transaction::from_sql_and_values(
475 DbBackend::Postgres,
476 r#"SELECT "cake"."id", "cake"."name" FROM "cake""#,
477 []
478 ),
479 ]
480 );
481 }
482
483 #[smol_potat::test]
484 async fn test_transaction_2() {
485 let db = MockDatabase::new(DbBackend::Postgres).into_connection();
486
487 let result = db
488 .transaction::<_, (), MyErr>(|txn| {
489 Box::pin(async move {
490 let _ = cake::Entity::find().one(txn).await;
491 Err(MyErr("test".to_owned()))
492 })
493 })
494 .await;
495
496 match result {
497 Err(TransactionError::Transaction(err)) => {
498 assert_eq!(err, MyErr("test".to_owned()))
499 }
500 _ => unreachable!(),
501 }
502
503 assert_eq!(
504 db.into_transaction_log(),
505 [Transaction::many([
506 Statement::from_string(DbBackend::Postgres, "BEGIN"),
507 Statement::from_sql_and_values(
508 DbBackend::Postgres,
509 r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#,
510 [1u64.into()]
511 ),
512 Statement::from_string(DbBackend::Postgres, "ROLLBACK"),
513 ])]
514 );
515 }
516
517 #[smol_potat::test]
518 async fn test_nested_transaction_1() {
519 let db = MockDatabase::new(DbBackend::Postgres).into_connection();
520
521 db.transaction::<_, (), DbErr>(|txn| {
522 Box::pin(async move {
523 let _ = cake::Entity::find().one(txn).await;
524
525 txn.transaction::<_, (), DbErr>(|txn| {
526 Box::pin(async move {
527 let _ = fruit::Entity::find().all(txn).await;
528
529 Ok(())
530 })
531 })
532 .await
533 .unwrap();
534
535 Ok(())
536 })
537 })
538 .await
539 .unwrap();
540
541 assert_eq!(
542 db.into_transaction_log(),
543 [Transaction::many([
544 Statement::from_string(DbBackend::Postgres, "BEGIN"),
545 Statement::from_sql_and_values(
546 DbBackend::Postgres,
547 r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#,
548 [1u64.into()]
549 ),
550 Statement::from_string(DbBackend::Postgres, "SAVEPOINT savepoint_1"),
551 Statement::from_sql_and_values(
552 DbBackend::Postgres,
553 r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit""#,
554 []
555 ),
556 Statement::from_string(DbBackend::Postgres, "RELEASE SAVEPOINT savepoint_1"),
557 Statement::from_string(DbBackend::Postgres, "COMMIT"),
558 ]),]
559 );
560 }
561
562 #[smol_potat::test]
563 async fn test_nested_transaction_2() {
564 let db = MockDatabase::new(DbBackend::Postgres).into_connection();
565
566 db.transaction::<_, (), DbErr>(|txn| {
567 Box::pin(async move {
568 let _ = cake::Entity::find().one(txn).await;
569
570 txn.transaction::<_, (), DbErr>(|txn| {
571 Box::pin(async move {
572 let _ = fruit::Entity::find().all(txn).await;
573
574 txn.transaction::<_, (), DbErr>(|txn| {
575 Box::pin(async move {
576 let _ = cake::Entity::find().all(txn).await;
577
578 Ok(())
579 })
580 })
581 .await
582 .unwrap();
583
584 Ok(())
585 })
586 })
587 .await
588 .unwrap();
589
590 Ok(())
591 })
592 })
593 .await
594 .unwrap();
595
596 assert_eq!(
597 db.into_transaction_log(),
598 [Transaction::many([
599 Statement::from_string(DbBackend::Postgres, "BEGIN"),
600 Statement::from_sql_and_values(
601 DbBackend::Postgres,
602 r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#,
603 [1u64.into()]
604 ),
605 Statement::from_string(DbBackend::Postgres, "SAVEPOINT savepoint_1"),
606 Statement::from_sql_and_values(
607 DbBackend::Postgres,
608 r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit""#,
609 []
610 ),
611 Statement::from_string(DbBackend::Postgres, "SAVEPOINT savepoint_2"),
612 Statement::from_sql_and_values(
613 DbBackend::Postgres,
614 r#"SELECT "cake"."id", "cake"."name" FROM "cake""#,
615 []
616 ),
617 Statement::from_string(DbBackend::Postgres, "RELEASE SAVEPOINT savepoint_2"),
618 Statement::from_string(DbBackend::Postgres, "RELEASE SAVEPOINT savepoint_1"),
619 Statement::from_string(DbBackend::Postgres, "COMMIT"),
620 ]),]
621 );
622 }
623
624 #[smol_potat::test]
625 async fn test_stream_1() -> Result<(), DbErr> {
626 use futures::TryStreamExt;
627
628 let apple = fruit::Model {
629 id: 1,
630 name: "Apple".to_owned(),
631 cake_id: Some(1),
632 };
633
634 let orange = fruit::Model {
635 id: 2,
636 name: "orange".to_owned(),
637 cake_id: None,
638 };
639
640 let db = MockDatabase::new(DbBackend::Postgres)
641 .append_query_results([[apple.clone(), orange.clone()]])
642 .into_connection();
643
644 let mut stream = fruit::Entity::find().stream(&db).await?;
645
646 assert_eq!(stream.try_next().await?, Some(apple));
647
648 assert_eq!(stream.try_next().await?, Some(orange));
649
650 assert_eq!(stream.try_next().await?, None);
651
652 Ok(())
653 }
654
655 #[smol_potat::test]
656 async fn test_stream_2() -> Result<(), DbErr> {
657 use fruit::Entity as Fruit;
658 use futures::TryStreamExt;
659
660 let db = MockDatabase::new(DbBackend::Postgres)
661 .append_query_results([Vec::<fruit::Model>::new()])
662 .into_connection();
663
664 let mut stream = Fruit::find().stream(&db).await?;
665
666 while let Some(item) = stream.try_next().await? {
667 let _item: fruit::ActiveModel = item.into();
668 }
669
670 Ok(())
671 }
672
673 #[smol_potat::test]
674 async fn test_stream_in_transaction() -> Result<(), DbErr> {
675 use futures::TryStreamExt;
676
677 let apple = fruit::Model {
678 id: 1,
679 name: "Apple".to_owned(),
680 cake_id: Some(1),
681 };
682
683 let orange = fruit::Model {
684 id: 2,
685 name: "orange".to_owned(),
686 cake_id: None,
687 };
688
689 let db = MockDatabase::new(DbBackend::Postgres)
690 .append_query_results([[apple.clone(), orange.clone()]])
691 .into_connection();
692
693 let txn = db.begin().await?;
694
695 if let Ok(mut stream) = fruit::Entity::find().stream(&txn).await {
696 assert_eq!(stream.try_next().await?, Some(apple));
697
698 assert_eq!(stream.try_next().await?, Some(orange));
699
700 assert_eq!(stream.try_next().await?, None);
701
702 }
704
705 txn.commit().await?;
706
707 Ok(())
708 }
709
710 #[smol_potat::test]
711 async fn test_mocked_join() {
712 let row = (
713 cake::Model {
714 id: 1,
715 name: "Apple Cake".to_owned(),
716 },
717 fruit::Model {
718 id: 2,
719 name: "Apple".to_owned(),
720 cake_id: Some(1),
721 },
722 );
723 let mocked_row = row.into_mock_row();
724
725 let a_id = mocked_row.try_get::<i32, _>("A_id");
726 assert!(a_id.is_ok());
727 assert_eq!(1, a_id.unwrap());
728 let b_id = mocked_row.try_get::<i32, _>("B_id");
729 assert!(b_id.is_ok());
730 assert_eq!(2, b_id.unwrap());
731 }
732
733 #[smol_potat::test]
734 async fn test_find_also_related_1() -> Result<(), DbErr> {
735 let db = MockDatabase::new(DbBackend::Postgres)
736 .append_query_results([[(
737 cake::Model {
738 id: 1,
739 name: "Apple Cake".to_owned(),
740 },
741 fruit::Model {
742 id: 2,
743 name: "Apple".to_owned(),
744 cake_id: Some(1),
745 },
746 )]])
747 .into_connection();
748
749 assert_eq!(
750 cake::Entity::find()
751 .find_also_related(fruit::Entity)
752 .all(&db)
753 .await?,
754 [(
755 cake::Model {
756 id: 1,
757 name: "Apple Cake".to_owned(),
758 },
759 Some(fruit::Model {
760 id: 2,
761 name: "Apple".to_owned(),
762 cake_id: Some(1),
763 })
764 )]
765 );
766
767 assert_eq!(
768 db.into_transaction_log(),
769 [Transaction::from_sql_and_values(
770 DbBackend::Postgres,
771 r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name", "fruit"."id" AS "B_id", "fruit"."name" AS "B_name", "fruit"."cake_id" AS "B_cake_id" FROM "cake" LEFT JOIN "fruit" ON "cake"."id" = "fruit"."cake_id""#,
772 []
773 ),]
774 );
775
776 Ok(())
777 }
778
779 #[cfg(feature = "postgres-array")]
780 #[smol_potat::test]
781 async fn test_postgres_array_1() -> Result<(), DbErr> {
782 mod collection {
783 use crate as sea_orm;
784 use crate::entity::prelude::*;
785
786 #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
787 #[sea_orm(table_name = "collection")]
788 pub struct Model {
789 #[sea_orm(primary_key)]
790 pub id: i32,
791 pub integers: Vec<i32>,
792 pub integers_opt: Option<Vec<i32>>,
793 }
794
795 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
796 pub enum Relation {}
797
798 impl ActiveModelBehavior for ActiveModel {}
799 }
800
801 let db = MockDatabase::new(DbBackend::Postgres)
802 .append_query_results([[
803 collection::Model {
804 id: 1,
805 integers: vec![1, 2, 3],
806 integers_opt: Some(vec![1, 2, 3]),
807 },
808 collection::Model {
809 id: 2,
810 integers: vec![],
811 integers_opt: Some(vec![]),
812 },
813 collection::Model {
814 id: 3,
815 integers: vec![3, 1, 4],
816 integers_opt: None,
817 },
818 ]])
819 .into_connection();
820
821 assert_eq!(
822 collection::Entity::find().all(&db).await?,
823 [
824 collection::Model {
825 id: 1,
826 integers: vec![1, 2, 3],
827 integers_opt: Some(vec![1, 2, 3]),
828 },
829 collection::Model {
830 id: 2,
831 integers: vec![],
832 integers_opt: Some(vec![]),
833 },
834 collection::Model {
835 id: 3,
836 integers: vec![3, 1, 4],
837 integers_opt: None,
838 },
839 ]
840 );
841
842 assert_eq!(
843 db.into_transaction_log(),
844 [Transaction::from_sql_and_values(
845 DbBackend::Postgres,
846 r#"SELECT "collection"."id", "collection"."integers", "collection"."integers_opt" FROM "collection""#,
847 []
848 ),]
849 );
850
851 Ok(())
852 }
853
854 #[smol_potat::test]
855 async fn test_query_err() {
856 let db = MockDatabase::new(DbBackend::MySql)
857 .append_query_errors([query_err("this is a mock query error")])
858 .into_connection();
859
860 assert_eq!(
861 cake::Entity::find().all(&db).await,
862 Err(query_err("this is a mock query error"))
863 );
864 }
865
866 #[smol_potat::test]
867 async fn test_exec_err() {
868 let db = MockDatabase::new(DbBackend::MySql)
869 .append_exec_errors([exec_err("this is a mock exec error")])
870 .into_connection();
871
872 let model = cake::ActiveModel::new();
873
874 assert_eq!(
875 model.save(&db).await,
876 Err(exec_err("this is a mock exec error"))
877 );
878 }
879}