test(unit): broaden coverage — quota formula + auth token/JWT decode

Backend: extract the per-user quota formula from compute_storage_quota into a
pure `quota_limit_bytes(free_disk, tolerance, active)` (behavior-preserving) and
unit-test it: tolerance scaling, floor of fractional results, active<1 clamped to
1 (divide-by-zero guard), zero free disk, single-uploader identity.

Frontend (jsdom + browser:true mock): auth.ts token storage and JWT claim decode
— setAuth/getToken/getPin round-trip, clearAuth keeps the PIN (for recovery),
clearPin, getExpiry (exp seconds→ms Date; null on missing/malformed), getRole
(role claim; null on absent/malformed). Adds jsdom devDependency.

Backend: 25 passing. Frontend: 18 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
fabi
2026-07-01 19:53:03 +02:00
parent 2c44006392
commit 226e455328
4 changed files with 644 additions and 1 deletions

View File

@@ -331,6 +331,13 @@ pub struct QuotaEstimate {
pub tolerance: f64,
}
/// Pure per-user quota formula: `floor((free_disk * tolerance) / max(active, 1))`.
/// Extracted from `compute_storage_quota` so it's unit-testable without a DB or disk.
fn quota_limit_bytes(free_disk: i64, tolerance: f64, active_uploaders: i64) -> i64 {
let active = active_uploaders.max(1);
((free_disk as f64 * tolerance) / active as f64).floor() as i64
}
/// Computes the per-user storage quota using
/// `floor((free_disk * tolerance) / max(active_uploaders, 1))`. Returns `limit_bytes =
/// None` whenever the storage quota is currently disabled — callers should skip the
@@ -362,7 +369,7 @@ pub async fn compute_storage_quota(state: &AppState) -> QuotaEstimate {
}) as i64;
let limit_bytes = if quota_on && storage_quota_on {
Some(((free_disk as f64 * tolerance) / active as f64).floor() as i64)
Some(quota_limit_bytes(free_disk, tolerance, active))
} else {
None
};
@@ -425,3 +432,37 @@ pub async fn get_original(
.body(Body::from_stream(stream))
.map_err(|e| AppError::Internal(e.into()))
}
#[cfg(test)]
mod tests {
use super::quota_limit_bytes;
#[test]
fn divides_free_space_by_uploaders_with_tolerance() {
// 1000 * 0.75 / 3 = 250
assert_eq!(quota_limit_bytes(1000, 0.75, 3), 250);
}
#[test]
fn floors_fractional_results() {
// 1000 * 0.75 / 7 = 107.14… → 107
assert_eq!(quota_limit_bytes(1000, 0.75, 7), 107);
}
#[test]
fn active_uploaders_below_one_is_clamped_to_one() {
// Guards against divide-by-zero when no one has uploaded yet.
assert_eq!(quota_limit_bytes(1000, 1.0, 0), 1000);
assert_eq!(quota_limit_bytes(1000, 1.0, -5), 1000);
}
#[test]
fn zero_free_disk_yields_zero() {
assert_eq!(quota_limit_bytes(0, 0.75, 3), 0);
}
#[test]
fn full_tolerance_is_identity_for_a_single_uploader() {
assert_eq!(quota_limit_bytes(500, 1.0, 1), 500);
}
}