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
use ::sea_orm_migration::prelude::MigratorTrait;

#[cfg(feature = "tokio")]
use ::tokio::runtime::Builder;

use crate::queries::get_table_schemas;
use crate::queries::new_test_db_connection;
use crate::queries::TableSchema;

///
/// Runs a given `Migrator` against a new database.
/// It's migrations will be run up and down in series.
///
/// If a Migration differs when going down, an error will be raised.
///
/// Note for performance reasons, this works in reverse order of migrations.
///
#[cfg(feature = "tokio")]
pub fn assert_migrator_reversible<M>(migrator: M)
where
    M: MigratorTrait,
{
    Builder::new_current_thread()
        .enable_time()
        .build()
        .expect("Expect to be able to start Tokio runtime for testing")
        .block_on(async move { assert_migrator_reversible_async(migrator).await });
}

///
/// This is an `async` version of `assert_migrator_reversible`.
///
pub async fn assert_migrator_reversible_async<M>(migrator: M)
where
    M: MigratorTrait,
{
    let maybe_index = find_index_of_non_reversible_migration_async(migrator).await;
    if let Some(index) = maybe_index {
        panic!("Migration at index {} is not reversible", index);
    }
}

///
/// Returns the index of the first migration it can find, which is not
/// reversible.
///
/// `None` is returned if they are all reversible.
///
/// Note for performance reasons, this will check migrations in reverse order.
///
/*
 * The plan is to use this in build tests.
 * So most of the time we should expect the test to pass.
 * We optimise for this event.
 *
 * The fast algorithm I know of ...
 *  - Run each migration in order, and store the structure as we go up.
 *  - Then run each migration down. Find the first that doesn't match.
 *  - This results in searching in reverse order.
 *
 */
#[cfg(feature = "tokio")]
pub fn find_index_of_non_reversible_migration<M>(migrator: M) -> Option<usize>
where
    M: MigratorTrait,
{
    Builder::new_current_thread()
        .enable_time()
        .build()
        .expect("Expect to be able to start Tokio runtime for testing")
        .block_on(async move { find_index_of_non_reversible_migration_async(migrator).await })
}

///
/// This is an `async` version of `find_index_of_non_reversible_migration`.
///
pub async fn find_index_of_non_reversible_migration_async<M>(_migrator: M) -> Option<usize>
where
    M: MigratorTrait,
{
    // Create temp file.
    let db_connection = new_test_db_connection().await;

    let num_migrations = M::migrations().len();
    let mut migration_step_schemas: Vec<Vec<TableSchema>> = Vec::with_capacity(num_migrations);

    // Go up all migrations.
    for _ in 0..num_migrations {
        let table_schemas = get_table_schemas(&db_connection).await;
        migration_step_schemas.push(table_schemas);

        <M as MigratorTrait>::up(&db_connection, Some(1))
            .await
            .expect("expect migration up should succeed");
    }

    // Go down all migrations.
    for i in 0..num_migrations {
        <M as MigratorTrait>::down(&db_connection, Some(1))
            .await
            .expect("expect migration down should succeed");

        let down_table_schemas = get_table_schemas(&db_connection).await;
        let up_table_schemas = migration_step_schemas
            .pop()
            .expect("expect up table schemas should exist");

        if down_table_schemas != up_table_schemas {
            return Some(i);
        }
    }

    None
}