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