add test structs
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -724,6 +724,7 @@ dependencies = [
|
|||||||
"quick-xml",
|
"quick-xml",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@@ -10,3 +10,4 @@ reqwest = { version = "0.12", features = ["stream", "json", "rustls-tls"], defau
|
|||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
quick-xml = "0.38"
|
quick-xml = "0.38"
|
||||||
|
thiserror = "2"
|
||||||
|
109
src/lib.rs
109
src/lib.rs
@@ -4,6 +4,7 @@ mod web;
|
|||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use thiserror::Error;
|
||||||
use tokio::{net::TcpListener, sync::RwLock};
|
use tokio::{net::TcpListener, sync::RwLock};
|
||||||
|
|
||||||
pub async fn start(
|
pub async fn start(
|
||||||
@@ -14,7 +15,9 @@ pub async fn start(
|
|||||||
listener: TcpListener,
|
listener: TcpListener,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let state = Arc::new(RwLock::new(
|
let state = Arc::new(RwLock::new(
|
||||||
Feed::new(title, link, desc, filter_titles).await.unwrap(),
|
LiveFeed::new(title, link, desc, filter_titles)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
));
|
));
|
||||||
|
|
||||||
web::serve(state, listener).await?;
|
web::serve(state, listener).await?;
|
||||||
@@ -22,7 +25,57 @@ pub async fn start(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Feed {
|
#[derive(Error, Debug)]
|
||||||
|
enum FetchError {}
|
||||||
|
|
||||||
|
trait Feed {
|
||||||
|
async fn fetch(&mut self) -> Result<(), Box<dyn std::error::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
struct TestFeed {
|
||||||
|
episodes: Vec<Broadcast>,
|
||||||
|
pub(crate) amount_fetch_calls: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl Default for TestFeed {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
episodes: vec![Broadcast::test()],
|
||||||
|
amount_fetch_calls: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl rss::ToRss for TestFeed {
|
||||||
|
fn title(&self) -> &str {
|
||||||
|
"Test RSS Title"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn link(&self) -> &str {
|
||||||
|
"https://test.rss"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desc(&self) -> &str {
|
||||||
|
"Test RSS Desc"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn episodes(&self) -> &Vec<crate::Broadcast> {
|
||||||
|
&self.episodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl Feed for TestFeed {
|
||||||
|
async fn fetch(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
self.amount_fetch_calls += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LiveFeed {
|
||||||
episodes: Vec<Broadcast>,
|
episodes: Vec<Broadcast>,
|
||||||
title: String,
|
title: String,
|
||||||
link: String,
|
link: String,
|
||||||
@@ -30,7 +83,27 @@ struct Feed {
|
|||||||
filter_titles: Vec<String>,
|
filter_titles: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Feed {
|
impl Feed for LiveFeed {
|
||||||
|
async fn fetch(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let broadcasts = self.get_all_broadcasts().await?;
|
||||||
|
|
||||||
|
for broadcast in broadcasts {
|
||||||
|
if !self.has_broadcast_url(&broadcast) {
|
||||||
|
if let Some(broadcast) = Broadcast::from_url(broadcast).await.unwrap() {
|
||||||
|
self.episodes.push(broadcast);
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.only_keep_last_episodes();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LiveFeed {
|
||||||
async fn new(
|
async fn new(
|
||||||
title: String,
|
title: String,
|
||||||
link: String,
|
link: String,
|
||||||
@@ -50,24 +123,6 @@ impl Feed {
|
|||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let broadcasts = self.get_all_broadcasts().await?;
|
|
||||||
|
|
||||||
for broadcast in broadcasts {
|
|
||||||
if !self.has_broadcast_url(&broadcast) {
|
|
||||||
if let Some(broadcast) = Broadcast::from_url(broadcast).await.unwrap() {
|
|
||||||
self.episodes.push(broadcast);
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.only_keep_last_episodes();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn only_keep_last_episodes(&mut self) {
|
fn only_keep_last_episodes(&mut self) {
|
||||||
self.episodes = self.episodes.clone().into_iter().rev().take(10).collect();
|
self.episodes = self.episodes.clone().into_iter().rev().take(10).collect();
|
||||||
// only keep last 10
|
// only keep last 10
|
||||||
@@ -117,6 +172,18 @@ struct Broadcast {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Broadcast {
|
impl Broadcast {
|
||||||
|
#[cfg(test)]
|
||||||
|
fn test() -> Self {
|
||||||
|
use chrono::Local;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
url: "test.url".into(),
|
||||||
|
media_url: "test.media.url".into(),
|
||||||
|
title: "Test title".into(),
|
||||||
|
timestamp: Local::now().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn from_url(url: String) -> Result<Option<Self>, Box<dyn std::error::Error>> {
|
async fn from_url(url: String) -> Result<Option<Self>, Box<dyn std::error::Error>> {
|
||||||
let data: Value = reqwest::get(&url).await?.json().await?;
|
let data: Value = reqwest::get(&url).await?.json().await?;
|
||||||
let Some(streams) = data["streams"].as_array() else {
|
let Some(streams) = data["streams"].as_array() else {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
use crate::{Broadcast, Feed};
|
use crate::{Broadcast, LiveFeed};
|
||||||
|
|
||||||
pub(super) trait ToRss {
|
pub(super) trait ToRss {
|
||||||
fn title(&self) -> &str;
|
fn title(&self) -> &str;
|
||||||
@@ -39,7 +39,7 @@ pub(super) trait ToRss {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToRss for Feed {
|
impl ToRss for LiveFeed {
|
||||||
fn title(&self) -> &str {
|
fn title(&self) -> &str {
|
||||||
&self.title
|
&self.title
|
||||||
}
|
}
|
||||||
|
42
src/web.rs
42
src/web.rs
@@ -1,10 +1,10 @@
|
|||||||
use crate::{rss::ToRss, Feed};
|
use crate::{rss::ToRss, Feed, LiveFeed};
|
||||||
use axum::{extract::State, http::HeaderMap, response::IntoResponse, routing::get, Router};
|
use axum::{extract::State, http::HeaderMap, response::IntoResponse, routing::get, Router};
|
||||||
use reqwest::header;
|
use reqwest::header;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::{net::TcpListener, sync::RwLock};
|
use tokio::{net::TcpListener, sync::RwLock};
|
||||||
|
|
||||||
async fn stream_handler(State(state): State<Arc<RwLock<Feed>>>) -> impl IntoResponse {
|
async fn stream_handler<T: Feed + ToRss>(State(state): State<Arc<RwLock<T>>>) -> impl IntoResponse {
|
||||||
state.write().await.fetch().await.unwrap();
|
state.write().await.fetch().await.unwrap();
|
||||||
|
|
||||||
let content = state.read().await.to_rss();
|
let content = state.read().await.to_rss();
|
||||||
@@ -15,7 +15,7 @@ async fn stream_handler(State(state): State<Arc<RwLock<Feed>>>) -> impl IntoResp
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn serve(
|
pub(super) async fn serve(
|
||||||
state: Arc<RwLock<Feed>>,
|
state: Arc<RwLock<LiveFeed>>,
|
||||||
listener: TcpListener,
|
listener: TcpListener,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
@@ -26,3 +26,39 @@ pub(super) async fn serve(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#[cfg(test)]
|
||||||
|
//mod tests {
|
||||||
|
// use crate::{rss::ToRss, TestFeed};
|
||||||
|
// use axum::http::StatusCode;
|
||||||
|
// use std::sync::Arc;
|
||||||
|
// use tokio::sync::RwLock;
|
||||||
|
//
|
||||||
|
// #[tokio::test]
|
||||||
|
// async fn serve_serves_rss() {
|
||||||
|
// let feed = Arc::new(RwLock::new(TestFeed::default()));
|
||||||
|
//
|
||||||
|
// let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||||
|
// let addr = listener.local_addr().unwrap();
|
||||||
|
//
|
||||||
|
// tokio::spawn(super::serve(feed.clone(), listener));
|
||||||
|
//
|
||||||
|
// let client = reqwest::Client::new();
|
||||||
|
// let resp = client.get(format!("http://{}", addr)).send().await.unwrap();
|
||||||
|
//
|
||||||
|
// assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
// assert_eq!(
|
||||||
|
// resp.headers()
|
||||||
|
// .get("content-type")
|
||||||
|
// .unwrap()
|
||||||
|
// .to_str()
|
||||||
|
// .unwrap(),
|
||||||
|
// "application/rss+xml"
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// let body = resp.text().await.unwrap();
|
||||||
|
// assert_eq!(body, feed.read().await.to_rss());
|
||||||
|
//
|
||||||
|
// assert_eq!(feed.read().await.amount_fetch_calls, 1);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
Reference in New Issue
Block a user