Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions crates/buzz-test-client/tests/e2e_media_extended.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,19 @@ fn tiny_jpeg() -> Vec<u8> {
]
}

/// 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<u8> {
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<u8> {
// 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
Expand Down Expand Up @@ -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
Expand All @@ -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(),
])
Expand Down
60 changes: 26 additions & 34 deletions crates/buzz-test-client/tests/e2e_media_video.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> {
fn box_wrap(fourcc: &[u8; 4], payload: &[u8]) -> Vec<u8> {
let size = (8 + payload.len()) as u32;
Expand Down Expand Up @@ -263,8 +271,9 @@ fn build_test_mp4() -> Vec<u8> {
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
Expand Down Expand Up @@ -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<u8> {
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<u8> {
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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(),
])
Expand Down
Loading