Files
gridline/desktop/src-tauri/src/commands/open_files.test.rs
T
adrianbonpin 88e7e45818 feat(mysql): native SQL execution — fix #41 type decoding, in-process restore, .sql open-with (#42)
Fixes #41.

MySQL columns now decode by their declared type instead of a blind try_get chain,
so every non-string type (integers, DECIMAL, BIT, YEAR, DATE/TIME/DATETIME/TIMESTAMP)
renders its value instead of NULL. MySQL restore runs in-process over the sqlx driver
(no bundled MariaDB client, so MySQL 8 caching_sha2_password works) and the Clean toggle
is implemented. .sql files can be opened with Gridline from the OS file manager.

Verified: cargo test --lib 442 passed / 0 failed; bun run test 133 files / 1151 tests
exit 0; tsc clean; both live MySQL 8 integration tests pass with the bundled CLI hidden.
2026-09-29 08:52:47 +08:00

108 lines
3.3 KiB
Rust

use super::*;
fn tmp_sql(name: &str, repeats: usize) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("gridline-open-{name}"));
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join("sample.sql");
std::fs::write(&file, "SELECT 1;".repeat(repeats.max(1))).unwrap();
file
}
#[test]
fn rejects_a_relative_path() {
assert!(validate_sql_path_with_cap("student_db.sql", 1024).is_none());
}
#[test]
fn rejects_a_non_sql_extension() {
let f = tmp_sql("ext", 1);
let renamed = f.with_extension("txt");
std::fs::rename(&f, &renamed).unwrap();
assert!(validate_sql_path_with_cap(&renamed.to_string_lossy(), 1024).is_none());
}
#[test]
fn rejects_a_directory() {
let dir = std::env::temp_dir().join("gridline-open-dir");
std::fs::create_dir_all(dir.join("not-a-file.sql")).unwrap();
assert!(
validate_sql_path_with_cap(&dir.join("not-a-file.sql").to_string_lossy(), 1024).is_none()
);
}
#[test]
fn rejects_a_missing_file() {
let p = std::env::temp_dir().join("gridline-definitely-missing-xyz.sql");
assert!(validate_sql_path_with_cap(&p.to_string_lossy(), 1024).is_none());
}
#[test]
fn rejects_a_file_over_the_cap() {
let f = tmp_sql("big", 200);
assert!(validate_sql_path_with_cap(&f.to_string_lossy(), 16).is_none());
}
#[test]
fn rejects_a_symlink() {
#[cfg(unix)]
{
let f = tmp_sql("sym", 1);
let link = f.with_file_name("link.sql");
let _ = std::fs::remove_file(&link);
std::os::unix::fs::symlink(&f, &link).unwrap();
assert!(
validate_sql_path_with_cap(&link.to_string_lossy(), 1024).is_none(),
"a symlink must not be accepted (spec §6)"
);
}
}
#[test]
fn accepts_a_valid_sql_file_with_an_uppercase_extension() {
let f = tmp_sql("ok", 1);
let upper = f.with_file_name("SAMPLE.SQL");
std::fs::rename(&f, &upper).unwrap();
assert!(validate_sql_path_with_cap(&upper.to_string_lossy(), 1024).is_some());
}
#[test]
fn accept_path_dedupes_the_same_path() {
let buffer = PendingOpenFiles::default();
let f = tmp_sql("dedupe", 1);
let s = f.to_string_lossy().to_string();
assert!(accept_path(&buffer, &s));
assert!(!accept_path(&buffer, &s), "second accept must be deduped");
}
#[test]
fn accept_path_ignores_an_invalid_path() {
let buffer = PendingOpenFiles::default();
assert!(!accept_path(&buffer, "relative.sql"));
}
#[test]
fn take_returns_contents_then_drains() {
let buffer = PendingOpenFiles::default();
let f = tmp_sql("drain", 1);
assert!(accept_path(&buffer, &f.to_string_lossy()));
let first = take_from_buffer(&buffer).expect("first drain");
assert_eq!(first.len(), 1);
assert_eq!(first[0].name, "sample.sql");
assert_eq!(first[0].content, "SELECT 1;");
let second = take_from_buffer(&buffer).expect("second drain");
assert!(second.is_empty(), "buffer must be drained, not reread");
}
#[test]
fn rejects_a_non_utf8_file() {
let dir = std::env::temp_dir().join("gridline-open-binary");
std::fs::create_dir_all(&dir).unwrap();
let f = dir.join("binary.sql");
std::fs::write(&f, [0xff, 0xfe, 0x00]).unwrap();
let buffer = PendingOpenFiles::default();
assert!(accept_path(&buffer, &f.to_string_lossy()));
assert!(take_from_buffer(&buffer).is_err(), "non-UTF8 must fail closed");
}