The blob-removal commit (6b4c4dc) failed CI's fmt gate — long assert! lines
and a doc comment that stock rustfmt wraps. No behaviour change; just
`cargo fmt`. Verified via `nix develop -c cargo fmt --check`.
117 lines
4.1 KiB
Rust
117 lines
4.1 KiB
Rust
//! Full-stack test for **ADR-0005 Amendment 2** (drop `blob`): a saved
|
|
//! v1 project that declares a `blob` column is migrated to v2 (`blob` →
|
|
//! `text`) on open, and a rebuild from the migrated text reconstructs the
|
|
//! column as `text` with the row data preserved — the base64 cell survives
|
|
//! as a recoverable string. This exercises the real migrator + the
|
|
//! `rebuild_from_text` path the runtime forces when a blob column was
|
|
//! converted (the runtime loop itself isn't booted in integration tests).
|
|
|
|
use std::fs;
|
|
|
|
use rdbms_playground::db::Database;
|
|
use rdbms_playground::dsl::Type;
|
|
use rdbms_playground::persistence::Persistence;
|
|
use rdbms_playground::persistence::migrations::{
|
|
MigratorRegistry, body_declares_blob_column, ensure_project_yaml_migrated,
|
|
};
|
|
|
|
fn rt() -> tokio::runtime::Runtime {
|
|
tokio::runtime::Builder::new_current_thread()
|
|
.enable_all()
|
|
.build()
|
|
.expect("tokio rt")
|
|
}
|
|
|
|
#[test]
|
|
fn v1_blob_project_migrates_to_text_and_rebuilds() {
|
|
let dir = tempfile::tempdir().expect("tempdir");
|
|
let root = dir.path();
|
|
|
|
// A hand-written v1 project: a serial PK + a `blob` column carrying a
|
|
// (hand-edited, base64) value — the only way bytes ever reached a blob
|
|
// column, since no command can insert one.
|
|
let yaml = concat!(
|
|
"version: 1\n",
|
|
"project:\n created_at: 2026-01-01T00:00:00Z\n mode: simple\n",
|
|
"tables:\n",
|
|
" - name: Files\n",
|
|
" primary_key: [id]\n",
|
|
" columns:\n",
|
|
" - { name: id, type: serial }\n",
|
|
" - { name: data, type: blob }\n",
|
|
"relationships: []\n",
|
|
"indexes: []\n",
|
|
);
|
|
fs::write(root.join("project.yaml"), yaml).expect("write yaml");
|
|
fs::create_dir(root.join("data")).expect("data dir");
|
|
fs::write(root.join("data").join("Files.csv"), "id,data\n1,aGVsbG8=\n").expect("write csv");
|
|
|
|
// The runtime's blob-detection signal sees the column…
|
|
assert!(body_declares_blob_column(yaml), "blob column detected");
|
|
|
|
// 1. Migration runs (as the runtime does on open).
|
|
let outcome = ensure_project_yaml_migrated(root, &MigratorRegistry::production())
|
|
.expect("migration succeeds");
|
|
assert_eq!(outcome.migrated_from, Some(1), "v1 project was migrated");
|
|
|
|
let migrated = fs::read_to_string(root.join("project.yaml")).expect("read migrated");
|
|
assert!(
|
|
migrated.contains("version: 2"),
|
|
"version bumped: {migrated}"
|
|
);
|
|
assert!(
|
|
migrated.contains("{ name: data, type: text }"),
|
|
"blob column rewritten to text: {migrated}"
|
|
);
|
|
assert!(
|
|
!migrated.contains("type: blob"),
|
|
"no blob type remains: {migrated}"
|
|
);
|
|
// The pre-migration original is preserved as a .bak.
|
|
let bak = fs::read_to_string(root.join("project.yaml.v1.bak")).expect("read bak");
|
|
assert!(
|
|
bak.contains("type: blob"),
|
|
"bak keeps the original blob column"
|
|
);
|
|
|
|
// 2. Rebuild from the migrated text (as the runtime forces when a blob
|
|
// column was converted): the column is now `text` and the row data
|
|
// survives as a recoverable string.
|
|
let db = Database::open_with_persistence(
|
|
root.join("playground.db"),
|
|
Persistence::new(root.to_path_buf()),
|
|
)
|
|
.expect("open db");
|
|
rt().block_on(async {
|
|
db.rebuild_from_text(root.to_path_buf(), None)
|
|
.await
|
|
.expect("rebuild");
|
|
|
|
let desc = db
|
|
.describe_table("Files".to_string())
|
|
.await
|
|
.expect("describe");
|
|
let data_col = desc
|
|
.columns
|
|
.iter()
|
|
.find(|c| c.name == "data")
|
|
.expect("data column present");
|
|
assert_eq!(
|
|
data_col.user_type,
|
|
Some(Type::Text),
|
|
"the former blob column is now text",
|
|
);
|
|
|
|
let rows = db
|
|
.query_data("Files".to_string(), None, None)
|
|
.await
|
|
.expect("query");
|
|
assert_eq!(rows.rows.len(), 1, "row preserved through the migration");
|
|
assert_eq!(
|
|
rows.rows[0][1].as_deref(),
|
|
Some("aGVsbG8="),
|
|
"the base64 cell survives as recoverable text",
|
|
);
|
|
});
|
|
}
|