Expanded the e2e test suite and fake device data
Check, format and test / build (push) Successful in 46s

- fake_records now generates a full year (2026) of classic workday
  punches (09:00-13:00, 14:00-18:00) for the employees in fake_users;
  this spans many 1024-byte blocks, exercising the multi-block
  streaming path
- fake_users/fake_records are now public so tests can assert against
  them instead of hardcoded expectations
- scoped #[allow(clippy::unwrap_used)] to only the functions that
  actually use unwrap, instead of a blanket file-level allow
- added gets_total_record_count, streams_records, and
  gets_none_for_unknown_employee tests

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 15:25:18 +02:00
co-authored by Claude Sonnet 5
parent 9977db0e46
commit 9a637190b5
2 changed files with 93 additions and 15 deletions
+43 -13
View File
@@ -2,31 +2,59 @@
//! client against it, grounded in the protocol documented at
//! <https://nicolabelluti.me/posts/2024/05/studying-a-communication-protocol/>.
use chrono::{Datelike, Local, TimeZone, Timelike};
use chrono::{Datelike, Duration, Local, TimeZone, Timelike};
use r701::{Clock, Record};
use std::collections::HashMap;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
/// The employee names the fake device reports
fn fake_users() -> HashMap<u32, String> {
HashMap::from([(1, "mario".to_string())])
pub fn fake_users() -> HashMap<u32, String> {
HashMap::from([(1, "alice".to_string()), (2, "bob".to_string())])
}
/// The attendance records the fake device reports
fn fake_records() -> Vec<Record> {
vec![Record {
employee_id: 1,
clock: Clock::FirstIn,
datetime: Local
.with_ymd_and_hms(1970, 1, 1, 0, 0, 0)
.single()
.unwrap(),
}]
/// The attendance records the fake device reports: a full year of classic
/// workday punches (09:00 - 13:00, 14:00 - 18:00) for the employees in
/// `fake_users()`, staggered by punch time rather than grouped by employee.
#[allow(clippy::unwrap_used)]
pub fn fake_records() -> Vec<Record> {
let first_day = Local
.with_ymd_and_hms(2026, 1, 1, 9, 0, 0)
.single()
.unwrap();
let mut employee_ids: Vec<u32> = fake_users().into_keys().collect();
employee_ids.sort_unstable();
let mut records = Vec::new();
for day in 0..365 {
let morning_in = first_day + Duration::days(day);
let morning_out = morning_in + Duration::hours(4);
let afternoon_in = morning_out + Duration::hours(1);
let afternoon_out = afternoon_in + Duration::hours(4);
for (clock, datetime) in [
(Clock::FirstIn, morning_in),
(Clock::FirstOut, morning_out),
(Clock::SecondIn, afternoon_in),
(Clock::SecondOut, afternoon_out),
] {
for &employee_id in &employee_ids {
records.push(Record {
employee_id,
clock,
datetime,
});
}
}
}
records
}
/// Starts a fake R701 device in the background and returns the address to
/// connect to.
#[allow(clippy::unwrap_used)]
pub async fn spawn() -> String {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap().to_string();
@@ -70,6 +98,7 @@ pub async fn spawn() -> String {
/// Builds the 34-byte get-name response: a 12-byte header (whether the
/// employee was found), followed by their name (if found) null-padded to
/// 10 bytes, followed by 12 don't-care bytes.
#[allow(clippy::unwrap_used)]
fn get_name_response(payload: &[u8]) -> Vec<u8> {
let employee_id = u32::from_le_bytes(payload[2..6].try_into().unwrap());
let mut response = [0u8; 34];
@@ -99,6 +128,7 @@ fn get_total_record_count_response() -> Vec<u8> {
/// requested 1024-byte block of records (`ff`-padded past the last real
/// one, since records don't align to 1024-byte block boundaries), and 2
/// trailing bytes.
#[allow(clippy::unwrap_used)]
fn get_record_bytes_response(payload: &[u8]) -> Vec<u8> {
let sequence_number = u16::from_le_bytes(payload[8..10].try_into().unwrap());
let all_bytes: Vec<u8> = fake_records().iter().flat_map(encode_record).collect();