Skip to content

Commit

Permalink
Add migration unit tests (#157)
Browse files Browse the repository at this point in the history
* Add migration unit tests

* document the helpers
  • Loading branch information
jayvdb authored Nov 4, 2023
1 parent af3f14e commit e3dc63b
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 12 deletions.
19 changes: 7 additions & 12 deletions butane_core/src/codegen/migration.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::*;
use crate::migrations::adb::{AColumn, ATable, MANY_SUFFIX};
use crate::migrations::adb::{create_many_table, AColumn, ATable};
use crate::migrations::{MigrationMut, MigrationsMut};
use crate::Result;
use syn::{Field, ItemStruct};
Expand Down Expand Up @@ -57,7 +57,7 @@ fn create_atables(ast_struct: &ItemStruct, config: &dbobj::Config) -> Vec<ATable
result.push(many_table(&table.name, f, &pk));
}
}
result.push(table);
result.insert(0, table);
result
}

Expand All @@ -67,16 +67,11 @@ fn many_table(main_table_name: &str, many_field: &Field, pk_field: &Field) -> AT
.clone()
.expect("fields must be named")
.to_string();
let mut table = ATable::new(format!("{main_table_name}_{field_name}{MANY_SUFFIX}"));
let col = AColumn::new_simple("owner", get_deferred_sql_type(&pk_field.ty));
table.add_column(col);
let col = AColumn::new_simple(
"has",
get_many_sql_type(many_field)
.unwrap_or_else(|| panic!("Mis-identified Many field {field_name}")),
);
table.add_column(col);
table
let many_field_type = get_many_sql_type(many_field)
.unwrap_or_else(|| panic!("Mis-identified Many field {field_name}"));
let pk_field_type = get_deferred_sql_type(&pk_field.ty);

create_many_table(main_table_name, &field_name, many_field_type, pk_field_type)
}

fn is_nullable(field: &Field) -> bool {
Expand Down
16 changes: 16 additions & 0 deletions butane_core/src/migrations/adb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,22 @@ impl AColumn {
}
}

/// Create a table for the [crate::many::Many] relationship.
/// Should not be used directly, except in tests.
pub fn create_many_table(
main_table_name: &str,
many_field_name: &str,
many_field_type: DeferredSqlType,
main_table_pk_field_type: DeferredSqlType,
) -> ATable {
let mut table = ATable::new(format!("{main_table_name}_{many_field_name}{MANY_SUFFIX}"));
let col = AColumn::new_simple("owner", main_table_pk_field_type);
table.add_column(col);
let col = AColumn::new_simple("has", many_field_type);
table.add_column(col);
table
}

/// Individual operation use to apply a migration.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum Operation {
Expand Down
276 changes: 276 additions & 0 deletions butane_core/tests/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,279 @@ fn stable_add_column_alpha_order() {
]
);
}

#[test]
fn add_table_fkey() {
let known_int_type = DeferredSqlType::KnownId(TypeIdentifier::Ty(SqlType::Int));

let old = ADB::default();
let mut new = ADB::default();
let mut table_a = ATable::new("a".to_owned());
let column = AColumn::new(
"id".to_owned(),
known_int_type.clone(),
false, // nullable
true, // pk
false, // auto
false, // unique
None, // default
);
table_a.add_column(column);
new.replace_table(table_a.clone());

let mut table_b = ATable::new("b".to_owned());
let column = AColumn::new(
"b".to_owned(),
DeferredSqlType::Deferred(TypeKey::PK("a".to_owned())),
false, // nullable
true, // pk
false, // auto
false, // unique
None, // default
);
table_b.add_column(column);
new.replace_table(table_b.clone());

new.resolve_types().unwrap();

let mut resolved_table_b = ATable::new("b".to_owned());
let column = AColumn::new(
"b".to_owned(),
known_int_type.clone(),
false, // nullable
true, // pk
false, // auto
false, // unique
None, // default
);
resolved_table_b.add_column(column);

let ops = diff(&old, &new);

assert_eq!(
ops,
vec![
Operation::AddTable(table_a),
Operation::AddTable(resolved_table_b.clone()),
]
);
}

/// Creates the test case for adding a foreign key, returning the migration operations,
/// the target ADB, and the tables which should be expected to be created.
fn create_add_renamed_table_fkey_ops() -> (Vec<Operation>, ADB, ATable, ATable) {
let known_int_type = DeferredSqlType::KnownId(TypeIdentifier::Ty(SqlType::Int));

let old = ADB::default();
let mut new = ADB::default();
let mut table_a = ATable::new("a_table".to_owned());
let column = AColumn::new(
"id".to_owned(),
known_int_type.clone(),
false, // nullable
true, // pk
false, // auto
false, // unique
None, // default
);
table_a.add_column(column);
new.replace_table(table_a.clone());

// Add the type "a" renamed to table "a_table"
let type_name = TypeKey::PK("a".to_owned());
let table_name = DeferredSqlType::Deferred(TypeKey::PK("a_table".to_owned()));
new.add_type(type_name, table_name);

let mut table_b = ATable::new("b".to_owned());
let column = AColumn::new(
"b".to_owned(),
DeferredSqlType::Deferred(TypeKey::PK("a".to_owned())),
false, // nullable
true, // pk
false, // auto
false, // unique
None, // default
);
table_b.add_column(column);
new.replace_table(table_b.clone());

new.resolve_types().unwrap();

let mut resolved_table_b = ATable::new("b".to_owned());
let column = AColumn::new(
"b".to_owned(),
known_int_type.clone(),
false, // nullable
true, // pk
false, // auto
false, // unique
None, // default
);
resolved_table_b.add_column(column);

let ops = diff(&old, &new);

(ops, new, table_a, resolved_table_b)
}

