faces: cover hydrate_face_with_person — assigned + unassigned branches
Two unit tests pinning the response shape that PATCH/POST /image/faces relies on. They use the existing in-memory SQLite harness and exercise the helper directly: - assigned: person_name resolves through the persons join and bbox / source / person_id round-trip cleanly. - unassigned: person_name is None (not stale, not omitted), person_id is None. These would have caught the prior regression — when the handlers returned a bare FaceDetectionRow, person_name was structurally absent from the response shape. A test that asserts person_name is populated when person_id is set forces the join (or any equivalent) to exist. A dangling-person_id case isn't covered: the FK on face_detections makes that state structurally impossible at rest (ON DELETE SET NULL zeroes the column when a person is removed), so there's nothing to defend against.
This commit is contained in:
76
src/faces.rs
76
src/faces.rs
@@ -3153,4 +3153,80 @@ mod tests {
|
|||||||
assert!(img.height() <= 100);
|
assert!(img.height() <= 100);
|
||||||
assert!(img.width() > 0 && img.height() > 0);
|
assert!(img.width() > 0 && img.height() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── hydrate_face_with_person — PATCH/POST /image/faces response shape ──
|
||||||
|
|
||||||
|
fn seed_library_and_face(dao: &mut SqliteFaceDao, person_id: Option<i32>) -> FaceDetectionRow {
|
||||||
|
diesel::sql_query(
|
||||||
|
"INSERT OR IGNORE INTO libraries (id, name, root_path, created_at) \
|
||||||
|
VALUES (1, 'main', '/tmp', 0)",
|
||||||
|
)
|
||||||
|
.execute(dao.connection.lock().unwrap().deref_mut())
|
||||||
|
.expect("seed libraries");
|
||||||
|
dao.store_detection(
|
||||||
|
&ctx(),
|
||||||
|
InsertFaceDetectionInput {
|
||||||
|
library_id: 1,
|
||||||
|
content_hash: "h-hydrate".into(),
|
||||||
|
rel_path: "p.jpg".into(),
|
||||||
|
bbox: Some((0.1, 0.2, 0.3, 0.4)),
|
||||||
|
embedding: Some(vec![0u8; 2048]),
|
||||||
|
confidence: Some(0.9),
|
||||||
|
source: "manual".into(),
|
||||||
|
person_id,
|
||||||
|
status: "detected".into(),
|
||||||
|
model_version: "buffalo_l".into(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hydrate_face_carries_person_name_when_assigned() {
|
||||||
|
// Regression guard for the bug where PATCH /image/faces/{id}
|
||||||
|
// returned a bare FaceDetectionRow (no person_name), causing
|
||||||
|
// the carousel overlay's optimistic replace to drop the VFD
|
||||||
|
// label off the bbox after every save. The handler hydrates
|
||||||
|
// via this helper; if anyone refactors the helper to skip the
|
||||||
|
// persons join, this test fails.
|
||||||
|
let mut dao = fresh_dao();
|
||||||
|
let p = dao
|
||||||
|
.create_person(
|
||||||
|
&ctx(),
|
||||||
|
&CreatePersonReq {
|
||||||
|
name: "Alice".into(),
|
||||||
|
notes: None,
|
||||||
|
entity_id: None,
|
||||||
|
is_ignored: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let row = seed_library_and_face(&mut dao, Some(p.id));
|
||||||
|
let joined =
|
||||||
|
hydrate_face_with_person(&mut dao, &ctx(), row).expect("hydrate assigned");
|
||||||
|
assert_eq!(joined.person_id, Some(p.id));
|
||||||
|
assert_eq!(joined.person_name.as_deref(), Some("Alice"));
|
||||||
|
// Bbox + confidence + source must round-trip — these are what
|
||||||
|
// the optimistic-replace also keys on.
|
||||||
|
assert!((joined.bbox_x - 0.1).abs() < 1e-6);
|
||||||
|
assert!((joined.bbox_y - 0.2).abs() < 1e-6);
|
||||||
|
assert!((joined.bbox_w - 0.3).abs() < 1e-6);
|
||||||
|
assert!((joined.bbox_h - 0.4).abs() < 1e-6);
|
||||||
|
assert_eq!(joined.source, "manual");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hydrate_face_leaves_person_name_null_when_unassigned() {
|
||||||
|
// Mirror branch: an unassigned face must hydrate cleanly with
|
||||||
|
// person_name = None, not a stale value left over from a
|
||||||
|
// previously-assigned row's serialization.
|
||||||
|
let mut dao = fresh_dao();
|
||||||
|
let row = seed_library_and_face(&mut dao, None);
|
||||||
|
let joined =
|
||||||
|
hydrate_face_with_person(&mut dao, &ctx(), row).expect("hydrate unassigned");
|
||||||
|
assert!(joined.person_id.is_none());
|
||||||
|
assert!(joined.person_name.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user