diff --git a/crates/buzz-test-client/tests/e2e_media_extended.rs b/crates/buzz-test-client/tests/e2e_media_extended.rs index 56b11b90d1e..58a94dd3e88 100644 --- a/crates/buzz-test-client/tests/e2e_media_extended.rs +++ b/crates/buzz-test-client/tests/e2e_media_extended.rs @@ -127,6 +127,19 @@ fn tiny_jpeg() -> Vec { ] } +/// An 8x8 PNG of random pixels, so every call has its own blob hash. +/// Publishing a hash leaves a sticky publication marker. Until the hash has a +/// readable reference (never, if the event is rejected), the relay refuses +/// uploader previews of it. A blob of its own keeps a publishing test from +/// reaching the fixed blobs, like `tiny_jpeg`, that other media tests read. +fn unique_png() -> Vec { + let mut png = Vec::new(); + image::RgbImage::from_fn(8, 8, |_, _| image::Rgb(rand::random())) + .write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png) + .expect("encode PNG"); + png +} + fn tiny_png() -> Vec { // Valid 2x2 red PNG generated by ffmpeg, with ffmpeg's pHYs chunk stripped: // `validate_png_metadata_free` rejects pHYs as an identity channel, so the @@ -658,10 +671,10 @@ async fn test_ws_valid_imeta() { let channel_id = channel_uuid.to_string(); println!("Channel: {channel_id}"); - // Upload a JPEG to get a valid sha256 - let jpeg = tiny_jpeg(); - let sha256 = hex::encode(Sha256::digest(&jpeg)); - let resp = upload(&http, &keys, &jpeg).await; + // Upload an image of its own to get a valid sha256 + let png = unique_png(); + let sha256 = hex::encode(Sha256::digest(&png)); + let resp = upload(&http, &keys, &png).await; assert_eq!(resp.status(), 200); // Connect via WebSocket @@ -675,10 +688,10 @@ async fn test_ws_valid_imeta() { Tag::parse(["h", &channel_id]).unwrap(), Tag::parse([ "imeta", - &format!("url http://localhost:3000/media/{sha256}.jpg"), - "m image/jpeg", + &format!("url http://localhost:3000/media/{sha256}.png"), + "m image/png", &format!("x {sha256}"), - "size 347", + &format!("size {}", png.len()), ]) .unwrap(), ]) diff --git a/crates/buzz-test-client/tests/e2e_media_video.rs b/crates/buzz-test-client/tests/e2e_media_video.rs index 819838aa2c2..323cb45f30d 100644 --- a/crates/buzz-test-client/tests/e2e_media_video.rs +++ b/crates/buzz-test-client/tests/e2e_media_video.rs @@ -79,10 +79,18 @@ fn blossom_auth_header(event: &nostr::Event) -> String { ) } -/// Build a minimal but structurally valid fast-start MP4 (H.264, 1s, 320×240). +/// Build a minimal but structurally valid fast-start MP4 (H.264, 1s, 320×240) +/// that is unique to this call. /// -/// Layout: ftyp | moov(mvhd + trak(tkhd + mdia(mdhd + hdlr + minf(vmhd + dinf + stbl)))) | mdat +/// Layout: ftyp | moov(mvhd + trak(tkhd + mdia(mdhd + hdlr + minf(vmhd + dinf + stbl)))) | mdat | free /// This is enough for `infer` to detect video/mp4 and for the `mp4` crate to parse. +/// +/// The trailing `free` box carries a random nonce so every test uploads its own +/// blob. Tests run in parallel against one relay, and under +/// `BUZZ_MEDIA_READ_MODE=enforce` any event that publishes a hash records a +/// sticky publication marker, even if the event is later rejected. That marker +/// ends uploader-preview reads of the hash, so a shared blob let one test's +/// publish turn another test's GET into a 404. fn build_test_mp4() -> Vec { fn box_wrap(fourcc: &[u8; 4], payload: &[u8]) -> Vec { let size = (8 + payload.len()) as u32; @@ -263,8 +271,9 @@ fn build_test_mp4() -> Vec { let moov_payload = [&mvhd[..], &trak].concat(); let moov = box_wrap(b"moov", &moov_payload); let mdat = box_wrap(b"mdat", &[]); + let nonce = box_wrap(b"free", uuid::Uuid::new_v4().as_bytes()); - [ftyp, moov, mdat].concat() + [ftyp, moov, mdat, nonce].concat() } /// Upload a valid MP4 video via Blossom, verify the BlobDescriptor includes @@ -481,34 +490,17 @@ fn relay_ws_url() -> String { .replace("https://", "wss://") } -/// Minimal valid JPEG (1x1 pixel) — used as a poster frame blob. -fn tiny_jpeg() -> Vec { - vec![ - 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, - 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xDB, 0x00, 0x43, 0x00, 0x08, 0x06, 0x06, 0x07, 0x06, - 0x05, 0x08, 0x07, 0x07, 0x07, 0x09, 0x09, 0x08, 0x0A, 0x0C, 0x14, 0x0D, 0x0C, 0x0B, 0x0B, - 0x0C, 0x19, 0x12, 0x13, 0x0F, 0x14, 0x1D, 0x1A, 0x1F, 0x1E, 0x1D, 0x1A, 0x1C, 0x1C, 0x20, - 0x24, 0x2E, 0x27, 0x20, 0x22, 0x2C, 0x23, 0x1C, 0x1C, 0x28, 0x37, 0x29, 0x2C, 0x30, 0x31, - 0x34, 0x34, 0x34, 0x1F, 0x27, 0x39, 0x3D, 0x38, 0x32, 0x3C, 0x2E, 0x33, 0x34, 0x32, 0xFF, - 0xC0, 0x00, 0x0B, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x11, 0x00, 0xFF, 0xC4, 0x00, - 0x1F, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, - 0xFF, 0xC4, 0x00, 0xB5, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, - 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, - 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, - 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, - 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, - 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, - 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, - 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, - 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, - 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, - 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, - 0xF8, 0xF9, 0xFA, 0xFF, 0xDA, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3F, 0x00, 0x7B, 0x94, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xD9, - ] +/// An 8x8 PNG of random pixels, so every call has its own blob hash. +/// Publishing a hash leaves a sticky publication marker. Until the hash has a +/// readable reference (never, if the event is rejected), the relay refuses +/// uploader previews of it. A blob of its own keeps a publishing test from +/// reaching the fixed blobs, like `tiny_jpeg`, that other media tests read. +fn unique_png() -> Vec { + let mut png = Vec::new(); + image::RgbImage::from_fn(8, 8, |_, _| image::Rgb(rand::random())) + .write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png) + .expect("encode PNG"); + png } /// Upload a video + poster, then send a message with imeta `image` field @@ -561,8 +553,8 @@ async fn test_video_poster_imeta_accepted_via_ws() { let video_desc: serde_json::Value = video_resp.json().await.unwrap(); let video_size = video_desc["size"].as_u64().unwrap(); - // 3. Upload poster (tiny JPEG) - let poster = tiny_jpeg(); + // 3. Upload poster (unique PNG) + let poster = unique_png(); let poster_sha = hex::encode(Sha256::digest(&poster)); let poster_auth = sign_blossom_auth(&keys, &poster_sha); let poster_resp = client @@ -593,7 +585,7 @@ async fn test_video_poster_imeta_accepted_via_ws() { "m video/mp4", &format!("x {video_sha}"), &format!("size {video_size}"), - &format!("image {base}/media/{poster_sha}.jpg"), + &format!("image {base}/media/{poster_sha}.png"), ]) .unwrap(), ])