use log::debug; use rusqlite::{params, Connection, OptionalExtension}; use std::error::Error; /// Struct for each query line #[derive(Debug)] pub struct TweetToToot { pub tweet_id: u64, pub toot_id: u64, } /// if None is passed, read the last tweet from DB /// if a tweet_id is passed, read this particular tweet from DB pub fn read_state( conn: &Connection, s: Option, ) -> Result, Box> { debug!("Reading tweet_id {:?}", s); let query: String = match s { Some(i) => format!("SELECT * FROM tweet_to_toot WHERE toot_id = {i}"), None => "SELECT * FROM tweet_to_toot ORDER BY toot_id DESC LIMIT 1".to_string(), }; let mut stmt = conn.prepare(&query)?; let t = stmt .query_row([], |row| { Ok(TweetToToot { tweet_id: row.get("tweet_id")?, toot_id: row.get("toot_id")?, }) }) .optional()?; Ok(t) } /// Writes last treated tweet id and toot id to the db #[allow(dead_code)] pub fn write_state(conn: &Connection, t: TweetToToot) -> Result<(), Box> { debug!("Write struct {:?}", t); conn.execute( "INSERT INTO tweet_to_toot (tweet_id, toot_id) VALUES (?1, ?2)", params![t.tweet_id, t.toot_id], )?; Ok(()) } /// Initiates the DB from path pub fn init_db(d: &str) -> Result<(), Box> { debug!("Initializing DB for Scootaloo"); let conn = Connection::open(d)?; conn.execute( "CREATE TABLE IF NOT EXISTS tweet_to_toot ( tweet_id INTEGER, toot_id INTEGER PRIMARY KEY )", [], )?; Ok(()) } #[cfg(test)] mod tests { use super::*; use std::{fs::remove_file, path::Path}; #[test] fn test_init_db() { let d = "/tmp/test_init_db.sqlite"; init_db(d).unwrap(); // check that file exist assert!(Path::new(d).exists()); // open said file let conn = Connection::open(d).unwrap(); conn.execute("SELECT * from tweet_to_toot;", []).unwrap(); remove_file(d).unwrap(); } #[test] fn test_init_init_db() { // init_db fn should be idempotent so let’s test that let d = "/tmp/test_init_init_db.sqlite"; init_db(d).unwrap(); let conn = Connection::open(d).unwrap(); conn.execute( "INSERT INTO tweet_to_toot (tweet_id, toot_id) VALUES (100, 1001);", [], ) .unwrap(); init_db(d).unwrap(); remove_file(d).unwrap(); } #[test] fn test_write_state() { let d = "/tmp/test_write_state.sqlite"; init_db(d).unwrap(); let conn = Connection::open(d).unwrap(); let t_in = TweetToToot { tweet_id: 123456789, toot_id: 987654321, }; write_state(&conn, t_in).unwrap(); let mut stmt = conn.prepare("SELECT * FROM tweet_to_toot;").unwrap(); let t_out = stmt .query_row([], |row| { Ok(TweetToToot { tweet_id: row.get("tweet_id").unwrap(), toot_id: row.get("toot_id").unwrap(), }) }) .unwrap(); assert_eq!(t_out.tweet_id, 123456789); assert_eq!(t_out.toot_id, 987654321); remove_file(d).unwrap(); } #[test] fn test_none_to_tweet_id_read_state() { let d = "/tmp/test_none_to_tweet_id_read_state.sqlite"; init_db(d).unwrap(); let conn = Connection::open(d).unwrap(); conn.execute( "INSERT INTO tweet_to_toot (tweet_id, toot_id) VALUES (101, 1001), (102, 1002);", [], ) .unwrap(); let t_out = read_state(&conn, None).unwrap().unwrap(); remove_file(d).unwrap(); assert_eq!(t_out.tweet_id, 102); assert_eq!(t_out.toot_id, 1002); } #[test] fn test_none_to_none_read_state() { let d = "/tmp/test_none_to_none_read_state.sqlite"; init_db(d).unwrap(); let conn = Connection::open(d).unwrap(); let t_out = read_state(&conn, None).unwrap(); remove_file(d).unwrap(); assert!(t_out.is_none()); } #[test] fn test_tweet_id_to_none_read_state() { let d = "/tmp/test_tweet_id_to_none_read_state.sqlite"; init_db(d).unwrap(); let conn = Connection::open(d).unwrap(); conn.execute( "INSERT INTO tweet_to_toot (tweet_id, toot_id) VALUES (100, 1000);", [], ) .unwrap(); let t_out = read_state(&conn, Some(1200)).unwrap(); remove_file(d).unwrap(); assert!(t_out.is_none()); } #[test] fn test_tweet_id_to_tweet_id_read_state() { let d = "/tmp/test_tweet_id_to_tweet_id_read_state.sqlite"; init_db(d).unwrap(); let conn = Connection::open(d).unwrap(); conn.execute( "INSERT INTO tweet_to_toot (tweet_id, toot_id) VALUES (100, 1000);", [], ) .unwrap(); let t_out = read_state(&conn, Some(1000)).unwrap().unwrap(); remove_file(d).unwrap(); assert_eq!(t_out.tweet_id, 100); assert_eq!(t_out.toot_id, 1000); } }