1use std::collections::HashSet;
2use std::fmt;
3use std::fmt::Display;
4use std::str::FromStr;
5use std::sync::Arc;
6use std::time::SystemTime;
7
8use async_trait::async_trait;
9use cadence::{CountedExt, StatsdClient};
10use deadpool_redis::Config;
11use deadpool_redis::redis::{AsyncCommands, SetExpiry, SetOptions, pipe};
12use uuid::Uuid;
13
14use crate::db::redis::StorableNotification;
15use crate::db::{
16 DbSettings, Notification, User,
17 client::{DbClient, FetchMessageResponse},
18 error::{DbError, DbResult},
19};
20use crate::util::{ms_since_epoch, sec_since_epoch};
21
22use super::RedisDbSettings;
23
24fn now_secs() -> u64 {
25 SystemTime::now()
27 .duration_since(SystemTime::UNIX_EPOCH)
28 .unwrap()
29 .as_secs()
30}
31
32struct Uaid<'a>(&'a Uuid);
34
35impl<'a> Display for Uaid<'a> {
36 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37 write!(f, "{}", self.0.as_hyphenated())
38 }
39}
40
41impl<'a> From<Uaid<'a>> for String {
42 fn from(uaid: Uaid) -> String {
43 uaid.0.as_hyphenated().to_string()
44 }
45}
46
47struct ChannelId<'a>(&'a Uuid);
48
49impl<'a> Display for ChannelId<'a> {
50 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
51 write!(f, "{}", self.0.as_hyphenated())
52 }
53}
54
55impl<'a> From<ChannelId<'a>> for String {
56 fn from(chid: ChannelId) -> String {
57 chid.0.as_hyphenated().to_string()
58 }
59}
60
61#[derive(Clone)]
62pub struct RedisClientImpl {
64 pub pool: deadpool_redis::Pool,
66 metrics: Arc<StatsdClient>,
68 router_opts: SetOptions,
69 notification_opts: SetOptions,
71}
72
73impl RedisClientImpl {
74 pub fn new(metrics: Arc<StatsdClient>, settings: &DbSettings) -> DbResult<Self> {
75 debug!("🐰 New redis client");
76 let dsn = settings.dsn.clone().ok_or(DbError::General(
77 "Redis DSN not configured. Set `db_dsn` to `redis://HOST:PORT` in settings.".to_owned(),
78 ))?;
79 let db_settings = RedisDbSettings::try_from(settings.db_settings.as_ref())?;
80 info!("🐰 {:#?}", db_settings);
81 let router_ttl_secs = db_settings.router_ttl.unwrap_or_default().as_secs();
82 let notification_ttl_secs = db_settings.notification_ttl.unwrap_or_default().as_secs();
83
84 let config = Config::from_url(dsn);
85 let pool = config
86 .builder()
87 .map_err(|e| DbError::General(format!("Could not create Redis pool: {:?}", e)))?
88 .create_timeout(db_settings.create_timeout)
89 .runtime(deadpool_redis::Runtime::Tokio1)
90 .build()
91 .map_err(|e| DbError::General(format!("Could not create Redis pool: {:?}", e)))?;
92 Ok(Self {
98 pool,
99 metrics,
100 router_opts: SetOptions::default().with_expiration(SetExpiry::EX(router_ttl_secs)),
101 notification_opts: SetOptions::default()
102 .with_expiration(SetExpiry::EX(notification_ttl_secs)),
103 })
104 }
105
106 async fn connection(&self) -> DbResult<deadpool_redis::Connection> {
111 self.pool.get().await.map_err(|e| {
112 DbError::RedisError(redis::RedisError::from((
113 redis::ErrorKind::Io,
114 "Could not get Redis connection from pool",
115 format!("{:?}", e),
116 )))
117 })
118 }
119
120 fn user_key(&self, uaid: &Uaid) -> String {
121 format!("autopush/user/{}", uaid)
122 }
123
124 fn last_co_key(&self, uaid: &Uaid) -> String {
126 format!("autopush/co/{}", uaid)
127 }
128
129 fn storage_timestamp_key(&self, uaid: &Uaid) -> String {
131 format!("autopush/timestamp/{}", uaid)
132 }
133
134 fn channel_list_key(&self, uaid: &Uaid) -> String {
135 format!("autopush/channels/{}", uaid)
136 }
137
138 fn message_list_key(&self, uaid: &Uaid) -> String {
139 format!("autopush/msgs/{}", uaid)
140 }
141
142 fn message_exp_list_key(&self, uaid: &Uaid) -> String {
143 format!("autopush/msgs_exp/{}", uaid)
144 }
145
146 fn message_key(&self, uaid: &Uaid, chidmessageid: &str) -> String {
147 format!("autopush/msg/{}/{}", uaid, chidmessageid)
148 }
149
150 #[cfg(feature = "reliable_report")]
151 fn reliability_key(
152 &self,
153 reliability_id: &str,
154 state: &crate::reliability::ReliabilityState,
155 ) -> String {
156 format!("autopush/reliability/{}/{}", reliability_id, state)
157 }
158
159 #[cfg(test)]
160 async fn fetch_message(&self, uaid: &Uuid, chidmessageid: &str) -> DbResult<Option<String>> {
162 let message_key = self.message_key(&Uaid(uaid), chidmessageid);
163 let mut con = self.connection().await?;
164 debug!("🐰 Fetching message from {}", &message_key);
165 let message = con.get::<String, Option<String>>(message_key).await?;
166 Ok(message)
167 }
168}
169
170#[async_trait]
171impl DbClient for RedisClientImpl {
172 async fn add_user(&self, user: &User) -> DbResult<()> {
174 let uaid = Uaid(&user.uaid);
175 let user_key = self.user_key(&uaid);
176 let mut con = self.connection().await?;
177 let co_key = self.last_co_key(&uaid);
178 trace!("🐰 Adding user {} at {}:{}", &user.uaid, &user_key, &co_key);
179 trace!("🐰 Logged at {}", &user.connected_at);
180 pipe()
181 .set_options(co_key, ms_since_epoch(), self.router_opts.clone())
182 .set_options(
183 user_key,
184 serde_json::to_string(user)?,
185 self.router_opts.clone(),
186 )
187 .exec_async(&mut con)
188 .await?;
189 Ok(())
190 }
191
192 async fn update_user(&self, user: &mut User) -> DbResult<bool> {
204 trace!("🐰 Updating user");
205 let mut con = self.connection().await?;
206 let co_key = self.last_co_key(&Uaid(&user.uaid));
207 let last_co: Option<u64> = con.get(&co_key).await?;
208 if last_co.is_some_and(|c| c < user.connected_at) {
209 trace!(
210 "🐰 Was connected at {}, now at {}",
211 last_co.unwrap(),
212 &user.connected_at
213 );
214 self.add_user(user).await?;
215 Ok(true)
216 } else {
217 Ok(false)
218 }
219 }
220
221 async fn get_user(&self, uaid: &Uuid) -> DbResult<Option<User>> {
222 let mut con = self.connection().await?;
223 let user_key = self.user_key(&Uaid(uaid));
224 let user: Option<User> = con
225 .get::<&str, Option<String>>(&user_key)
226 .await?
227 .and_then(|s| serde_json::from_str(s.as_ref()).ok());
228 if user.is_some() {
229 trace!("🐰 Found a record for {}", &uaid);
230 }
231 Ok(user)
232 }
233
234 async fn remove_user(&self, uaid: &Uuid) -> DbResult<()> {
235 let uaid = Uaid(uaid);
236 let mut con = self.connection().await?;
237 let user_key = self.user_key(&uaid);
238 let co_key = self.last_co_key(&uaid);
239 let chan_list_key = self.channel_list_key(&uaid);
240 let msg_list_key = self.message_list_key(&uaid);
241 let exp_list_key = self.message_exp_list_key(&uaid);
242 let timestamp_key = self.storage_timestamp_key(&uaid);
243 pipe()
244 .del(&user_key)
245 .del(&co_key)
246 .del(&chan_list_key)
247 .del(&msg_list_key)
248 .del(&exp_list_key)
249 .del(×tamp_key)
250 .exec_async(&mut con)
251 .await?;
252 Ok(())
253 }
254
255 async fn add_channel(&self, uaid: &Uuid, channel_id: &Uuid) -> DbResult<()> {
256 let uaid = Uaid(uaid);
257 let mut con = self.connection().await?;
258 let co_key = self.last_co_key(&uaid);
259 let chan_list_key = self.channel_list_key(&uaid);
260
261 let _: () = pipe()
262 .rpush(chan_list_key, channel_id.as_hyphenated().to_string())
263 .set_options(co_key, ms_since_epoch(), self.router_opts.clone())
264 .exec_async(&mut con)
265 .await?;
266 Ok(())
267 }
268
269 async fn add_channels(&self, uaid: &Uuid, channels: HashSet<Uuid>) -> DbResult<()> {
271 let uaid = Uaid(uaid);
272 let mut con = self.connection().await?;
274 let co_key = self.last_co_key(&uaid);
275 let chan_list_key = self.channel_list_key(&uaid);
276 pipe()
277 .set_options(co_key, ms_since_epoch(), self.router_opts.clone())
278 .rpush(
279 chan_list_key,
280 channels
281 .into_iter()
282 .map(|c| c.as_hyphenated().to_string())
283 .collect::<Vec<String>>(),
284 )
285 .exec_async(&mut con)
286 .await?;
287 Ok(())
288 }
289
290 async fn get_channels(&self, uaid: &Uuid) -> DbResult<HashSet<Uuid>> {
291 let uaid = Uaid(uaid);
292 let mut con = self.connection().await?;
293 let chan_list_key = self.channel_list_key(&uaid);
294 let channels: HashSet<Uuid> = con
295 .lrange::<&str, HashSet<String>>(&chan_list_key, 0, -1)
296 .await?
297 .into_iter()
298 .filter_map(|s| Uuid::from_str(&s).ok())
299 .collect();
300 trace!("🐰 Found {} channels for {}", channels.len(), &uaid);
301 Ok(channels)
302 }
303
304 async fn remove_channel(&self, uaid: &Uuid, channel_id: &Uuid) -> DbResult<bool> {
306 let uaid = Uaid(uaid);
307 let channel_id = ChannelId(channel_id);
308 let mut con = self.connection().await?;
309 let co_key = self.last_co_key(&uaid);
310 let chan_list_key = self.channel_list_key(&uaid);
311 trace!("🐰 Removing channel {}", channel_id);
313 let (status,): (bool,) = pipe()
314 .set_options(co_key, ms_since_epoch(), self.router_opts.clone())
315 .ignore()
316 .lrem(&chan_list_key, 1, channel_id.to_string())
317 .query_async(&mut con)
318 .await?;
319 Ok(status)
320 }
321
322 async fn remove_node_id(
324 &self,
325 uaid: &Uuid,
326 _node_id: &str,
327 _connected_at: u64,
328 _version: &Option<Uuid>,
329 ) -> DbResult<bool> {
330 if let Some(mut user) = self.get_user(uaid).await? {
331 user.node_id = None;
332 self.update_user(&mut user).await?;
333 }
334 Ok(true)
335 }
336
337 async fn save_message(&self, uaid: &Uuid, message: Notification) -> DbResult<()> {
341 let uaid = Uaid(uaid);
342 let mut con = self.connection().await?;
343 let msg_list_key = self.message_list_key(&uaid);
344 let exp_list_key = self.message_exp_list_key(&uaid);
345 let msg_id = &message.chidmessageid();
346 let msg_key = self.message_key(&uaid, msg_id);
347 let storable: StorableNotification = message.into();
348
349 debug!("🐰 Saving message {} :: {:?}", &msg_key, &storable);
350 trace!(
351 "🐰 timestamp: {:?}",
352 &storable.timestamp.to_be_bytes().to_vec()
353 );
354
355 let expiry = now_secs() + storable.ttl;
358 trace!("🐰 Message Expiry {}, currently:{} ", expiry, now_secs());
359
360 let mut pipe = pipe();
361
362 let is_topic = storable.topic.is_some();
367
368 let notif_opts = self
371 .notification_opts
372 .clone()
373 .with_expiration(SetExpiry::EXAT(expiry));
374
375 debug!("🐰 Saving to {}", &msg_key);
378 pipe.set_options(msg_key, serde_json::to_string(&storable)?, notif_opts)
379 .zadd(&exp_list_key, msg_id, expiry)
382 .zadd(&msg_list_key, msg_id, sec_since_epoch());
383
384 let _: () = pipe.exec_async(&mut con).await?;
385 self.metrics
386 .incr_with_tags("notification.message.stored")
387 .with_tag("topic", &is_topic.to_string())
388 .with_tag("database", &self.name())
389 .send();
390 Ok(())
391 }
392
393 async fn save_messages(&self, uaid: &Uuid, messages: Vec<Notification>) -> DbResult<()> {
398 for message in messages {
400 self.save_message(uaid, message).await?;
401 }
402 Ok(())
403 }
404
405 async fn increment_storage(&self, uaid: &Uuid, timestamp: u64) -> DbResult<()> {
407 let uaid = Uaid(uaid);
408 debug!("🐰🔥 Incrementing storage to {}", timestamp);
409 let msg_list_key = self.message_list_key(&uaid);
410 let exp_list_key = self.message_exp_list_key(&uaid);
411 let storage_timestamp_key = self.storage_timestamp_key(&uaid);
412 let mut con = self.connection().await?;
413 trace!("🐇 SEARCH: increment: {:?} - {}", &exp_list_key, timestamp);
414 let exp_id_list: Vec<String> = con.zrangebyscore(&exp_list_key, 0, timestamp).await?;
415 if !exp_id_list.is_empty() {
416 let delete_msg_keys: Vec<String> = exp_id_list
419 .clone()
420 .into_iter()
421 .map(|msg_id| self.message_key(&uaid, &msg_id))
422 .collect();
423
424 trace!(
425 "🐰🔥:rem: Deleting {} : [{:?}]",
426 msg_list_key, &delete_msg_keys
427 );
428 trace!("🐰🔥:rem: Deleting {} : [{:?}]", exp_list_key, &exp_id_list);
429 pipe()
430 .set_options::<_, _>(&storage_timestamp_key, timestamp, self.router_opts.clone())
431 .del(&delete_msg_keys)
432 .zrem(&msg_list_key, &exp_id_list)
433 .zrem(&exp_list_key, &exp_id_list)
434 .exec_async(&mut con)
435 .await?;
436 } else {
437 con.set_options::<_, _, ()>(
438 &storage_timestamp_key,
439 timestamp,
440 self.router_opts.clone(),
441 )
442 .await?;
443 }
444 Ok(())
445 }
446
447 async fn remove_message(&self, uaid: &Uuid, chidmessageid: &str) -> DbResult<()> {
449 let uaid = Uaid(uaid);
450 trace!(
451 "🐰 attemping to delete {:?} :: {:?}",
452 uaid.to_string(),
453 chidmessageid
454 );
455 let msg_key = self.message_key(&uaid, chidmessageid);
456 let msg_list_key = self.message_list_key(&uaid);
457 let exp_list_key = self.message_exp_list_key(&uaid);
458 debug!("🐰🔥 Deleting message {}", &msg_key);
459 let mut con = self.connection().await?;
460 trace!(
463 "🐰🔥:remsg: Deleting {} : {:?}",
464 msg_list_key, &chidmessageid
465 );
466 trace!(
467 "🐰🔥:remsg: Deleting {} : {:?}",
468 exp_list_key, &chidmessageid
469 );
470 pipe()
471 .del(&msg_key)
472 .zrem(&msg_list_key, chidmessageid)
473 .zrem(&exp_list_key, chidmessageid)
474 .exec_async(&mut con)
475 .await?;
476 self.metrics
477 .incr_with_tags("notification.message.deleted")
478 .with_tag("database", &self.name())
479 .send();
480 Ok(())
481 }
482
483 async fn fetch_topic_messages(
485 &self,
486 _uaid: &Uuid,
487 _limit: usize,
488 ) -> DbResult<FetchMessageResponse> {
489 Ok(FetchMessageResponse {
490 messages: vec![],
491 timestamp: None,
492 })
493 }
494
495 async fn fetch_timestamp_messages(
502 &self,
503 uaid: &Uuid,
504 timestamp: Option<u64>,
505 limit: usize,
506 ) -> DbResult<FetchMessageResponse> {
507 let uaid = Uaid(uaid);
508 trace!("🐰 Fetching {} messages since {:?}", limit, timestamp);
509 let mut con = self.connection().await?;
510 let msg_list_key = self.message_list_key(&uaid);
511 let timestamp = if let Some(timestamp) = timestamp {
512 timestamp
513 } else {
514 let storage_timestamp_key = self.storage_timestamp_key(&uaid);
515 con.get(&storage_timestamp_key).await.unwrap_or(0)
516 };
517 trace!(
519 "🐇 SEARCH: zrangebyscore {:?} {} +inf withscores limit 0 {:?}",
520 &msg_list_key, timestamp, limit,
521 );
522 let results = con
523 .zrangebyscore_limit_withscores::<&str, &str, &str, Vec<(String, u64)>>(
524 &msg_list_key,
525 ×tamp.to_string(),
526 "+inf",
527 0,
528 limit as isize,
529 )
530 .await?;
531 let (messages_id, mut scores): (Vec<String>, Vec<u64>) = results
532 .into_iter()
533 .map(|(id, s): (String, u64)| (self.message_key(&uaid, &id), s))
534 .unzip();
535 if messages_id.is_empty() {
536 trace!("🐰 No message found");
537 return Ok(FetchMessageResponse {
538 messages: vec![],
539 timestamp: None,
540 });
541 }
542 let messages: Vec<Notification> = con
543 .mget::<&Vec<String>, Vec<Option<String>>>(&messages_id)
544 .await?
545 .into_iter()
546 .filter_map(|opt: Option<String>| {
547 if let Some(m) = opt {
548 serde_json::from_str(&m)
549 .inspect_err(|e| {
550 error!("🐰 ERROR parsing entry: {:?}", e);
556 })
557 .ok()
558 } else {
559 None
560 }
561 })
562 .collect();
563 if messages.is_empty() {
564 trace!("🐰 No Valid messages found");
565 return Ok(FetchMessageResponse {
566 timestamp: None,
567 messages: vec![],
568 });
569 }
570 let timestamp = scores.pop();
571 trace!("🐰 Found {} messages until {:?}", messages.len(), timestamp);
572 Ok(FetchMessageResponse {
573 messages,
574 timestamp,
575 })
576 }
577
578 #[cfg(feature = "reliable_report")]
579 async fn log_report(
580 &self,
581 reliability_id: &str,
582 state: crate::reliability::ReliabilityState,
583 ) -> DbResult<()> {
584 use crate::MAX_NOTIFICATION_TTL_SECS;
585
586 trace!("🐰 Logging reliability report");
587 let mut con = self.connection().await?;
588 let reliability_key = self.reliability_key(reliability_id, &state);
590 let expiry = MAX_NOTIFICATION_TTL_SECS;
592 let opts = SetOptions::default().with_expiration(SetExpiry::EX(expiry));
593 let mut pipe = pipe();
594 pipe.set_options(reliability_key, sec_since_epoch(), opts)
595 .exec_async(&mut con)
596 .await?;
597 Ok(())
598 }
599
600 async fn health_check(&self) -> DbResult<bool> {
601 let _: () = self.connection().await?.ping().await?;
602 Ok(true)
603 }
604
605 async fn router_table_exists(&self) -> DbResult<bool> {
607 Ok(true)
608 }
609
610 async fn message_table_exists(&self) -> DbResult<bool> {
612 Ok(true)
613 }
614
615 fn box_clone(&self) -> Box<dyn DbClient> {
616 Box::new(self.clone())
617 }
618
619 fn name(&self) -> String {
620 "Redis".to_owned()
621 }
622
623 fn pool_status(&self) -> Option<deadpool::Status> {
624 None
625 }
626}
627
628#[cfg(test)]
629mod tests {
630 use crate::{logging::init_test_logging, util::ms_since_epoch};
631 use rand::prelude::*;
632 use std::env;
633
634 use super::*;
635 const TEST_CHID: &str = "DECAFBAD-0000-0000-0000-0123456789AB";
636 const TOPIC_CHID: &str = "DECAFBAD-1111-0000-0000-0123456789AB";
637
638 fn new_client() -> DbResult<RedisClientImpl> {
639 let host = env::var("REDIS_HOST").unwrap_or("localhost".into());
641 let env_dsn = format!("redis://{host}");
642 debug!("🐰 Connecting to {env_dsn}");
643 let settings = DbSettings {
644 dsn: Some(env_dsn),
645 db_settings: "".into(),
646 };
647 let metrics = Arc::new(StatsdClient::builder("", cadence::NopMetricSink).build());
648 RedisClientImpl::new(metrics, &settings)
649 }
650
651 fn gen_test_user() -> String {
652 let mut rng = rand::rng();
654 let test_num = rng.random::<u8>();
655 format!("DEADBEEF-0000-0000-{:04}-{:012}", test_num, now_secs())
656 }
657
658 #[actix_rt::test]
659 async fn health_check() {
660 let client = new_client().unwrap();
661
662 let result = client.health_check().await;
663 assert!(result.is_ok());
664 assert!(result.unwrap());
665 }
666
667 #[actix_rt::test]
669 async fn wipe_expired() -> DbResult<()> {
670 init_test_logging();
671 let client = new_client()?;
672
673 let connected_at = ms_since_epoch();
674
675 let uaid = Uuid::parse_str(&gen_test_user()).unwrap();
676 let chid = Uuid::parse_str(TEST_CHID).unwrap();
677
678 let node_id = "test_node".to_owned();
679
680 let _ = client.remove_user(&uaid).await;
682
683 let test_user = User {
684 uaid,
685 router_type: "webpush".to_owned(),
686 connected_at,
687 router_data: None,
688 node_id: Some(node_id.clone()),
689 ..Default::default()
690 };
691
692 let _ = client.remove_user(&uaid).await;
695
696 let timestamp = now_secs();
698 client.add_user(&test_user).await?;
699 let test_notification = crate::db::Notification {
700 channel_id: chid,
701 version: "test".to_owned(),
702 ttl: 1,
703 timestamp,
704 data: Some("Encrypted".into()),
705 sortkey_timestamp: Some(timestamp),
706 ..Default::default()
707 };
708 client.save_message(&uaid, test_notification).await?;
709 client.increment_storage(&uaid, timestamp + 1).await?;
710 let msgs = client.fetch_timestamp_messages(&uaid, None, 999).await?;
711 assert_eq!(msgs.messages.len(), 0);
712 assert!(client.remove_user(&uaid).await.is_ok());
713 Ok(())
714 }
715
716 #[actix_rt::test]
719 async fn run_gauntlet() -> DbResult<()> {
720 init_test_logging();
721 let client = new_client()?;
722
723 let connected_at = ms_since_epoch();
724
725 let user_id = &gen_test_user();
726 let uaid = Uuid::parse_str(user_id).unwrap();
727 let chid = Uuid::parse_str(TEST_CHID).unwrap();
728 let topic_chid = Uuid::parse_str(TOPIC_CHID).unwrap();
729
730 let node_id = "test_node".to_owned();
731
732 let _ = client.remove_user(&uaid).await;
734
735 let test_user = User {
736 uaid,
737 router_type: "webpush".to_owned(),
738 connected_at,
739 router_data: None,
740 node_id: Some(node_id.clone()),
741 ..Default::default()
742 };
743
744 let _ = client.remove_user(&uaid).await;
747
748 client.add_user(&test_user).await?;
750 let fetched = client.get_user(&uaid).await?;
751 assert!(fetched.is_some());
752 let fetched = fetched.unwrap();
753 assert_eq!(fetched.router_type, "webpush".to_owned());
754
755 let connected_at = ms_since_epoch();
757
758 client.add_channel(&uaid, &chid).await?;
760 let channels = client.get_channels(&uaid).await?;
761 assert!(channels.contains(&chid));
762
763 let mut new_channels: HashSet<Uuid> = HashSet::new();
765 new_channels.insert(chid);
766 for _ in 1..10 {
767 new_channels.insert(uuid::Uuid::new_v4());
768 }
769 let chid_to_remove = uuid::Uuid::new_v4();
770 new_channels.insert(chid_to_remove);
771 client.add_channels(&uaid, new_channels.clone()).await?;
772 let channels = client.get_channels(&uaid).await?;
773 assert_eq!(channels, new_channels);
774
775 assert!(client.remove_channel(&uaid, &chid_to_remove).await?);
777 assert!(!client.remove_channel(&uaid, &chid_to_remove).await?);
778 new_channels.remove(&chid_to_remove);
779 let channels = client.get_channels(&uaid).await?;
780 assert_eq!(channels, new_channels);
781
782 let mut updated = User {
786 connected_at,
787 ..test_user.clone()
788 };
789 let result = client.update_user(&mut updated).await;
790 assert!(result.is_ok());
791 assert!(!result.unwrap());
792
793 let fetched2 = client.get_user(&fetched.uaid).await?.unwrap();
795 assert_eq!(fetched.connected_at, fetched2.connected_at);
796
797 let mut updated = User {
799 connected_at: fetched.connected_at + 300,
800 ..fetched2
801 };
802 let result = client.update_user(&mut updated).await;
803 assert!(result.is_ok());
804 assert!(result.unwrap());
805 assert_ne!(
806 fetched2.connected_at,
807 client.get_user(&uaid).await?.unwrap().connected_at
808 );
809
810 client
812 .increment_storage(
813 &fetched.uaid,
814 SystemTime::now()
815 .duration_since(SystemTime::UNIX_EPOCH)
816 .unwrap()
817 .as_secs(),
818 )
819 .await?;
820
821 let test_data = "An_encrypted_pile_of_crap".to_owned();
822 let timestamp = now_secs();
823 let sort_key = now_secs();
824 let fetch_timestamp = timestamp;
825 let test_notification = crate::db::Notification {
827 channel_id: chid,
828 version: "test".to_owned(),
829 ttl: 300,
830 timestamp,
831 data: Some(test_data.clone()),
832 sortkey_timestamp: Some(sort_key),
833 ..Default::default()
834 };
835 let res = client.save_message(&uaid, test_notification.clone()).await;
836 assert!(res.is_ok());
837
838 let mut fetched = client.fetch_timestamp_messages(&uaid, None, 999).await?;
839 assert_ne!(fetched.messages.len(), 0);
840 let fm = fetched.messages.pop().unwrap();
841 assert_eq!(fm.channel_id, test_notification.channel_id);
842 assert_eq!(fm.data, Some(test_data));
843
844 let fetched = client
846 .fetch_timestamp_messages(&uaid, Some(fetch_timestamp - 10), 999)
847 .await?;
848 assert_ne!(fetched.messages.len(), 0);
849
850 let fetched = client
852 .fetch_timestamp_messages(&uaid, Some(fetch_timestamp + 10), 999)
853 .await?;
854 assert_eq!(fetched.messages.len(), 0);
855
856 assert!(
858 client
859 .remove_message(&uaid, &test_notification.chidmessageid())
860 .await
861 .is_ok()
862 );
863
864 assert!(client.remove_channel(&uaid, &chid).await.is_ok());
865
866 let msgs = client
867 .fetch_timestamp_messages(&uaid, None, 999)
868 .await?
869 .messages;
870 assert!(msgs.is_empty());
871
872 client.add_channel(&uaid, &topic_chid).await?;
876 let test_data = "An_encrypted_pile_of_crap_with_a_topic".to_owned();
877 let timestamp = now_secs();
878 let sort_key = now_secs();
879
880 let test_notification_0 = crate::db::Notification {
882 channel_id: topic_chid,
883 version: "version0".to_owned(),
884 ttl: 300,
885 topic: Some("topic".to_owned()),
886 timestamp,
887 data: Some(test_data.clone()),
888 sortkey_timestamp: Some(sort_key),
889 ..Default::default()
890 };
891 assert!(
892 client
893 .save_message(&uaid, test_notification_0.clone())
894 .await
895 .is_ok()
896 );
897
898 let test_notification = crate::db::Notification {
899 timestamp: now_secs(),
900 version: "version1".to_owned(),
901 sortkey_timestamp: Some(sort_key + 10),
902 ..test_notification_0
903 };
904
905 assert!(
906 client
907 .save_message(&uaid, test_notification.clone())
908 .await
909 .is_ok()
910 );
911
912 let mut fetched = client.fetch_timestamp_messages(&uaid, None, 999).await?;
913 assert_eq!(fetched.messages.len(), 1);
914 let fm = fetched.messages.pop().unwrap();
915 assert_eq!(fm.channel_id, test_notification.channel_id);
916 assert_eq!(fm.data, Some(test_data));
917
918 let fetched = client.fetch_timestamp_messages(&uaid, None, 999).await?;
920 assert_ne!(fetched.messages.len(), 0);
921
922 assert!(
924 client
925 .remove_message(&uaid, &test_notification.chidmessageid())
926 .await
927 .is_ok()
928 );
929
930 assert!(client.remove_channel(&uaid, &topic_chid).await.is_ok());
931
932 let msgs = client
933 .fetch_timestamp_messages(&uaid, None, 999)
934 .await?
935 .messages;
936 assert!(msgs.is_empty());
937
938 let fetched = client.get_user(&uaid).await?.unwrap();
939 assert!(
940 client
941 .remove_node_id(&uaid, &node_id, connected_at, &fetched.version)
942 .await
943 .is_ok()
944 );
945 let fetched = client.get_user(&uaid).await?.unwrap();
947 assert_eq!(fetched.node_id, None);
948
949 assert!(client.remove_user(&uaid).await.is_ok());
950
951 assert!(client.get_user(&uaid).await?.is_none());
952 Ok(())
953 }
954
955 #[actix_rt::test]
956 async fn test_expiry() -> DbResult<()> {
957 init_test_logging();
959 let client = new_client()?;
960
961 let uaid = Uuid::parse_str(&gen_test_user()).unwrap();
962 let chid = Uuid::parse_str(TEST_CHID).unwrap();
963 let now = now_secs();
964
965 let test_notification = crate::db::Notification {
966 channel_id: chid,
967 version: "test".to_owned(),
968 ttl: 2,
969 timestamp: now,
970 data: Some("SomeData".into()),
971 sortkey_timestamp: Some(now),
972 ..Default::default()
973 };
974 debug!("Writing test notif");
975 let res = client.save_message(&uaid, test_notification.clone()).await;
976 assert!(res.is_ok());
977 let key = client.message_key(&Uaid(&uaid), &test_notification.chidmessageid());
978 debug!("Checking {}...", &key);
979 let msg = client
980 .fetch_message(&uaid, &test_notification.chidmessageid())
981 .await?;
982 assert!(!msg.unwrap().is_empty());
983 debug!("Purging...");
984 client.increment_storage(&uaid, now + 2).await?;
985 debug!("Checking {}...", &key);
986 let cc = client
987 .fetch_message(&uaid, &test_notification.chidmessageid())
988 .await;
989 assert_eq!(cc.unwrap(), None);
990 assert!(client.remove_user(&uaid).await.is_ok());
992 Ok(())
993 }
994}