feat: feed UI, in-app player, offline mode, settings
Adds an end-to-end integration test that runs the real pipeline against live YouTube Atom feeds, verifying the merged feed is newest-first across channels.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
//! End-to-end check of the real pipeline: Takeout CSV -> live Atom feeds ->
|
||||
//! SQLite -> feed query. Hits the network, so it is ignored by default.
|
||||
//! Run with: cargo test --test pipeline -- --ignored --nocapture
|
||||
|
||||
use flighttube_lib::{db::Db, feed, models::FeedFilter, takeout};
|
||||
|
||||
const CSV: &str = include_str!("fixtures/subscriptions.csv");
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn full_pipeline_against_live_feeds() {
|
||||
let channels = takeout::parse_csv(CSV).expect("CSV should parse");
|
||||
println!("parsed {} channels", channels.len());
|
||||
assert!(channels.len() >= 10);
|
||||
|
||||
let mut db = Db::open_in_memory_pub().expect("db");
|
||||
db.upsert_channels(&channels).unwrap();
|
||||
|
||||
let http = reqwest::Client::builder()
|
||||
.user_agent("FlightTube/0.1 (+desktop)")
|
||||
.timeout(std::time::Duration::from_secs(20))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut total = 0;
|
||||
for c in &channels {
|
||||
match feed::fetch_channel(&http, &c.id).await {
|
||||
Ok(v) => {
|
||||
println!(" {:<30} {} videos", c.title, v.len());
|
||||
total += v.len();
|
||||
db.upsert_videos(&v).unwrap();
|
||||
}
|
||||
Err(e) => println!(" {:<30} FAILED: {e}", c.title),
|
||||
}
|
||||
}
|
||||
assert!(total > 50, "expected a real feed, got {total} videos");
|
||||
|
||||
let all = db.list_feed(&FeedFilter::default()).unwrap();
|
||||
println!("\nfeed rows: {}", all.len());
|
||||
|
||||
// The whole point: newest first, across all channels.
|
||||
for w in all.windows(2) {
|
||||
assert!(w[0].published >= w[1].published, "feed must be newest-first");
|
||||
}
|
||||
println!("top 5 newest across all subscriptions:");
|
||||
for item in all.iter().take(5) {
|
||||
println!(
|
||||
" [{}] {} — {}",
|
||||
item.channel_title,
|
||||
item.title.chars().take(60).collect::<String>(),
|
||||
item.published
|
||||
);
|
||||
}
|
||||
|
||||
let no_shorts = db
|
||||
.list_feed(&FeedFilter { hide_shorts: true, ..Default::default() })
|
||||
.unwrap();
|
||||
println!("\nwith shorts hidden: {} (was {})", no_shorts.len(), all.len());
|
||||
assert!(no_shorts.len() <= all.len());
|
||||
assert!(no_shorts.iter().all(|i| !i.is_short));
|
||||
|
||||
// Nothing is downloaded, so the offline view must be empty.
|
||||
let offline = db
|
||||
.list_feed(&FeedFilter { downloaded_only: true, ..Default::default() })
|
||||
.unwrap();
|
||||
assert_eq!(offline.len(), 0, "nothing downloaded yet");
|
||||
println!("offline view with no downloads: {} rows (correct)", offline.len());
|
||||
}
|
||||
Reference in New Issue
Block a user