From 17824411c9eb891d58652be5f7def0521f6c133f Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Sat, 3 Jul 2021 00:02:45 +0800 Subject: [PATCH] Unit test sea-orm-codegen --- .github/workflows/rust.yml | 1 + Cargo.toml | 6 +- sea-orm-codegen/Cargo.toml | 4 + sea-orm-codegen/src/entity/column.rs | 193 +++++++++++++- sea-orm-codegen/src/entity/entity.rs | 241 +++++++++++++++++- sea-orm-codegen/src/entity/primary_key.rs | 25 ++ sea-orm-codegen/src/entity/relation.rs | 96 ++++++- sea-orm-codegen/src/entity/transformer.rs | 1 + sea-orm-codegen/src/entity/writer.rs | 252 ++++++++++++++++++- sea-orm-codegen/tests/entity/cake.rs | 74 ++++++ sea-orm-codegen/tests/entity/cake_filling.rs | 81 ++++++ sea-orm-codegen/tests/entity/filling.rs | 66 +++++ sea-orm-codegen/tests/entity/fruit.rs | 80 ++++++ sea-orm-codegen/tests/entity/mod.rs | 7 + sea-orm-codegen/tests/entity/prelude.rs | 7 + sea-orm-codegen/tests/entity/vendor.rs | 72 ++++++ sea-orm-codegen/tests/mod.rs | 127 ++++++++++ 17 files changed, 1298 insertions(+), 35 deletions(-) create mode 100644 sea-orm-codegen/tests/entity/cake.rs create mode 100644 sea-orm-codegen/tests/entity/cake_filling.rs create mode 100644 sea-orm-codegen/tests/entity/filling.rs create mode 100644 sea-orm-codegen/tests/entity/fruit.rs create mode 100644 sea-orm-codegen/tests/entity/mod.rs create mode 100644 sea-orm-codegen/tests/entity/prelude.rs create mode 100644 sea-orm-codegen/tests/entity/vendor.rs create mode 100644 sea-orm-codegen/tests/mod.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8853cca30..4ce00b596 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -31,3 +31,4 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test + args: --all diff --git a/Cargo.toml b/Cargo.toml index 88bdfccc3..665ea0d8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ futures = { version = "^0.3" } futures-util = { version = "^0.3" } sea-query = { version = "^0.12" } sea-orm-macros = { path = "sea-orm-macros", optional = true } +sea-orm-codegen = { path = "sea-orm-codegen", optional = true } serde = { version = "^1.0", features = [ "derive" ] } sqlx = { version = "^0.5", optional = true } strum = { git = "https://github.com/SeaQL/strum.git", branch = "sea-orm", version = "^0.21", features = [ "derive", "sea-orm" ] } @@ -49,8 +50,9 @@ sea-orm = { path = ".", features = ["sqlx-sqlite", "sqlx-json", "sqlx-chrono", " [features] debug-print = [] -default = [ "macros", "with-json", "with-chrono", "with-rust_decimal", "mock" ] +default = [ "macros", "codegen", "with-json", "with-chrono", "with-rust_decimal", "mock" ] macros = [ "sea-orm-macros" ] +codegen = [ "sea-orm-codegen" ] mock = [] with-json = [ "serde_json", "sea-query/with-json" ] with-chrono = [ "chrono", "sea-query/with-chrono" ] @@ -67,4 +69,4 @@ runtime-async-std-native-tls = [ "sqlx/runtime-async-std-native-tls" ] runtime-tokio-native-tls = [ "sqlx/runtime-tokio-native-tls" ] runtime-actix-rustls = [ "sqlx/runtime-actix-rustls" ] runtime-async-std-rustls = [ "sqlx/runtime-async-std-rustls" ] -runtime-tokio-rustls = [ "sqlx/runtime-tokio-rustls" ] \ No newline at end of file +runtime-tokio-rustls = [ "sqlx/runtime-tokio-rustls" ] diff --git a/sea-orm-codegen/Cargo.toml b/sea-orm-codegen/Cargo.toml index a72a366a8..100314c95 100644 --- a/sea-orm-codegen/Cargo.toml +++ b/sea-orm-codegen/Cargo.toml @@ -23,3 +23,7 @@ syn = { version = "^1", default-features = false, features = [ "derive", "parsin quote = "^1" heck = "^0.3" proc-macro2 = "^1" + +[dev-dependencies] +async-std = { version = "^1.9", features = [ "attributes" ] } +sea-orm = { path = "../", features = ["mock", "sqlx-json", "sqlx-chrono", "runtime-async-std-native-tls"] } diff --git a/sea-orm-codegen/src/entity/column.rs b/sea-orm-codegen/src/entity/column.rs index e077c0a6a..2dbeab521 100644 --- a/sea-orm-codegen/src/entity/column.rs +++ b/sea-orm-codegen/src/entity/column.rs @@ -22,7 +22,7 @@ impl Column { } pub fn get_rs_type(&self) -> TokenStream { - let ident = match self.col_type { + let ident: TokenStream = match self.col_type { ColumnType::Char(_) | ColumnType::String(_) | ColumnType::Text @@ -32,18 +32,18 @@ impl Column { | ColumnType::Date | ColumnType::Json | ColumnType::JsonBinary - | ColumnType::Custom(_) => format_ident!("String"), - ColumnType::TinyInteger(_) => format_ident!("i8"), - ColumnType::SmallInteger(_) => format_ident!("i16"), - ColumnType::Integer(_) => format_ident!("i32"), - ColumnType::BigInteger(_) => format_ident!("i64"), - ColumnType::Float(_) | ColumnType::Decimal(_) | ColumnType::Money(_) => { - format_ident!("f32") - } - ColumnType::Double(_) => format_ident!("f64"), - ColumnType::Binary(_) => format_ident!("Vec"), - ColumnType::Boolean => format_ident!("bool"), - }; + | ColumnType::Custom(_) => "String", + ColumnType::TinyInteger(_) => "i8", + ColumnType::SmallInteger(_) => "i16", + ColumnType::Integer(_) => "i32", + ColumnType::BigInteger(_) => "i64", + ColumnType::Float(_) | ColumnType::Decimal(_) | ColumnType::Money(_) => "f32", + ColumnType::Double(_) => "f64", + ColumnType::Binary(_) => "Vec", + ColumnType::Boolean => "bool", + } + .parse() + .unwrap(); match self.not_null { true => quote! { #ident }, false => quote! { Option<#ident> }, @@ -102,6 +102,12 @@ impl Column { } } +impl From for Column { + fn from(col_def: ColumnDef) -> Self { + (&col_def).into() + } +} + impl From<&ColumnDef> for Column { fn from(col_def: &ColumnDef) -> Self { let name = col_def.get_column_name(); @@ -145,3 +151,164 @@ impl From<&ColumnDef> for Column { } } } + +#[cfg(test)] +mod tests { + use crate::Column; + use proc_macro2::TokenStream; + use quote::quote; + use sea_query::{Alias, ColumnDef, ColumnType, SeaRc}; + + fn setup() -> Vec { + macro_rules! make_col { + ($name:expr, $col_type:expr) => { + Column { + name: $name.to_owned(), + col_type: $col_type, + auto_increment: false, + not_null: false, + unique: false, + } + }; + } + vec![ + make_col!("id", ColumnType::String(Some(255))), + make_col!( + "cake_id", + ColumnType::Custom(SeaRc::new(Alias::new("cus_col"))) + ), + make_col!("CakeId", ColumnType::TinyInteger(None)), + make_col!("CakeId", ColumnType::SmallInteger(None)), + make_col!("CakeId", ColumnType::Integer(Some(11))), + make_col!("CakeFillingId", ColumnType::BigInteger(None)), + make_col!("cake-filling-id", ColumnType::Float(None)), + make_col!("CAKE_FILLING_ID", ColumnType::Double(None)), + make_col!("CAKE-FILLING-ID", ColumnType::Binary(None)), + make_col!("CAKE", ColumnType::Boolean), + ] + } + + #[test] + fn test_get_name_snake_case() { + let columns = setup(); + let snack_cases = vec![ + "id", + "cake_id", + "cake_id", + "cake_id", + "cake_id", + "cake_filling_id", + "cake_filling_id", + "cake_filling_id", + "cake_filling_id", + "cake", + ]; + for (col, snack_case) in columns.into_iter().zip(snack_cases) { + assert_eq!(col.get_name_snake_case().to_string(), snack_case); + } + } + + #[test] + fn test_get_name_camel_case() { + let columns = setup(); + let camel_cases = vec![ + "Id", + "CakeId", + "CakeId", + "CakeId", + "CakeId", + "CakeFillingId", + "CakeFillingId", + "CakeFillingId", + "CakeFillingId", + "Cake", + ]; + for (col, camel_case) in columns.into_iter().zip(camel_cases) { + assert_eq!(col.get_name_camel_case().to_string(), camel_case); + } + } + + #[test] + fn test_get_rs_type() { + let columns = setup(); + let rs_types = vec![ + "String", "String", "i8", "i16", "i32", "i64", "f32", "f64", "Vec", "bool", + ]; + for (mut col, rs_type) in columns.into_iter().zip(rs_types) { + let rs_type: TokenStream = rs_type.parse().unwrap(); + + col.not_null = true; + assert_eq!(col.get_rs_type().to_string(), quote!(#rs_type).to_string()); + + col.not_null = false; + assert_eq!( + col.get_rs_type().to_string(), + quote!(Option<#rs_type>).to_string() + ); + } + } + + #[test] + fn test_get_def() { + let columns = setup(); + let col_defs = vec![ + "ColumnType::String(Some(255u32)).def()", + "ColumnType::Custom(\"cus_col\".to_owned()).def()", + "ColumnType::TinyInteger.def()", + "ColumnType::SmallInteger.def()", + "ColumnType::Integer.def()", + "ColumnType::BigInteger.def()", + "ColumnType::Float.def()", + "ColumnType::Double.def()", + "ColumnType::Binary.def()", + "ColumnType::Boolean.def()", + ]; + for (mut col, col_def) in columns.into_iter().zip(col_defs) { + let mut col_def: TokenStream = col_def.parse().unwrap(); + + col.not_null = true; + assert_eq!(col.get_def().to_string(), col_def.to_string()); + + col.not_null = false; + col_def.extend(quote!(.null())); + assert_eq!(col.get_def().to_string(), col_def.to_string()); + + col.unique = true; + col_def.extend(quote!(.unique())); + assert_eq!(col.get_def().to_string(), col_def.to_string()); + } + } + + #[test] + fn test_from_column_def() { + let column: Column = ColumnDef::new(Alias::new("id")).string().into(); + assert_eq!( + column.get_def().to_string(), + quote! { + ColumnType::String(None).def().null() + } + .to_string() + ); + + let column: Column = ColumnDef::new(Alias::new("id")).string().not_null().into(); + assert!(column.not_null); + + let column: Column = ColumnDef::new(Alias::new("id")) + .string() + .unique_key() + .not_null() + .into(); + assert!(column.unique); + assert!(column.not_null); + + let column: Column = ColumnDef::new(Alias::new("id")) + .string() + .auto_increment() + .unique_key() + .not_null() + .into(); + assert!(column.auto_increment); + assert!(column.unique); + assert!(column.not_null); + } +} diff --git a/sea-orm-codegen/src/entity/entity.rs b/sea-orm-codegen/src/entity/entity.rs index 2abb4a7ab..aeaed68d3 100644 --- a/sea-orm-codegen/src/entity/entity.rs +++ b/sea-orm-codegen/src/entity/entity.rs @@ -111,15 +111,242 @@ impl Entity { .collect() } - pub fn get_relation_rel_find_helpers(&self) -> Vec { - self.relations - .iter() - .map(|rel| rel.get_rel_find_helper()) - .collect() - } - pub fn get_primary_key_auto_increment(&self) -> Ident { let auto_increment = self.columns.iter().any(|col| col.auto_increment); format_ident!("{}", auto_increment) } } + +#[cfg(test)] +mod tests { + use crate::{Column, Entity, PrimaryKey, Relation, RelationType}; + use quote::format_ident; + use sea_query::ColumnType; + + fn setup() -> Entity { + Entity { + table_name: "special_cake".to_owned(), + columns: vec![ + Column { + name: "id".to_owned(), + col_type: ColumnType::String(None), + auto_increment: false, + not_null: false, + unique: false, + }, + Column { + name: "name".to_owned(), + col_type: ColumnType::String(None), + auto_increment: false, + not_null: false, + unique: false, + }, + ], + relations: vec![ + Relation { + ref_table: "fruit".to_owned(), + columns: vec!["id".to_owned()], + ref_columns: vec!["cake_id".to_owned()], + rel_type: RelationType::HasOne, + }, + Relation { + ref_table: "filling".to_owned(), + columns: vec!["id".to_owned()], + ref_columns: vec!["cake_id".to_owned()], + rel_type: RelationType::HasOne, + }, + ], + primary_keys: vec![PrimaryKey { + name: "id".to_owned(), + }], + } + } + + #[test] + fn test_get_table_name_snake_case() { + let entity = setup(); + + assert_eq!( + entity.get_table_name_snake_case(), + "special_cake".to_owned() + ); + } + + #[test] + fn test_get_table_name_camel_case() { + let entity = setup(); + + assert_eq!(entity.get_table_name_camel_case(), "SpecialCake".to_owned()); + } + + #[test] + fn test_get_table_name_snake_case_ident() { + let entity = setup(); + + assert_eq!( + entity.get_table_name_snake_case_ident(), + format_ident!("{}", "special_cake") + ); + } + + #[test] + fn test_get_table_name_camel_case_ident() { + let entity = setup(); + + assert_eq!( + entity.get_table_name_camel_case_ident(), + format_ident!("{}", "SpecialCake") + ); + } + + #[test] + fn test_get_column_names_snake_case() { + let entity = setup(); + + for (i, elem) in entity.get_column_names_snake_case().into_iter().enumerate() { + assert_eq!(elem, entity.columns[i].get_name_snake_case()); + } + } + + #[test] + fn test_get_column_names_camel_case() { + let entity = setup(); + + for (i, elem) in entity.get_column_names_camel_case().into_iter().enumerate() { + assert_eq!(elem, entity.columns[i].get_name_camel_case()); + } + } + + #[test] + fn test_get_column_rs_types() { + let entity = setup(); + + for (i, elem) in entity.get_column_rs_types().into_iter().enumerate() { + assert_eq!( + elem.to_string(), + entity.columns[i].get_rs_type().to_string() + ); + } + } + + #[test] + fn test_get_column_defs() { + let entity = setup(); + + for (i, elem) in entity.get_column_defs().into_iter().enumerate() { + assert_eq!(elem.to_string(), entity.columns[i].get_def().to_string()); + } + } + + #[test] + fn test_get_primary_key_names_snake_case() { + let entity = setup(); + + for (i, elem) in entity + .get_primary_key_names_snake_case() + .into_iter() + .enumerate() + { + assert_eq!(elem, entity.primary_keys[i].get_name_snake_case()); + } + } + + #[test] + fn test_get_primary_key_names_camel_case() { + let entity = setup(); + + for (i, elem) in entity + .get_primary_key_names_camel_case() + .into_iter() + .enumerate() + { + assert_eq!(elem, entity.primary_keys[i].get_name_camel_case()); + } + } + + #[test] + fn test_get_relation_ref_tables_snake_case() { + let entity = setup(); + + for (i, elem) in entity + .get_relation_ref_tables_snake_case() + .into_iter() + .enumerate() + { + assert_eq!(elem, entity.relations[i].get_ref_table_snake_case()); + } + } + + #[test] + fn test_get_relation_ref_tables_camel_case() { + let entity = setup(); + + for (i, elem) in entity + .get_relation_ref_tables_camel_case() + .into_iter() + .enumerate() + { + assert_eq!(elem, entity.relations[i].get_ref_table_camel_case()); + } + } + + #[test] + fn test_get_relation_defs() { + let entity = setup(); + + for (i, elem) in entity.get_relation_defs().into_iter().enumerate() { + assert_eq!(elem.to_string(), entity.relations[i].get_def().to_string()); + } + } + + #[test] + fn test_get_relation_rel_types() { + let entity = setup(); + + for (i, elem) in entity.get_relation_rel_types().into_iter().enumerate() { + assert_eq!(elem, entity.relations[i].get_rel_type()); + } + } + + #[test] + fn test_get_relation_columns_camel_case() { + let entity = setup(); + + for (i, elem) in entity + .get_relation_columns_camel_case() + .into_iter() + .enumerate() + { + assert_eq!(elem, entity.relations[i].get_column_camel_case()); + } + } + + #[test] + fn test_get_relation_ref_columns_camel_case() { + let entity = setup(); + + for (i, elem) in entity + .get_relation_ref_columns_camel_case() + .into_iter() + .enumerate() + { + assert_eq!(elem, entity.relations[i].get_ref_column_camel_case()); + } + } + + #[test] + fn test_get_primary_key_auto_increment() { + let mut entity = setup(); + + assert_eq!( + entity.get_primary_key_auto_increment(), + format_ident!("{}", false) + ); + + entity.columns[0].auto_increment = true; + assert_eq!( + entity.get_primary_key_auto_increment(), + format_ident!("{}", true) + ); + } +} diff --git a/sea-orm-codegen/src/entity/primary_key.rs b/sea-orm-codegen/src/entity/primary_key.rs index 5efbc038e..3a7d6b171 100644 --- a/sea-orm-codegen/src/entity/primary_key.rs +++ b/sea-orm-codegen/src/entity/primary_key.rs @@ -16,3 +16,28 @@ impl PrimaryKey { format_ident!("{}", self.name.to_camel_case()) } } + +#[cfg(test)] +mod tests { + use crate::PrimaryKey; + + fn setup() -> PrimaryKey { + PrimaryKey { + name: "cake_id".to_owned(), + } + } + + #[test] + fn test_get_name_snake_case() { + let primary_key = setup(); + + assert_eq!(primary_key.get_name_snake_case(), "cake_id".to_owned()); + } + + #[test] + fn test_get_name_camel_case() { + let primary_key = setup(); + + assert_eq!(primary_key.get_name_camel_case(), "CakeId".to_owned()); + } +} diff --git a/sea-orm-codegen/src/entity/relation.rs b/sea-orm-codegen/src/entity/relation.rs index 8eab372d3..d675137ab 100644 --- a/sea-orm-codegen/src/entity/relation.rs +++ b/sea-orm-codegen/src/entity/relation.rs @@ -64,10 +64,6 @@ impl Relation { pub fn get_ref_column_camel_case(&self) -> Ident { format_ident!("{}", self.ref_columns[0].to_camel_case()) } - - pub fn get_rel_find_helper(&self) -> Ident { - format_ident!("find_{}", self.ref_table.to_snake_case()) - } } impl From<&TableForeignKey> for Relation { @@ -87,3 +83,95 @@ impl From<&TableForeignKey> for Relation { } } } + +#[cfg(test)] +mod tests { + use crate::{Relation, RelationType}; + use proc_macro2::TokenStream; + + fn setup() -> Vec { + vec![ + Relation { + ref_table: "fruit".to_owned(), + columns: vec!["id".to_owned()], + ref_columns: vec!["cake_id".to_owned()], + rel_type: RelationType::HasOne, + }, + Relation { + ref_table: "filling".to_owned(), + columns: vec!["filling_id".to_owned()], + ref_columns: vec!["id".to_owned()], + rel_type: RelationType::BelongsTo, + }, + Relation { + ref_table: "filling".to_owned(), + columns: vec!["filling_id".to_owned()], + ref_columns: vec!["id".to_owned()], + rel_type: RelationType::HasMany, + }, + ] + } + + #[test] + fn test_get_ref_table_snake_case() { + let relations = setup(); + let snake_cases = vec!["fruit", "filling", "filling"]; + for (rel, snake_case) in relations.into_iter().zip(snake_cases) { + assert_eq!(rel.get_ref_table_snake_case().to_string(), snake_case); + } + } + + #[test] + fn test_get_ref_table_camel_case() { + let relations = setup(); + let camel_cases = vec!["Fruit", "Filling", "Filling"]; + for (rel, camel_case) in relations.into_iter().zip(camel_cases) { + assert_eq!(rel.get_ref_table_camel_case().to_string(), camel_case); + } + } + + #[test] + fn test_get_def() { + let relations = setup(); + let rel_defs = vec![ + "Entity::has_one(super::fruit::Entity).into()", + "Entity::belongs_to(super::filling::Entity) \ + .from(Column::FillingId) \ + .to(super::filling::Column::Id) \ + .into()", + "Entity::has_many(super::filling::Entity).into()", + ]; + for (rel, rel_def) in relations.into_iter().zip(rel_defs) { + let rel_def: TokenStream = rel_def.parse().unwrap(); + + assert_eq!(rel.get_def().to_string(), rel_def.to_string()); + } + } + + #[test] + fn test_get_rel_type() { + let relations = setup(); + let rel_types = vec!["has_one", "belongs_to", "has_many"]; + for (rel, rel_type) in relations.into_iter().zip(rel_types) { + assert_eq!(rel.get_rel_type(), rel_type); + } + } + + #[test] + fn test_get_column_camel_case() { + let relations = setup(); + let cols = vec!["Id", "FillingId", "FillingId"]; + for (rel, col) in relations.into_iter().zip(cols) { + assert_eq!(rel.get_column_camel_case(), col); + } + } + + #[test] + fn test_get_ref_column_camel_case() { + let relations = setup(); + let ref_cols = vec!["CakeId", "Id", "Id"]; + for (rel, ref_col) in relations.into_iter().zip(ref_cols) { + assert_eq!(rel.get_ref_column_camel_case(), ref_col); + } + } +} diff --git a/sea-orm-codegen/src/entity/transformer.rs b/sea-orm-codegen/src/entity/transformer.rs index c49a871e7..ad48d518d 100644 --- a/sea-orm-codegen/src/entity/transformer.rs +++ b/sea-orm-codegen/src/entity/transformer.rs @@ -98,6 +98,7 @@ impl EntityTransformer { } } } + println!("{:#?}", entities); Ok(EntityWriter { entities }) } } diff --git a/sea-orm-codegen/src/entity/writer.rs b/sea-orm-codegen/src/entity/writer.rs index 3bd8fb597..267b00d60 100644 --- a/sea-orm-codegen/src/entity/writer.rs +++ b/sea-orm-codegen/src/entity/writer.rs @@ -116,9 +116,7 @@ impl EntityWriter { Self::gen_impl_relation_trait(entity), ]; code_blocks.extend(Self::gen_impl_related(entity)); - code_blocks.extend(vec![ - Self::gen_impl_active_model_behavior(), - ]); + code_blocks.extend(vec![Self::gen_impl_active_model_behavior()]); code_blocks } @@ -152,7 +150,7 @@ impl EntityWriter { quote! { #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] pub struct Model { - #(pub #column_names_snake_case: #column_rs_types),* + #(pub #column_names_snake_case: #column_rs_types,)* } } } @@ -162,7 +160,7 @@ impl EntityWriter { quote! { #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { - #(#column_names_camel_case),* + #(#column_names_camel_case,)* } } } @@ -172,7 +170,7 @@ impl EntityWriter { quote! { #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { - #(#primary_key_names_camel_case),* + #(#primary_key_names_camel_case,)* } } } @@ -193,7 +191,7 @@ impl EntityWriter { quote! { #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { - #(#relation_ref_tables_camel_case),* + #(#relation_ref_tables_camel_case,)* } } } @@ -207,7 +205,7 @@ impl EntityWriter { fn def(&self) -> ColumnDef { match self { - #(Self::#column_names_camel_case => #column_defs),* + #(Self::#column_names_camel_case => #column_defs,)* } } } @@ -223,7 +221,7 @@ impl EntityWriter { } } else { quote! { - #(Self::#relation_ref_tables_camel_case => #relation_defs),* + #(Self::#relation_ref_tables_camel_case => #relation_defs,)* } }; quote! { @@ -276,3 +274,239 @@ impl EntityWriter { } } } + +#[cfg(test)] +mod tests { + use crate::{Column, Entity, EntityWriter, PrimaryKey, Relation, RelationType}; + use proc_macro2::TokenStream; + use sea_query::ColumnType; + use std::io::{self, BufRead, BufReader}; + + const ENTITY_FILES: [&'static str; 5] = [ + include_str!("../../tests/entity/cake.rs"), + include_str!("../../tests/entity/cake_filling.rs"), + include_str!("../../tests/entity/filling.rs"), + include_str!("../../tests/entity/fruit.rs"), + include_str!("../../tests/entity/vendor.rs"), + ]; + + fn setup() -> Vec { + vec![ + Entity { + table_name: "cake".to_owned(), + columns: vec![ + Column { + name: "id".to_owned(), + col_type: ColumnType::Integer(Some(11)), + auto_increment: true, + not_null: true, + unique: false, + }, + Column { + name: "name".to_owned(), + col_type: ColumnType::String(Some(255)), + auto_increment: false, + not_null: true, + unique: false, + }, + ], + relations: vec![ + Relation { + ref_table: "cake_filling".to_owned(), + columns: vec![], + ref_columns: vec![], + rel_type: RelationType::HasMany, + }, + Relation { + ref_table: "fruit".to_owned(), + columns: vec![], + ref_columns: vec![], + rel_type: RelationType::HasMany, + }, + ], + primary_keys: vec![PrimaryKey { + name: "id".to_owned(), + }], + }, + Entity { + table_name: "cake_filling".to_owned(), + columns: vec![ + Column { + name: "cake_id".to_owned(), + col_type: ColumnType::Integer(Some(11)), + auto_increment: false, + not_null: true, + unique: false, + }, + Column { + name: "filling_id".to_owned(), + col_type: ColumnType::Integer(Some(11)), + auto_increment: false, + not_null: true, + unique: false, + }, + ], + relations: vec![ + Relation { + ref_table: "cake".to_owned(), + columns: vec!["cake_id".to_owned()], + ref_columns: vec!["id".to_owned()], + rel_type: RelationType::BelongsTo, + }, + Relation { + ref_table: "filling".to_owned(), + columns: vec!["filling_id".to_owned()], + ref_columns: vec!["id".to_owned()], + rel_type: RelationType::BelongsTo, + }, + ], + primary_keys: vec![ + PrimaryKey { + name: "cake_id".to_owned(), + }, + PrimaryKey { + name: "filling_id".to_owned(), + }, + ], + }, + Entity { + table_name: "filling".to_owned(), + columns: vec![ + Column { + name: "id".to_owned(), + col_type: ColumnType::Integer(Some(11)), + auto_increment: true, + not_null: true, + unique: false, + }, + Column { + name: "name".to_owned(), + col_type: ColumnType::String(Some(255)), + auto_increment: false, + not_null: true, + unique: false, + }, + ], + relations: vec![Relation { + ref_table: "cake_filling".to_owned(), + columns: vec![], + ref_columns: vec![], + rel_type: RelationType::HasMany, + }], + primary_keys: vec![PrimaryKey { + name: "id".to_owned(), + }], + }, + Entity { + table_name: "fruit".to_owned(), + columns: vec![ + Column { + name: "id".to_owned(), + col_type: ColumnType::Integer(Some(11)), + auto_increment: true, + not_null: true, + unique: false, + }, + Column { + name: "name".to_owned(), + col_type: ColumnType::String(Some(255)), + auto_increment: false, + not_null: true, + unique: false, + }, + Column { + name: "cake_id".to_owned(), + col_type: ColumnType::Integer(Some(11)), + auto_increment: false, + not_null: false, + unique: false, + }, + ], + relations: vec![ + Relation { + ref_table: "cake".to_owned(), + columns: vec!["cake_id".to_owned()], + ref_columns: vec!["id".to_owned()], + rel_type: RelationType::BelongsTo, + }, + Relation { + ref_table: "vendor".to_owned(), + columns: vec![], + ref_columns: vec![], + rel_type: RelationType::HasMany, + }, + ], + primary_keys: vec![PrimaryKey { + name: "id".to_owned(), + }], + }, + Entity { + table_name: "vendor".to_owned(), + columns: vec![ + Column { + name: "id".to_owned(), + col_type: ColumnType::Integer(Some(11)), + auto_increment: true, + not_null: true, + unique: false, + }, + Column { + name: "name".to_owned(), + col_type: ColumnType::String(Some(255)), + auto_increment: false, + not_null: true, + unique: false, + }, + Column { + name: "fruit_id".to_owned(), + col_type: ColumnType::Integer(Some(11)), + auto_increment: false, + not_null: false, + unique: false, + }, + ], + relations: vec![Relation { + ref_table: "fruit".to_owned(), + columns: vec!["fruit_id".to_owned()], + ref_columns: vec!["id".to_owned()], + rel_type: RelationType::BelongsTo, + }], + primary_keys: vec![PrimaryKey { + name: "id".to_owned(), + }], + }, + ] + } + + #[test] + fn test_gen_code_blocks() -> io::Result<()> { + let entities = setup(); + + assert_eq!(entities.len(), ENTITY_FILES.len()); + + for (i, entity) in entities.iter().enumerate() { + let mut reader = BufReader::new(ENTITY_FILES[i].as_bytes()); + let mut lines: Vec = Vec::new(); + + reader.read_until(b';', &mut Vec::new())?; + + let mut line = String::new(); + while reader.read_line(&mut line)? > 0 { + lines.push(line.to_owned()); + line.clear(); + } + let content = lines.join(""); + let expected: TokenStream = content.parse().unwrap(); + let generated = EntityWriter::gen_code_blocks(entity) + .into_iter() + .skip(1) + .fold(TokenStream::new(), |mut acc, tok| { + acc.extend(tok); + acc + }); + assert_eq!(expected.to_string(), generated.to_string()); + } + + Ok(()) + } +} diff --git a/sea-orm-codegen/tests/entity/cake.rs b/sea-orm-codegen/tests/entity/cake.rs new file mode 100644 index 000000000..12e7c4aba --- /dev/null +++ b/sea-orm-codegen/tests/entity/cake.rs @@ -0,0 +1,74 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "cake" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub id: i32, + pub name: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Name, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + CakeFilling, + Fruit, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Name => ColumnType::String(Some(255u32)).def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::CakeFilling => Entity::has_many(super::cake_filling::Entity).into(), + Self::Fruit => Entity::has_many(super::fruit::Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::CakeFilling.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/sea-orm-codegen/tests/entity/cake_filling.rs b/sea-orm-codegen/tests/entity/cake_filling.rs new file mode 100644 index 000000000..1ac649207 --- /dev/null +++ b/sea-orm-codegen/tests/entity/cake_filling.rs @@ -0,0 +1,81 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "cake_filling" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub cake_id: i32, + pub filling_id: i32, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + CakeId, + FillingId, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + CakeId, + FillingId, +} + +impl PrimaryKeyTrait for PrimaryKey { + fn auto_increment() -> bool { + false + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Cake, + Filling, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::CakeId => ColumnType::Integer.def(), + Self::FillingId => ColumnType::Integer.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Cake => Entity::belongs_to(super::cake::Entity) + .from(Column::CakeId) + .to(super::cake::Column::Id) + .into(), + Self::Filling => Entity::belongs_to(super::filling::Entity) + .from(Column::FillingId) + .to(super::filling::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Cake.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Filling.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/sea-orm-codegen/tests/entity/filling.rs b/sea-orm-codegen/tests/entity/filling.rs new file mode 100644 index 000000000..752317a4c --- /dev/null +++ b/sea-orm-codegen/tests/entity/filling.rs @@ -0,0 +1,66 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "filling" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub id: i32, + pub name: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Name, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + CakeFilling, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Name => ColumnType::String(Some(255u32)).def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::CakeFilling => Entity::has_many(super::cake_filling::Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::CakeFilling.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/sea-orm-codegen/tests/entity/fruit.rs b/sea-orm-codegen/tests/entity/fruit.rs new file mode 100644 index 000000000..72c37c1bb --- /dev/null +++ b/sea-orm-codegen/tests/entity/fruit.rs @@ -0,0 +1,80 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "fruit" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub id: i32, + pub name: String, + pub cake_id: Option , +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Name, + CakeId, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Cake, + Vendor, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Name => ColumnType::String(Some(255u32)).def(), + Self::CakeId => ColumnType::Integer.def().null(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Cake => Entity::belongs_to(super::cake::Entity) + .from(Column::CakeId) + .to(super::cake::Column::Id) + .into(), + Self::Vendor => Entity::has_many(super::vendor::Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Cake.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Vendor.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/sea-orm-codegen/tests/entity/mod.rs b/sea-orm-codegen/tests/entity/mod.rs new file mode 100644 index 000000000..395d29f96 --- /dev/null +++ b/sea-orm-codegen/tests/entity/mod.rs @@ -0,0 +1,7 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +pub mod cake; +pub mod cake_filling; +pub mod filling; +pub mod fruit; +pub mod vendor; diff --git a/sea-orm-codegen/tests/entity/prelude.rs b/sea-orm-codegen/tests/entity/prelude.rs new file mode 100644 index 000000000..b4e85c783 --- /dev/null +++ b/sea-orm-codegen/tests/entity/prelude.rs @@ -0,0 +1,7 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +pub use super::cake::Entity as Cake; +pub use super::cake_filling::Entity as CakeFilling; +pub use super::filling::Entity as Filling; +pub use super::fruit::Entity as Fruit; +pub use super::vendor::Entity as Vendor; diff --git a/sea-orm-codegen/tests/entity/vendor.rs b/sea-orm-codegen/tests/entity/vendor.rs new file mode 100644 index 000000000..320400f4c --- /dev/null +++ b/sea-orm-codegen/tests/entity/vendor.rs @@ -0,0 +1,72 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 + +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "vendor" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub id: i32, + pub name: String, + pub fruit_id: Option , +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Name, + FruitId, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Fruit, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Name => ColumnType::String(Some(255u32)).def(), + Self::FruitId => ColumnType::Integer.def().null(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Fruit => Entity::belongs_to(super::fruit::Entity) + .from(Column::FruitId) + .to(super::fruit::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/sea-orm-codegen/tests/mod.rs b/sea-orm-codegen/tests/mod.rs new file mode 100644 index 000000000..12c180661 --- /dev/null +++ b/sea-orm-codegen/tests/mod.rs @@ -0,0 +1,127 @@ +mod entity; + +use entity::*; + +use sea_orm::{entity::*, error::*, MockDatabase, MockExecResult, Transaction}; + +#[async_std::test] +async fn test_insert() -> Result<(), DbErr> { + let exec_result = MockExecResult { + last_insert_id: 1, + rows_affected: 1, + }; + + let db = MockDatabase::new() + .append_exec_results(vec![exec_result.clone()]) + .into_connection(); + + let apple = cake::ActiveModel { + name: Set("Apple Pie".to_owned()), + ..Default::default() + }; + + let insert_result = cake::Entity::insert(apple).exec(&db).await?; + + assert_eq!(insert_result.last_insert_id, exec_result.last_insert_id); + + assert_eq!( + db.into_transaction_log(), + vec![Transaction::from_sql_and_values( + r#"INSERT INTO "cake" ("name") VALUES ($1)"#, + vec!["Apple Pie".into()] + )] + ); + + Ok(()) +} + +#[async_std::test] +async fn test_select() -> Result<(), DbErr> { + let query_results = vec![cake_filling::Model { + cake_id: 2, + filling_id: 3, + }]; + + let db = MockDatabase::new() + .append_query_results(vec![query_results.clone()]) + .into_connection(); + + let selected_models = cake_filling::Entity::find_by_id((2, 3)).all(&db).await?; + + assert_eq!(selected_models, query_results); + + assert_eq!( + db.into_transaction_log(), + vec![Transaction::from_sql_and_values([ + r#"SELECT "cake_filling"."cake_id", "cake_filling"."filling_id" FROM "cake_filling""#, + r#"WHERE "cake_filling"."cake_id" = $1 AND "cake_filling"."filling_id" = $2"#, + ].join(" ").as_str(), + vec![2i32.into(), 3i32.into()] + )] + ); + + Ok(()) +} + +#[async_std::test] +async fn test_update() -> Result<(), DbErr> { + let exec_result = MockExecResult { + last_insert_id: 1, + rows_affected: 1, + }; + + let db = MockDatabase::new() + .append_exec_results(vec![exec_result.clone()]) + .into_connection(); + + let orange = fruit::ActiveModel { + id: Set(1), + name: Set("Orange".to_owned()), + ..Default::default() + }; + + let updated_model = fruit::Entity::update(orange.clone()).exec(&db).await?; + + assert_eq!(updated_model, orange); + + assert_eq!( + db.into_transaction_log(), + vec![Transaction::from_sql_and_values( + r#"UPDATE "fruit" SET "name" = $1 WHERE "fruit"."id" = $2"#, + vec!["Orange".into(), 1i32.into()] + )] + ); + + Ok(()) +} + +#[async_std::test] +async fn test_delete() -> Result<(), DbErr> { + let exec_result = MockExecResult { + last_insert_id: 1, + rows_affected: 1, + }; + + let db = MockDatabase::new() + .append_exec_results(vec![exec_result.clone()]) + .into_connection(); + + let orange = fruit::ActiveModel { + id: Set(3), + ..Default::default() + }; + + let delete_result = fruit::Entity::delete(orange).exec(&db).await?; + + assert_eq!(delete_result.rows_affected, exec_result.rows_affected); + + assert_eq!( + db.into_transaction_log(), + vec![Transaction::from_sql_and_values( + r#"DELETE FROM "fruit" WHERE "fruit"."id" = $1"#, + vec![3i32.into()] + )] + ); + + Ok(()) +}