How to add test to check that SeaORM table is correct?

In this example there is a test to check that a table made with SeaORM syntax is correct. I want to use a test like that in a code base very similar to the one from the SeaORM tutorial.

./Cargo.toml

[package]
name = "media_wiki"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
futures = "0.3.21"
sea-orm = { version = "^0.9.0", features = [ "sqlx-sqlite", "runtime-async-std-native-tls", "macros" ] }
sea-orm-migration = "^0.9.0"

./src/main.rs

mod migrator;

use futures::executor::block_on;
use sea_orm::{ConnectionTrait, Database, DbBackend, DbErr, Statement};
use sea_orm_migration::prelude::*;

const DATABASE_URL: &str = "sqlite:./sqlite.db?mode=rwc";

async fn run() -> Result<(), DbErr> {
    let db = Database::connect(DATABASE_URL).await?;

    let db_name = "media_wiki_db";
    let db = &match db.get_database_backend() {
        DbBackend::MySql => {
            db.execute(Statement::from_string(
                db.get_database_backend(),
                format!("CREATE DATABASE IF NOT EXISTS `{}`;", db_name),
            ))
            .await?;
 
            let url = format!("{}/{}", DATABASE_URL, db_name);
            Database::connect(&url).await?
        }
        DbBackend::Postgres => {
            db.execute(Statement::from_string(
                db.get_database_backend(),
                format!("DROP DATABASE IF EXISTS \"{}\";", db_name),
            ))
            .await?;
            db.execute(Statement::from_string(
                db.get_database_backend(),
                format!("CREATE DATABASE \"{}\";", db_name),
            ))
            .await?;
 
            let url = format!("{}/{}", DATABASE_URL, db_name);
            Database::connect(&url).await?
        }
        DbBackend::Sqlite => db,
    };

    let schema_manager = SchemaManager::new(db); // To investigate the schema

    migrator::Migrator::refresh(db).await?;
    assert!(schema_manager.has_table("File").await?);
    assert!(schema_manager.has_table("FileName").await?);

    Ok(())
}

fn main() {
    if let Err(err) = block_on(run()) {
        panic!("{}", err);
    }
}

./src/migrator/mod.rs

use sea_orm_migration::prelude::*;

mod m20220726_000001_create_schema;

pub struct Migrator;

#[async_trait::async_trait]
impl MigratorTrait for Migrator {
    fn migrations() -> Vec<Box<dyn MigrationTrait>> {
        vec![
            Box::new(m20220726_000001_create_schema::Migration),
        ]
    }
}

./src/migrator/m20220726_000001_create_schema.rs

use sea_orm_migration::prelude::*;

pub struct Migration;

impl MigrationName for Migration {
    fn name(&self) -> &str {
        "m_20220726_000001_create_schema"
    }
}

#[async_trait::async_trait]
impl MigrationTrait for Migration {
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .create_table(file_table())
            .create_table(file_name_table())
            .await
    }
    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .drop_table(Table::drop().table(File::Table).to_owned())
            .drop_table(Table::drop().table(FileName::Table).to_owned())
            .await
    }
}

#[derive(Iden)]
pub enum File {
    Table,
    Id,
    Rating,
}

#[derive(Iden)]
pub enum FileName {
    Table,
    Id,
    Name,
    Lang,
    FileId,
}

fn file_table() -> TableCreateStatement {
    Table::create()
        .table(File::Table)
        .if_not_exists()
        .col(
            ColumnDef::new(File::Id)
                .integer()
                .not_null()
                .auto_increment()
                .primary_key(),
        )
        .col(ColumnDef::new(File::Rating).double())
        .to_owned()
}

