Skip to content

Commit 71a9630

Browse files
authored
test(rust): add file_command_tests for read/write IPC (closes #259) (#270)
7 new tests cover the file-IO Tauri commands that don't need a MySQL container (so they can run as plain unit tests inline with the module). read_file_contents: - Returns file body for a valid file - Rejects missing path with 'Invalid path' or 'Failed to read file' - Rejects directory paths with 'Not a regular file' write_file_contents: - Creates a new file with the given body - Overwrites an existing file - Rejects paths whose parent directory doesn't exist with 'Invalid path' Roundtrip: - Unicode payload (umlauts + emoji) survives write→read byte-for-byte Added tempfile as a [dev-dependencies] entry to the root sqlpilot crate (was already a dep of mas-core). Cargo.lock bumps accordingly. The rest of the 28 IPC commands (#259 scope) require MySQL or a Tauri context and are covered indirectly by service-layer integration tests. Picking the no-Docker subset keeps this PR runnable on the current dev environment (Docker seed SQL is broken locally — separate infra issue). Closes #259
1 parent 5b7d550 commit 71a9630

3 files changed

Lines changed: 113 additions & 0 deletions

File tree

src-tauri/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,6 @@ crate-type = ["staticlib", "cdylib", "rlib"]
6464
default = []
6565
beta-ai = ["dep:mas-ai"]
6666
custom-protocol = ["tauri/custom-protocol"]
67+
68+
[dev-dependencies]
69+
tempfile = "3"

src-tauri/src/commands/mod.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,3 +621,112 @@ mod platform_tests {
621621
}
622622
}
623623
}
624+
625+
#[cfg(test)]
626+
mod file_command_tests {
627+
//! Tests for the file-IO Tauri commands (`read_file_contents`,
628+
//! `write_file_contents`). These don't require a MySQL container so
629+
//! they run as plain unit tests alongside the rest of the module.
630+
use super::{read_file_contents, write_file_contents};
631+
use std::fs;
632+
use tempfile::TempDir;
633+
634+
#[tokio::test]
635+
async fn read_file_contents_returns_file_body() {
636+
let dir = TempDir::new().expect("tempdir");
637+
let path = dir.path().join("hello.sql");
638+
fs::write(&path, "SELECT 1;\nSELECT 2;\n").expect("write");
639+
640+
let contents = read_file_contents(path.to_string_lossy().to_string())
641+
.await
642+
.expect("read ok");
643+
assert_eq!(contents, "SELECT 1;\nSELECT 2;\n");
644+
}
645+
646+
#[tokio::test]
647+
async fn read_file_contents_rejects_missing_path() {
648+
let dir = TempDir::new().expect("tempdir");
649+
let path = dir.path().join("does-not-exist.sql");
650+
651+
let err = read_file_contents(path.to_string_lossy().to_string())
652+
.await
653+
.unwrap_err();
654+
// Either "Invalid path" (canonicalize fails) or "Failed to read file"
655+
// (canonicalize succeeds because parent exists, then read fails).
656+
assert!(
657+
err.contains("Invalid path") || err.contains("Failed to read file"),
658+
"unexpected error: {err}",
659+
);
660+
}
661+
662+
#[tokio::test]
663+
async fn read_file_contents_rejects_directory_path() {
664+
let dir = TempDir::new().expect("tempdir");
665+
let err = read_file_contents(dir.path().to_string_lossy().to_string())
666+
.await
667+
.unwrap_err();
668+
assert!(
669+
err.contains("Not a regular file"),
670+
"unexpected error: {err}"
671+
);
672+
}
673+
674+
#[tokio::test]
675+
async fn write_file_contents_creates_file_with_body() {
676+
let dir = TempDir::new().expect("tempdir");
677+
let path = dir.path().join("out.sql");
678+
679+
write_file_contents(
680+
path.to_string_lossy().to_string(),
681+
"CREATE TABLE x (id INT)".to_string(),
682+
)
683+
.await
684+
.expect("write ok");
685+
686+
let written = fs::read_to_string(&path).expect("read back");
687+
assert_eq!(written, "CREATE TABLE x (id INT)");
688+
}
689+
690+
#[tokio::test]
691+
async fn write_file_contents_overwrites_existing_file() {
692+
let dir = TempDir::new().expect("tempdir");
693+
let path = dir.path().join("out.sql");
694+
fs::write(&path, "OLD CONTENT").expect("write seed");
695+
696+
write_file_contents(
697+
path.to_string_lossy().to_string(),
698+
"NEW CONTENT".to_string(),
699+
)
700+
.await
701+
.expect("write ok");
702+
703+
let written = fs::read_to_string(&path).expect("read back");
704+
assert_eq!(written, "NEW CONTENT");
705+
}
706+
707+
#[tokio::test]
708+
async fn write_file_contents_rejects_missing_parent_dir() {
709+
let dir = TempDir::new().expect("tempdir");
710+
let path = dir.path().join("nope").join("nope").join("out.sql");
711+
712+
let err = write_file_contents(path.to_string_lossy().to_string(), "x".to_string())
713+
.await
714+
.unwrap_err();
715+
assert!(err.contains("Invalid path"), "unexpected error: {err}");
716+
}
717+
718+
#[tokio::test]
719+
async fn read_write_roundtrip_preserves_unicode_content() {
720+
let dir = TempDir::new().expect("tempdir");
721+
let path = dir.path().join("rt.sql");
722+
723+
let payload = "SELECT 'unicode: ümläut 🚀';\n-- comment\nSELECT 2;";
724+
write_file_contents(path.to_string_lossy().to_string(), payload.to_string())
725+
.await
726+
.expect("write");
727+
let round_tripped = read_file_contents(path.to_string_lossy().to_string())
728+
.await
729+
.expect("read");
730+
assert_eq!(round_tripped, payload);
731+
}
732+
}

0 commit comments

Comments
 (0)