# Test Specification — OutputManager ## Acceptance Criteria Traceability | AC ID | Acceptance Criterion | Test IDs | Coverage | |-------|---------------------|----------|----------| | AC-26 | Total RAM ≤6GB (OutputManager must not contribute significant memory) | PT-02 | Covered | | AC-27 | Coexist with YOLO pipeline — recording must not block inference | IT-01, PT-01 | Covered | Note: OutputManager has no direct performance ACs from the acceptance criteria. Its tests ensure it supports the system's recording, logging, and operator delivery requirements defined in the architecture. --- ## Integration Tests ### IT-01: log_detection Writes Valid JSON-Line **Summary**: Verify log_detection appends a correctly formatted JSON line to the detection log file. **Traces to**: AC-27 **Input data**: - DetectionLogEntry: {frame_id: 1000, label: "footpath_winter", confidence: 0.72, tier: 2, centerX: 0.5, centerY: 0.3} - Output dir: temporary test directory **Expected result**: - detections.jsonl file exists in output dir - Last line is valid JSON parseable to a dict with all input fields - Trailing newline present **Max execution time**: 50ms **Dependencies**: Writable filesystem --- ### IT-02: record_frame Saves JPEG to Sequential Filename **Summary**: Verify record_frame encodes and saves frame as JPEG with correct naming. **Traces to**: AC-27 **Input data**: - Frame: numpy array (1080, 1920, 3), random pixel data - frame_id: 42, level: 1 **Expected result**: - File `42.jpg` exists in output dir `frames/` subdirectory - File is a valid JPEG (OpenCV can re-read it) - File size > 0 and < 500KB (reasonable JPEG of 1080p noise) **Max execution time**: 100ms **Dependencies**: Writable filesystem --- ### IT-03: log_health Writes Health Entry **Summary**: Verify log_health appends a JSON line with health data. **Traces to**: AC-27 **Input data**: - HealthLogEntry: {timestamp: epoch, t_junction_c: 65.0, vlm_available: true, gimbal_available: true, semantic_available: true} **Expected result**: - health.jsonl file exists - Last line contains all input fields as valid JSON **Max execution time**: 50ms **Dependencies**: Writable filesystem --- ### IT-04: log_gimbal_command Appends to Gimbal Log **Summary**: Verify gimbal command strings are appended to the gimbal log file. **Traces to**: AC-27 **Input data**: - cmd_str: "SET_ANGLES pan=10.0 tilt=-20.0 zoom=5.0" **Expected result**: - gimbal.log file exists - Last line matches the input command string **Max execution time**: 50ms **Dependencies**: Writable filesystem --- ### IT-05: report_to_operator Formats Detection in YOLO Schema **Summary**: Verify operator delivery formats detections with centerX, centerY, width, height, classNum, label, confidence. **Traces to**: AC-27 **Input data**: - list of 3 Detection objects **Expected result**: - Output matches existing YOLO output format (same field names, same coordinate normalization) - All 3 detections present in output **Max execution time**: 50ms **Dependencies**: None --- ### IT-06: get_storage_status Returns Correct NVMe Stats **Summary**: Verify storage status reports accurate free space percentage. **Traces to**: AC-26 **Input data**: - Output dir on test filesystem **Expected result**: - StorageStatus: nvme_free_pct in [0, 100], frames_recorded ≥ 0, detections_logged ≥ 0 - should_reduce_recording matches threshold logic (true if free < 20%) **Max execution time**: 50ms **Dependencies**: Writable filesystem --- ### IT-07: Circular Buffer Triggers on Low Storage **Summary**: Verify should_reduce_recording becomes true when free space drops below 20%. **Traces to**: AC-26 **Input data**: - Mock statvfs to report 15% free space **Expected result**: - get_storage_status().should_reduce_recording == true - At 25% free → should_reduce_recording == false **Max execution time**: 50ms **Dependencies**: Mock filesystem stats --- ### IT-08: Init Creates Output Directory Structure **Summary**: Verify init() creates the expected directory structure. **Traces to**: AC-27 **Input data**: - output_dir: temporary path that does not exist yet **Expected result**: - Directory created with subdirectories for frames - No errors **Max execution time**: 100ms **Dependencies**: Writable filesystem --- ### IT-09: WriteError Does Not Block Caller **Summary**: Verify that a disk write failure (e.g., permission denied) is caught and does not propagate as an unhandled exception. **Traces to**: AC-27 **Input data**: - Output dir set to a read-only path **Expected result**: - log_detection raises no unhandled exception (catches WriteError internally) - Error counter incremented - Function returns normally **Max execution time**: 50ms **Dependencies**: Read-only filesystem path --- ## Performance Tests ### PT-01: Frame Recording Throughput at L2 Rate **Summary**: Verify OutputManager can sustain 30 FPS frame recording without becoming a bottleneck. **Traces to**: AC-27 **Load scenario**: - 30 frames/second, 1080p JPEG encoding + write - Duration: 10 seconds (300 frames) - Ramp-up: immediate **Expected results**: | Metric | Target | Failure Threshold | |--------|--------|-------------------| | Sustained write rate | ≥30 FPS | <25 FPS | | Encoding latency (p95) | ≤20ms | >33ms | | Dropped frames | 0 | >5 | | Write throughput | ≥3 MB/s | <2 MB/s | **Resource limits**: - CPU: ≤20% (JPEG encoding) - Memory: ≤100MB (buffer) --- ### PT-02: Memory Usage Under Sustained Load **Summary**: Verify no memory leak during continuous logging and recording. **Traces to**: AC-26 **Load scenario**: - 1000 log_detection calls + 300 record_frame calls - Duration: 60 seconds - Measure RSS before and after **Expected results**: | Metric | Target | Failure Threshold | |--------|--------|-------------------| | Memory growth | ≤10MB | >50MB | | Memory leak rate | 0 MB/min | >5 MB/min | **Resource limits**: - Memory: ≤100MB total for OutputManager --- ## Security Tests ### ST-01: Detection Log Does Not Contain Raw Image Data **Summary**: Verify detection JSON lines contain metadata only, not embedded image data. **Traces to**: AC-27 **Attack vector**: Information leakage through oversized log entries **Test procedure**: 1. Log 10 detections 2. Read detections.jsonl 3. Verify no field contains base64, raw bytes, or binary data **Expected behavior**: Each JSON line is < 1KB; only text/numeric fields. **Pass criteria**: All lines < 1KB, no binary data patterns. **Fail criteria**: Any line contains embedded image data or exceeds 10KB. --- ### ST-02: Path Traversal Prevention in Output Directory **Summary**: Verify frame_id or other inputs cannot cause writes outside the output directory. **Traces to**: AC-27 **Attack vector**: Path traversal via crafted frame_id **Test procedure**: 1. Call record_frame with frame_id containing "../" characters (e.g., as uint64 this shouldn't be possible, but verify string conversion) 2. Verify file is written inside output_dir only **Expected behavior**: File written within output_dir; no file created outside. **Pass criteria**: All files within output_dir subtree. **Fail criteria**: File created outside output_dir. --- ## Acceptance Tests ### AT-01: Full Flight Recording Session **Summary**: Verify OutputManager correctly handles a simulated 5-minute flight with mixed L1 and L2 recording. **Traces to**: AC-27 **Preconditions**: - Temporary output directory with sufficient space - Config: recording_l1_fps=2, recording_l2_fps=30 **Steps**: | Step | Action | Expected Result | |------|--------|-----------------| | 1 | init(output_dir) | Directory structure created | | 2 | Simulate 3 min L1: record_frame at 2 FPS | 360 frames written | | 3 | Simulate 1 min L2: record_frame at 30 FPS | 1800 frames written | | 4 | Log 50 detections during L2 | detections.jsonl has 50 lines | | 5 | get_storage_status() | frames_recorded=2160, detections_logged=50 | --- ### AT-02: Storage Reduction Under Pressure **Summary**: Verify the storage management signals reduce recording at the right thresholds. **Traces to**: AC-26 **Preconditions**: - Mock filesystem with configurable free space **Steps**: | Step | Action | Expected Result | |------|--------|-----------------| | 1 | Set free space to 25% | should_reduce_recording = false | | 2 | Set free space to 15% | should_reduce_recording = true | | 3 | Set free space to 8% | should_reduce_recording = true (critical) | --- ## Test Data Management **Required test data**: | Data Set | Description | Source | Size | |----------|-------------|--------|------| | sample_frames | 10 sample 1080p frames for recording tests | Generated (random or real) | ~10 MB | | sample_detections | 50 DetectionLogEntry dicts | Generated fixtures | ~5 KB | | sample_health | 10 HealthLogEntry dicts | Generated fixtures | ~2 KB | **Setup procedure**: 1. Create temporary output directory 2. Call init(output_dir) **Teardown procedure**: 1. Delete temporary output directory and all contents **Data isolation strategy**: Each test uses its own temporary directory. No shared output paths between tests.