//! Query Feeds repository CRUD and FeedFilter integration tests. mod common; use bb_db::{CreateQueryFeed, QueryCondition, QueryFeedId}; use bb_feed::{FeedFilter, FeedGenerator}; // ── Query Feeds Repository CRUD ────────────────────────────────────── #[tokio::test] async fn query_feed_create_and_list() { let db = common::test_db().await; let qf = db .query_feeds() .create(CreateQueryFeed { name: "Rust stuff".to_string(), rules: vec![QueryCondition { field: "title".to_string(), operator: "contains".to_string(), value: "rust".to_string(), }], }) .await .unwrap(); assert_eq!(qf.name, "Rust stuff"); assert!(!qf.id.to_string().is_empty()); let all = db.query_feeds().list_all().await.unwrap(); assert_eq!(all.len(), 1); assert_eq!(all[0].name, "Rust stuff"); let rules = all[0].rules_vec(); assert_eq!(rules.len(), 1); assert_eq!(rules[0].field, "title"); assert_eq!(rules[0].operator, "contains"); assert_eq!(rules[0].value, "rust"); } #[tokio::test] async fn query_feed_get_by_id() { let db = common::test_db().await; let qf = db .query_feeds() .create(CreateQueryFeed { name: "Stars".to_string(), rules: vec![QueryCondition { field: "starred".to_string(), operator: "is".to_string(), value: "true".to_string(), }], }) .await .unwrap(); let fetched = db.query_feeds().get(qf.id).await.unwrap().unwrap(); assert_eq!(fetched.name, "Stars"); assert_eq!(fetched.id, qf.id); } #[tokio::test] async fn query_feed_get_nonexistent_returns_none() { let db = common::test_db().await; let fake_id = QueryFeedId::new(); let result = db.query_feeds().get(fake_id).await.unwrap(); assert!(result.is_none()); } #[tokio::test] async fn query_feed_update() { let db = common::test_db().await; let qf = db .query_feeds() .create(CreateQueryFeed { name: "Original".to_string(), rules: vec![QueryCondition { field: "title".to_string(), operator: "contains".to_string(), value: "old".to_string(), }], }) .await .unwrap(); let new_rules = vec![QueryCondition { field: "author".to_string(), operator: "equals".to_string(), value: "alice".to_string(), }]; db.query_feeds() .update(qf.id, "Renamed", &new_rules) .await .unwrap(); let fetched = db.query_feeds().get(qf.id).await.unwrap().unwrap(); assert_eq!(fetched.name, "Renamed"); let rules = fetched.rules_vec(); assert_eq!(rules.len(), 1); assert_eq!(rules[0].field, "author"); } #[tokio::test] async fn query_feed_delete() { let db = common::test_db().await; let qf = db .query_feeds() .create(CreateQueryFeed { name: "Temporary".to_string(), rules: vec![], }) .await .unwrap(); assert_eq!(db.query_feeds().list_all().await.unwrap().len(), 1); db.query_feeds().delete(qf.id).await.unwrap(); assert!(db.query_feeds().list_all().await.unwrap().is_empty()); assert!(db.query_feeds().get(qf.id).await.unwrap().is_none()); } #[tokio::test] async fn query_feed_rules_json_roundtrip() { let db = common::test_db().await; let rules = vec![ QueryCondition { field: "title".to_string(), operator: "contains".to_string(), value: "rust".to_string(), }, QueryCondition { field: "source".to_string(), operator: "equals".to_string(), value: "rss".to_string(), }, QueryCondition { field: "starred".to_string(), operator: "is".to_string(), value: "true".to_string(), }, ]; let qf = db .query_feeds() .create(CreateQueryFeed { name: "Complex".to_string(), rules: rules.clone(), }) .await .unwrap(); let fetched = db.query_feeds().get(qf.id).await.unwrap().unwrap(); let roundtripped = fetched.rules_vec(); assert_eq!(roundtripped.len(), 3); assert_eq!(roundtripped[0].field, "title"); assert_eq!(roundtripped[1].field, "source"); assert_eq!(roundtripped[2].field, "starred"); } // ── Query Feed + FeedFilter Integration ────────────────────────────── #[tokio::test] async fn query_feed_filter_items_by_conditions() { let db = common::test_db().await; let feed_rss = common::create_rss_feed(&db, "RSS", "https://example.com/rss").await; let feed_hn = common::create_other_feed(&db, "hn", "HN").await; common::insert_item(&db, &feed_rss, "rss:1", "Rust Programming Guide", 1).await; common::insert_item(&db, &feed_rss, "rss:2", "Python Tutorial", 2).await; common::insert_item(&db, &feed_hn, "hn:1", "Rust on HN", 1).await; // Create a query feed that filters for "rust" in title + source=rss let conditions = vec![ QueryCondition { field: "title".to_string(), operator: "contains".to_string(), value: "rust".to_string(), }, QueryCondition { field: "source".to_string(), operator: "equals".to_string(), value: "rss".to_string(), }, ]; let filter = FeedFilter::from_conditions(conditions); let fg = FeedGenerator::new(db).with_filter(filter); let result = fg.get_items(0).await.unwrap(); assert_eq!(result.items.len(), 1); assert_eq!(result.items[0].id.item_id, "rss:1"); }