fn file_name_table() -> TableCreateStatement {
    Table::create()
        .table(FileName::Table)
        .if_not_exists()
        .col(
            ColumnDef::new(FileName::Id)
                .integer()
                .not_null()
                .auto_increment()
                .primary_key(),
        )
        .col(
            ColumnDef::new(FileName::Name)
                .text()
                .not_null(),
        )
        .col(ColumnDef::new(FileName::Lang)
                .text()
                .not_null(),
        )
        .col(
            ColumnDef::new(FileName::FileId)
                .integer()
                .not_null(),
        )
        .foreign_key(
            ForeignKey::create()
                .name("fk_file-name_file")
                .from(FileName::Table, FileName::FileId)
                .to(File::Table, File::Id)
                .on_delete(ForeignKeyAction::Cascade)
                .on_update(ForeignKeyAction::Cascade)
        )
        .to_owned()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_create_file_table() {
        assert_eq!(
            file_table().to_string(SqliteQueryBuilder),
            vec![
            r#"CREATE TABLE IF NOT EXISTS "File" ("#,
                r#""Id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,"#,
                r#""Rating" double,"#,
            r#")"#,
            ].join(" ")
        );
    }

    #[test]
    fn test_create_file_name_table() {
        assert_eq!(
            file_name_table().to_string(SqliteQueryBuilder),
            vec![
            r#"CREATE TABLE IF NOT EXISTS "FileName" ("#,
                r#""Id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,"#,
                r#""Name" text NOT NULL,"#,
                r#""Lang" text NOT NULL,"#,
                r#""FileId" integer NOT NULL,"#,
            r#")"#,
            ].join(" ")
        );
    }
}

When I run cargo test I get the following error:

❯ cargo test
   Compiling media_wiki v0.1.0 (/home/user/code/rust/media_wiki)
error[E0599]: no method named `create_table` found for opaque type `impl futures::Future<Output = Result<(), sea_orm_migration::DbErr>>` in the current scope
  --> src/migrator/m20220726_000001_create_schema.rs:16:14
   |
16 |             .create_table(file_name_table())
   |              ^^^^^^^^^^^^ method not found in `impl futures::Future<Output = Result<(), sea_orm_migration::DbErr>>`

error[E0599]: no method named `drop_table` found for opaque type `impl futures::Future<Output = Result<(), sea_orm_migration::DbErr>>` in the current scope
  --> src/migrator/m20220726_000001_create_schema.rs:22:14
   |
22 |             .drop_table(Table::drop().table(FileName::Table).to_owned())
   |              ^^^^^^^^^^ method not found in `impl futures::Future<Output = Result<(), sea_orm_migration::DbErr>>`

For more information about this error, try `rustc --explain E0599`.
error: could not compile `media_wiki` due to 2 previous errors

You're treating &SchemaManager as if it follows the builder pattern, but the compiler is telling you instead that create_table returns a Future that resolves to <Result<(), DbErr>. That means that you have to make each call to create_table on a separate line, without chaining them together:

        manager.create_table(file_table()).await?;
        manager.create_table(file_name_table()).await

Same thing for drop_table.

1 Like

Thanks.

Here is the corrected code

./Cargo.toml

[package]
name = "media_wiki"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
futures = "0.3.21"
sea-orm = { version = "^0.9.0", features = [ "sqlx-sqlite", "runtime-async-std-native-tls", "macros" ] }
sea-orm-migration = "^0.9.0"

./src/main.rs

mod migrator;

use futures::executor::block_on;
use sea_orm::{ConnectionTrait, Database, DbBackend, DbErr, Statement};
use sea_orm_migration::prelude::*;

const DATABASE_URL: &str = "sqlite:./sqlite.db?mode=rwc";

async fn run() -> Result<(), DbErr> {
    let db = Database::connect(DATABASE_URL).await?;

    let db_name = "media_wiki_db";
    let db = &match db.get_database_backend() {
        DbBackend::MySql => {
            db.execute(Statement::from_string(
                db.get_database_backend(),
                format!("CREATE DATABASE IF NOT EXISTS `{}`;", db_name),
            ))
            .await?;
 
            let url = format!("{}/{}", DATABASE_URL, db_name);
            Database::connect(&url).await?
        }
        DbBackend::Postgres => {
            db.execute(Statement::from_string(
                db.get_database_backend(),
                format!("DROP DATABASE IF EXISTS \"{}\";", db_name),
            ))
            .await?;
            db.execute(Statement::from_string(
                db.get_database_backend(),
                format!("CREATE DATABASE \"{}\";", db_name),
            ))
            .await?;
 
            let url = format!("{}/{}", DATABASE_URL, db_name);
            Database::connect(&url).await?
        }
        DbBackend::Sqlite => db,
    };

    let schema_manager = SchemaManager::new(db); // To investigate the schema

    migrator::Migrator::refresh(db).await?;
    assert!(schema_manager.has_table("File").await?);
    assert!(schema_manager.has_table("FileName").await?);

    Ok(())
}

