Skip to content

Commit 92ecada

Browse files
test: add Rust unit tests + CI
Adds Rust unit tests for src-tauri and runs them in CI (and in pre-commit when relevant files are staged). Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
1 parent fb9999e commit 92ecada

File tree

5 files changed

+183
-0
lines changed

5 files changed

+183
-0
lines changed

.githooks/pre-commit

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,20 @@ if ! bun run test; then
3434
exit 1
3535
fi
3636

37+
# Run Rust unit tests only when relevant Rust/Tauri files are staged
38+
STAGED_FILES=$(git diff --cached --name-only)
39+
if echo "$STAGED_FILES" | grep -Eq '^frontend/src-tauri/.*\.(rs|toml|lock)$'; then
40+
echo "Running Rust unit tests..."
41+
cd "$REPO_ROOT/frontend/src-tauri" || exit 1
42+
if ! cargo test --all-targets; then
43+
echo ""
44+
echo "Error: Rust tests failed! Please fix the test failures before committing."
45+
echo "Run 'cd frontend/src-tauri && cargo test --all-targets' to see the errors."
46+
exit 1
47+
fi
48+
else
49+
echo "No staged Rust changes detected; skipping Rust unit tests."
50+
fi
51+
3752
echo "All checks passed! Proceeding with commit..."
3853
exit 0

.github/workflows/rust-tests.yml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: Rust Unit Tests
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
jobs:
10+
rust-tests:
11+
runs-on: ubuntu-latest-8-cores
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- name: Install Rust
16+
uses: dtolnay/rust-toolchain@stable
17+
18+
- name: Install sccache
19+
run: |
20+
SCCACHE_VERSION=0.8.2
21+
SCCACHE_URL="https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl.tar.gz"
22+
curl -L "$SCCACHE_URL" | tar xz
23+
sudo mv sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl/sccache /usr/local/bin/
24+
chmod +x /usr/local/bin/sccache
25+
sccache --version
26+
27+
- name: Cache sccache
28+
uses: actions/cache@v4
29+
with:
30+
path: ~/.cache/sccache
31+
key: ${{ runner.os }}-sccache-rust-tests-${{ hashFiles('**/Cargo.lock') }}
32+
restore-keys: |
33+
${{ runner.os }}-sccache-rust-tests-
34+
${{ runner.os }}-sccache-
35+
36+
- name: Install Linux dependencies
37+
run: |
38+
sudo apt-get update
39+
sudo apt-get install -y \
40+
libwebkit2gtk-4.1-dev \
41+
libssl-dev \
42+
libgtk-3-dev \
43+
libayatana-appindicator3-dev \
44+
librsvg2-dev \
45+
pkg-config
46+
47+
- name: Configure sccache
48+
run: |
49+
echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
50+
echo "SCCACHE_DIR=$HOME/.cache/sccache" >> $GITHUB_ENV
51+
echo "SCCACHE_CACHE_SIZE=2G" >> $GITHUB_ENV
52+
53+
- name: Run unit tests
54+
working-directory: ./frontend/src-tauri
55+
run: cargo test --all-targets
56+
57+
- name: Show sccache stats
58+
run: sccache --show-stats

frontend/src-tauri/src/pdf_extractor.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,74 @@ pub async fn extract_document_content(
4848
status: "completed".to_string(),
4949
})
5050
}
51+
52+
#[cfg(test)]
53+
mod tests {
54+
use super::extract_document_content;
55+
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
56+
57+
#[tokio::test]
58+
async fn extract_document_content_text_plain_success() {
59+
let file_base64 = BASE64.encode(b"Hello, Maple!");
60+
61+
let resp = extract_document_content(
62+
file_base64,
63+
"hello.txt".to_string(),
64+
"text/plain".to_string(),
65+
)
66+
.await
67+
.expect("expected text/plain extraction to succeed");
68+
69+
assert_eq!(resp.status, "completed");
70+
assert_eq!(resp.document.filename, "hello.txt");
71+
assert_eq!(resp.document.text_content, "Hello, Maple!");
72+
}
73+
74+
#[tokio::test]
75+
async fn extract_document_content_rejects_unsupported_file_type() {
76+
let file_base64 = BASE64.encode(b"whatever");
77+
78+
let err = extract_document_content(
79+
file_base64,
80+
"file.bin".to_string(),
81+
"application/octet-stream".to_string(),
82+
)
83+
.await
84+
.expect_err("expected unsupported file type to error");
85+
86+
assert!(
87+
err.contains("Unsupported file type"),
88+
"unexpected error: {err}"
89+
);
90+
}
91+
92+
#[tokio::test]
93+
async fn extract_document_content_rejects_invalid_base64() {
94+
let err = extract_document_content(
95+
"not base64".to_string(),
96+
"file.txt".to_string(),
97+
"txt".to_string(),
98+
)
99+
.await
100+
.expect_err("expected invalid base64 to error");
101+
102+
assert!(
103+
err.contains("Failed to decode base64 file"),
104+
"unexpected error: {err}"
105+
);
106+
}
107+
108+
#[tokio::test]
109+
async fn extract_document_content_rejects_invalid_utf8_for_text_files() {
110+
let file_base64 = BASE64.encode([0xff, 0xfe, 0xfd]);
111+
112+
let err = extract_document_content(file_base64, "bad.txt".to_string(), "txt".to_string())
113+
.await
114+
.expect_err("expected invalid utf-8 to error");
115+
116+
assert!(
117+
err.contains("Failed to decode text file"),
118+
"unexpected error: {err}"
119+
);
120+
}
121+
}

frontend/src-tauri/src/tts.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,3 +1002,41 @@ pub async fn tts_delete_models(state: tauri::State<'_, Mutex<TTSState>>) -> Resu
10021002
log::info!("TTS models deleted");
10031003
Ok(())
10041004
}
1005+
1006+
#[cfg(test)]
1007+
mod tests {
1008+
use super::*;
1009+
1010+
#[test]
1011+
fn bytes_to_hex_is_lowercase_and_zero_padded() {
1012+
assert_eq!(bytes_to_hex(&[0x00, 0xab, 0xff]), "00abff");
1013+
}
1014+
1015+
#[test]
1016+
fn preprocess_text_strips_markdown_and_emoji_and_adds_period() {
1017+
assert_eq!(preprocess_text("**Hello** _world_ 😊"), "Hello world.");
1018+
}
1019+
1020+
#[test]
1021+
fn preprocess_text_does_not_add_punctuation_if_already_present() {
1022+
assert_eq!(preprocess_text("Hi!"), "Hi!");
1023+
}
1024+
1025+
#[test]
1026+
fn chunk_text_splits_long_sentence_by_words_when_needed() {
1027+
let chunks = chunk_text("Hello world. Bye.", 10);
1028+
assert_eq!(
1029+
chunks,
1030+
vec![
1031+
"Hello".to_string(),
1032+
"world.".to_string(),
1033+
"Bye.".to_string()
1034+
]
1035+
);
1036+
}
1037+
1038+
#[test]
1039+
fn chunk_text_returns_single_empty_chunk_for_empty_input() {
1040+
assert_eq!(chunk_text(" ", 10), vec![String::new()]);
1041+
}
1042+
}

setup-hooks.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ echo "The pre-commit hook will now:"
1818
echo " 1. Check code formatting with 'bun run format:check'"
1919
echo " 2. Run 'bun run build' to ensure the project builds"
2020
echo " 3. Run 'bun run test' to ensure unit tests pass"
21+
echo " 4. Run Rust unit tests with 'cargo test --all-targets' when Rust/Tauri files are staged"

0 commit comments

Comments
 (0)