From f88e02478ae95820bde49b811f7382f10166c6a9 Mon Sep 17 00:00:00 2001 From: rzuasti Date: Tue, 24 Feb 2026 17:25:57 -0500 Subject: [PATCH] enhanced device database structure, added methods to device db module and unit tests --- .../01-initial_structure/up.sql | 5 +- backend/src/db/devices.rs | 149 +++++++++++++++++- .../device_finders/arp/packet_send_receive.rs | 12 +- backend/src/model/devices.rs | 38 ++++- backend/tests/database_setup/02-devices.sql | 6 + 5 files changed, 196 insertions(+), 14 deletions(-) create mode 100644 backend/tests/database_setup/02-devices.sql diff --git a/backend/database_migrations/01-initial_structure/up.sql b/backend/database_migrations/01-initial_structure/up.sql index 88f40ba..32c7cc7 100644 --- a/backend/database_migrations/01-initial_structure/up.sql +++ b/backend/database_migrations/01-initial_structure/up.sql @@ -2,7 +2,10 @@ CREATE TABLE devices( mac_address TEXT PRIMARY KEY, ipv4_address TEXT NOT NULL, vendor TEXT, - last_seen TEXT NOT NULL + last_seen TEXT NOT NULL, + is_registered INTEGER NOT NULL, + owner TEXT, + device_type TEXT ); CREATE TABLE notifications( diff --git a/backend/src/db/devices.rs b/backend/src/db/devices.rs index 2a767de..cddb6b4 100644 --- a/backend/src/db/devices.rs +++ b/backend/src/db/devices.rs @@ -11,7 +11,7 @@ pub fn read(mac_address: String) -> Option { let conn = db::get_db_connection(); let result: Result = conn.query_one( - "SELECT mac_address, ipv4_address, vendor, last_seen FROM devices WHERE mac_address=?1", + "SELECT mac_address, ipv4_address, vendor, last_seen, is_registered, owner, device_type FROM devices WHERE mac_address=?1", params![mac_address], |row| { Ok(Device { @@ -19,6 +19,9 @@ pub fn read(mac_address: String) -> Option { ipv4_address: row.get(1)?, vendor: row.get(2)?, last_seen: row.get(3)?, + is_registered: row.get(4)?, + owner: row.get(5)?, + device_type: row.get(6)?, }) }, ); @@ -41,8 +44,8 @@ pub fn insert(device: Device) -> Result<(), DbError> { let conn = db::get_db_connection(); match conn.execute( - "INSERT INTO devices (mac_address, ipv4_address, vendor, last_seen) VALUES (?1, ?2, ?3, ?4)", - params![device.mac_address, device.ipv4_address, device.vendor, device.last_seen]) { + "INSERT INTO devices (mac_address, ipv4_address, vendor, last_seen, is_registered, owner, device_type) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", + params![device.mac_address, device.ipv4_address, device.vendor, device.last_seen, device.is_registered, device.owner, device.device_type]) { Ok(_) => { debug!("Device inserted into database: {}", device); Ok(()) @@ -57,12 +60,15 @@ pub fn insert(device: Device) -> Result<(), DbError> { pub fn update(device: Device) -> Result<(), DbError> { let conn = db::get_db_connection(); match conn.execute( - "UPDATE devices SET ipv4_address=?1, vendor=?2, last_seen=?3 WHERE mac_address=?4", + "UPDATE devices SET ipv4_address=?1, vendor=?2, last_seen=?3, is_registered=?4, owner=?5, device_type=?6 WHERE mac_address=?7", params![ device.ipv4_address, device.vendor, device.last_seen, - device.mac_address + device.is_registered, + device.owner, + device.device_type, + device.mac_address, ], ) { Ok(_) => { @@ -75,3 +81,136 @@ pub fn update(device: Device) -> Result<(), DbError> { } } } + +#[cfg(test)] +mod tests { + use chrono::{TimeZone, Utc}; + + use super::*; + use crate::tests_common; + + #[tokio::test] + async fn test_read() { + tests_common::setup().await; + // Read a non-existant device and validate for None + assert!( + read("zz:zz:zz:zz:zz:zz".to_string()).is_none(), + "Device with MAC zz:zz:zz:zz:zz:zz should not exist" + ); + + // Read all testing devices and validate their values + // 'aa:aa:aa:aa:aa:aa', '192.168.0.1', 'Vendor 1', '2026-01-01 11:11:11', 0, '', '' + let device1 = read("aa:aa:aa:aa:aa:aa".to_string()).unwrap(); + assert_eq!( + device1.mac_address, "aa:aa:aa:aa:aa:aa", + "Invalid MAC address for device aa:aa:aa:aa:aa:aa" + ); + assert_eq!( + device1.device_type, "", + "Device type should be empty for device {}", + device1.mac_address + ); + assert_eq!( + device1.ipv4_address, "192.168.0.1", + "Invalid IP address for device {}", + device1.mac_address + ); + assert!( + !device1.is_registered, + "Device {} should not be registered", + device1.mac_address + ); + assert_eq!( + device1.last_seen, + Utc.with_ymd_and_hms(2026, 1, 1, 11, 11, 11).unwrap(), + "Invalid last_seen for device {}", + device1.mac_address + ); + assert_eq!( + device1.owner, "", + "Invalid owner for device {}", + device1.mac_address + ); + assert_eq!( + device1.vendor, "Vendor 1", + "Invalid vendor for device {}", + device1.mac_address + ); + + // 'bb:bb:bb:bb:bb:bb', '192.168.0.2', 'Vendor 2', '2026-02-03 13:14:15', 1, 'John', 'Phone' + let device2 = read("bb:bb:bb:bb:bb:bb".to_string()).unwrap(); + assert_eq!( + device2.mac_address, "bb:bb:bb:bb:bb:bb", + "Invalid MAC address for device bb:bb:bb:bb:bb:bb" + ); + assert_eq!( + device2.device_type, "Phone", + "Device type should be 'Phone' for device {}", + device2.mac_address + ); + assert_eq!( + device2.ipv4_address, "192.168.0.2", + "Invalid IP address for device {}", + device2.mac_address + ); + assert!( + device2.is_registered, + "Device {} should not be registered", + device2.mac_address + ); + assert_eq!( + device2.last_seen, + Utc.with_ymd_and_hms(2026, 2, 3, 13, 14, 15).unwrap(), + "Invalid last_seen for device {}", + device2.mac_address + ); + assert_eq!( + device2.owner, "John", + "Invalid owner for device {}", + device2.mac_address + ); + assert_eq!( + device2.vendor, "Vendor 2", + "Invalid vendor for device {}", + device2.mac_address + ); + + // 'cc:cc:cc:cc:cc:cc', '192.168.0.3', 'Vendor 3', '2026-02-17 20:11:00', 1, 'Sarah', 'Laptop' + let device3 = read("cc:cc:cc:cc:cc:cc".to_string()).unwrap(); + assert_eq!( + device3.mac_address, "cc:cc:cc:cc:cc:cc", + "Invalid MAC address for device cc:cc:cc:cc:cc:cc" + ); + assert_eq!( + device3.device_type, "Laptop", + "Device type should be 'Laptop' for device {}", + device3.mac_address + ); + assert_eq!( + device3.ipv4_address, "192.168.0.3", + "Invalid IP address for device {}", + device3.mac_address + ); + assert!( + device3.is_registered, + "Device {} should not be registered", + device3.mac_address + ); + assert_eq!( + device3.last_seen, + Utc.with_ymd_and_hms(2026, 2, 17, 20, 11, 00).unwrap(), + "Invalid last_seen for device {}", + device3.mac_address + ); + assert_eq!( + device3.owner, "Sarah", + "Invalid owner for device {}", + device3.mac_address + ); + assert_eq!( + device3.vendor, "Vendor 3", + "Invalid vendor for device {}", + device3.mac_address + ); + } +} diff --git a/backend/src/device_finders/arp/packet_send_receive.rs b/backend/src/device_finders/arp/packet_send_receive.rs index 3d53172..019ffa0 100644 --- a/backend/src/device_finders/arp/packet_send_receive.rs +++ b/backend/src/device_finders/arp/packet_send_receive.rs @@ -106,12 +106,12 @@ pub async fn listen_for_packets( "Found online device - IP addr={} - MAC addr={} - vendor={}", packet_ip_address, packet_mac_address, packet_vendor ); - devices.push(Device { - mac_address: packet_mac_address, - ipv4_address: packet_ip_address, - vendor: packet_vendor, - last_seen: Local::now().to_utc(), - }); + devices.push(Device::new( + packet_mac_address, + packet_ip_address, + packet_vendor, + Local::now().to_utc(), + )); } } } diff --git a/backend/src/model/devices.rs b/backend/src/model/devices.rs index 2cc67cb..81ebebb 100644 --- a/backend/src/model/devices.rs +++ b/backend/src/model/devices.rs @@ -9,14 +9,48 @@ pub struct Device { pub vendor: String, #[serde(with = "ts_seconds")] pub last_seen: DateTime, + pub is_registered: bool, + pub owner: String, + pub device_type: String, +} + +impl Device { + pub fn new( + mac_address: String, + ipv4_address: String, + vendor: String, + last_seen: DateTime, + ) -> Self { + Self { + mac_address: mac_address, + ipv4_address: ipv4_address, + vendor: vendor, + last_seen: last_seen, + is_registered: false, + owner: "".to_string(), + device_type: "".to_string(), + } + } } impl fmt::Display for Device { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "mac={}, ip={}, vendor={}, last_seen={}", - self.mac_address, self.ipv4_address, self.vendor, self.last_seen + "mac={}, ip={}, vendor={}, last_seen={}, is_registered={}, owner={}, device_type={}", + self.mac_address, + self.ipv4_address, + self.vendor, + self.last_seen, + self.is_registered, + self.owner, + self.device_type ) } } + +impl PartialEq for Device { + fn eq(&self, other: &Self) -> bool { + self.mac_address == other.mac_address + } +} diff --git a/backend/tests/database_setup/02-devices.sql b/backend/tests/database_setup/02-devices.sql new file mode 100644 index 0000000..2752ab6 --- /dev/null +++ b/backend/tests/database_setup/02-devices.sql @@ -0,0 +1,6 @@ +INSERT INTO DEVICES (mac_address, ipv4_address, vendor, last_seen, is_registered, owner, device_type) + VALUES ('aa:aa:aa:aa:aa:aa', '192.168.0.1', 'Vendor 1', '2026-01-01 11:11:11', 0, '', ''); +INSERT INTO DEVICES (mac_address, ipv4_address, vendor, last_seen, is_registered, owner, device_type) + VALUES ('bb:bb:bb:bb:bb:bb', '192.168.0.2', 'Vendor 2', '2026-02-03 13:14:15', 1, 'John', 'Phone'); +INSERT INTO DEVICES (mac_address, ipv4_address, vendor, last_seen, is_registered, owner, device_type) + VALUES ('cc:cc:cc:cc:cc:cc', '192.168.0.3', 'Vendor 3', '2026-02-17 20:11:00', 1, 'Sarah', 'Laptop');