zkteco 1.0.0

A pure-Rust client for ZKTeco biometric attendance / access-control terminals (TCP/UDP protocol). A faithful port of the Python `pyzk` library.
Documentation
//! Offline tests for the wire protocol and (de)serialization.
//!
//! These run without a device. They exercise the public API surface and a few
//! synthetic byte buffers shaped exactly as a real device would send them.

use chrono::{NaiveDate, NaiveDateTime};
use zkteco::{Attendance, Finger, User, USER_ADMIN, USER_DEFAULT};

/// Build a `NaiveDateTime` succinctly.
fn dt(y: i32, mo: u32, d: u32, h: u32, mi: u32, s: u32) -> NaiveDateTime {
    NaiveDate::from_ymd_opt(y, mo, d)
        .unwrap()
        .and_hms_opt(h, mi, s)
        .unwrap()
}

#[test]
fn finger_full_record_roundtrips_length_and_header() {
    let template = vec![0xAA, 0xBB, 0xCC, 0xDD];
    let finger = Finger::new(7, 2, 1, template.clone());
    let packed = finger.repack();

    // Layout: <H H b b {n}s> = size+6, uid, fid, valid, template
    assert_eq!(packed.len(), 6 + template.len());
    assert_eq!(
        u16::from_le_bytes([packed[0], packed[1]]),
        (template.len() + 6) as u16
    );
    assert_eq!(u16::from_le_bytes([packed[2], packed[3]]), 7); // uid
    assert_eq!(packed[4], 2); // fid
    assert_eq!(packed[5], 1); // valid
    assert_eq!(&packed[6..], &template[..]);
    assert_eq!(finger.size(), 4);
}

#[test]
fn user_privilege_helpers() {
    let admin = User::new(1, "Boss", USER_ADMIN, "", "", "1", 0);
    assert!(admin.is_enabled());
    assert_eq!(admin.usertype(), USER_ADMIN);

    // Bit 0 set means disabled.
    let disabled = User::new(2, "Temp", USER_DEFAULT | 1, "", "", "2", 0);
    assert!(disabled.is_disabled());
    assert!(!disabled.is_enabled());
}

#[test]
fn attendance_punch_labels() {
    let base = dt(2024, 1, 2, 3, 4, 5);
    let a = Attendance::new("42", base, 0, 0, 42);
    assert_eq!(a.punch_label(), "check-in");
    let b = Attendance::new("42", base, 0, 1, 42);
    assert_eq!(b.punch_label(), "check-out");
    let c = Attendance::new("42", base, 0, 9, 42);
    assert_eq!(c.punch_label(), "punch 9");
}

#[test]
fn attendance_display_format() {
    let a = Attendance::new("1001", dt(2024, 5, 6, 7, 8, 9), 1, 0, 5);
    assert_eq!(
        a.to_string(),
        "<Attendance>: 1001 : 2024-05-06 07:08:09 (1, 0)"
    );
}

#[test]
fn user_display_format() {
    let u = User::new(3, "Alice", USER_DEFAULT, "", "", "300", 0);
    assert_eq!(u.to_string(), "<User>: [uid:3, name:Alice user_id:300]");
}