mirror of
https://github.com/aljazceru/pubky-core.git
synced 2026-01-27 09:54:50 +01:00
chore: Moved e2e tests / Fixes circular dependency (#88)
* moved e2e tests * moved e2e tests to its own workspace member * fmt
This commit is contained in:
committed by
GitHub
parent
8e1056c397
commit
ca0995cb23
3
e2e/src/lib.rs
Normal file
3
e2e/src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
// E2E tests
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
447
e2e/src/tests/auth.rs
Normal file
447
e2e/src/tests/auth.rs
Normal file
@@ -0,0 +1,447 @@
|
||||
use pkarr::Keypair;
|
||||
use pubky_common::capabilities::{Capabilities, Capability};
|
||||
use pubky_testnet::Testnet;
|
||||
use reqwest::StatusCode;
|
||||
use std::time::Duration;
|
||||
|
||||
#[tokio::test]
|
||||
async fn basic_authn() {
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
let server = testnet.run_homeserver().await.unwrap();
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
|
||||
let keypair = Keypair::random();
|
||||
|
||||
client
|
||||
.signup(&keypair, &server.public_key(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let session = client
|
||||
.session(&keypair.public_key())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(session.capabilities().contains(&Capability::root()));
|
||||
|
||||
client.signout(&keypair.public_key()).await.unwrap();
|
||||
|
||||
{
|
||||
let session = client.session(&keypair.public_key()).await.unwrap();
|
||||
|
||||
assert!(session.is_none());
|
||||
}
|
||||
|
||||
client.signin(&keypair).await.unwrap();
|
||||
|
||||
{
|
||||
let session = client
|
||||
.session(&keypair.public_key())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(session.pubky(), &keypair.public_key());
|
||||
assert!(session.capabilities().contains(&Capability::root()));
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn authz() {
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
let server = testnet.run_homeserver().await.unwrap();
|
||||
|
||||
let http_relay = testnet.run_http_relay().await.unwrap();
|
||||
let http_relay_url = http_relay.local_link_url();
|
||||
|
||||
let keypair = Keypair::random();
|
||||
let pubky = keypair.public_key();
|
||||
|
||||
// Third party app side
|
||||
let capabilities: Capabilities = "/pub/pubky.app/:rw,/pub/foo.bar/file:r".try_into().unwrap();
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
|
||||
let pubky_auth_request = client.auth_request(http_relay_url, &capabilities).unwrap();
|
||||
|
||||
// Authenticator side
|
||||
{
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
|
||||
client
|
||||
.signup(&keypair, &server.public_key(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
client
|
||||
.send_auth_token(&keypair, pubky_auth_request.url())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let public_key = pubky_auth_request.response().await.unwrap();
|
||||
|
||||
assert_eq!(&public_key, &pubky);
|
||||
|
||||
let session = client.session(&pubky).await.unwrap().unwrap();
|
||||
assert_eq!(session.capabilities(), &capabilities.0);
|
||||
|
||||
// Test access control enforcement
|
||||
|
||||
client
|
||||
.put(format!("pubky://{pubky}/pub/pubky.app/foo"))
|
||||
.body(vec![])
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.error_for_status()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
client
|
||||
.put(format!("pubky://{pubky}/pub/pubky.app"))
|
||||
.body(vec![])
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.status(),
|
||||
StatusCode::FORBIDDEN
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
client
|
||||
.put(format!("pubky://{pubky}/pub/foo.bar/file"))
|
||||
.body(vec![])
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.status(),
|
||||
StatusCode::FORBIDDEN
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn multiple_users() {
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
let server = testnet.run_homeserver().await.unwrap();
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
|
||||
let first_keypair = Keypair::random();
|
||||
let second_keypair = Keypair::random();
|
||||
|
||||
client
|
||||
.signup(&first_keypair, &server.public_key(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
client
|
||||
.signup(&second_keypair, &server.public_key(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let session = client
|
||||
.session(&first_keypair.public_key())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(session.pubky(), &first_keypair.public_key());
|
||||
assert!(session.capabilities().contains(&Capability::root()));
|
||||
|
||||
let session = client
|
||||
.session(&second_keypair.public_key())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(session.pubky(), &second_keypair.public_key());
|
||||
assert!(session.capabilities().contains(&Capability::root()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn authz_timeout_reconnect() {
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
let server = testnet.run_homeserver().await.unwrap();
|
||||
|
||||
let http_relay = testnet.run_http_relay().await.unwrap();
|
||||
let http_relay_url = http_relay.local_link_url();
|
||||
|
||||
let keypair = Keypair::random();
|
||||
let pubky = keypair.public_key();
|
||||
|
||||
// Third party app side
|
||||
let capabilities: Capabilities = "/pub/pubky.app/:rw,/pub/foo.bar/file:r".try_into().unwrap();
|
||||
|
||||
let client = testnet
|
||||
.client_builder()
|
||||
.request_timeout(Duration::from_millis(1000))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let pubky_auth_request = client.auth_request(http_relay_url, &capabilities).unwrap();
|
||||
|
||||
// Authenticator side
|
||||
{
|
||||
let url = pubky_auth_request.url().clone();
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
client
|
||||
.signup(&keypair, &server.public_key(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_millis(400)).await;
|
||||
// loop {
|
||||
client.send_auth_token(&keypair, &url).await.unwrap();
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
let public_key = pubky_auth_request.response().await.unwrap();
|
||||
|
||||
assert_eq!(&public_key, &pubky);
|
||||
|
||||
let session = client.session(&pubky).await.unwrap().unwrap();
|
||||
assert_eq!(session.capabilities(), &capabilities.0);
|
||||
|
||||
// Test access control enforcement
|
||||
|
||||
client
|
||||
.put(format!("pubky://{pubky}/pub/pubky.app/foo"))
|
||||
.body(vec![])
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.error_for_status()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
client
|
||||
.put(format!("pubky://{pubky}/pub/pubky.app"))
|
||||
.body(vec![])
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.status(),
|
||||
StatusCode::FORBIDDEN
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
client
|
||||
.put(format!("pubky://{pubky}/pub/foo.bar/file"))
|
||||
.body(vec![])
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.status(),
|
||||
StatusCode::FORBIDDEN
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_signup_with_token() {
|
||||
// 1. Start a test homeserver with closed signups (i.e. signup tokens required)
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
let server = testnet.run_homeserver_with_signup_tokens().await.unwrap();
|
||||
|
||||
let admin_password = "admin";
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
let keypair = Keypair::random();
|
||||
|
||||
// 2. Try to signup with an invalid token "AAAAA" and expect failure.
|
||||
let invalid_signup = client
|
||||
.signup(&keypair, &server.public_key(), Some("AAAA-BBBB-CCCC"))
|
||||
.await;
|
||||
assert!(
|
||||
invalid_signup.is_err(),
|
||||
"Signup should fail with an invalid signup token"
|
||||
);
|
||||
|
||||
// 3. Call the admin endpoint to generate a valid signup token.
|
||||
// The admin endpoint is protected via the header "X-Admin-Password"
|
||||
// and the password we set up above.
|
||||
let admin_url = format!(
|
||||
"https://{}/admin/generate_signup_token",
|
||||
server.public_key()
|
||||
);
|
||||
|
||||
// 3.1. Call the admin endpoint *with a WRONG admin password* to ensure we get 401 UNAUTHORIZED.
|
||||
let wrong_password_response = client
|
||||
.get(&admin_url)
|
||||
.header("X-Admin-Password", "wrong_admin_password")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
wrong_password_response.status(),
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"Wrong admin password should return 401"
|
||||
);
|
||||
|
||||
// 3.1 Now call the admin endpoint again, this time with the correct password.
|
||||
let admin_response = client
|
||||
.get(&admin_url)
|
||||
.header("X-Admin-Password", admin_password)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
admin_response.status(),
|
||||
StatusCode::OK,
|
||||
"Admin endpoint should return OK"
|
||||
);
|
||||
let valid_token = admin_response.text().await.unwrap(); // The token string.
|
||||
|
||||
// 4. Now signup with the valid token. Expect success and a session back.
|
||||
let session = client
|
||||
.signup(&keypair, &server.public_key(), Some(&valid_token))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(
|
||||
!session.pubky().to_string().is_empty(),
|
||||
"Session should contain a valid public key"
|
||||
);
|
||||
|
||||
// 5. Finally, sign in with the same keypair and verify that a session is returned.
|
||||
let signin_session = client.signin(&keypair).await.unwrap();
|
||||
assert_eq!(
|
||||
signin_session.pubky(),
|
||||
&keypair.public_key(),
|
||||
"Signed-in session should correspond to the same public key"
|
||||
);
|
||||
}
|
||||
|
||||
// This test verifies that when a signin happens immediately after signup,
|
||||
// the record is not republished on signin (its timestamp remains unchanged)
|
||||
// but when a signin happens after the record is “old” (in test, after 1 second),
|
||||
// the record is republished (its timestamp increases).
|
||||
#[tokio::test]
|
||||
async fn test_republish_on_signin() {
|
||||
// Setup the testnet and run a homeserver.
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
let server = testnet.run_homeserver().await.unwrap();
|
||||
// Create a client that will republish conditionally if a record is older than 1 second
|
||||
let client = testnet
|
||||
.client_builder()
|
||||
.max_record_age(Duration::from_secs(1))
|
||||
.build()
|
||||
.unwrap();
|
||||
let keypair = Keypair::random();
|
||||
|
||||
// Signup publishes a new record.
|
||||
client
|
||||
.signup(&keypair, &server.public_key(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
// Resolve the record and get its timestamp.
|
||||
let record1 = client
|
||||
.pkarr()
|
||||
.resolve_most_recent(&keypair.public_key())
|
||||
.await
|
||||
.unwrap();
|
||||
let ts1 = record1.timestamp().as_u64();
|
||||
|
||||
// Immediately sign in. This spawns a background task to update the record
|
||||
// with PublishStrategy::IfOlderThan.
|
||||
client.signin(&keypair).await.unwrap();
|
||||
// Wait a short time to let the background task complete.
|
||||
tokio::time::sleep(Duration::from_millis(5)).await;
|
||||
let record2 = client
|
||||
.pkarr()
|
||||
.resolve_most_recent(&keypair.public_key())
|
||||
.await
|
||||
.unwrap();
|
||||
let ts2 = record2.timestamp().as_u64();
|
||||
|
||||
// Because the record is fresh (less than 1 second old in our test configuration),
|
||||
// the background task should not republish it. The timestamp should remain the same.
|
||||
assert_eq!(
|
||||
ts1, ts2,
|
||||
"Record republished too early; timestamps should be equal"
|
||||
);
|
||||
|
||||
// Wait long enough for the record to be considered 'old' (greater than 1 second).
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
// Sign in again. Now the background task should trigger a republish.
|
||||
client.signin(&keypair).await.unwrap();
|
||||
tokio::time::sleep(Duration::from_millis(5)).await;
|
||||
let record3 = client
|
||||
.pkarr()
|
||||
.resolve_most_recent(&keypair.public_key())
|
||||
.await
|
||||
.unwrap();
|
||||
let ts3 = record3.timestamp().as_u64();
|
||||
|
||||
// Now the republished record's timestamp should be greater than before.
|
||||
assert!(
|
||||
ts3 > ts2,
|
||||
"Record was not republished after threshold exceeded"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_republish_homeserver() {
|
||||
// Setup the testnet and run a homeserver.
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
let server = testnet.run_homeserver().await.unwrap();
|
||||
// Create a client that will republish conditionally if a record is older than 1 second
|
||||
let client = testnet
|
||||
.client_builder()
|
||||
.max_record_age(Duration::from_secs(1))
|
||||
.build()
|
||||
.unwrap();
|
||||
let keypair = Keypair::random();
|
||||
|
||||
// Signup publishes a new record.
|
||||
client
|
||||
.signup(&keypair, &server.public_key(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
// Resolve the record and get its timestamp.
|
||||
let record1 = client
|
||||
.pkarr()
|
||||
.resolve_most_recent(&keypair.public_key())
|
||||
.await
|
||||
.unwrap();
|
||||
let ts1 = record1.timestamp().as_u64();
|
||||
|
||||
// Immediately call republish_homeserver.
|
||||
// Since the record is fresh, republish should do nothing.
|
||||
client
|
||||
.republish_homeserver(&keypair, &server.public_key())
|
||||
.await
|
||||
.unwrap();
|
||||
let record2 = client
|
||||
.pkarr()
|
||||
.resolve_most_recent(&keypair.public_key())
|
||||
.await
|
||||
.unwrap();
|
||||
let ts2 = record2.timestamp().as_u64();
|
||||
assert_eq!(
|
||||
ts1, ts2,
|
||||
"Record republished too early; timestamp should be equal"
|
||||
);
|
||||
|
||||
// Wait long enough for the record to be considered 'old'.
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
// Call republish_homeserver again; now the record should be updated.
|
||||
client
|
||||
.republish_homeserver(&keypair, &server.public_key())
|
||||
.await
|
||||
.unwrap();
|
||||
let record3 = client
|
||||
.pkarr()
|
||||
.resolve_most_recent(&keypair.public_key())
|
||||
.await
|
||||
.unwrap();
|
||||
let ts3 = record3.timestamp().as_u64();
|
||||
assert!(
|
||||
ts3 > ts2,
|
||||
"Record was not republished after threshold exceeded"
|
||||
);
|
||||
}
|
||||
32
e2e/src/tests/http.rs
Normal file
32
e2e/src/tests/http.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use pubky_testnet::Testnet;
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_get_pubky() {
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
let homeserver = testnet.run_homeserver().await.unwrap();
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
|
||||
let response = client
|
||||
.get(format!("https://{}/", homeserver.public_key()))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.status(), 200)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_get_icann() {
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
|
||||
let response = client
|
||||
.request(Default::default(), "https://example.com/")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
}
|
||||
3
e2e/src/tests/mod.rs
Normal file
3
e2e/src/tests/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod auth;
|
||||
mod http;
|
||||
mod public;
|
||||
794
e2e/src/tests/public.rs
Normal file
794
e2e/src/tests/public.rs
Normal file
@@ -0,0 +1,794 @@
|
||||
use bytes::Bytes;
|
||||
use pkarr::Keypair;
|
||||
use pubky_testnet::Testnet;
|
||||
use reqwest::{Method, StatusCode};
|
||||
|
||||
#[tokio::test]
|
||||
async fn put_get_delete() {
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
let server = testnet.run_homeserver().await.unwrap();
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
|
||||
let keypair = Keypair::random();
|
||||
|
||||
client
|
||||
.signup(&keypair, &server.public_key(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let url = format!("pubky://{}/pub/foo.txt", keypair.public_key());
|
||||
let url = url.as_str();
|
||||
|
||||
client
|
||||
.put(url)
|
||||
.body(vec![0, 1, 2, 3, 4])
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.error_for_status()
|
||||
.unwrap();
|
||||
|
||||
let response = client.get(url).send().await.unwrap().bytes().await.unwrap();
|
||||
|
||||
assert_eq!(response, bytes::Bytes::from(vec![0, 1, 2, 3, 4]));
|
||||
|
||||
client
|
||||
.delete(url)
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.error_for_status()
|
||||
.unwrap();
|
||||
|
||||
let response = client.get(url).send().await.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unauthorized_put_delete() {
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
let server = testnet.run_homeserver().await.unwrap();
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
|
||||
let keypair = Keypair::random();
|
||||
|
||||
client
|
||||
.signup(&keypair, &server.public_key(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let public_key = keypair.public_key();
|
||||
|
||||
let url = format!("pubky://{public_key}/pub/foo.txt");
|
||||
let url = url.as_str();
|
||||
|
||||
let other_client = testnet.client_builder().build().unwrap();
|
||||
{
|
||||
let other = Keypair::random();
|
||||
|
||||
// TODO: remove extra client after switching to subdomains.
|
||||
other_client
|
||||
.signup(&other, &server.public_key(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
other_client
|
||||
.put(url)
|
||||
.body(vec![0, 1, 2, 3, 4])
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.status(),
|
||||
StatusCode::UNAUTHORIZED
|
||||
);
|
||||
}
|
||||
|
||||
client
|
||||
.put(url)
|
||||
.body(vec![0, 1, 2, 3, 4])
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
{
|
||||
let other = Keypair::random();
|
||||
|
||||
// TODO: remove extra client after switching to subdomains.
|
||||
other_client
|
||||
.signup(&other, &server.public_key(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
other_client.delete(url).send().await.unwrap().status(),
|
||||
StatusCode::UNAUTHORIZED
|
||||
);
|
||||
}
|
||||
|
||||
let response = client.get(url).send().await.unwrap().bytes().await.unwrap();
|
||||
|
||||
assert_eq!(response, bytes::Bytes::from(vec![0, 1, 2, 3, 4]));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list() {
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
let server = testnet.run_homeserver().await.unwrap();
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
|
||||
let keypair = Keypair::random();
|
||||
|
||||
client
|
||||
.signup(&keypair, &server.public_key(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let pubky = keypair.public_key();
|
||||
|
||||
let urls = vec![
|
||||
format!("pubky://{pubky}/pub/a.wrong/a.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/a.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/b.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"),
|
||||
format!("pubky://{pubky}/pub/example.wrong/a.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/c.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/d.txt"),
|
||||
format!("pubky://{pubky}/pub/z.wrong/a.txt"),
|
||||
];
|
||||
|
||||
for url in urls {
|
||||
client.put(url).body(vec![0]).send().await.unwrap();
|
||||
}
|
||||
|
||||
let url = format!("pubky://{pubky}/pub/example.com/extra");
|
||||
|
||||
{
|
||||
let list = client.list(&url).unwrap().send().await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/example.com/a.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/b.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/c.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/d.txt"),
|
||||
],
|
||||
"normal list with no limit or cursor"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let list = client.list(&url).unwrap().limit(2).send().await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/example.com/a.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/b.txt"),
|
||||
],
|
||||
"normal list with limit but no cursor"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let list = client
|
||||
.list(&url)
|
||||
.unwrap()
|
||||
.limit(2)
|
||||
.cursor("a.txt")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/example.com/b.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/c.txt"),
|
||||
],
|
||||
"normal list with limit and a file cursor"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let list = client
|
||||
.list(&url)
|
||||
.unwrap()
|
||||
.limit(2)
|
||||
.cursor("cc-nested/")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/d.txt"),
|
||||
],
|
||||
"normal list with limit and a directory cursor"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let list = client
|
||||
.list(&url)
|
||||
.unwrap()
|
||||
.limit(2)
|
||||
.cursor(&format!("pubky://{pubky}/pub/example.com/a.txt"))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/example.com/b.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/c.txt"),
|
||||
],
|
||||
"normal list with limit and a full url cursor"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let list = client
|
||||
.list(&url)
|
||||
.unwrap()
|
||||
.limit(2)
|
||||
.cursor("/a.txt")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/example.com/b.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/c.txt"),
|
||||
],
|
||||
"normal list with limit and a leading / cursor"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let list = client
|
||||
.list(&url)
|
||||
.unwrap()
|
||||
.reverse(true)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/example.com/d.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/c.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/b.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/a.txt"),
|
||||
],
|
||||
"reverse list with no limit or cursor"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let list = client
|
||||
.list(&url)
|
||||
.unwrap()
|
||||
.reverse(true)
|
||||
.limit(2)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/example.com/d.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"),
|
||||
],
|
||||
"reverse list with limit but no cursor"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let list = client
|
||||
.list(&url)
|
||||
.unwrap()
|
||||
.reverse(true)
|
||||
.limit(2)
|
||||
.cursor("d.txt")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/c.txt"),
|
||||
],
|
||||
"reverse list with limit and cursor"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_shallow() {
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
let server = testnet.run_homeserver().await.unwrap();
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
|
||||
let keypair = Keypair::random();
|
||||
|
||||
client
|
||||
.signup(&keypair, &server.public_key(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let pubky = keypair.public_key();
|
||||
|
||||
let urls = vec![
|
||||
format!("pubky://{pubky}/pub/a.com/a.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/a.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/b.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/c.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/d.txt"),
|
||||
format!("pubky://{pubky}/pub/example.con/d.txt"),
|
||||
format!("pubky://{pubky}/pub/example.con"),
|
||||
format!("pubky://{pubky}/pub/file"),
|
||||
format!("pubky://{pubky}/pub/file2"),
|
||||
format!("pubky://{pubky}/pub/z.com/a.txt"),
|
||||
];
|
||||
|
||||
for url in urls {
|
||||
client.put(url).body(vec![0]).send().await.unwrap();
|
||||
}
|
||||
|
||||
let url = format!("pubky://{pubky}/pub/");
|
||||
|
||||
{
|
||||
let list = client
|
||||
.list(&url)
|
||||
.unwrap()
|
||||
.shallow(true)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/a.com/"),
|
||||
format!("pubky://{pubky}/pub/example.com/"),
|
||||
format!("pubky://{pubky}/pub/example.con"),
|
||||
format!("pubky://{pubky}/pub/example.con/"),
|
||||
format!("pubky://{pubky}/pub/file"),
|
||||
format!("pubky://{pubky}/pub/file2"),
|
||||
format!("pubky://{pubky}/pub/z.com/"),
|
||||
],
|
||||
"normal list shallow"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let list = client
|
||||
.list(&url)
|
||||
.unwrap()
|
||||
.shallow(true)
|
||||
.limit(2)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/a.com/"),
|
||||
format!("pubky://{pubky}/pub/example.com/"),
|
||||
],
|
||||
"normal list shallow with limit but no cursor"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let list = client
|
||||
.list(&url)
|
||||
.unwrap()
|
||||
.shallow(true)
|
||||
.limit(2)
|
||||
.cursor("example.com/a.txt")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/example.com/"),
|
||||
format!("pubky://{pubky}/pub/example.con"),
|
||||
],
|
||||
"normal list shallow with limit and a file cursor"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let list = client
|
||||
.list(&url)
|
||||
.unwrap()
|
||||
.shallow(true)
|
||||
.limit(3)
|
||||
.cursor("example.com/")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/example.con"),
|
||||
format!("pubky://{pubky}/pub/example.con/"),
|
||||
format!("pubky://{pubky}/pub/file"),
|
||||
],
|
||||
"normal list shallow with limit and a directory cursor"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let list = client
|
||||
.list(&url)
|
||||
.unwrap()
|
||||
.reverse(true)
|
||||
.shallow(true)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/z.com/"),
|
||||
format!("pubky://{pubky}/pub/file2"),
|
||||
format!("pubky://{pubky}/pub/file"),
|
||||
format!("pubky://{pubky}/pub/example.con/"),
|
||||
format!("pubky://{pubky}/pub/example.con"),
|
||||
format!("pubky://{pubky}/pub/example.com/"),
|
||||
format!("pubky://{pubky}/pub/a.com/"),
|
||||
],
|
||||
"reverse list shallow"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let list = client
|
||||
.list(&url)
|
||||
.unwrap()
|
||||
.reverse(true)
|
||||
.shallow(true)
|
||||
.limit(2)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/z.com/"),
|
||||
format!("pubky://{pubky}/pub/file2"),
|
||||
],
|
||||
"reverse list shallow with limit but no cursor"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let list = client
|
||||
.list(&url)
|
||||
.unwrap()
|
||||
.shallow(true)
|
||||
.reverse(true)
|
||||
.limit(2)
|
||||
.cursor("file2")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/file"),
|
||||
format!("pubky://{pubky}/pub/example.con/"),
|
||||
],
|
||||
"reverse list shallow with limit and a file cursor"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let list = client
|
||||
.list(&url)
|
||||
.unwrap()
|
||||
.shallow(true)
|
||||
.reverse(true)
|
||||
.limit(2)
|
||||
.cursor("example.con/")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
format!("pubky://{pubky}/pub/example.con"),
|
||||
format!("pubky://{pubky}/pub/example.com/"),
|
||||
],
|
||||
"reverse list shallow with limit and a directory cursor"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_events() {
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
let server = testnet.run_homeserver().await.unwrap();
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
|
||||
let keypair = Keypair::random();
|
||||
|
||||
client
|
||||
.signup(&keypair, &server.public_key(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let pubky = keypair.public_key();
|
||||
|
||||
let urls = vec![
|
||||
format!("pubky://{pubky}/pub/a.com/a.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/a.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/b.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/c.txt"),
|
||||
format!("pubky://{pubky}/pub/example.com/d.txt"),
|
||||
format!("pubky://{pubky}/pub/example.con/d.txt"),
|
||||
format!("pubky://{pubky}/pub/example.con"),
|
||||
format!("pubky://{pubky}/pub/file"),
|
||||
format!("pubky://{pubky}/pub/file2"),
|
||||
format!("pubky://{pubky}/pub/z.com/a.txt"),
|
||||
];
|
||||
|
||||
for url in urls {
|
||||
client.put(&url).body(vec![0]).send().await.unwrap();
|
||||
client.delete(url).send().await.unwrap();
|
||||
}
|
||||
|
||||
let feed_url = format!("https://{}/events/", server.public_key());
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
|
||||
let cursor;
|
||||
|
||||
{
|
||||
let response = client
|
||||
.request(Method::GET, format!("{feed_url}?limit=10"))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let text = response.text().await.unwrap();
|
||||
let lines = text.split('\n').collect::<Vec<_>>();
|
||||
|
||||
cursor = lines.last().unwrap().split(" ").last().unwrap().to_string();
|
||||
|
||||
assert_eq!(
|
||||
lines,
|
||||
vec![
|
||||
format!("PUT pubky://{pubky}/pub/a.com/a.txt"),
|
||||
format!("DEL pubky://{pubky}/pub/a.com/a.txt"),
|
||||
format!("PUT pubky://{pubky}/pub/example.com/a.txt"),
|
||||
format!("DEL pubky://{pubky}/pub/example.com/a.txt"),
|
||||
format!("PUT pubky://{pubky}/pub/example.com/b.txt"),
|
||||
format!("DEL pubky://{pubky}/pub/example.com/b.txt"),
|
||||
format!("PUT pubky://{pubky}/pub/example.com/c.txt"),
|
||||
format!("DEL pubky://{pubky}/pub/example.com/c.txt"),
|
||||
format!("PUT pubky://{pubky}/pub/example.com/d.txt"),
|
||||
format!("DEL pubky://{pubky}/pub/example.com/d.txt"),
|
||||
format!("cursor: {cursor}",)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let response = client
|
||||
.request(Method::GET, format!("{feed_url}?limit=10&cursor={cursor}"))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let text = response.text().await.unwrap();
|
||||
let lines = text.split('\n').collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(
|
||||
lines,
|
||||
vec![
|
||||
format!("PUT pubky://{pubky}/pub/example.con/d.txt"),
|
||||
format!("DEL pubky://{pubky}/pub/example.con/d.txt"),
|
||||
format!("PUT pubky://{pubky}/pub/example.con"),
|
||||
format!("DEL pubky://{pubky}/pub/example.con"),
|
||||
format!("PUT pubky://{pubky}/pub/file"),
|
||||
format!("DEL pubky://{pubky}/pub/file"),
|
||||
format!("PUT pubky://{pubky}/pub/file2"),
|
||||
format!("DEL pubky://{pubky}/pub/file2"),
|
||||
format!("PUT pubky://{pubky}/pub/z.com/a.txt"),
|
||||
format!("DEL pubky://{pubky}/pub/z.com/a.txt"),
|
||||
lines.last().unwrap().to_string()
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn read_after_event() {
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
let server = testnet.run_homeserver().await.unwrap();
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
|
||||
let keypair = Keypair::random();
|
||||
|
||||
client
|
||||
.signup(&keypair, &server.public_key(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let pubky = keypair.public_key();
|
||||
|
||||
let url = format!("pubky://{pubky}/pub/a.com/a.txt");
|
||||
|
||||
client.put(&url).body(vec![0]).send().await.unwrap();
|
||||
|
||||
let feed_url = format!("https://{}/events/", server.public_key());
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
|
||||
{
|
||||
let response = client
|
||||
.request(Method::GET, format!("{feed_url}?limit=10"))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let text = response.text().await.unwrap();
|
||||
let lines = text.split('\n').collect::<Vec<_>>();
|
||||
|
||||
let cursor = lines.last().unwrap().split(" ").last().unwrap().to_string();
|
||||
|
||||
assert_eq!(
|
||||
lines,
|
||||
vec![
|
||||
format!("PUT pubky://{pubky}/pub/a.com/a.txt"),
|
||||
format!("cursor: {cursor}",)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
let response = client.get(url).send().await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let body = response.bytes().await.unwrap();
|
||||
|
||||
assert_eq!(body.as_ref(), &[0]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dont_delete_shared_blobs() {
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
let homeserver = testnet.run_homeserver().await.unwrap();
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
|
||||
let homeserver_pubky = homeserver.public_key();
|
||||
|
||||
let user_1 = Keypair::random();
|
||||
let user_2 = Keypair::random();
|
||||
|
||||
client
|
||||
.signup(&user_1, &homeserver_pubky, None)
|
||||
.await
|
||||
.unwrap();
|
||||
client
|
||||
.signup(&user_2, &homeserver_pubky, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let user_1_id = user_1.public_key();
|
||||
let user_2_id = user_2.public_key();
|
||||
|
||||
let url_1 = format!("pubky://{user_1_id}/pub/pubky.app/file/file_1");
|
||||
let url_2 = format!("pubky://{user_2_id}/pub/pubky.app/file/file_1");
|
||||
|
||||
let file = vec![1];
|
||||
client.put(&url_1).body(file.clone()).send().await.unwrap();
|
||||
client.put(&url_2).body(file.clone()).send().await.unwrap();
|
||||
|
||||
// Delete file 1
|
||||
client
|
||||
.delete(url_1)
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.error_for_status()
|
||||
.unwrap();
|
||||
|
||||
let blob = client
|
||||
.get(url_2)
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.bytes()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(blob, file);
|
||||
|
||||
let feed_url = format!("https://{}/events/", homeserver.public_key());
|
||||
|
||||
let response = client
|
||||
.request(Method::GET, feed_url)
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.error_for_status()
|
||||
.unwrap();
|
||||
|
||||
let text = response.text().await.unwrap();
|
||||
let lines = text.split('\n').collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(
|
||||
lines,
|
||||
vec![
|
||||
format!("PUT pubky://{user_1_id}/pub/pubky.app/file/file_1",),
|
||||
format!("PUT pubky://{user_2_id}/pub/pubky.app/file/file_1",),
|
||||
format!("DEL pubky://{user_1_id}/pub/pubky.app/file/file_1",),
|
||||
lines.last().unwrap().to_string()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stream() {
|
||||
// TODO: test better streaming API
|
||||
let testnet = Testnet::run().await.unwrap();
|
||||
let server = testnet.run_homeserver().await.unwrap();
|
||||
|
||||
let client = testnet.client_builder().build().unwrap();
|
||||
|
||||
let keypair = Keypair::random();
|
||||
|
||||
client
|
||||
.signup(&keypair, &server.public_key(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let url = format!("pubky://{}/pub/foo.txt", keypair.public_key());
|
||||
let url = url.as_str();
|
||||
|
||||
let bytes = Bytes::from(vec![0; 1024 * 1024]);
|
||||
|
||||
client.put(url).body(bytes.clone()).send().await.unwrap();
|
||||
|
||||
let response = client.get(url).send().await.unwrap().bytes().await.unwrap();
|
||||
|
||||
assert_eq!(response, bytes);
|
||||
|
||||
client.delete(url).send().await.unwrap();
|
||||
|
||||
let response = client.get(url).send().await.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
Reference in New Issue
Block a user