diff --git a/tests/test_refactoring_contracts.py b/tests/test_refactoring_contracts.py
new file mode 100644
index 0000000..60f170b
--- /dev/null
+++ b/tests/test_refactoring_contracts.py
@@ -0,0 +1,502 @@
+"""Behavioral contracts captured before extracting attendance services and routes."""
+
+import base64
+import socket
+from datetime import datetime as RealDateTime
+
+import cv2
+import numpy as np
+import pandas as pd
+import pytest
+
+import attendance as attendance_module
+import face_attendance_app as app_module
+from modules.identification import match_face
+
+
+class FrozenDateTime:
+ @classmethod
+ def now(cls):
+ return RealDateTime(2025, 4, 3, 2, 1, 5, 6789)
+
+
+@pytest.fixture(autouse=True)
+def forbid_hardware_and_network(monkeypatch):
+ def unexpected_io(*_args, **_kwargs):
+ pytest.fail("characterization tests must not use hardware or network")
+
+ monkeypatch.setattr(app_module.cv2, "VideoCapture", unexpected_io)
+ monkeypatch.setattr(socket.socket, "connect", unexpected_io)
+ monkeypatch.setattr(socket.socket, "connect_ex", unexpected_io)
+ monkeypatch.setattr(socket, "create_connection", unexpected_io)
+
+
+@pytest.fixture
+def isolated_app(tmp_path, monkeypatch):
+ monkeypatch.setattr(app_module, "BASE_DIR", tmp_path)
+ return app_module.create_app()
+
+
+def jpeg_base64():
+ frame = np.zeros((40, 40, 3), dtype=np.uint8)
+ success, buffer = cv2.imencode(".jpg", frame)
+ assert success
+ return base64.b64encode(buffer.tobytes()).decode("ascii")
+
+
+def test_routes_keep_their_current_http_methods(isolated_app):
+ routes = {
+ rule.rule: {
+ method
+ for method in rule.methods
+ if method not in {"HEAD", "OPTIONS"}
+ }
+ for rule in isolated_app.url_map.iter_rules()
+ }
+
+ assert routes["/"] == {"GET"}
+ assert routes["/video_feed"] == {"GET"}
+ assert routes["/recognize"] == {"POST"}
+ assert routes["/enroll"] == {"POST"}
+ assert routes["/attendance"] == {"GET"}
+ assert routes["/enrolled-persons"] == {"GET"}
+ assert routes["/health"] == {"GET"}
+
+
+def test_index_keeps_primary_dashboard_controls():
+ response = app_module.create_app().test_client().get("/")
+
+ assert response.status_code == 200
+ for marker in (
+ b"
Face Attendance System",
+ b'id="startBtn"',
+ b"Start Recognition",
+ b'id="enrollBtn"',
+ b"Enroll New Person",
+ b'id="attendanceBtn"',
+ b"View Attendance",
+ ):
+ assert marker in response.data
+
+
+def test_health_response_keeps_timestamp_format(isolated_app, monkeypatch):
+ monkeypatch.setattr(app_module, "datetime", FrozenDateTime)
+
+ response = isolated_app.test_client().get("/health")
+
+ assert response.status_code == 200
+ assert response.get_json() == {
+ "status": "ok",
+ "timestamp": "2025-04-03T02:01:05.006789",
+ }
+
+
+def test_recognize_returns_multiple_known_faces_and_marks_duplicates_once(
+ isolated_app, monkeypatch
+):
+ monkeypatch.setattr(
+ app_module,
+ "_load_known_faces_from_folder",
+ lambda *_args: ([np.zeros(128, dtype=np.float32)], ["Alice"]),
+ )
+ monkeypatch.setattr(
+ app_module,
+ "detect_faces",
+ lambda *_args, **_kwargs: [
+ (0, 4, 4, 0),
+ (0, 8, 4, 4),
+ (4, 4, 8, 0),
+ ],
+ )
+ monkeypatch.setattr(
+ app_module,
+ "encode_faces",
+ lambda *_args: [
+ np.zeros(128, dtype=np.float32),
+ np.ones(128, dtype=np.float32) * 10,
+ np.zeros(128, dtype=np.float32),
+ ],
+ )
+ monkeypatch.setattr(app_module, "datetime", FrozenDateTime)
+
+ response = isolated_app.test_client().post(
+ "/recognize",
+ json={"frame": "data:image/jpeg;base64," + jpeg_base64()},
+ )
+
+ payload = response.get_json()
+ attendance = pd.read_csv(
+ isolated_app.config["ATTENDANCE_SYSTEM"].get_attendance_file()
+ )
+ annotated = cv2.imdecode(
+ np.frombuffer(
+ base64.b64decode(payload["annotated_frame"]),
+ dtype=np.uint8,
+ ),
+ cv2.IMREAD_COLOR,
+ )
+
+ assert response.status_code == 200
+ assert payload["success"] is True
+ assert payload["recognized_names"] == ["Alice", "Alice"]
+ assert payload["timestamp"] == "2025-04-03 02:01:05"
+ assert annotated is not None
+ assert attendance["Name"].tolist() == ["Alice"]
+
+
+def test_recognize_no_faces_returns_empty_names(isolated_app, monkeypatch):
+ monkeypatch.setattr(
+ app_module, "_load_known_faces_from_folder", lambda *_args: ([], [])
+ )
+ monkeypatch.setattr(
+ app_module,
+ "detect_faces",
+ lambda *_args, **_kwargs: [],
+ )
+ monkeypatch.setattr(app_module, "encode_faces", lambda *_args: [])
+
+ response = isolated_app.test_client().post(
+ "/recognize",
+ json={"frame": jpeg_base64()},
+ )
+
+ assert response.status_code == 200
+ assert response.get_json()["recognized_names"] == []
+
+
+def test_recognize_malformed_base64_keeps_400_error(isolated_app):
+ response = isolated_app.test_client().post(
+ "/recognize",
+ json={"frame": "not base64"},
+ )
+
+ assert response.status_code == 400
+ assert response.get_json() == {"error": "Failed to decode frame"}
+
+
+def test_recognize_encode_failure_keeps_500_error(isolated_app, monkeypatch):
+ frame = jpeg_base64()
+ monkeypatch.setattr(
+ app_module, "_load_known_faces_from_folder", lambda *_args: ([], [])
+ )
+ monkeypatch.setattr(
+ app_module, "detect_faces", lambda *_args, **_kwargs: []
+ )
+ monkeypatch.setattr(app_module, "encode_faces", lambda *_args: [])
+ monkeypatch.setattr(app_module.cv2, "imencode", lambda *_args: (False, None))
+
+ response = isolated_app.test_client().post(
+ "/recognize",
+ json={"frame": frame},
+ )
+
+ assert response.status_code == 500
+ assert response.get_json() == {"error": "Failed to encode result frame"}
+
+
+@pytest.mark.parametrize("name", ["", " ", ".", "..", "../outside", "a\\\\b"])
+def test_enroll_rejects_invalid_names(isolated_app, name):
+ response = isolated_app.test_client().post(
+ "/enroll",
+ json={"name": name, "frame": "unused"},
+ )
+
+ assert response.status_code == 400
+ assert response.get_json() == {"error": "A valid name is required"}
+
+
+def test_enroll_rejects_malformed_frame(isolated_app):
+ response = isolated_app.test_client().post(
+ "/enroll",
+ json={"name": "Alice", "frame": "not base64"},
+ )
+
+ assert response.status_code == 400
+ assert response.get_json() == {"error": "Failed to decode frame"}
+
+
+def test_enroll_requires_a_detected_face(isolated_app, monkeypatch):
+ monkeypatch.setattr(
+ app_module, "detect_faces", lambda *_args, **_kwargs: []
+ )
+ monkeypatch.setattr(app_module, "encode_faces", lambda *_args: [])
+
+ response = isolated_app.test_client().post(
+ "/enroll",
+ json={"name": "Alice", "frame": jpeg_base64()},
+ )
+
+ assert response.status_code == 400
+ assert response.get_json() == {"error": "No face detected"}
+ assert not (isolated_app.config["KNOWN_FACES_FOLDER"] / "Alice").exists()
+
+
+def test_enroll_uses_existing_timestamped_jpeg_name(
+ isolated_app, monkeypatch
+):
+ monkeypatch.setattr(
+ app_module, "detect_faces", lambda *_args, **_kwargs: [(1, 9, 9, 1)]
+ )
+ monkeypatch.setattr(
+ app_module,
+ "encode_faces",
+ lambda *_args: [np.zeros(128, dtype=np.float32)],
+ )
+ monkeypatch.setattr(app_module, "datetime", FrozenDateTime)
+
+ response = isolated_app.test_client().post(
+ "/enroll",
+ json={"name": "Alice", "frame": jpeg_base64()},
+ )
+
+ files = list(
+ (isolated_app.config["KNOWN_FACES_FOLDER"] / "Alice").glob("*.jpg")
+ )
+ assert response.status_code == 200
+ assert response.get_json() == {
+ "success": True,
+ "message": "Successfully enrolled Alice",
+ }
+ assert [path.name for path in files] == ["20250403_020105_006789.jpg"]
+
+
+def test_enroll_preserves_unicode_name_in_path_and_response(
+ isolated_app, monkeypatch
+):
+ monkeypatch.setattr(
+ app_module, "detect_faces", lambda *_args, **_kwargs: [(1, 9, 9, 1)]
+ )
+ monkeypatch.setattr(
+ app_module,
+ "encode_faces",
+ lambda *_args: [np.zeros(128, dtype=np.float32)],
+ )
+ monkeypatch.setattr(app_module, "datetime", FrozenDateTime)
+ saved_paths = []
+ monkeypatch.setattr(
+ app_module.cv2,
+ "imwrite",
+ lambda path, _frame: saved_paths.append(path) or True,
+ )
+ person_name = "Zoë 東京"
+
+ response = isolated_app.test_client().post(
+ "/enroll",
+ json={"name": f" {person_name} ", "frame": jpeg_base64()},
+ )
+
+ assert response.status_code == 200
+ assert response.get_json() == {
+ "success": True,
+ "message": f"Successfully enrolled {person_name}",
+ }
+ assert saved_paths == [
+ str(
+ isolated_app.config["KNOWN_FACES_FOLDER"]
+ / person_name
+ / "20250403_020105_006789.jpg"
+ )
+ ]
+
+
+def test_enroll_save_failure_keeps_500_response(isolated_app, monkeypatch):
+ monkeypatch.setattr(
+ app_module, "detect_faces", lambda *_args, **_kwargs: [(1, 9, 9, 1)]
+ )
+ monkeypatch.setattr(
+ app_module,
+ "encode_faces",
+ lambda *_args: [np.zeros(128, dtype=np.float32)],
+ )
+ monkeypatch.setattr(app_module.cv2, "imwrite", lambda *_args: False)
+
+ response = isolated_app.test_client().post(
+ "/enroll",
+ json={"name": "Alice", "frame": jpeg_base64()},
+ )
+
+ assert response.status_code == 500
+ assert response.get_json() == {"error": "Failed to save enrollment image"}
+
+
+def test_attendance_returns_empty_list_when_storage_summary_fails(
+ isolated_app, monkeypatch
+):
+ monkeypatch.setattr(app_module, "datetime", FrozenDateTime)
+ monkeypatch.setattr(
+ isolated_app.config["ATTENDANCE_SYSTEM"],
+ "get_attendance_summary",
+ lambda: None,
+ )
+
+ response = isolated_app.test_client().get("/attendance")
+
+ assert response.status_code == 200
+ assert response.get_json() == {
+ "success": True,
+ "attendance": [],
+ "date": "2025-04-03",
+ "count": 0,
+ }
+
+
+def test_attendance_csv_freezes_date_timestamp_and_column_order(
+ tmp_path, monkeypatch
+):
+ monkeypatch.setattr(attendance_module, "get_date", lambda: "2025-04-03")
+ monkeypatch.setattr(
+ attendance_module, "get_timestamp", lambda: "02:01:05"
+ )
+ system = attendance_module.AttendanceSystem(str(tmp_path))
+
+ person_name = "Zoë 東京"
+ assert system.mark_attendance(person_name) is True
+ path = tmp_path / "Attendance_2025-04-03.csv"
+ saved = pd.read_csv(path)
+
+ assert list(saved.columns) == ["Name", "Time", "Status"]
+ assert saved.to_dict(orient="records") == [
+ {"Name": person_name, "Time": "02:01:05", "Status": "Present"}
+ ]
+
+
+def test_attendance_storage_error_does_not_mark_person(
+ tmp_path, monkeypatch
+):
+ system = attendance_module.AttendanceSystem(str(tmp_path))
+
+ def fail_read(*_args, **_kwargs):
+ raise OSError("simulated storage read failure")
+
+ monkeypatch.setattr(attendance_module.pd, "read_csv", fail_read)
+
+ assert system.mark_attendance("Alice") is False
+ assert system.marked_today == set()
+
+
+def test_match_face_keeps_first_label_for_equal_distance():
+ encoding = np.zeros(128, dtype=np.float32)
+ known_encodings = np.stack(
+ [
+ np.ones(128, dtype=np.float32),
+ -np.ones(128, dtype=np.float32),
+ ]
+ )
+
+ name, distance = match_face(
+ encoding,
+ known_encodings,
+ ["First", "Second"],
+ tolerance=20,
+ )
+
+ assert name == "First"
+ assert distance == pytest.approx(np.sqrt(128))
+
+
+def test_match_face_accepts_distance_at_tolerance_boundary():
+ encoding = np.zeros(128, dtype=np.float32)
+ encoding[0] = 0.5
+
+ name, distance = match_face(
+ encoding,
+ np.zeros((1, 128), dtype=np.float32),
+ ["Alice"],
+ tolerance=0.5,
+ )
+
+ assert name == "Alice"
+ assert distance == pytest.approx(0.5)
+
+
+def test_match_face_keeps_unknown_result_above_tolerance():
+ name, distance = match_face(
+ np.ones(128, dtype=np.float32),
+ np.zeros((1, 128), dtype=np.float32),
+ ["Alice"],
+ tolerance=0.6,
+ )
+
+ assert name == "Unknown"
+ assert distance == pytest.approx(np.sqrt(128))
+
+
+def test_video_feed_releases_camera_after_normal_end(isolated_app, monkeypatch):
+ class FakeVideoCapture:
+ def __init__(self):
+ self.releases = 0
+
+ def read(self):
+ return False, None
+
+ def release(self):
+ self.releases += 1
+
+ capture = FakeVideoCapture()
+ destroyed = []
+ monkeypatch.setattr(
+ app_module.cv2, "VideoCapture", lambda _index: capture
+ )
+ monkeypatch.setattr(
+ app_module.cv2, "destroyAllWindows", lambda: destroyed.append(True)
+ )
+ monkeypatch.setattr(
+ app_module,
+ "_load_known_faces_from_folder",
+ lambda *_args: ([], []),
+ )
+
+ response = isolated_app.test_client().get("/video_feed", buffered=False)
+ body = list(response.response)
+ response.close()
+
+ assert response.status_code == 200
+ assert response.mimetype == "multipart/x-mixed-replace"
+ assert body == []
+ assert capture.releases == 1
+ assert destroyed == [True]
+
+
+def test_video_feed_releases_camera_when_frame_processing_raises(
+ isolated_app, monkeypatch
+):
+ frame = np.zeros((40, 40, 3), dtype=np.uint8)
+
+ class FakeVideoCapture:
+ def __init__(self):
+ self.releases = 0
+ self.reads = 0
+
+ def read(self):
+ self.reads += 1
+ if self.reads == 1:
+ return True, frame.copy()
+ return False, None
+
+ def release(self):
+ self.releases += 1
+
+ capture = FakeVideoCapture()
+ destroyed = []
+ monkeypatch.setattr(
+ app_module.cv2, "VideoCapture", lambda _index: capture
+ )
+ monkeypatch.setattr(
+ app_module.cv2, "destroyAllWindows", lambda: destroyed.append(True)
+ )
+ monkeypatch.setattr(
+ app_module,
+ "_load_known_faces_from_folder",
+ lambda *_args: ([], []),
+ )
+
+ def fail_processing(*_args, **_kwargs):
+ raise RuntimeError("simulated frame processing failure")
+
+ monkeypatch.setattr(app_module, "_recognize_frame", fail_processing)
+
+ with pytest.raises(RuntimeError, match="simulated frame processing failure"):
+ isolated_app.test_client().get("/video_feed", buffered=False)
+
+ assert capture.releases == 1
+ assert destroyed == [True]