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 <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
This commit is contained in:
Wes
2026-08-21 18:44:14 -06:00
parent ec68dccf33
commit 896768fbf0
4 changed files with 93 additions and 32 deletions

View File

@@ -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" }

View File

@@ -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);
}

View File

@@ -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()

Binary file not shown.