rocksky 0.3.0

Async Rust SDK for the Rocksky XRPC API.
Documentation
//! Feed resource tests.

mod common;

use common::{mock_client, mock_client_with_token};
use serde_json::json;
use wiremock::matchers::{method, path, query_param};
use wiremock::{Mock, ResponseTemplate};

#[tokio::test]
async fn search_returns_heterogeneous_hits() {
    let (server, client) = mock_client().await;
    Mock::given(method("GET"))
        .and(path("/xrpc/app.rocksky.feed.search"))
        .and(query_param("query", "kate"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "hits": [
                {"type": "song", "title": "Wuthering Heights"},
                {"type": "artist", "name": "Kate Bush"}
            ],
            "estimatedTotalHits": 2,
            "processingTimeMs": 7,
        })))
        .mount(&server)
        .await;

    let results = client.feed().search("kate").await.unwrap();
    assert_eq!(results.hits.len(), 2);
    assert_eq!(results.estimated_total_hits, Some(2));
    assert_eq!(results.processing_time_ms, Some(7));
    assert_eq!(results.hits[0]["type"], "song");
}

#[tokio::test]
async fn get_feed_with_cursor() {
    let (server, client) = mock_client().await;
    Mock::given(method("GET"))
        .and(path("/xrpc/app.rocksky.feed.getFeed"))
        .and(query_param("feed", "at://generator"))
        .and(query_param("cursor", "abc"))
        .and(query_param("limit", "25"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "feed": [{"scrobble": {"id": "s1", "title": "T"}}],
            "cursor": "next",
        })))
        .mount(&server)
        .await;

    let f = client
        .feed()
        .get("at://generator")
        .cursor("abc")
        .limit(25)
        .send()
        .await
        .unwrap();
    assert_eq!(f.cursor.as_deref(), Some("next"));
    assert_eq!(f.feed.len(), 1);
    assert!(f.feed[0].scrobble.is_some());
}

#[tokio::test]
async fn stories_with_feed_and_following_filters() {
    let (server, client) = mock_client_with_token("tkn").await;
    Mock::given(method("GET"))
        .and(path("/xrpc/app.rocksky.feed.getStories"))
        .and(query_param("size", "10"))
        .and(query_param(
            "feed",
            "at://did:plc:abc/app.rocksky.feed.generator/metalcore",
        ))
        .and(query_param("following", "true"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "stories": [{
                "id": "st1",
                "handle": "alice.bsky.social",
                "title": "Heaven",
                "artist": "BMTH",
            }],
        })))
        .mount(&server)
        .await;

    let stories = client
        .feed()
        .stories()
        .size(10)
        .feed("at://did:plc:abc/app.rocksky.feed.generator/metalcore")
        .following(true)
        .send()
        .await
        .unwrap();
    assert_eq!(stories.len(), 1);
    assert_eq!(stories[0].artist.as_deref(), Some("BMTH"));
}

#[tokio::test]
async fn recommendations_unwraps_inner_array() {
    let (server, client) = mock_client().await;
    Mock::given(method("GET"))
        .and(path("/xrpc/app.rocksky.feed.getRecommendations"))
        .and(query_param("did", "did:plc:alice"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "recommendations": [{
                "title": "Suggested",
                "artist": "X",
                "recommendationScore": 88,
                "source": "knn",
            }],
            "cursor": null,
        })))
        .mount(&server)
        .await;

    let r = client
        .feed()
        .recommendations("did:plc:alice")
        .send()
        .await
        .unwrap();
    assert_eq!(r.recommendations.len(), 1);
    assert_eq!(r.recommendations[0].recommendation_score, Some(88));
    assert_eq!(r.recommendations[0].source.as_deref(), Some("knn"));
}