use std::path::{Path, PathBuf};
use sofie_model::{App, Module};
const TEMPLATE: &str = include_str!("../module_role.sql");
const FILE_NAME: &str = "0000_module_role.sql";
const SQLITE_TEXT: &str =
"-- @generated by `sofie gen` once and never rewritten: SQLite has no database roles.\n";
pub(crate) fn missing_role_migrations(app: &App) -> Vec<(PathBuf, String)> {
app.modules
.iter()
.filter(|module| !module.migrations.is_empty())
.flat_map(role_migrations)
.filter(|(path, _)| !path.exists())
.collect()
}
fn role_migrations(module: &Module) -> Vec<(PathBuf, String)> {
let folder = module.krate.path.join("migrations");
let postgres = TEMPLATE.replace("{module}", &module.krate.crate_name);
let mut files = vec![(folder.join(FILE_NAME), postgres)];
if !module.sqlite_migrations.is_empty() {
files.push((
folder.join("sqlite").join(FILE_NAME),
SQLITE_TEXT.to_owned(),
));
}
files
}
pub(crate) fn with_role_migrations(app: &App, missing: &[(PathBuf, String)]) -> App {
let mut app = app.clone();
for module in &mut app.modules {
let folder = module.krate.path.join("migrations");
add_in_folder(&mut module.migrations, missing, &folder);
add_in_folder(
&mut module.sqlite_migrations,
missing,
&folder.join("sqlite"),
);
}
app
}
fn add_in_folder(migrations: &mut Vec<PathBuf>, missing: &[(PathBuf, String)], folder: &Path) {
migrations.extend(
missing
.iter()
.map(|(path, _)| path)
.filter(|path| path.parent() == Some(folder))
.cloned(),
);
migrations.sort();
}
#[cfg(test)]
mod tests {
use sofie_model::testing::{TemporaryDirectory, app, module};
use super::*;
use crate::gen_command::{GenMode, GenOutcome, sync_file};
#[test]
fn a_module_with_migrations_gets_its_role_migration_first_and_once() {
let directory = TemporaryDirectory::new("sofie-module-roles");
let mut catalog = module("catalog", directory.path("modules/catalog"));
catalog.migrations = vec![directory.write(
"modules/catalog/migrations/0001_products.sql",
"CREATE TABLE product (id uuid PRIMARY KEY);",
)];
let empty = module("search", directory.path("modules/search"));
let model = app(directory.root(), vec![catalog, empty]);
let missing = missing_role_migrations(&model);
let with_roles = with_role_migrations(&model, &missing);
let role_file = directory.path("modules/catalog/migrations/0000_module_role.sql");
assert_eq!(missing.len(), 1);
assert_eq!(missing[0].0, role_file);
assert!(
missing[0]
.1
.contains("SELECT sofie.module_role('catalog');")
);
assert!(!missing[0].1.contains("{module}"));
assert_eq!(with_roles.modules[0].migrations[0], role_file);
let text = Some(missing[0].1.clone());
assert_eq!(
sync_file(&role_file, text.clone(), GenMode::Check).unwrap(),
Some(GenOutcome::Stale(role_file.clone()))
);
sync_file(&role_file, text, GenMode::Write).unwrap();
assert_eq!(missing_role_migrations(&model), []);
}
#[test]
fn a_module_with_sqlite_migrations_gets_a_sqlite_role_migration_too() {
let directory = TemporaryDirectory::new("sofie-module-roles-sqlite");
let mut catalog = module("catalog", directory.path("modules/catalog"));
catalog.migrations = vec![directory.write(
"modules/catalog/migrations/0001_products.sql",
"CREATE TABLE product (id uuid PRIMARY KEY);",
)];
catalog.sqlite_migrations = vec![directory.write(
"modules/catalog/migrations/sqlite/0001_products.sql",
"CREATE TABLE catalog_product (id TEXT PRIMARY KEY);",
)];
let model = app(directory.root(), vec![catalog]);
let missing = missing_role_migrations(&model);
let with_roles = with_role_migrations(&model, &missing);
let sqlite_file = directory.path("modules/catalog/migrations/sqlite/0000_module_role.sql");
assert_eq!(missing.len(), 2);
assert_eq!(missing[1].0, sqlite_file);
assert!(!missing[1].1.contains("SELECT"));
assert_eq!(with_roles.modules[0].sqlite_migrations[0], sqlite_file);
assert_eq!(
with_roles.modules[0].migrations[0],
directory.path("modules/catalog/migrations/0000_module_role.sql")
);
}
}