feedbin_api 0.5.2

Rust implementation of the Feedbin REST API
Documentation
extern crate rand;
use crate::CacheRequestResponse;
use crate::CacheResult;
use crate::CreateSubscriptionResult;
use crate::FeedbinApi;
use rand::distr::Alphanumeric;
use rand::{rng, RngExt};
use reqwest::Client;
use std::env;
use url::Url;

fn test_setup_env() -> FeedbinApi {
    dotenv::dotenv().ok(); // Intentionally ignoring errors if .env file doesn't exist
    let url = env::var("FEEDBIN_URL").expect("Failed to read FEEDBIN_URL");
    let user = env::var("FEEDBIN_USERNAME").expect("Failed to read FEEDBIN_USERNAME");
    let password = env::var("FEEDBIN_PASSWORD").expect("Failed to read FEEDBIN_PASSWORD");

    let url = Url::parse(&url).unwrap();
    FeedbinApi::new(&url, user, password)
}

#[tokio::test(flavor = "current_thread")]
async fn authenticated_valid_password() {
    let api = test_setup_env();
    assert!(api.is_authenticated(&Client::new()).await.unwrap());
}

#[tokio::test(flavor = "current_thread")]
async fn authenticated_invalid_password() {
    let api = test_setup_env().with_password("invalid password");
    assert!(!api.is_authenticated(&Client::new()).await.unwrap());
}

#[tokio::test(flavor = "current_thread")]
async fn is_reachable_valid_password() {
    let api = test_setup_env();
    assert!(api.is_reachable(&Client::new()).await.unwrap());
}

#[tokio::test(flavor = "current_thread")]
async fn is_not_reachable_valid_password() {
    let api = test_setup_env()
        .with_base_url(&Url::parse("http://notarealsubdomain.example.com").unwrap());
    assert!(!api.is_reachable(&Client::new()).await.unwrap());
}

#[tokio::test(flavor = "current_thread")]
async fn is_reachable_invalid_password() {
    let api = test_setup_env().with_password("invalid password");
    assert!(api.is_reachable(&Client::new()).await.unwrap());
}

#[tokio::test(flavor = "current_thread")]
async fn create_and_delete_subscription() {
    // Creates a new subscription, verifies that the response is correct (Created; expect URL),
    // ensures we can lookup that subscription up, and then deletes it
    let api = test_setup_env();

    // Create a subscription to a new unique ATOM feed
    let token: Vec<u8> = rng().sample_iter(&Alphanumeric).take(16).collect();
    let token = std::str::from_utf8(&token).unwrap();
    let mut url = Url::parse("https://www.brendanlong.com/feeds/all.atom.xml").unwrap();
    url.set_query(Some(&format!("token={}", token)));
    match api
        .create_subscription(&Client::new(), url.as_str())
        .await
        .unwrap()
    {
        // New subscription should be "Created"
        CreateSubscriptionResult::Created(subscription) => {
            // The new subscription should have the URL we gave
            // Note that this is only guaranteed because we gave a direct link to an ATOM feed.
            // Feedbin also allows links to HTML pages, where it will find the feed for you
            assert_eq!(
                subscription.feed_url,
                url.as_str(),
                "Returned Subscription URL should match the URL we used to create the subscription"
            );

            // Lookup subscription in the list and by ID and ensure our new subscription shows up
            let response = match api
                .get_subscriptions(&Client::new(), None, None, None)
                .await
                .unwrap()
            {
                CacheRequestResponse::Modified(CacheResult {
                    value: response,
                    cache: _,
                }) => response,
                CacheRequestResponse::NotModified => panic!(),
            };
            assert_eq!(
                response
                    .iter()
                    .filter(|sub| sub.id == subscription.id)
                    .count(),
                1,
                "Newly created subscription should exist in the full list of subscriptions"
            );
            assert_eq!(
                api.get_subscription(&Client::new(), subscription.id)
                    .await
                    .unwrap()
                    .id,
                subscription.id,
                "Newly created subscription can be looked up by ID"
            );

            // Delete the subscription and ensure that it doesn't exist anymore
            api.delete_subscription(&Client::new(), subscription.id)
                .await
                .unwrap();
            let response = match api
                .get_subscriptions(&Client::new(), None, None, None)
                .await
                .unwrap()
            {
                CacheRequestResponse::Modified(CacheResult {
                    value: response,
                    cache: _,
                }) => response,
                CacheRequestResponse::NotModified => panic!(),
            };
            assert_eq!(
                response
                    .iter()
                    .filter(|sub| sub.id == subscription.id)
                    .count(),
                0,
                "Deleted subscription does not exist in the subscription list"
            );
            assert!(
                api.get_subscription(&Client::new(), subscription.id + 1000)
                    .await
                    .is_err(),
                "Deleted subscription cannot be looked up by ID"
            );
        }
        _ => panic!(),
    }
}

#[tokio::test(flavor = "current_thread")]
async fn get_entries() {
    let api = test_setup_env();
    let entries = api
        .get_entries(&Client::new(), None, None, None, None, Some(true), true)
        .await
        .unwrap();
    assert!(!entries.is_empty());
}

#[tokio::test(flavor = "current_thread")]
async fn http_cache() {
    let api = test_setup_env();
    let cache = match api
        .get_subscriptions(&Client::new(), None, None, None)
        .await
        .unwrap()
    {
        CacheRequestResponse::Modified(CacheResult {
            value: _subscriptions,
            cache,
        }) => cache,
        CacheRequestResponse::NotModified => None,
    };

    if let CacheRequestResponse::Modified(_) = api
        .get_subscriptions(&Client::new(), None, None, cache)
        .await
        .unwrap()
    {
        unreachable!()
    }
}