Added notification read and insert from database

This commit is contained in:
rzuasti
2026-02-23 11:04:57 -05:00
parent 47788a6738
commit e38b875052
2 changed files with 257 additions and 20 deletions
+237 -19
View File
@@ -1,6 +1,7 @@
use crate::db;
use crate::db::error::DbError;
use log::debug;
use log::{debug, error};
use rusqlite::params;
use crate::model::notifications::Notification;
@@ -28,6 +29,55 @@ pub fn list() -> Result<Vec<Notification>, DbError> {
Ok(notifications)
}
pub fn insert(notification: Notification) -> Result<i64, DbError> {
let conn = db::get_db_connection();
match conn.execute(
"INSERT INTO notifications (created_on, notification_type, title, body, is_new) VALUES (?1, ?2, ?3, ?4, ?5)",
params![notification.created_on, notification.notification_type, notification.title, notification.body, notification.is_new]) {
Ok(_) => {
debug!("Notification inserted into database: {}", notification);
Ok(conn.last_insert_rowid())
},
Err(error) => {
error!("Error inserting notification ({notification}) into database: {error}");
Err(DbError::from(error))
},
}
}
pub fn read(id: i64) -> Option<Notification> {
let conn = db::get_db_connection();
let result: Result<Notification, rusqlite::Error> = conn.query_one(
"SELECT id, created_on, notification_type, title, body, is_new FROM notifications WHERE id=?1",
params![id],
|row| {
Ok(Notification {
id: row.get(0)?,
created_on: row.get(1)?,
notification_type: row.get(2)?,
title: row.get(3)?,
body: row.get(4)?,
is_new: row.get(5)?,
})
},
);
match result {
Ok(value) => Some(value),
Err(error) => {
match error {
rusqlite::Error::QueryReturnedNoRows => {
debug!("No notification found for id={id}.")
}
_ => error!("Error reading notification from database: {error}"),
};
None
}
}
}
#[cfg(test)]
mod tests {
use chrono::{TimeZone, Utc};
@@ -35,16 +85,184 @@ mod tests {
use super::*;
use crate::{model::notifications::NotificationType, tests_common};
#[tokio::test]
async fn test_insert_default() {
tests_common::setup().await;
// Insert notification and read with returned ID
let created_on = Utc::now();
let inserted_id = insert(Notification::new(
created_on,
NotificationType::DeviceOnlineAfterTime,
"New notification title".to_string(),
"New notification body".to_string(),
true,
))
.unwrap();
assert!(
inserted_id >= 0,
"Inserted notification id should be positive"
);
// Validate all fields
let inserted_notification = read(inserted_id).unwrap();
assert_eq!(
inserted_notification.notification_type,
NotificationType::DeviceOnlineAfterTime,
"Wrong notification type (should be DeviceOnlineAfterTime)"
);
assert_eq!(
inserted_notification.is_new, true,
"Notification should be new"
);
assert_eq!(
inserted_notification.created_on, created_on,
"Wrong created_on (should be ${created_on})"
);
assert_eq!(
inserted_notification.title, "New notification title",
"Wrong title (should be 'New notification title')"
);
assert_eq!(
inserted_notification.body, "New notification body",
"Wrong body (should be 'New notification body')"
);
// Insert and validate notification without title nor body
let created_on = Utc::now();
let inserted_id = insert(Notification::new(
created_on,
NotificationType::Other,
"".to_string(),
"".to_string(),
false,
))
.unwrap();
assert!(
inserted_id >= 0,
"Inserted notification id should be positive"
);
// Validate all fields
let inserted_notification = read(inserted_id).unwrap();
assert_eq!(
inserted_notification.notification_type,
NotificationType::Other,
"Wrong notification type (should be Other)"
);
assert_eq!(
inserted_notification.is_new, false,
"Notification should not be new"
);
assert_eq!(
inserted_notification.created_on, created_on,
"Wrong created_on (should be ${created_on})"
);
assert_eq!(
inserted_notification.title, "",
"Wrong title (should be empty)"
);
assert_eq!(
inserted_notification.body, "",
"Wrong body (should be empty)"
);
}
#[tokio::test]
async fn test_read_default() {
tests_common::setup().await;
// Read existing notification
let notification_option = read(2);
// Notification should not be empty
assert!(
notification_option.is_some(),
"Notification id=2 should be read"
);
// Validate all fields
let notification = notification_option.unwrap();
assert_eq!(notification.id, 2, "Wrong notification id (should be 2)");
assert_eq!(
notification.notification_type,
NotificationType::NewDeviceFound,
"Wrong notification type (should be NewDeviceFound)"
);
assert_eq!(notification.is_new, false, "Notification should not be new");
assert_eq!(
notification.created_on,
Utc.with_ymd_and_hms(2026, 1, 4, 8, 10, 13).unwrap(),
"Wrong created_on (should be 2026-01-04 08:10:13)"
);
assert_eq!(
notification.title, "Read new device found",
"Wrong title (should be 'Read new device found')"
);
assert_eq!(
notification.body, "Body read new device found",
"Wrong body (should be 'Body read new device found')"
);
// Read non-existant notification
assert!(
read(999999999).is_none(),
"Notification id=999999999 should not exist"
);
// Insert notification and read with returned ID
let created_on = Utc::now();
let inserted_id = insert(Notification::new(
created_on,
NotificationType::DeviceOnlineAfterTime,
"New notification title".to_string(),
"New notification body".to_string(),
true,
))
.unwrap();
// Validate all fields
let inserted_notification = read(inserted_id).unwrap();
assert_eq!(
inserted_notification.notification_type,
NotificationType::DeviceOnlineAfterTime,
"Wrong notification type (should be DeviceOnlineAfterTime)"
);
assert_eq!(
inserted_notification.is_new, true,
"Notification should be new"
);
assert_eq!(
inserted_notification.created_on, created_on,
"Wrong created_on (should be ${created_on})"
);
assert_eq!(
inserted_notification.title, "New notification title",
"Wrong title (should be 'New notification title')"
);
assert_eq!(
inserted_notification.body, "New notification body",
"Wrong body (should be 'New notification body')"
);
// Read non-existant notification
assert!(
read(999999999).is_none(),
"Notification id=999999999 should not exist"
);
}
#[tokio::test]
async fn test_list_default() {
tests_common::setup().await;
let notifications = list().unwrap();
// There should be 3 unread notifications
assert_eq!(
notifications.len(),
3,
"There should be 3 notifications in the list"
// There should be at least 3 unread notifications
assert!(
notifications.len() >= 3,
"There should be at least 3 notifications in the list"
);
// All notifications must be unread/new
@@ -56,32 +274,32 @@ mod tests {
);
}
// There should be only one of each type
assert_eq!(
1,
// There should be at least one of each type
assert!(
notifications
.iter()
.filter(|notification| notification.notification_type == NotificationType::Other)
.count(),
"There should be only one notification of type Other"
.count()
>= 1,
"There should be at least one notification of type Other"
);
assert_eq!(
1,
assert!(
notifications
.iter()
.filter(|notification| notification.notification_type
== NotificationType::DeviceOnlineAfterTime)
.count(),
"There should be only one notification of type Other"
.count()
>= 1,
"There should be at least one notification of type DeviceOnlineAfterTime"
);
assert_eq!(
1,
assert!(
notifications
.iter()
.filter(|notification| notification.notification_type
== NotificationType::NewDeviceFound)
.count(),
"There should be only one notification of type Other"
.count()
>= 1,
"There should be at least one notification of type Other"
);
// Check date of notification 1
+20 -1
View File
@@ -14,6 +14,25 @@ pub struct Notification {
pub is_new: bool,
}
impl Notification {
pub fn new(
created_on: DateTime<Utc>,
notification_type: NotificationType,
title: String,
body: String,
is_new: bool,
) -> Self {
Self {
id: -1,
created_on: created_on,
notification_type: notification_type,
title: title,
body: body,
is_new: is_new,
}
}
}
impl fmt::Display for Notification {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
@@ -30,7 +49,7 @@ impl PartialEq for Notification {
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum NotificationType {
NewDeviceFound,
DeviceOnlineAfterTime,