#[test]
fn add_renamed_table_fkey() {
let (ops, _, table_a, resolved_table_b) = create_add_renamed_table_fkey_ops();

assert_eq!(
ops,
vec![
Operation::AddTable(table_a),
Operation::AddTable(resolved_table_b.clone()),
]
);
}

#[test]
fn add_renamed_table_fkey_ddl_sqlite() {
let (ops, new, ..) = create_add_renamed_table_fkey_ops();

let backend = butane_core::db::get_backend("sqlite").unwrap();
let sql = backend.create_migration_sql(&new, ops).unwrap();
let sql_lines: Vec<&str> = sql.lines().collect();
assert_eq!(
sql_lines,
vec![
"CREATE TABLE a_table (",
"id INTEGER NOT NULL PRIMARY KEY",
");",
"CREATE TABLE b (",
"b INTEGER NOT NULL PRIMARY KEY",
");",
]
);
}

#[test]
fn add_renamed_table_fkey_ddl_pg() {
let (ops, new, ..) = create_add_renamed_table_fkey_ops();

let backend = butane_core::db::get_backend("pg").unwrap();
let sql = backend.create_migration_sql(&new, ops).unwrap();
let sql_lines: Vec<&str> = sql.lines().collect();
assert_eq!(
sql_lines,
vec![
"CREATE TABLE a_table (",
"id INTEGER NOT NULL PRIMARY KEY",
");",
"CREATE TABLE b (",
"b INTEGER NOT NULL PRIMARY KEY",
");",
]
);
}

/// Creates the test case for adding a many table, returning the migration operations,
/// the target ADB, and the tables which should be expected to be created.
fn create_add_table_many_ops() -> (Vec<Operation>, ADB, ATable, ATable, ATable) {
let known_int_type = DeferredSqlType::KnownId(TypeIdentifier::Ty(SqlType::Int));

let old = ADB::default();
let mut new = ADB::default();

let id_column = AColumn::new(
"id".to_owned(),
known_int_type.clone(),
false, // nullable
true, // pk
false, // auto
false, // unique
None, // default
);

let mut table_a = ATable::new("a".to_owned());
table_a.add_column(id_column.clone());
new.replace_table(table_a.clone());

let mut table_b = ATable::new("b".to_owned());
table_b.add_column(id_column);

new.replace_table(table_b.clone());

let many_table = butane_core::migrations::adb::create_many_table(
"b",
"many_a",
DeferredSqlType::Deferred(TypeKey::PK("a".to_owned())),
known_int_type.clone(),
);
new.replace_table(many_table.clone());

new.resolve_types().unwrap();

let mut resolved_many_table = ATable::new(many_table.name);
let resolved_owner_column = AColumn::new_simple("owner", known_int_type.clone());
let resolved_has_column = AColumn::new_simple("has", known_int_type.clone());
resolved_many_table.add_column(resolved_owner_column);
resolved_many_table.add_column(resolved_has_column);

let ops = diff(&old, &new);
(ops, new, table_a, table_b, resolved_many_table)
}

#[test]
fn add_table_many() {
let (ops, _, table_a, table_b, resolved_many_table) = create_add_table_many_ops();

assert_eq!(
ops,
vec![
Operation::AddTable(table_a),
Operation::AddTable(table_b.clone()),
Operation::AddTable(resolved_many_table.clone()),
]
);
}

#[test]
fn add_table_many_ddl_sqlite() {
let (ops, new, ..) = create_add_table_many_ops();

let backend = butane_core::db::get_backend("sqlite").unwrap();
let sql = backend.create_migration_sql(&new, ops).unwrap();
let sql_lines: Vec<&str> = sql.lines().collect();
assert_eq!(
sql_lines,
vec![
"CREATE TABLE a (",
"id INTEGER NOT NULL PRIMARY KEY",
");",
"CREATE TABLE b (",
"id INTEGER NOT NULL PRIMARY KEY",
");",
"CREATE TABLE b_many_a_Many (",
"owner INTEGER NOT NULL,",
"has INTEGER NOT NULL",
");",
]
);
}

#[test]
fn add_table_many_ddl_pg() {
let (ops, new, ..) = create_add_table_many_ops();

let backend = butane_core::db::get_backend("pg").unwrap();
let sql = backend.create_migration_sql(&new, ops).unwrap();
let sql_lines: Vec<&str> = sql.lines().collect();
assert_eq!(
sql_lines,
vec![
"CREATE TABLE a (",
"id INTEGER NOT NULL PRIMARY KEY",
");",
"CREATE TABLE b (",
"id INTEGER NOT NULL PRIMARY KEY",
");",
"CREATE TABLE b_many_a_Many (",
"owner INTEGER NOT NULL,",
"has INTEGER NOT NULL",
");",
]
);
}

0 comments on commit e3dc63b

Please sign in to comment.