use captchaforge::mouse_sampler::MouseSampler;
use proptest::prelude::*;
proptest! {
#[test]
fn sampled_trace_steps_are_finite(
x0 in -2000.0f32..2000.0,
y0 in -2000.0f32..2000.0,
x1 in -2000.0f32..2000.0,
y1 in -2000.0f32..2000.0,
) {
let sampler = MouseSampler::new();
let trace = sampler.sample(x0, y0, x1, y1);
for step in &trace.steps {
prop_assert!(step.dx.is_finite(),
"dx={} non-finite for ({},{})→({},{})", step.dx, x0, y0, x1, y1);
prop_assert!(step.dy.is_finite(),
"dy={} non-finite for ({},{})→({},{})", step.dy, x0, y0, x1, y1);
}
}
#[test]
fn sampled_trace_lands_near_target(
x0 in -500.0f32..500.0,
y0 in -500.0f32..500.0,
x1 in -500.0f32..500.0,
y1 in -500.0f32..500.0,
) {
let sampler = MouseSampler::new();
let trace = sampler.sample(x0, y0, x1, y1);
let final_x: f32 = trace.steps.iter().map(|s| s.dx).sum();
let final_y: f32 = trace.steps.iter().map(|s| s.dy).sum();
let actual_end_x = x0 + final_x;
let actual_end_y = y0 + final_y;
let tol = 100.0;
prop_assert!((actual_end_x - x1).abs() < tol,
"ended at x={}, expected {}, dx={}", actual_end_x, x1, final_x);
prop_assert!((actual_end_y - y1).abs() < tol,
"ended at y={}, expected {}, dy={}", actual_end_y, y1, final_y);
}
#[test]
fn sampled_trace_is_non_empty(
x0 in -100.0f32..100.0,
y0 in -100.0f32..100.0,
x1 in -100.0f32..100.0,
y1 in -100.0f32..100.0,
) {
let sampler = MouseSampler::new();
let trace = sampler.sample(x0, y0, x1, y1);
prop_assert!(!trace.steps.is_empty(),
"sampled trace must have ≥1 step");
}
#[test]
fn sampled_trace_duration_is_humanlike(
x0 in -200.0f32..200.0,
y0 in -200.0f32..200.0,
x1 in -200.0f32..200.0,
y1 in -200.0f32..200.0,
) {
let sampler = MouseSampler::new();
let trace = sampler.sample(x0, y0, x1, y1);
let total_ms: u32 = trace.steps.iter().map(|s| s.dt_ms).sum();
prop_assert!(total_ms > 0, "total duration must be positive");
prop_assert!(total_ms <= 8000,
"total duration {} ms exceeds humanlike upper bound", total_ms);
}
}
#[test]
fn bundled_corpus_is_non_empty() {
let sampler = MouseSampler::new();
assert!(!sampler.corpus().is_empty(),
"MouseSampler::new() must bundle real traces — empty corpus would force the solver to use the synthetic single-step fallback");
}
#[test]
fn extra_traces_extend_corpus() {
use captchaforge::mouse_sampler::{Step, Trace};
let sampler = MouseSampler::new();
let baseline = sampler.corpus().len();
let extra = vec![Trace {
steps: vec![Step {
dx: 1.0,
dy: 0.0,
dt_ms: 100,
}],
}];
let extended = MouseSampler::new().with_extra_traces(extra);
assert_eq!(extended.corpus().len(), baseline + 1);
}