mirror of
https://framagit.org/veretcle/scootaloo.git
synced 2025-07-21 09:31:19 +02:00
305 lines
7.7 KiB
Rust
305 lines
7.7 KiB
Rust
use std::error::Error;
|
||
|
||
use log::debug;
|
||
|
||
use rusqlite::{params, Connection, OptionalExtension};
|
||
|
||
/// Struct for each query line
|
||
#[derive(Debug)]
|
||
pub struct TweetToToot {
|
||
pub twitter_screen_name: String,
|
||
pub tweet_id: u64,
|
||
pub toot_id: String,
|
||
}
|
||
|
||
/// 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,
|
||
n: &str,
|
||
s: Option<u64>,
|
||
) -> Result<Option<TweetToToot>, Box<dyn Error>> {
|
||
debug!("Reading tweet_id {:?}", s);
|
||
let query: String = match s {
|
||
Some(i) => format!("SELECT * FROM tweet_to_toot WHERE tweet_id = {} and twitter_screen_name = \"{}\"", i, n),
|
||
None => format!("SELECT * FROM tweet_to_toot WHERE twitter_screen_name = \"{}\" ORDER BY tweet_id DESC LIMIT 1", n),
|
||
};
|
||
|
||
let mut stmt = conn.prepare(&query)?;
|
||
|
||
let t = stmt
|
||
.query_row([], |row| {
|
||
Ok(TweetToToot {
|
||
twitter_screen_name: row.get("twitter_screen_name")?,
|
||
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
|
||
pub fn write_state(conn: &Connection, t: TweetToToot) -> Result<(), Box<dyn Error>> {
|
||
debug!("Write struct {:?}", t);
|
||
conn.execute(
|
||
"INSERT INTO tweet_to_toot (twitter_screen_name, tweet_id, toot_id) VALUES (?1, ?2, ?3)",
|
||
params![t.twitter_screen_name, t.tweet_id, t.toot_id],
|
||
)?;
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// Initiates the DB from path
|
||
pub fn init_db(d: &str) -> Result<(), Box<dyn Error>> {
|
||
debug!("Initializing DB for Scootaloo");
|
||
let conn = Connection::open(d)?;
|
||
|
||
conn.execute(
|
||
"CREATE TABLE IF NOT EXISTS tweet_to_toot (
|
||
twitter_screen_name TEXT NOT NULL,
|
||
tweet_id INTEGER PRIMARY KEY,
|
||
toot_id TEXT UNIQUE
|
||
)",
|
||
[],
|
||
)?;
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// Migrate DB from 0.6.x to 0.7.x
|
||
pub fn migrate_db(d: &str, s: &str) -> Result<(), Box<dyn Error>> {
|
||
debug!("Migrating DB for Scootaloo");
|
||
|
||
let conn = Connection::open(d)?;
|
||
|
||
let res = conn.execute(
|
||
&format!(
|
||
"ALTER TABLE tweet_to_toot
|
||
ADD COLUMN twitter_screen_name TEXT NOT NULL
|
||
DEFAULT \"{}\"",
|
||
s
|
||
),
|
||
[],
|
||
);
|
||
|
||
match res {
|
||
Err(e) => match e.to_string().as_str() {
|
||
"duplicate column name: twitter_screen_name" => Ok(()),
|
||
_ => Err(e.into()),
|
||
},
|
||
_ => 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 (twitter_screen_name, tweet_id, toot_id)
|
||
VALUES
|
||
('tamerelol', 100, 'A');",
|
||
[],
|
||
)
|
||
.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 {
|
||
twitter_screen_name: "tamerelol".to_string(),
|
||
tweet_id: 123456789,
|
||
toot_id: "987654321".to_string(),
|
||
};
|
||
|
||
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 {
|
||
twitter_screen_name: row.get("twitter_screen_name").unwrap(),
|
||
tweet_id: row.get("tweet_id").unwrap(),
|
||
toot_id: row.get("toot_id").unwrap(),
|
||
})
|
||
})
|
||
.unwrap();
|
||
|
||
assert_eq!(&t_out.twitter_screen_name, "tamerelol");
|
||
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 (twitter_screen_name, tweet_id, toot_id)
|
||
VALUES
|
||
('tamerelol', 101, 'A'),
|
||
('tamerelol', 102, 'B');",
|
||
[],
|
||
)
|
||
.unwrap();
|
||
|
||
let t_out = read_state(&conn, "tamerelol", None).unwrap().unwrap();
|
||
|
||
remove_file(d).unwrap();
|
||
|
||
assert_eq!(t_out.tweet_id, 102);
|
||
assert_eq!(t_out.toot_id, "B");
|
||
}
|
||
|
||
#[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, "tamerelol", 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 (twitter_screen_name, tweet_id, toot_id)
|
||
VALUES
|
||
('tamerelol', 100, 'A');",
|
||
[],
|
||
)
|
||
.unwrap();
|
||
|
||
let t_out = read_state(&conn, "tamerelol", Some(101)).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 (twitter_screen_name, tweet_id, toot_id)
|
||
VALUES
|
||
('tamerelol', 100, 'A');",
|
||
[],
|
||
)
|
||
.unwrap();
|
||
|
||
let t_out = read_state(&conn, "tamerelol", Some(100)).unwrap().unwrap();
|
||
|
||
remove_file(d).unwrap();
|
||
|
||
assert_eq!(t_out.tweet_id, 100);
|
||
assert_eq!(t_out.toot_id, "A");
|
||
}
|
||
|
||
#[test]
|
||
fn test_migrate_db_add_column() {
|
||
let d = "/tmp/test_migrate_db_add_column.sqlite";
|
||
|
||
let conn = Connection::open(d).unwrap();
|
||
|
||
conn.execute(
|
||
"CREATE TABLE IF NOT EXISTS tweet_to_toot (
|
||
tweet_id INTEGER PRIMARY KEY,
|
||
toot_id TEXT UNIQUE
|
||
)",
|
||
[],
|
||
)
|
||
.unwrap();
|
||
|
||
migrate_db(d, "tamerelol").unwrap();
|
||
|
||
let mut stmt = conn.prepare("PRAGMA table_info(tweet_to_toot);").unwrap();
|
||
|
||
let mut t = stmt.query([]).unwrap();
|
||
|
||
while let Some(row) = t.next().unwrap() {
|
||
if row.get::<usize, u8>(0).unwrap() == 2 {
|
||
assert_eq!(
|
||
row.get::<usize, String>(1).unwrap(),
|
||
"twitter_screen_name".to_string()
|
||
);
|
||
}
|
||
}
|
||
|
||
remove_file(d).unwrap();
|
||
}
|
||
|
||
#[test]
|
||
fn test_migrate_db_no_add_column() {
|
||
let d = "/tmp/test_migrate_db_no_add_column.sqlite";
|
||
|
||
init_db(d).unwrap();
|
||
|
||
migrate_db(d, "tamerelol").unwrap();
|
||
|
||
remove_file(d).unwrap();
|
||
}
|
||
}
|