From 896768fbf0f387d4cf092927b88f7a1e46321bd5 Mon Sep 17 00:00:00 2001 From: Wes Date: Fri, 21 Aug 2026 18:44:14 -0600 Subject: [PATCH] test(desktop): enforce avconvert relay contract Run the extensionless QuickTime conversion against a tiny checked-in H.264 fixture on every macOS test pass, and validate all AVFoundation output with the relay's production video validator before upload. Co-authored-by: Wes Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> Signed-off-by: Wes --- desktop/src-tauri/Cargo.toml | 4 +- .../src-tauri/src/commands/media_transcode.rs | 67 ++++++++++-------- .../generate-quicktime-screen-recording.swift | 54 ++++++++++++++ .../fixtures/quicktime-screen-recording.mov | Bin 0 -> 1148 bytes 4 files changed, 93 insertions(+), 32 deletions(-) create mode 100644 desktop/src-tauri/tests/fixtures/generate-quicktime-screen-recording.swift create mode 100644 desktop/src-tauri/tests/fixtures/quicktime-screen-recording.mov diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index 3f7189deea..d75402d950 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -58,6 +58,7 @@ objc2-foundation = { version = "0.3.2", default-features = false, features = ["N objc2-user-notifications = { version = "0.3.2", default-features = false, features = ["block2", "UNNotification", "UNNotificationContent", "UNNotificationRequest", "UNNotificationResponse", "UNNotificationSettings", "UNNotificationTrigger", "UNUserNotificationCenter"] } keyring = { version = "3.6.3", default-features = false, features = ["apple-native", "vendored"], optional = true } security-framework = { version = "3.7.0", features = ["OSX_10_15"] } +buzz_media_pkg = { package = "buzz-media", path = "../../crates/buzz-media" } window-vibrancy = "0.6" user-idle = { version = "0.6", default-features = false } plist = "1" @@ -153,6 +154,3 @@ tauri-utils = "2" # `test-util` enables tokio's paused-clock (`start_paused`) so the relay # admission gate tests can assert exact wait durations without real sleeps. tokio = { version = "1", features = ["test-util"] } -# The relay's media validation, so the snapshot-sharing tests can prove the -# full export → sanitize → relay-accept → import contract end to end. -buzz_media_pkg = { package = "buzz-media", path = "../../crates/buzz-media" } diff --git a/desktop/src-tauri/src/commands/media_transcode.rs b/desktop/src-tauri/src/commands/media_transcode.rs index 7e878ad5dd..69b3f1b078 100644 --- a/desktop/src-tauri/src/commands/media_transcode.rs +++ b/desktop/src-tauri/src/commands/media_transcode.rs @@ -486,6 +486,35 @@ fn prepare_avconvert_source( Ok((staged, true)) } +/// Reject output that the relay cannot ingest before spending bandwidth. +/// +/// `avconvert` can preserve metadata tracks from some source containers, while +/// the relay deliberately accepts exactly one H.264 video track and at most +/// one AAC audio track. Validate locally so those inputs fail with a useful +/// conversion error instead of a later generic upload rejection. +#[cfg(target_os = "macos")] +fn validate_avconvert_output(path: &std::path::Path) -> Result<(), String> { + let config = buzz_media_pkg::MediaConfig { + s3_endpoint: String::new(), + s3_access_key: String::new(), + s3_secret_key: String::new(), + s3_bucket: String::new(), + s3_region: "us-east-1".to_string(), + s3_addressing_style: buzz_media_pkg::S3AddressingStyle::Path, + max_image_bytes: 50 * 1024 * 1024, + max_gif_bytes: 10 * 1024 * 1024, + max_video_bytes: 500 * 1024 * 1024, + max_file_bytes: 100 * 1024 * 1024, + public_base_url: String::new(), + upload_records_enabled: false, + upload_ip_header: None, + upload_port_header: None, + }; + buzz_media_pkg::validation::validate_video_file(path, &config) + .map(|_| ()) + .map_err(|error| format!("Video conversion produced an unsupported file: {error}")) +} + /// Transcode a video with macOS's built-in AVFoundation converter. /// /// Release builds do not bundle ffmpeg, and a stock macOS installation does @@ -536,6 +565,7 @@ fn transcode_with_avconvert( return Err("upload cancelled".to_string()); } + validate_avconvert_output(&output)?; std::fs::read(&output).map_err(|error| format!("failed to read converted video: {error}")) })(); @@ -626,43 +656,22 @@ mod tests { #[cfg(target_os = "macos")] #[test] - fn test_avconvert_normalizes_configured_quicktime_fixture() { - let Some(source) = std::env::var_os("BUZZ_TEST_QUICKTIME_PATH") else { - eprintln!("skipping avconvert round-trip: BUZZ_TEST_QUICKTIME_PATH is unset"); - return; - }; - let staged_source = std::env::temp_dir().join(format!( - "buzz-avconvert-configured-test-{}", - uuid::Uuid::new_v4() - )); - std::fs::copy(&source, &staged_source).expect("stage extensionless QuickTime fixture"); + fn test_avconvert_normalizes_extensionless_quicktime_fixture() { + let fixture = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/quicktime-screen-recording.mov"); + let staged_source = + std::env::temp_dir().join(format!("buzz-avconvert-fixture-{}", uuid::Uuid::new_v4())); + std::fs::copy(fixture, &staged_source).expect("stage extensionless QuickTime fixture"); let video = transcode_with_avconvert(&staged_source, None).expect("convert QuickTime fixture"); let _ = std::fs::remove_file(staged_source); let converted = std::env::temp_dir().join(format!( - "buzz-avconvert-configured-output-{}.mp4", + "buzz-avconvert-fixture-output-{}.mp4", uuid::Uuid::new_v4() )); std::fs::write(&converted, &video).expect("write converted fixture"); - let relay_config = buzz_media_pkg::MediaConfig { - s3_endpoint: String::new(), - s3_access_key: String::new(), - s3_secret_key: String::new(), - s3_bucket: String::new(), - s3_region: "us-east-1".to_string(), - s3_addressing_style: buzz_media_pkg::S3AddressingStyle::Path, - max_image_bytes: 50 * 1024 * 1024, - max_gif_bytes: 10 * 1024 * 1024, - max_video_bytes: 500 * 1024 * 1024, - max_file_bytes: 100 * 1024 * 1024, - public_base_url: String::new(), - upload_records_enabled: false, - upload_ip_header: None, - upload_port_header: None, - }; - buzz_media_pkg::validation::validate_video_file(&converted, &relay_config) - .expect("relay rejected avconvert output"); + validate_avconvert_output(&converted).expect("relay rejected avconvert output"); let _ = std::fs::remove_file(converted); } diff --git a/desktop/src-tauri/tests/fixtures/generate-quicktime-screen-recording.swift b/desktop/src-tauri/tests/fixtures/generate-quicktime-screen-recording.swift new file mode 100644 index 0000000000..e2a5eebe70 --- /dev/null +++ b/desktop/src-tauri/tests/fixtures/generate-quicktime-screen-recording.swift @@ -0,0 +1,54 @@ +// Generates the tiny H.264 QuickTime fixture beside this file. +// Run: swift tests/fixtures/generate-quicktime-screen-recording.swift \ +// tests/fixtures/quicktime-screen-recording.mov +import AVFoundation +import CoreVideo +import Foundation + +let output = URL(fileURLWithPath: CommandLine.arguments[1]) +try? FileManager.default.removeItem(at: output) +let writer = try AVAssetWriter(outputURL: output, fileType: .mov) +let input = AVAssetWriterInput( + mediaType: .video, + outputSettings: [ + AVVideoCodecKey: AVVideoCodecType.h264, + AVVideoWidthKey: 64, + AVVideoHeightKey: 64, + ] +) +let adaptor = AVAssetWriterInputPixelBufferAdaptor( + assetWriterInput: input, + sourcePixelBufferAttributes: [ + kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA, + kCVPixelBufferWidthKey as String: 64, + kCVPixelBufferHeightKey as String: 64, + ] +) +writer.add(input) +guard writer.startWriting() else { fatalError("start failed: \(writer.error!)") } +writer.startSession(atSourceTime: .zero) +input.requestMediaDataWhenReady(on: DispatchQueue(label: "fixture")) { + for frame in 0..<3 { + while !input.isReadyForMoreMediaData { usleep(1_000) } + var buffer: CVPixelBuffer? + CVPixelBufferPoolCreatePixelBuffer(nil, adaptor.pixelBufferPool!, &buffer) + guard let pixel = buffer else { fatalError("pixel buffer allocation failed") } + CVPixelBufferLockBaseAddress(pixel, []) + memset( + CVPixelBufferGetBaseAddress(pixel), + Int32(frame * 32), + CVPixelBufferGetDataSize(pixel) + ) + CVPixelBufferUnlockBaseAddress(pixel, []) + guard adaptor.append( + pixel, + withPresentationTime: CMTime(value: CMTimeValue(frame), timescale: 30) + ) else { fatalError("append failed: \(writer.error!)") } + } + input.markAsFinished() + writer.finishWriting { + guard writer.status == .completed else { fatalError("finish failed: \(writer.error!)") } + exit(0) + } +} +dispatchMain() diff --git a/desktop/src-tauri/tests/fixtures/quicktime-screen-recording.mov b/desktop/src-tauri/tests/fixtures/quicktime-screen-recording.mov new file mode 100644 index 0000000000000000000000000000000000000000..015fdb5b3c2b588d387252b43e29e1162e2c7175 GIT binary patch literal 1148 zcmZuwPe>GD6o22Evg(?fOzjey6I!kjg%(k`;m{gMY~iIPXi291M%|oo*PUV8T!J+y zs!L@?kcY%e9wGt{iWd+4!Kg!Bx)j15)zeY<0%Xwz|g=zgw zXK?8J$m#Uhw`8ictaxUED(+_EgSX6;##Y`x=TEpF9%+2MIeEOL`~LiFbIt7bDi>^f zr7U$InZSMpd#2PG2OZh^G{! z5(5~EV4crexR06?;+jRwKuQ5Uh{HKDj%2bL*4L7`){wQyhAg(reOy(jcsR`Xc) zP%;~G5C_I_OANPqff~E38#+f#psfzd@lcaxW$f$}?-NiYeA;nJLSV)1`