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:
Severin Alexander Bühler
2025-03-21 13:15:33 +02:00
committed by GitHub
parent 8e1056c397
commit ca0995cb23
17 changed files with 1566 additions and 1456 deletions

3
e2e/src/lib.rs Normal file
View File

@@ -0,0 +1,3 @@
// E2E tests
#[cfg(test)]
mod tests;

447
e2e/src/tests/auth.rs Normal file
View 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
View 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
View File

@@ -0,0 +1,3 @@
mod auth;
mod http;
mod public;

794
e2e/src/tests/public.rs Normal file
View 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);
}