Half-ass attempt at the local-first appview endpoints like ref impl

This commit is contained in:
lewis
2025-12-12 00:28:57 +02:00
parent e90488fbad
commit 2ededf32a6
27 changed files with 2355 additions and 94 deletions
+149
View File
@@ -0,0 +1,149 @@
mod common;
use common::{base_url, client, create_account_and_login};
use reqwest::StatusCode;
use serde_json::{json, Value};
#[tokio::test]
async fn test_get_author_feed_returns_appview_data() {
let client = client();
let base = base_url().await;
let (jwt, did) = create_account_and_login(&client).await;
let res = client
.get(format!(
"{}/xrpc/app.bsky.feed.getAuthorFeed?actor={}",
base, did
))
.header("Authorization", format!("Bearer {}", jwt))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let body: Value = res.json().await.unwrap();
assert!(body["feed"].is_array(), "Response should have feed array");
let feed = body["feed"].as_array().unwrap();
assert_eq!(feed.len(), 1, "Feed should have 1 post from appview");
assert_eq!(
feed[0]["post"]["record"]["text"].as_str(),
Some("Author feed post from appview"),
"Post text should match appview response"
);
}
#[tokio::test]
async fn test_get_actor_likes_returns_appview_data() {
let client = client();
let base = base_url().await;
let (jwt, did) = create_account_and_login(&client).await;
let res = client
.get(format!(
"{}/xrpc/app.bsky.feed.getActorLikes?actor={}",
base, did
))
.header("Authorization", format!("Bearer {}", jwt))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let body: Value = res.json().await.unwrap();
assert!(body["feed"].is_array(), "Response should have feed array");
let feed = body["feed"].as_array().unwrap();
assert_eq!(feed.len(), 1, "Feed should have 1 liked post from appview");
assert_eq!(
feed[0]["post"]["record"]["text"].as_str(),
Some("Liked post from appview"),
"Post text should match appview response"
);
}
#[tokio::test]
async fn test_get_post_thread_returns_appview_data() {
let client = client();
let base = base_url().await;
let (jwt, did) = create_account_and_login(&client).await;
let res = client
.get(format!(
"{}/xrpc/app.bsky.feed.getPostThread?uri=at://{}/app.bsky.feed.post/test123",
base, did
))
.header("Authorization", format!("Bearer {}", jwt))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let body: Value = res.json().await.unwrap();
assert!(body["thread"].is_object(), "Response should have thread object");
assert_eq!(
body["thread"]["$type"].as_str(),
Some("app.bsky.feed.defs#threadViewPost"),
"Thread should be a threadViewPost"
);
assert_eq!(
body["thread"]["post"]["record"]["text"].as_str(),
Some("Thread post from appview"),
"Post text should match appview response"
);
}
#[tokio::test]
async fn test_get_feed_returns_appview_data() {
let client = client();
let base = base_url().await;
let (jwt, _did) = create_account_and_login(&client).await;
let res = client
.get(format!(
"{}/xrpc/app.bsky.feed.getFeed?feed=at://did:plc:test/app.bsky.feed.generator/test",
base
))
.header("Authorization", format!("Bearer {}", jwt))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let body: Value = res.json().await.unwrap();
assert!(body["feed"].is_array(), "Response should have feed array");
let feed = body["feed"].as_array().unwrap();
assert_eq!(feed.len(), 1, "Feed should have 1 post from appview");
assert_eq!(
feed[0]["post"]["record"]["text"].as_str(),
Some("Custom feed post from appview"),
"Post text should match appview response"
);
}
#[tokio::test]
async fn test_register_push_proxies_to_appview() {
let client = client();
let base = base_url().await;
let (jwt, _did) = create_account_and_login(&client).await;
let res = client
.post(format!(
"{}/xrpc/app.bsky.notification.registerPush",
base
))
.header("Authorization", format!("Bearer {}", jwt))
.json(&json!({
"serviceDid": "did:web:example.com",
"token": "test-push-token",
"platform": "ios",
"appId": "xyz.bsky.app"
}))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
}
+116
View File
@@ -233,6 +233,122 @@ async fn setup_mock_appview(mock_server: &MockServer) {
})))
.mount(mock_server)
.await;
Mock::given(method("GET"))
.and(path("/xrpc/app.bsky.feed.getTimeline"))
.respond_with(
ResponseTemplate::new(200)
.insert_header("atproto-repo-rev", "0")
.set_body_json(json!({
"feed": [],
"cursor": null
}))
)
.mount(mock_server)
.await;
Mock::given(method("GET"))
.and(path("/xrpc/app.bsky.feed.getAuthorFeed"))
.respond_with(
ResponseTemplate::new(200)
.insert_header("atproto-repo-rev", "0")
.set_body_json(json!({
"feed": [{
"post": {
"uri": "at://did:plc:mock-author/app.bsky.feed.post/from-appview-author",
"cid": "bafyappview123",
"author": {"did": "did:plc:mock-author", "handle": "mock.author"},
"record": {
"$type": "app.bsky.feed.post",
"text": "Author feed post from appview",
"createdAt": "2025-01-01T00:00:00Z"
},
"indexedAt": "2025-01-01T00:00:00Z"
}
}],
"cursor": "author-cursor"
})),
)
.mount(mock_server)
.await;
Mock::given(method("GET"))
.and(path("/xrpc/app.bsky.feed.getActorLikes"))
.respond_with(
ResponseTemplate::new(200)
.insert_header("atproto-repo-rev", "0")
.set_body_json(json!({
"feed": [{
"post": {
"uri": "at://did:plc:mock-likes/app.bsky.feed.post/liked-post",
"cid": "bafyliked123",
"author": {"did": "did:plc:mock-likes", "handle": "mock.likes"},
"record": {
"$type": "app.bsky.feed.post",
"text": "Liked post from appview",
"createdAt": "2025-01-01T00:00:00Z"
},
"indexedAt": "2025-01-01T00:00:00Z"
}
}],
"cursor": null
})),
)
.mount(mock_server)
.await;
Mock::given(method("GET"))
.and(path("/xrpc/app.bsky.feed.getPostThread"))
.respond_with(
ResponseTemplate::new(200)
.insert_header("atproto-repo-rev", "0")
.set_body_json(json!({
"thread": {
"$type": "app.bsky.feed.defs#threadViewPost",
"post": {
"uri": "at://did:plc:mock/app.bsky.feed.post/thread-post",
"cid": "bafythread123",
"author": {"did": "did:plc:mock", "handle": "mock.handle"},
"record": {
"$type": "app.bsky.feed.post",
"text": "Thread post from appview",
"createdAt": "2025-01-01T00:00:00Z"
},
"indexedAt": "2025-01-01T00:00:00Z"
},
"replies": []
}
})),
)
.mount(mock_server)
.await;
Mock::given(method("GET"))
.and(path("/xrpc/app.bsky.feed.getFeed"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"feed": [{
"post": {
"uri": "at://did:plc:mock-feed/app.bsky.feed.post/custom-feed-post",
"cid": "bafyfeed123",
"author": {"did": "did:plc:mock-feed", "handle": "mock.feed"},
"record": {
"$type": "app.bsky.feed.post",
"text": "Custom feed post from appview",
"createdAt": "2025-01-01T00:00:00Z"
},
"indexedAt": "2025-01-01T00:00:00Z"
}
}],
"cursor": null
})))
.mount(mock_server)
.await;
Mock::given(method("POST"))
.and(path("/xrpc/app.bsky.notification.registerPush"))
.respond_with(ResponseTemplate::new(200))
.mount(mock_server)
.await;
}
async fn spawn_app(database_url: String) -> String {
+122
View File
@@ -0,0 +1,122 @@
mod common;
use common::{base_url, client, create_account_and_login};
use serde_json::json;
#[tokio::test]
async fn test_get_timeline_requires_auth() {
let client = client();
let base = base_url().await;
let res = client
.get(format!("{}/xrpc/app.bsky.feed.getTimeline", base))
.send()
.await
.unwrap();
assert_eq!(res.status(), 401);
}
#[tokio::test]
async fn test_get_author_feed_requires_actor() {
let client = client();
let base = base_url().await;
let (jwt, _did) = create_account_and_login(&client).await;
let res = client
.get(format!("{}/xrpc/app.bsky.feed.getAuthorFeed", base))
.header("Authorization", format!("Bearer {}", jwt))
.send()
.await
.unwrap();
assert_eq!(res.status(), 400);
}
#[tokio::test]
async fn test_get_actor_likes_requires_actor() {
let client = client();
let base = base_url().await;
let (jwt, _did) = create_account_and_login(&client).await;
let res = client
.get(format!("{}/xrpc/app.bsky.feed.getActorLikes", base))
.header("Authorization", format!("Bearer {}", jwt))
.send()
.await
.unwrap();
assert_eq!(res.status(), 400);
}
#[tokio::test]
async fn test_get_post_thread_requires_uri() {
let client = client();
let base = base_url().await;
let (jwt, _did) = create_account_and_login(&client).await;
let res = client
.get(format!("{}/xrpc/app.bsky.feed.getPostThread", base))
.header("Authorization", format!("Bearer {}", jwt))
.send()
.await
.unwrap();
assert_eq!(res.status(), 400);
}
#[tokio::test]
async fn test_get_feed_requires_auth() {
let client = client();
let base = base_url().await;
let res = client
.get(format!(
"{}/xrpc/app.bsky.feed.getFeed?feed=at://did:plc:test/app.bsky.feed.generator/test",
base
))
.send()
.await
.unwrap();
assert_eq!(res.status(), 401);
}
#[tokio::test]
async fn test_get_feed_requires_feed_param() {
let client = client();
let base = base_url().await;
let (jwt, _did) = create_account_and_login(&client).await;
let res = client
.get(format!("{}/xrpc/app.bsky.feed.getFeed", base))
.header("Authorization", format!("Bearer {}", jwt))
.send()
.await
.unwrap();
assert_eq!(res.status(), 400);
}
#[tokio::test]
async fn test_register_push_requires_auth() {
let client = client();
let base = base_url().await;
let res = client
.post(format!(
"{}/xrpc/app.bsky.notification.registerPush",
base
))
.json(&json!({
"serviceDid": "did:web:example.com",
"token": "test-token",
"platform": "ios",
"appId": "xyz.bsky.app"
}))
.send()
.await
.unwrap();
assert_eq!(res.status(), 401);
}
+1 -1
View File
@@ -23,7 +23,7 @@ fn write_varint(buf: &mut Vec<u8>, mut value: u64) {
async fn test_import_rejects_car_for_different_user() {
let client = client();
let (token_a, did_a) = create_account_and_login(&client).await;
let (token_a, _did_a) = create_account_and_login(&client).await;
let (_token_b, did_b) = create_account_and_login(&client).await;
let export_res = client