Practice Test Library
src/core/src/repositories/practice_library.rs powers the combined "All" tab of the Practice Library, which lists published tests across Reading, Writing, and Listening in one feed.
Why merge-sort in Rust instead of SQL UNION
reading_tests, writing_tests, and listening_tests don't share a column shape, and sqlx's compile-time-checked macros (query!/query_as!) require a single, statically known shape per query — incompatible with a heterogeneous UNION across the three tables. Instead:
find_reading_page,find_writing_page,find_listening_pageeach fetch published rows from their own table, normalized into a commonPracticeTestRow { id, title, module, difficulty, duration, created_at }.find_all_page(pool, offset, limit)fetches all rows from all three modules (unbounded,i64::MAXlimit per module), concatenates them, sorts the combined vector bycreated_at DESCin Rust, then paginates in-memory by slicing[start..end]:
let mut all: Vec<PracticeTestRow> = Vec::with_capacity(...);
all.extend(reading);
all.extend(writing);
all.extend(listening);
all.sort_by(|a, b| b.created_at.cmp(&a.created_at));
// ... then slice [start..end] for the requested page
This trades some efficiency (fetching the full unpaginated set from each module every call) for compile-time query safety and simplicity — acceptable given the local-first, single-user scale of this app.
Merging session progress onto each card
merge_sessions(pool, user_id, rows) builds a PracticeTestCard per row by looking up the user's user_test_sessions and, for each (test_type, test_id) pair, picking the session with the highest attempt_number:
match latest.get(&key) {
Some(existing) if existing.attempt_number >= session.attempt_number => {}
_ => { latest.insert(key, session); }
}
PracticeTestCard (src/core/src/models/practice_library.rs) shape:
pub struct PracticeTestCard {
pub id: String,
pub title: String,
pub module: String,
pub difficulty: String,
pub duration: String,
pub status: String, // session status, or "not_started" if no session exists
pub progress_percent: i64, // 0 if no session
pub score_band: Option<f64>,
pub last_active_at: Option<String>,
pub session_id: Option<String>,
pub created_at: String,
}
If no session exists yet for a test, the card falls back to status: "not_started" and progress_percent: 0.
Frontend consumption
listPracticeTests() (src/ui/lib/tauri.ts) calls the list_practice_tests command and returns a PaginatedResponse<PracticeTestCardDto> (see Tauri IPC Contract for the pagination shape). TestLibrary.tsx (src/ui/pages/) renders this feed, letting candidates browse and resume tests across all three modules from a single screen.