mirror of
https://github.com/block/buzz.git
synced 2026-10-28 13:01:10 +00:00
122 lines
4.3 KiB
Rust
122 lines
4.3 KiB
Rust
use futures_util::{SinkExt, StreamExt};
|
|
use nostr::{EventBuilder, JsonUtil, Keys, Kind, Tag, Timestamp};
|
|
use serde_json::{json, Value};
|
|
use tokio::net::TcpListener;
|
|
use tokio_tungstenite::{accept_async, tungstenite::Message};
|
|
|
|
async fn exercise(tamper: bool) -> std::process::Output {
|
|
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
let address = listener.local_addr().unwrap();
|
|
let author = Keys::generate();
|
|
let channel = uuid::Uuid::new_v4().to_string();
|
|
let event = EventBuilder::new(Kind::Custom(9), "worker complete")
|
|
.tags([Tag::parse(["h", channel.as_str()]).unwrap()])
|
|
.custom_created_at(Timestamp::from(100))
|
|
.sign_with_keys(&author)
|
|
.unwrap();
|
|
let live_event = EventBuilder::new(Kind::Custom(9), "live progress")
|
|
.tags([Tag::parse(["h", channel.as_str()]).unwrap()])
|
|
.custom_created_at(Timestamp::from(101))
|
|
.sign_with_keys(&author)
|
|
.unwrap();
|
|
let mut event: Value = serde_json::from_str(&event.as_json()).unwrap();
|
|
if tamper {
|
|
event["content"] = json!("forged");
|
|
}
|
|
let expected_author = author.public_key().to_hex();
|
|
let channel_for_server = channel.clone();
|
|
let server = tokio::spawn(async move {
|
|
let (socket, _) = listener.accept().await.unwrap();
|
|
let mut ws = accept_async(socket).await.unwrap();
|
|
ws.send(Message::Text(
|
|
json!(["AUTH", "test-challenge"]).to_string().into(),
|
|
))
|
|
.await
|
|
.unwrap();
|
|
let auth: Value =
|
|
serde_json::from_str(ws.next().await.unwrap().unwrap().to_text().unwrap()).unwrap();
|
|
assert_eq!(auth[0], "AUTH");
|
|
let signed: nostr::Event = serde_json::from_value(auth[1].clone()).unwrap();
|
|
signed.verify().unwrap();
|
|
ws.send(Message::Text(
|
|
json!(["OK", auth[1]["id"], true, ""]).to_string().into(),
|
|
))
|
|
.await
|
|
.unwrap();
|
|
let req: Value =
|
|
serde_json::from_str(ws.next().await.unwrap().unwrap().to_text().unwrap()).unwrap();
|
|
assert_eq!(req[0], "REQ");
|
|
assert_eq!(req[2]["#h"], json!([channel_for_server]));
|
|
assert_eq!(req[2]["authors"], json!([expected_author]));
|
|
assert_eq!(req[2]["since"], 100);
|
|
for frame in [
|
|
json!(["EVENT", req[1], event]),
|
|
json!(["EOSE", req[1]]),
|
|
json!(["EVENT", req[1], live_event]),
|
|
json!(["CLOSED", req[1], "fixture finished"]),
|
|
] {
|
|
if ws
|
|
.send(Message::Text(frame.to_string().into()))
|
|
.await
|
|
.is_err()
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
let output = tokio::task::spawn_blocking(move || {
|
|
std::process::Command::new(env!("CARGO_BIN_EXE_buzz"))
|
|
.env(
|
|
"BUZZ_PRIVATE_KEY",
|
|
Keys::generate().secret_key().to_secret_hex(),
|
|
)
|
|
.args([
|
|
"--relay",
|
|
&format!("ws://{address}"),
|
|
"messages",
|
|
"watch",
|
|
"--channel",
|
|
&channel,
|
|
"--author",
|
|
&author.public_key().to_hex(),
|
|
"--since",
|
|
"100",
|
|
])
|
|
.output()
|
|
.unwrap()
|
|
})
|
|
.await
|
|
.unwrap();
|
|
server.await.unwrap();
|
|
output
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn authenticated_watch_emits_history_and_ready_then_reports_closure() {
|
|
let output = exercise(false).await;
|
|
assert!(!output.status.success());
|
|
let lines: Vec<Value> = String::from_utf8(output.stdout)
|
|
.unwrap()
|
|
.lines()
|
|
.map(|l| serde_json::from_str(l).unwrap())
|
|
.collect();
|
|
assert_eq!(lines.len(), 3);
|
|
assert_eq!(lines[2]["event"]["content"], "live progress");
|
|
assert_eq!(lines[0]["event"]["content"], "worker complete");
|
|
assert!(lines[0]["event"]["sig"].is_string());
|
|
assert_eq!(lines[1], json!({"type":"ready"}));
|
|
assert!(String::from_utf8(output.stderr)
|
|
.unwrap()
|
|
.contains("fixture finished"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn forged_event_never_reaches_stdout() {
|
|
let output = exercise(true).await;
|
|
assert!(!output.status.success());
|
|
assert!(output.stdout.is_empty());
|
|
assert!(String::from_utf8(output.stderr)
|
|
.unwrap()
|
|
.contains("invalid signed event"));
|
|
}
|