i5-add-tests (part 2) #7
16 changed files with 300 additions and 48 deletions
|
@ -1,14 +1,16 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub fn find(site: &str, channel_name: &str) -> Result<String> {
|
||||
use crate::fetch::FetchGet;
|
||||
|
||||
pub fn find(site: &str, channel_name: &str, e: &FetchGet) -> Result<String> {
|
||||
if let Some(channel_prefix) = channel_name.chars().next() {
|
||||
if channel_prefix != '@' {
|
||||
return Err(format!("Channel Name must begin with an '@': {}", channel_name).into());
|
||||
}
|
||||
}
|
||||
let channel_url = format!("{}{}", site, channel_name);
|
||||
let response = reqwest::blocking::get(channel_url)?;
|
||||
let rss_url = scraper::Html::parse_document(&response.text()?)
|
||||
let response = (e)(&channel_url)?;
|
||||
let rss_url = scraper::Html::parse_document(&response)
|
||||
.select(&scraper::Selector::parse("link[title='RSS']").unwrap())
|
||||
.next()
|
||||
.unwrap()
|
||||
|
@ -18,3 +20,40 @@ pub fn find(site: &str, channel_name: &str) -> Result<String> {
|
|||
.to_string();
|
||||
Ok(rss_url)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::fetch::Response;
|
||||
|
||||
use super::*;
|
||||
#[test]
|
||||
fn finds_rss_url() -> Result<()> {
|
||||
//given
|
||||
let fetch_get = &(get as FetchGet);
|
||||
//when
|
||||
let result = find("site", "@channel", fetch_get)?;
|
||||
//then
|
||||
assert_eq!(result, "the-rss-url");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_if_channel_name_is_invalid() -> Result<()> {
|
||||
//given
|
||||
let fetch_get = &(get as FetchGet);
|
||||
//when
|
||||
let result = find("site", "invalid-channel-name", fetch_get);
|
||||
//then
|
||||
assert!(result.is_err());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get(_url: &str) -> Result<Response> {
|
||||
Ok(r#"
|
||||
<html>
|
||||
<link title="RSS" href="the-rss-url">
|
||||
</html>
|
||||
"#
|
||||
.to_string())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use atom_syndication::Feed;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn get(url: &str) -> Result<Feed> {
|
||||
use atom_syndication::Feed;
|
||||
|
||||
pub fn reqwest_blocking_get(url: &str) -> Result<Feed> {
|
||||
let content = reqwest::blocking::get(url)?.bytes()?;
|
||||
let channel = Feed::read_from(&content[..])?;
|
||||
Ok(channel)
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use crate::fetch::FetchGet;
|
||||
|
||||
mod find;
|
||||
mod get;
|
||||
|
||||
use atom_syndication::Feed;
|
||||
pub use find::find;
|
||||
pub use get::get;
|
||||
pub use get::reqwest_blocking_get;
|
||||
|
||||
type Feed = atom_syndication::Feed;
|
||||
pub struct FeedEnv {
|
||||
pub find: FeedFind,
|
||||
pub get: FeedGet,
|
||||
}
|
||||
|
||||
pub type FeedFind = fn(&str, &str) -> Result<String>;
|
||||
pub type FeedFind = fn(&str, &str, &FetchGet) -> Result<String>;
|
||||
pub type FeedGet = fn(&str) -> Result<Feed>;
|
||||
|
|
12
src/fetch.rs
12
src/fetch.rs
|
@ -3,7 +3,13 @@ use crate::prelude::*;
|
|||
use atom_syndication::Link;
|
||||
use std::process::Command;
|
||||
|
||||
pub struct FetchEnv {
|
||||
pub download: FetchDownload,
|
||||
pub get: FetchGet,
|
||||
}
|
||||
|
||||
pub type FetchDownload = fn(&Link) -> Result<()>;
|
||||
pub type FetchGet = fn(&str) -> Result<Response>;
|
||||
|
||||
pub fn download(link: &Link) -> Result<()> {
|
||||
let cmd = "yt-dlp";
|
||||
|
@ -20,3 +26,9 @@ pub fn download(link: &Link) -> Result<()> {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub type Response = String;
|
||||
|
||||
pub fn get(url: &str) -> Result<Response> {
|
||||
Ok(reqwest::blocking::get(url)?.text()?)
|
||||
}
|
||||
|
|
|
@ -15,3 +15,75 @@ pub fn add(link: &Link, file_name: &str) -> Result<()> {
|
|||
writeln!(file, "{}", link.href)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
history::Link,
|
||||
test_utils::{create_text_file, read_text_file},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn creates_file_if_missing() -> Result<()> {
|
||||
//given
|
||||
let (dir, file_name) =
|
||||
create_text_file("download.txt", include_bytes!("../../test/data/empty.txt"))?;
|
||||
std::fs::remove_file(&file_name)?;
|
||||
|
||||
let link = Link {
|
||||
href: "foo".to_string(),
|
||||
rel: "bar".to_string(),
|
||||
hreflang: None,
|
||||
mime_type: None,
|
||||
title: None,
|
||||
length: None,
|
||||
};
|
||||
//when
|
||||
add(&link, &file_name)?;
|
||||
|
||||
//then
|
||||
let content: Vec<String> = read_text_file(&file_name)?;
|
||||
drop(dir);
|
||||
|
||||
let expected = vec!["foo".to_string()];
|
||||
assert_eq!(content, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appends_to_exising_file() -> Result<()> {
|
||||
// given
|
||||
let (dir, file_name) = create_text_file(
|
||||
"download.txt",
|
||||
include_bytes!("../../test/data/downloads.txt"),
|
||||
)?;
|
||||
|
||||
let link = Link {
|
||||
href: "foo".to_string(),
|
||||
rel: "bar".to_string(),
|
||||
hreflang: None,
|
||||
mime_type: None,
|
||||
title: None,
|
||||
length: None,
|
||||
};
|
||||
//when
|
||||
add(&link, &file_name)?;
|
||||
|
||||
//then
|
||||
let content: Vec<String> = read_text_file(&file_name)?;
|
||||
drop(dir);
|
||||
|
||||
let expected = vec![
|
||||
"line-1".to_string(),
|
||||
"line-2".to_string(),
|
||||
"foo".to_string(),
|
||||
];
|
||||
assert_eq!(content, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,3 +15,86 @@ pub fn find(link: &Link, file_name: &str) -> Result<bool> {
|
|||
}
|
||||
Ok(false) // is not already downloaded
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{history::Link, test_utils::create_text_file};
|
||||
|
||||
use super::*;
|
||||
#[test]
|
||||
fn true_if_line_exists() -> Result<()> {
|
||||
//given
|
||||
let (dir, file_name) =
|
||||
create_text_file("file", include_bytes!("../../test/data/with-llamma.txt"))?;
|
||||
let link = Link {
|
||||
href: "llamma".to_string(),
|
||||
rel: "".to_string(),
|
||||
hreflang: None,
|
||||
mime_type: None,
|
||||
title: None,
|
||||
length: None,
|
||||
};
|
||||
|
||||
//when
|
||||
let result = find(&link, &file_name)?;
|
||||
|
||||
//then
|
||||
drop(dir);
|
||||
|
||||
assert_eq!(result, true);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn false_if_line_absent() -> Result<()> {
|
||||
//given
|
||||
let (dir, file_name) =
|
||||
create_text_file("file", include_bytes!("../../test/data/without-llamma.txt"))?;
|
||||
let link = Link {
|
||||
href: "llamma".to_string(),
|
||||
rel: "".to_string(),
|
||||
hreflang: None,
|
||||
mime_type: None,
|
||||
title: None,
|
||||
length: None,
|
||||
};
|
||||
|
||||
//when
|
||||
let result = find(&link, &file_name)?;
|
||||
|
||||
//then
|
||||
drop(dir);
|
||||
|
||||
assert_eq!(result, false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn false_if_embedded_within_line() -> Result<()> {
|
||||
//given
|
||||
let (dir, file_name) = create_text_file(
|
||||
"file",
|
||||
include_bytes!("../../test/data/with-embedded-llamma.txt"),
|
||||
)?;
|
||||
let link = Link {
|
||||
href: "llamma".to_string(),
|
||||
rel: "".to_string(),
|
||||
hreflang: None,
|
||||
mime_type: None,
|
||||
title: None,
|
||||
length: None,
|
||||
};
|
||||
|
||||
//when
|
||||
let result = find(&link, &file_name)?;
|
||||
|
||||
//then
|
||||
drop(dir);
|
||||
|
||||
assert_eq!(result, false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,5 +8,10 @@ pub use find::find;
|
|||
|
||||
type Link = atom_syndication::Link;
|
||||
|
||||
pub struct HistoryEnv {
|
||||
pub find: HistoryFind,
|
||||
pub add: HistoryAdd,
|
||||
}
|
||||
|
||||
pub type HistoryFind = fn(&Link, &str) -> Result<bool>;
|
||||
pub type HistoryAdd = fn(&Link, &str) -> Result<()>;
|
||||
|
|
36
src/lib.rs
36
src/lib.rs
|
@ -5,30 +5,30 @@ pub mod history;
|
|||
pub mod prelude;
|
||||
mod subscriptions;
|
||||
|
||||
use feed::{FeedFind, FeedGet};
|
||||
use fetch::FetchDownload;
|
||||
use history::{HistoryAdd, HistoryFind};
|
||||
#[cfg(test)]
|
||||
mod test_utils;
|
||||
|
||||
use feed::FeedEnv;
|
||||
use fetch::FetchEnv;
|
||||
use history::HistoryEnv;
|
||||
use prelude::*;
|
||||
|
||||
pub fn run(
|
||||
subscriptions: &str,
|
||||
history: &str,
|
||||
site: &str,
|
||||
feed_find: FeedFind,
|
||||
feed_get: FeedGet,
|
||||
history_find: HistoryFind,
|
||||
history_add: HistoryAdd,
|
||||
fetch_download: FetchDownload,
|
||||
) -> Result<()> {
|
||||
pub struct Env {
|
||||
pub feed: FeedEnv,
|
||||
pub history: HistoryEnv,
|
||||
pub fetch: FetchEnv,
|
||||
}
|
||||
|
||||
pub fn run(subscriptions: &str, history: &str, site: &str, e: Env) -> Result<()> {
|
||||
for channel_name in subscriptions::lines_from(subscriptions)? {
|
||||
println!("Channel: {}", channel_name);
|
||||
let feed_url = feed_find(site, &channel_name)?;
|
||||
for entry in feed_get(&feed_url)?.entries() {
|
||||
let feed_url = (e.feed.find)(site, &channel_name, &e.fetch.get)?;
|
||||
for entry in (e.feed.get)(&feed_url)?.entries() {
|
||||
if let Some(link) = entry.links().get(0).cloned() {
|
||||
if !history_find(&link, history)? {
|
||||
if !(e.history.find)(&link, history)? {
|
||||
println!("Downloading {}: {}", &channel_name, entry.title().as_str());
|
||||
fetch_download(&link)?;
|
||||
history_add(&link, history)?;
|
||||
(e.fetch.download)(&link)?;
|
||||
(e.history.add)(&link, history)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
22
src/main.rs
22
src/main.rs
|
@ -1,5 +1,7 @@
|
|||
use podal::prelude::*;
|
||||
|
||||
use podal::{feed::FeedEnv, fetch::FetchEnv, history::HistoryEnv};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
println!("Podal");
|
||||
let subscriptions = "subscriptions.txt";
|
||||
|
@ -10,11 +12,21 @@ fn main() -> Result<()> {
|
|||
subscriptions,
|
||||
history,
|
||||
site,
|
||||
podal::feed::find,
|
||||
podal::feed::get,
|
||||
podal::history::find,
|
||||
podal::history::add,
|
||||
podal::fetch::download,
|
||||
podal::Env {
|
||||
feed: FeedEnv {
|
||||
find: podal::feed::find,
|
||||
get: podal::feed::reqwest_blocking_get,
|
||||
},
|
||||
|
||||
history: HistoryEnv {
|
||||
find: podal::history::find,
|
||||
add: podal::history::add,
|
||||
},
|
||||
fetch: FetchEnv {
|
||||
download: podal::fetch::download,
|
||||
get: podal::fetch::get,
|
||||
},
|
||||
},
|
||||
)?;
|
||||
|
||||
println!("Done");
|
||||
|
|
|
@ -7,21 +7,18 @@ pub fn lines_from(file_name: &str) -> Result<Vec<String>> {
|
|||
let file = File::open(file_name)?;
|
||||
let reader = BufReader::new(file);
|
||||
let mut lines = vec![];
|
||||
for line in reader.lines() {
|
||||
if let Ok(line) = line {
|
||||
for line in reader.lines().flatten() {
|
||||
if line.starts_with('@') {
|
||||
lines.push(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(lines)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{fs::File, io::Write, str::from_utf8};
|
||||
|
||||
use tempfile::{tempdir, TempDir};
|
||||
use crate::test_utils::create_text_file;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -75,13 +72,4 @@ mod tests {
|
|||
assert_eq!(result, ["@sub1", "@sub3"]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_text_file(name: &str, data: &[u8]) -> Result<(TempDir, String)> {
|
||||
let data = from_utf8(data)?;
|
||||
let dir = tempdir()?;
|
||||
let filename = format!("{}", &dir.path().join(name).display());
|
||||
let file = File::create(&filename)?;
|
||||
write!(&file, "{data}")?;
|
||||
Ok((dir, filename))
|
||||
}
|
||||
}
|
||||
|
|
25
src/test_utils.rs
Normal file
25
src/test_utils.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use std::{
|
||||
fs::{read_to_string, File},
|
||||
io::Write,
|
||||
str::from_utf8,
|
||||
};
|
||||
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn create_text_file(name: &str, data: &[u8]) -> Result<(TempDir, String)> {
|
||||
let data = from_utf8(data)?;
|
||||
let dir = tempdir()?;
|
||||
let filename = format!("{}", &dir.path().join(name).display());
|
||||
let file = File::create(&filename)?;
|
||||
write!(&file, "{data}")?;
|
||||
Ok((dir, filename))
|
||||
}
|
||||
|
||||
pub fn read_text_file(file_name: &str) -> Result<Vec<String>> {
|
||||
Ok(read_to_string(file_name)?
|
||||
.lines()
|
||||
.map(String::from)
|
||||
.collect())
|
||||
}
|
2
test/data/downloads.txt
Normal file
2
test/data/downloads.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
line-1
|
||||
line-2
|
0
test/data/empty.txt
Normal file
0
test/data/empty.txt
Normal file
3
test/data/with-embedded-llamma.txt
Normal file
3
test/data/with-embedded-llamma.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
this is a text file
|
||||
embedded llamma
|
||||
it has three lines
|
3
test/data/with-llamma.txt
Normal file
3
test/data/with-llamma.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
this is a text file
|
||||
llamma
|
||||
it has three lines
|
2
test/data/without-llamma.txt
Normal file
2
test/data/without-llamma.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
this is a text file
|
||||
it has three lines
|
Loading…
Add table
Reference in a new issue