fn main() {
    if let Err(err) = block_on(run()) {
        panic!("{}", err);
    }
}

./src/migrator/mod.rs

use sea_orm_migration::prelude::*;

mod m20220726_000001_create_schema;

pub struct Migrator;

#[async_trait::async_trait]
impl MigratorTrait for Migrator {
    fn migrations() -> Vec<Box<dyn MigrationTrait>> {
        vec![
            Box::new(m20220726_000001_create_schema::Migration),
        ]
    }
}

./src/migrator/m20220726_000001_create_schema.rs

use sea_orm_migration::prelude::*;

pub struct Migration;

impl MigrationName for Migration {
    fn name(&self) -> &str {
        "m_20220726_000001_create_schema"
    }
}

#[async_trait::async_trait]
impl MigrationTrait for Migration {
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager.create_table(file_table()).await?;
        manager.create_table(file_name_table()).await
    }
    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager.drop_table(Table::drop().table(File::Table).to_owned()).await?;
        manager.drop_table(Table::drop().table(FileName::Table).to_owned()).await
    }
}

#[derive(Iden)]
pub enum File {
    Table,
    Id,
    Rating,
}

#[derive(Iden)]
pub enum FileName {
    Table,
    Id,
    Name,
    Lang,
    FileId,
}

fn file_table() -> TableCreateStatement {
    Table::create()
        .table(File::Table)
        .if_not_exists()
        .col(
            ColumnDef::new(File::Id)
                .integer()
                .not_null()
                .auto_increment()
                .primary_key(),
        )
        .col(ColumnDef::new(File::Rating).double())
        .to_owned()
}

fn file_name_table() -> TableCreateStatement {
    Table::create()
        .table(FileName::Table)
        .if_not_exists()
        .col(
            ColumnDef::new(FileName::Id)
                .integer()
                .not_null()
                .auto_increment()
                .primary_key(),
        )
        .col(
            ColumnDef::new(FileName::Name)
                .text()
                .not_null(),
        )
        .col(ColumnDef::new(FileName::Lang)
                .text()
                .not_null(),
        )
        .col(
            ColumnDef::new(FileName::FileId)
                .integer()
                .not_null(),
        )
        .foreign_key(
            ForeignKey::create()
                .name("fk_file-name_file")
                .from(FileName::Table, FileName::FileId)
                .to(File::Table, File::Id)
                .on_delete(ForeignKeyAction::Cascade)
                .on_update(ForeignKeyAction::Cascade)
        )
        .to_owned()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_create_file_table() {
        assert_eq!(
            file_table().to_string(SqliteQueryBuilder),
            vec![
            r#"CREATE TABLE IF NOT EXISTS "file" ("#,
                r#""id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,"#,
                r#""rating" real"#,
            r#")"#,
            ].join(" ")
        );
    }

    #[test]
    fn test_create_file_name_table() {
        assert_eq!(
            file_name_table().to_string(SqliteQueryBuilder),
            vec![
            r#"CREATE TABLE IF NOT EXISTS "file_name" ("#,
                r#""id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,"#,
                r#""name" text NOT NULL,"#,
                r#""lang" text NOT NULL,"#,
                r#""file_id" integer NOT NULL,"#,
                r#"FOREIGN KEY ("file_id") REFERENCES "file" ("id") ON DELETE CASCADE ON UPDATE CASCADE"#,
            r#")"#,
            ].join(" ")
        );
    }
}