Wrench
2026-08-25 14:56:17 -04:00
parent 8231a732e6
commit 67d8dca482

View File

@@ -78,13 +78,21 @@ pub(crate) const PROJECT_QUERY_EVENT_BOUND: u32 = 10_000;
pub(crate) async fn fetch_projects_for_channel(
client: &BuzzClient,
channel: &str,
) -> Result<Vec<Event>, CliError> {
fetch_projects_for_channel_bounded(client, channel, PROJECT_QUERY_EVENT_BOUND).await
}
async fn fetch_projects_for_channel_bounded(
client: &BuzzClient,
channel: &str,
max_events: u32,
) -> Result<Vec<Event>, CliError> {
let filter = serde_json::json!({
"kinds": [KIND_PROJECT],
"#buzz-channel": [channel],
});
let events: Vec<Event> = client
.query_all_bounded(filter, PROJECT_QUERY_EVENT_BOUND)
.query_all_bounded(filter, max_events)
.await?
.into_iter()
.map(|event| {
@@ -970,6 +978,53 @@ mod tests {
assert_eq!(body[0]["kinds"], serde_json::json!([KIND_PROJECT]));
}
#[tokio::test]
async fn channel_scoping_prevents_unrelated_heads_from_consuming_the_bound() {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
let channel = "11111111-1111-4111-8111-111111111111";
let target = build_project("target", None, None, &[], Some(channel), None)
.unwrap()
.sign_with_keys(&nostr::Keys::generate())
.unwrap();
let decoy_channel = "22222222-2222-4222-8222-222222222222";
let decoys = ["decoy-a", "decoy-b"].map(|slug| {
build_project(slug, None, None, &[], Some(decoy_channel), None)
.unwrap()
.sign_with_keys(&nostr::Keys::generate())
.unwrap()
});
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let base_url = format!("http://{}", listener.local_addr().unwrap());
let server = tokio::spawn(async move {
let (mut socket, _) = listener.accept().await.unwrap();
let mut buf = vec![0; 65_536];
let read = socket.read(&mut buf).await.unwrap();
let request = String::from_utf8_lossy(&buf[..read]);
let body = request.split("\r\n\r\n").nth(1).unwrap();
let filter: serde_json::Value = serde_json::from_str(body).unwrap();
let response_body = if filter[0]["#buzz-channel"] == serde_json::json!([channel]) {
serde_json::to_string(&[target]).unwrap()
} else {
serde_json::to_string(&decoys).unwrap()
};
let response = format!(
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{response_body}",
response_body.len()
);
socket.write_all(response.as_bytes()).await.unwrap();
});
let client =
crate::client::BuzzClient::new(base_url, nostr::Keys::generate(), None, None).unwrap();
let projects = fetch_projects_for_channel_bounded(&client, channel, 1)
.await
.unwrap();
server.await.unwrap();
assert_eq!(projects.len(), 1);
assert_eq!(project_slug(&projects[0]).as_deref(), Some("target"));
}
#[test]
fn project_channel_matching_ignores_unrelated_claims() {
let expected = "11111111-1111-4111-8111-111111111111";