//! Phase 1 tests: Remote actor discovery via webfinger (now integrated with follow)
mod support;
use support::ap::RemoteActor;
use support::{Client, Server, free_port};
/// Register a user through the web form.
fn register(client: &mut Client, name: &str) {
let email = format!("{name}@example.org");
let response = client.post_form(
"/register",
&[
("name", name),
("email", &email),
("password", "correct horse battery staple"),
],
);
assert_eq!(
response.status, 303,
"registering should log in\n{}",
response.body
);
}
/// Test: Follow a remote actor by handle (includes discovery)
#[tokio::test]
async fn discover_by_handle_succeeds() -> miette::Result<()> {
let port = free_port();
let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
server.wait_for_connection(port);
let mut alice_client = Client::new(port);
register(&mut alice_client, "alice");
// Start a remote actor that serves webfinger
let recorder_port = free_port();
let _recorder = RemoteActor::serve(recorder_port);
// Alice follows bob@127.0.0.1:recorder_port (discovery happens automatically)
let handle = format!("remote@127.0.0.1:{recorder_port}");
// POST to follow endpoint with handle (discovery + follow in one step)
let response = alice_client.post_form("/~alice/follow", &[("handle", &handle)]);
// Should redirect to the profile page after successful follow
assert_eq!(
response.status, 303,
"follow with handle should redirect\n{}",
response.body
);
// Verify the redirect includes a success notice
assert!(
response.location.as_ref().unwrap().contains("notice="),
"redirect should include notice parameter"
);
Ok(())
}
/// Test: Follow with unknown handle returns error
#[tokio::test]
async fn discover_unknown_handle_errors() -> miette::Result<()> {
let port = free_port();
let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
server.wait_for_connection(port);
let mut alice_client = Client::new(port);
register(&mut alice_client, "alice");
// Try to follow a non-existent user
let response =
alice_client.post_form("/~alice/follow", &[("handle", "nonexistent@example.com")]);
// Should redirect with an error parameter
assert_eq!(
response.status, 303,
"follow should redirect\n{}",
response.body
);
assert!(
response.location.as_ref().unwrap().contains("error="),
"redirect should include error parameter"
);
Ok(())
}
/// Test: Follow with bare actor URL skips webfinger
#[tokio::test]
async fn discover_by_url_skips_webfinger() -> miette::Result<()> {
let port = free_port();
let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
server.wait_for_connection(port);
let mut alice_client = Client::new(port);
register(&mut alice_client, "alice");
// Start a remote actor
let recorder_port = free_port();
let recorder = RemoteActor::serve(recorder_port);
// Follow using the bare actor URL (skips webfinger)
let response = alice_client.post_form("/~alice/follow", &[("actor_url", &recorder.actor_url)]);
assert_eq!(
response.status, 303,
"follow with actor_url should redirect\n{}",
response.body
);
Ok(())
}
/// Test: Discovery fails when JRD has no ActivityPub self link
#[tokio::test]
async fn discover_without_activitypub_link_errors() -> miette::Result<()> {
let port = free_port();
let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
server.wait_for_connection(port);
let mut alice_client = Client::new(port);
register(&mut alice_client, "alice");
// Try to follow a handle that doesn't exist (no webfinger response)
let response = alice_client.post_form(
"/~alice/follow",
&[("handle", "@nonexistent@invalid.example")],
);
assert_eq!(
response.status, 303,
"follow should redirect with error\n{}",
response.body
);
assert!(
response.location.as_ref().unwrap().contains("error="),
"redirect should include error parameter, got: {:?}",
response.location
);
Ok(())
}
/// Test: Discovery fails gracefully for malformed handle
#[tokio::test]
async fn discover_malformed_handle_errors() -> miette::Result<()> {
let port = free_port();
let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
server.wait_for_connection(port);
let mut alice_client = Client::new(port);
register(&mut alice_client, "alice");
// Try to follow with malformed handle (missing @)
let response = alice_client.post_form("/~alice/follow", &[("handle", "invalidhandle")]);
assert_eq!(
response.status, 303,
"follow should redirect with error\n{}",
response.body
);
assert!(
response.location.as_ref().unwrap().contains("error="),
"redirect should include error parameter for malformed handle, got: {:?}",
response.location
);
Ok(())
}
/// Test: Discovery with bare URL works without webfinger
#[tokio::test]
async fn discover_bare_url_skips_webfinger() -> miette::Result<()> {
let port = free_port();
let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
server.wait_for_connection(port);
let mut alice_client = Client::new(port);
register(&mut alice_client, "alice");
// Start a remote actor
let recorder_port = free_port();
let recorder = RemoteActor::serve(recorder_port);
// Follow using bare actor URL (should skip webfinger)
let response = alice_client.post_form("/~alice/follow", &[("handle", &recorder.actor_url)]);
assert_eq!(
response.status, 303,
"follow with bare URL should redirect\n{}",
response.body
);
assert!(
response.location.as_ref().unwrap().contains("notice="),
"redirect should include notice parameter for successful follow, got: {:?}",
response.location
);
Ok(())
}