/* Copyright 2020-2023, Savanni D'Gerinel This file is part of the Luminescent Dreams Tools. Luminescent Dreams Tools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Luminescent Dreams Tools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Lumeto. If not, see . */ #[macro_use] extern crate serde_derive; extern crate chrono; extern crate chrono_tz; extern crate dimensioned; extern crate emseries; #[cfg(test)] mod test { use chrono::{format::Fixed, prelude::*}; use chrono_tz::Etc::UTC; use dimensioned::si::{Kilogram, Meter, Second, M, S}; use emseries::*; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] struct Distance(Meter); #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] struct Duration(Second); #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] struct BikeTrip { datetime: DateTime, distance: Distance, duration: Duration, comments: String, } impl Recordable for BikeTrip { fn timestamp(&self) -> Timestamp { Timestamp::DateTime(self.datetime.clone()) } fn tags(&self) -> Vec { Vec::new() } } fn mk_trips() -> [BikeTrip; 5] { [ BikeTrip { datetime: UTC .with_ymd_and_hms(2011, 10, 29, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()), distance: Distance(58741.055 * M), duration: Duration(11040.0 * S), comments: String::from("long time ago"), }, BikeTrip { datetime: UTC .with_ymd_and_hms(2011, 10, 31, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()), distance: Distance(17702.0 * M), duration: Duration(2880.0 * S), comments: String::from("day 2"), }, BikeTrip { datetime: UTC .with_ymd_and_hms(2011, 11, 02, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()), distance: Distance(41842.945 * M), duration: Duration(7020.0 * S), comments: String::from("Do Some Distance!"), }, BikeTrip { datetime: UTC .with_ymd_and_hms(2011, 11, 04, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()), distance: Distance(34600.895 * M), duration: Duration(5580.0 * S), comments: String::from("I did a lot of distance back then"), }, BikeTrip { datetime: UTC .with_ymd_and_hms(2011, 11, 05, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()), distance: Distance(6437.376 * M), duration: Duration(960.0 * S), comments: String::from("day 5"), }, ] } fn run_test(test: T) -> () where T: FnOnce(tempfile::TempPath), { let tmp_file = tempfile::NamedTempFile::new().expect("temporary path created"); let tmp_path = tmp_file.into_temp_path(); test(tmp_path); } fn run(test: T) -> () where T: FnOnce(Series), { let tmp_file = tempfile::NamedTempFile::new().expect("temporary path created"); let tmp_path = tmp_file.into_temp_path(); let ts: Series = Series::open(&tmp_path).expect("the time series should open correctly"); test(ts); } #[test] pub fn can_add_and_retrieve_entries() { run(|mut series| { let trips = mk_trips(); let uuid = series .put(trips[0].clone()) .expect("expect a successful put"); let record_res = series.get(&uuid); for trip in &trips[1..=4] { series.put(trip.clone()).expect("expect a successful put"); } match record_res { None => assert!(false, "There should have been a value here"), Some(tr) => { assert_eq!( tr.timestamp(), Timestamp::DateTime( UTC.with_ymd_and_hms(2011, 10, 29, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()) ) ); assert_eq!(tr.data.duration, Duration(11040.0 * S)); assert_eq!(tr.data.comments, String::from("long time ago")); assert_eq!(tr.data, trips[0]); } } }) } #[test] pub fn can_search_for_an_entry_with_exact_time() { run_test(|path| { let trips = mk_trips(); let mut ts: Series = Series::open(&path).expect("expect the time series to open correctly"); for trip in &trips[0..=4] { ts.put(trip.clone()).expect("expect a successful put"); } let v: Vec<&Record> = ts .search(exact_time(Timestamp::DateTime( UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()), ))) .collect(); assert_eq!(v.len(), 1); assert_eq!(v[0].data, trips[1]); }) } #[test] pub fn can_get_entries_in_time_range() { run_test(|path| { let trips = mk_trips(); let mut ts: Series = Series::open(&path).expect("expect the time series to open correctly"); for trip in &trips[0..=4] { ts.put(trip.clone()).expect("expect a successful put"); } let v: Vec<&Record> = ts.search_sorted( time_range( Timestamp::DateTime( UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()), ), true, Timestamp::DateTime( UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()), ), true, ), |l, r| l.timestamp().cmp(&r.timestamp()), ); assert_eq!(v.len(), 3); assert_eq!(v[0].data, trips[1]); assert_eq!(v[1].data, trips[2]); assert_eq!(v[2].data, trips[3]); }) } #[test] pub fn persists_and_reads_an_entry() { run_test(|path| { let trips = mk_trips(); { let mut ts: Series = Series::open(&path).expect("expect the time series to open correctly"); for trip in &trips[0..=4] { ts.put(trip.clone()).expect("expect a successful put"); } } { let ts: Series = Series::open(&path).expect("expect the time series to open correctly"); let v: Vec<&Record> = ts.search_sorted( time_range( Timestamp::DateTime( UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()), ), true, Timestamp::DateTime( UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()), ), true, ), |l, r| l.timestamp().cmp(&r.timestamp()), ); assert_eq!(v.len(), 3); assert_eq!(v[0].data, trips[1]); assert_eq!(v[1].data, trips[2]); assert_eq!(v[2].data, trips[3]); } }) } #[test] pub fn can_write_to_existing_file() { run_test(|path| { let trips = mk_trips(); { let mut ts: Series = Series::open(&path).expect("expect the time series to open correctly"); for trip in &trips[0..=2] { ts.put(trip.clone()).expect("expect a successful put"); } } { let mut ts: Series = Series::open(&path).expect("expect the time series to open correctly"); let v: Vec<&Record> = ts.search_sorted( time_range( Timestamp::DateTime( UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()), ), true, Timestamp::DateTime( UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()), ) .into(), true, ), |l, r| l.timestamp().cmp(&r.timestamp()), ); assert_eq!(v.len(), 2); assert_eq!(v[0].data, trips[1]); assert_eq!(v[1].data, trips[2]); ts.put(trips[3].clone()).expect("expect a successful put"); ts.put(trips[4].clone()).expect("expect a successful put"); } { let ts: Series = Series::open(&path).expect("expect the time series to open correctly"); let v: Vec<&Record> = ts.search_sorted( time_range( Timestamp::DateTime( UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()), ), true, Timestamp::DateTime( UTC.with_ymd_and_hms(2011, 11, 05, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()), ), true, ), |l, r| l.timestamp().cmp(&r.timestamp()), ); assert_eq!(v.len(), 4); assert_eq!(v[0].data, trips[1]); assert_eq!(v[1].data, trips[2]); assert_eq!(v[2].data, trips[3]); assert_eq!(v[3].data, trips[4]); } }) } #[test] pub fn can_overwrite_existing_entry() { run_test(|path| { let trips = mk_trips(); let mut ts: Series = Series::open(&path).expect("expect the time series to open correctly"); ts.put(trips[0].clone()).expect("expect a successful put"); ts.put(trips[1].clone()).expect("expect a successful put"); let trip_id = ts.put(trips[2].clone()).expect("expect a successful put"); match ts.get(&trip_id) { None => assert!(false, "record not found"), Some(mut trip) => { trip.data.distance = Distance(50000.0 * M); ts.update(trip).expect("expect record to update"); } }; match ts.get(&trip_id) { None => assert!(false, "record not found"), Some(trip) => { assert_eq!( trip.data.datetime, UTC.with_ymd_and_hms(2011, 11, 02, 0, 0, 0).unwrap() ); assert_eq!(trip.data.distance, Distance(50000.0 * M)); assert_eq!(trip.data.duration, Duration(7020.0 * S)); assert_eq!(trip.data.comments, String::from("Do Some Distance!")); } } }) } #[test] pub fn record_overwrites_get_persisted() { run_test(|path| { let trips = mk_trips(); { let mut ts: Series = Series::open(&path).expect("expect the time series to open correctly"); ts.put(trips[0].clone()).expect("expect a successful put"); ts.put(trips[1].clone()).expect("expect a successful put"); let trip_id = ts.put(trips[2].clone()).expect("expect a successful put"); match ts.get(&trip_id) { None => assert!(false, "record not found"), Some(mut trip) => { trip.data.distance = Distance(50000.0 * M); ts.update(trip).expect("expect record to update"); } }; } { let ts: Series = Series::open(&path).expect("expect the time series to open correctly"); let trips: Vec<&Record> = ts.records().collect(); assert_eq!(trips.len(), 3); let trips: Vec<&Record> = ts .search(exact_time(Timestamp::DateTime( UTC.with_ymd_and_hms(2011, 11, 02, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()), ))) .collect(); assert_eq!(trips.len(), 1); assert_eq!( trips[0].data.datetime, UTC.with_ymd_and_hms(2011, 11, 02, 0, 0, 0) .unwrap() .with_timezone(&FixedOffset::east_opt(0).unwrap()) ); assert_eq!(trips[0].data.distance, Distance(50000.0 * M)); assert_eq!(trips[0].data.duration, Duration(7020.0 * S)); assert_eq!(trips[0].data.comments, String::from("Do Some Distance!")); } }) } #[test] pub fn can_delete_an_entry() { run_test(|path| { let trips = mk_trips(); { let mut ts: Series = Series::open(&path).expect("expect the time series to open correctly"); let trip_id = ts.put(trips[0].clone()).expect("expect a successful put"); ts.put(trips[1].clone()).expect("expect a successful put"); ts.put(trips[2].clone()).expect("expect a successful put"); ts.delete(&trip_id).expect("successful delete"); let recs: Vec<&Record> = ts.records().collect(); assert_eq!(recs.len(), 2); } { let ts: Series = Series::open(&path).expect("expect the time series to open correctly"); let recs: Vec<&Record> = ts.records().collect(); assert_eq!(recs.len(), 2); } }) } #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct Weight(Kilogram); #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct WeightRecord { pub date: chrono::NaiveDate, pub weight: Weight, } impl Recordable for WeightRecord { fn timestamp(&self) -> Timestamp { Timestamp::Date(self.date) } fn tags(&self) -> Vec { Vec::new() } } /* #[test] pub fn legacy_file_load() { let ts: Series = Series::open("fixtures/weight.json").expect("legacy series should open correctly"); let uid = "3330c5b0-783f-4919-b2c4-8169c38f65ff" .parse() .expect("something is wrong with this ID"); let rec = ts.get(&uid); match rec { None => assert!(false, "no record found"), Some(rec) => assert_eq!(rec.weight, Weight(77.79109 * KG)), } } */ }