test(backend): DB-backed tests for the risky SQL; test isolation; clippy cleanup
All 40 backend tests were pure-function tests — not one line of SQL ran under `cargo test`,
even though the riskiest code in the repo is SQL. Add 12 DB-backed `#[sqlx::test]` tests
(fresh throwaway DB per test, real migrations) pinning the invariants that code is
load-bearing for, each mutation-verified to fail on broken code:
export_epoch.rs (8): epoch is post-increment on release (a worker born pre-increment is
inert); epoch strictly monotonic across reopen; a retired-epoch worker's writes are
no-ops; export_current is EXACTLY the invariant (8-case table); the ViewerOnly
carry-forward carries a done ZIP forward AND matches nothing when the ZIP is unfinished
(the af997a8 strand bug); boot recovery doesn't clobber a live ZIP.
upload_concurrency.rs (4): the atomic quota UPDATE stops two stale-snapshot uploads from
both committing (sequential AND concurrent-tx via EPQ); the FOR SHARE upload lock
serializes against release, blocking a photo from committing after the export snapshot.
Deleting FOR SHARE, or the carry-forward's `status='done'`, each makes a test fail.
Test isolation: TRUNCATE now also resets disk_cache and sse_tickets. The stale disk
reading was harmless only while quotas were globally off in e2e (they no longer are — see
the new quota spec), i.e. two holes were masking each other.
clippy: `cargo clippy --all-targets -- -D warnings` now passes (it did not). Deleted the
dead jobs.rs (an unused BackgroundJob sketch) and unused model methods; `#[allow(dead_code)]`
+ comment on the sqlx FromRow field-sets that ARE populated by the DB. No SQL or logic
changed. `cargo test` requires DATABASE_URL by design.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -168,14 +168,14 @@ pub async fn upload(
|
||||
// Validate caption length. Counted in chars (code points) to match the
|
||||
// "Zeichen" wording in the error message — `.len()` would be bytes and
|
||||
// reject perfectly valid German/emoji captions early.
|
||||
if let Some(ref cap) = caption {
|
||||
if cap.chars().count() > MAX_CAPTION_LENGTH {
|
||||
let _ = tokio::fs::remove_file(&temp_abs).await;
|
||||
return Err(AppError::BadRequest(format!(
|
||||
"Beschreibung ist zu lang. Maximum: {} Zeichen.",
|
||||
MAX_CAPTION_LENGTH
|
||||
)));
|
||||
}
|
||||
if let Some(ref cap) = caption
|
||||
&& cap.chars().count() > MAX_CAPTION_LENGTH
|
||||
{
|
||||
let _ = tokio::fs::remove_file(&temp_abs).await;
|
||||
return Err(AppError::BadRequest(format!(
|
||||
"Beschreibung ist zu lang. Maximum: {} Zeichen.",
|
||||
MAX_CAPTION_LENGTH
|
||||
)));
|
||||
}
|
||||
|
||||
// Determine the file type from its magic bytes and require it to be on the
|
||||
@@ -555,6 +555,9 @@ pub struct QuotaEstimate {
|
||||
pub limit_bytes: Option<i64>,
|
||||
pub active_uploaders: i64,
|
||||
pub free_disk_bytes: i64,
|
||||
/// The tolerance factor the limit above was computed with. Carried on the snapshot so the
|
||||
/// number is self-describing; no caller reads it back today.
|
||||
#[allow(dead_code)]
|
||||
pub tolerance: f64,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user