# Blackbox Tests

## Positive Scenarios

### FT-P-01: Health check returns status before engine initialization

**Summary**: Verify the health endpoint responds correctly when the inference engine has not yet been initialized.
**Traces to**: AC-API-1, AC-EL-1
**Category**: API, Engine Lifecycle

**Preconditions**:
- Detections service is running
- No detection requests have been made (engine is not initialized)

**Input data**: None

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | `GET /health` | 200 OK with `{"status": "healthy", "aiAvailability": "None"}` |

**Expected outcome**: Health endpoint returns `status: "healthy"` and `aiAvailability: "None"` (engine not yet loaded).
**Max execution time**: 2s

---

### FT-P-02: Health check reflects engine availability after initialization

**Summary**: Verify the health endpoint reports the correct engine state after the engine has been initialized by a detection request.
**Traces to**: AC-API-1, AC-EL-2
**Category**: API, Engine Lifecycle

**Preconditions**:
- Detections service is running
- Mock-loader serves the ONNX model file
- At least one successful detection has been performed (engine initialized)

**Input data**: small-image

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | `POST /detect` with small-image (trigger engine init) | 200 OK with detection results |
| 2 | `GET /health` | 200 OK with `aiAvailability` set to `"Enabled"` or `"Warning"` |

**Expected outcome**: `aiAvailability` reflects an initialized engine state (not `"None"` or `"Downloading"`).
**Max execution time**: 30s (includes engine init on first call)

---

### FT-P-03: Single image detection returns detections

**Summary**: Verify that a valid small image submitted via POST /detect returns structured detection results.
**Traces to**: AC-DA-1, AC-API-2
**Category**: Detection Accuracy, API

**Preconditions**:
- Engine is initialized (or will be on this call)
- Mock-loader serves the model

**Input data**: small-image (640×480, contains detectable objects)

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | `POST /detect` with small-image as multipart file | 200 OK |
| 2 | Parse response JSON | Array of detection objects, each with `x`, `y`, `width`, `height`, `label`, `confidence` |
| 3 | Verify all confidence values | Every detection has `confidence >= 0.25` (default probability_threshold) |

**Expected outcome**: Non-empty array of DetectionDto objects. All confidences meet threshold. Each detection has valid bounding box coordinates (0.0–1.0 range).
**Max execution time**: 30s

---

### FT-P-04: Large image triggers GSD-based tiling

**Summary**: Verify that an image exceeding 1.5× model dimensions is tiled and processed with tile-level detection results merged.
**Traces to**: AC-IP-1, AC-IP-2
**Category**: Image Processing

**Preconditions**:
- Engine is initialized
- Config includes altitude, focal_length, sensor_width for GSD calculation

**Input data**: large-image (4000×3000)

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | `POST /detect` with large-image and config `{"altitude": 400, "focal_length": 24, "sensor_width": 23.5}` | 200 OK |
| 2 | Parse response JSON | Array of detections |
| 3 | Verify detection coordinates | Bounding box coordinates are in 0.0–1.0 range relative to the full original image |

**Expected outcome**: Detections returned for the full image. Coordinates are normalized to original image dimensions (not tile dimensions). Processing time is longer than small-image due to tiling.
**Max execution time**: 60s

---

### FT-P-05: Detection confidence filtering respects threshold

**Summary**: Verify that detections below the configured probability_threshold are filtered out.
**Traces to**: AC-DA-1
**Category**: Detection Accuracy

**Preconditions**:
- Engine is initialized

**Input data**: small-image

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | `POST /detect` with small-image and config `{"probability_threshold": 0.8}` | 200 OK |
| 2 | Parse response JSON | All returned detections have `confidence >= 0.8` |
| 3 | `POST /detect` with same image and config `{"probability_threshold": 0.1}` | 200 OK |
| 4 | Compare result counts | Step 3 returns >= number of detections from Step 1 |

**Expected outcome**: Higher threshold produces fewer or equal detections. No detection below threshold appears in results.
**Max execution time**: 30s

---

### FT-P-06: Overlapping detections are deduplicated

**Summary**: Verify that overlapping detections with containment ratio above threshold are deduplicated, keeping the higher-confidence one.
**Traces to**: AC-DA-2
**Category**: Detection Accuracy

**Preconditions**:
- Engine is initialized
- Image produces overlapping detections (dense scene)

**Input data**: small-image (scene with clustered objects)

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | `POST /detect` with small-image and config `{"tracking_intersection_threshold": 0.6}` | 200 OK |
| 2 | Collect detections | No two detections of the same class overlap by more than 60% containment ratio |
| 3 | `POST /detect` with same image and config `{"tracking_intersection_threshold": 0.01}` | 200 OK |
| 4 | Compare result counts | Step 3 returns fewer or equal detections (more aggressive dedup) |

**Expected outcome**: No pair of returned detections exceeds the configured overlap threshold.
**Max execution time**: 30s

---

### FT-P-07: Physical size filtering removes oversized detections

**Summary**: Verify that detections exceeding the MaxSizeM for their class (given GSD) are removed.
**Traces to**: AC-DA-4
**Category**: Detection Accuracy

**Preconditions**:
- Engine is initialized
- classes.json loaded with MaxSizeM values

**Input data**: small-image, config with known GSD parameters

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | `POST /detect` with small-image and config `{"altitude": 400, "focal_length": 24, "sensor_width": 23.5}` | 200 OK |
| 2 | For each detection, compute physical size from bounding box + GSD | No detection's physical size exceeds the MaxSizeM defined for its class in classes.json |

**Expected outcome**: All returned detections have plausible physical dimensions for their class.
**Max execution time**: 30s

---

### FT-P-08: Async media detection returns "started" immediately

**Summary**: Verify that POST /detect/{media_id} returns immediately with status "started" while processing continues in background.
**Traces to**: AC-API-3
**Category**: API

**Preconditions**:
- Engine is initialized
- Media file paths are available via config

**Input data**: jwt-token, test-video path in config

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | `POST /detect/test-media-001` with config paths and auth headers | 200 OK, `{"status": "started"}` |
| 2 | Measure response time | Response arrives within 1s (before video processing completes) |

**Expected outcome**: Immediate response with `{"status": "started"}`. Processing continues asynchronously.
**Max execution time**: 2s (response only; processing continues in background)

---

### FT-P-09: SSE streaming delivers detection events during async processing

**Summary**: Verify that SSE clients receive real-time detection events during async media detection.
**Traces to**: AC-API-4, AC-API-3
**Category**: API

**Preconditions**:
- Engine is initialized
- SSE client connected before triggering detection

**Input data**: jwt-token, test-video path in config

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | Open SSE connection: `GET /detect/stream` | Connection established |
| 2 | `POST /detect/test-media-002` with config and auth headers | `{"status": "started"}` |
| 3 | Listen on SSE connection | Receive events with `mediaStatus: "AIProcessing"` as frames are processed |
| 4 | Wait for completion | Final event with `mediaStatus: "AIProcessed"` and `percent: 100` |

**Expected outcome**: Multiple SSE events received. Events include detection data. Final event signals completion.
**Max execution time**: 120s

---

### FT-P-10: Video frame sampling processes every Nth frame

**Summary**: Verify that video processing respects the `frame_period_recognition` setting.
**Traces to**: AC-VP-1
**Category**: Video Processing

**Preconditions**:
- Engine is initialized
- SSE client connected

**Input data**: test-video (10s, 30fps = 300 frames), config `{"frame_period_recognition": 4}`

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | Open SSE connection | Connection established |
| 2 | `POST /detect/test-media-003` with config `{"frame_period_recognition": 4, "paths": ["/media/test-video.mp4"]}` | `{"status": "started"}` |
| 3 | Count distinct SSE events with detection data | Number of processed frames ≈ 300/4 = 75 (±10% tolerance for start/end frames) |

**Expected outcome**: Approximately 75 frames processed (not all 300). The count scales proportionally with frame_period_recognition.
**Max execution time**: 120s

---

### FT-P-11: Video annotation interval enforcement

**Summary**: Verify that annotations are not reported more frequently than `frame_recognition_seconds`.
**Traces to**: AC-VP-2
**Category**: Video Processing

**Preconditions**:
- Engine is initialized
- SSE client connected

**Input data**: test-video, config `{"frame_recognition_seconds": 2}`

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | Open SSE connection | Connection established |
| 2 | `POST /detect/test-media-004` with config `{"frame_recognition_seconds": 2, "paths": ["/media/test-video.mp4"]}` | `{"status": "started"}` |
| 3 | Record timestamps of consecutive SSE detection events | Minimum gap between consecutive annotation events ≥ 2 seconds |

**Expected outcome**: No two annotation events are closer than 2 seconds apart.
**Max execution time**: 120s

---

### FT-P-12: Video tracking accepts new annotations on movement

**Summary**: Verify that new annotations are accepted when detections move beyond the tracking threshold.
**Traces to**: AC-VP-3
**Category**: Video Processing

**Preconditions**:
- Engine is initialized
- SSE client connected
- Video contains moving objects

**Input data**: test-video, config with `tracking_distance_confidence > 0`

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | Open SSE connection | Connection established |
| 2 | `POST /detect/test-media-005` with config `{"tracking_distance_confidence": 0.05, "paths": ["/media/test-video.mp4"]}` | `{"status": "started"}` |
| 3 | Collect SSE events | Annotations are emitted when object positions change between frames |

**Expected outcome**: Annotations contain updated positions reflecting object movement. Static objects do not generate redundant annotations.
**Max execution time**: 120s

---

### FT-P-13: Weather mode class variants

**Summary**: Verify that the system supports detection across different weather mode class variants (Norm, Wint, Night).
**Traces to**: AC-OC-1
**Category**: Object Classes

**Preconditions**:
- Engine is initialized
- classes.json includes weather-mode variants

**Input data**: small-image

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | `POST /detect` with small-image | 200 OK |
| 2 | Inspect returned detection labels | Labels correspond to valid class names from classes.json (base or weather-variant) |

**Expected outcome**: All returned labels are valid entries from the 19-class × 3-mode registry.
**Max execution time**: 30s

---

### FT-P-14: Engine lazy initialization on first detection request

**Summary**: Verify that the engine is not initialized at startup but is initialized on the first detection request.
**Traces to**: AC-EL-1, AC-EL-2
**Category**: Engine Lifecycle

**Preconditions**:
- Fresh service start, no prior requests

**Input data**: small-image

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | `GET /health` immediately after service starts | `aiAvailability: "None"` — engine not loaded |
| 2 | `POST /detect` with small-image | 200 OK (may take longer — engine initializing) |
| 3 | `GET /health` | `aiAvailability` changed to `"Enabled"` or status indicating engine is active |

**Expected outcome**: Engine transitions from "None" to an active state only after a detection request.
**Max execution time**: 60s

---

### FT-P-15: ONNX fallback when GPU unavailable

**Summary**: Verify that the system falls back to ONNX Runtime when no compatible GPU is available.
**Traces to**: AC-EL-2, RESTRICT-HW-1
**Category**: Engine Lifecycle

**Preconditions**:
- Detections service running WITHOUT GPU runtime (CPU-only Docker profile)
- Mock-loader serves ONNX model

**Input data**: small-image

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | `POST /detect` with small-image | 200 OK with detection results |
| 2 | `GET /health` | `aiAvailability` indicates engine is active (ONNX fallback) |

**Expected outcome**: Detection succeeds via ONNX Runtime. No TensorRT-related errors.
**Max execution time**: 60s

---

### FT-P-16: Tile deduplication removes duplicate detections at tile boundaries

**Summary**: Verify that detections appearing in overlapping tile regions are deduplicated.
**Traces to**: AC-DA-3
**Category**: Detection Accuracy

**Preconditions**:
- Engine is initialized
- Large image that triggers tiling

**Input data**: large-image with config including GSD parameters and `big_image_tile_overlap_percent: 20`

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | `POST /detect` with large-image and tiling config | 200 OK |
| 2 | Inspect detections near tile boundaries | No two detections of the same class are within 0.01 coordinate difference of each other (TILE_DUPLICATE_CONFIDENCE_THRESHOLD) |

**Expected outcome**: Tile boundary detections are merged. No duplicates with near-identical coordinates remain.
**Max execution time**: 60s

---

## Negative Scenarios

### FT-N-01: Empty image returns 400

**Summary**: Verify that submitting an empty file to POST /detect returns a 400 error.
**Traces to**: AC-API-2 (negative case)
**Category**: API

**Preconditions**:
- Detections service is running

**Input data**: empty-image (zero-byte file)

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | `POST /detect` with empty-image as multipart file | 400 Bad Request |

**Expected outcome**: HTTP 400 with error message indicating empty or invalid image.
**Max execution time**: 5s

---

### FT-N-02: Invalid image data returns 400

**Summary**: Verify that submitting a corrupt/non-image file returns a 400 error.
**Traces to**: AC-API-2 (negative case)
**Category**: API

**Preconditions**:
- Detections service is running

**Input data**: corrupt-image (random binary data)

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | `POST /detect` with corrupt-image as multipart file | 400 Bad Request |

**Expected outcome**: HTTP 400. Image decoding fails gracefully with an error response (not a 500).
**Max execution time**: 5s

---

### FT-N-03: Detection when engine unavailable returns 503

**Summary**: Verify that a detection request returns 503 when the engine cannot be initialized.
**Traces to**: AC-API-2 (negative case), AC-EL-2
**Category**: API, Engine Lifecycle

**Preconditions**:
- Mock-loader configured to return errors (model download fails)
- Engine has not been previously initialized

**Input data**: small-image

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | Configure mock-loader to return 503 on model requests | — |
| 2 | `POST /detect` with small-image | 503 Service Unavailable or 422 |

**Expected outcome**: HTTP 503 or 422 error indicating engine is not available. No crash or unhandled exception.
**Max execution time**: 30s

---

### FT-N-04: Duplicate media_id returns 409

**Summary**: Verify that submitting a second async detection request with an already-active media_id returns 409.
**Traces to**: AC-API-3 (negative case)
**Category**: API

**Preconditions**:
- Engine is initialized
- An async detection is already in progress for media_id "dup-test"

**Input data**: jwt-token, test-video

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | `POST /detect/dup-test` with config and auth headers | `{"status": "started"}` |
| 2 | Immediately `POST /detect/dup-test` again (same media_id) | 409 Conflict |

**Expected outcome**: Second request is rejected with 409. First detection continues normally.
**Max execution time**: 5s

---

### FT-N-05: Missing classes.json prevents startup

**Summary**: Verify that the service fails or returns no detections when classes.json is not present.
**Traces to**: RESTRICT-SW-4
**Category**: Restrictions

**Preconditions**:
- Detections service started WITHOUT classes.json volume mount

**Input data**: None

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | Attempt to start detections service without classes.json | Service fails to start OR starts with empty class registry |
| 2 | If started: `POST /detect` with small-image | Empty detections or error response |

**Expected outcome**: Service either fails to start or returns no detections. No unhandled crash.
**Max execution time**: 30s

---

### FT-N-06: Loader service unreachable during model download

**Summary**: Verify that the system handles Loader service being unreachable during engine initialization.
**Traces to**: RESTRICT-ENV-1, AC-EL-2
**Category**: Resilience, Engine Lifecycle

**Preconditions**:
- Mock-loader is stopped or unreachable
- Engine not yet initialized

**Input data**: small-image

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | Stop mock-loader service | — |
| 2 | `POST /detect` with small-image | Error response (503 or 422) |
| 3 | `GET /health` | `aiAvailability` reflects error state |

**Expected outcome**: Detection fails gracefully. Health endpoint reflects the engine error state.
**Max execution time**: 30s

---

### FT-N-07: Annotations service unreachable — detection continues

**Summary**: Verify that async detection continues even when the Annotations service is unreachable.
**Traces to**: RESTRICT-ENV-2
**Category**: Resilience

**Preconditions**:
- Engine is initialized
- Mock-annotations is stopped or returns errors
- SSE client connected

**Input data**: jwt-token, test-video

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | Stop mock-annotations service | — |
| 2 | `POST /detect/test-media-006` with config and auth | `{"status": "started"}` |
| 3 | Listen on SSE | Detection events still arrive (annotations POST failure is silently caught) |
| 4 | Wait for completion | Final `AIProcessed` event received |

**Expected outcome**: Detection processing completes. SSE events are delivered. Annotations POST failure does not stop the detection pipeline.
**Max execution time**: 120s

---

### FT-N-08: SSE queue overflow is silently dropped

**Summary**: Verify that when an SSE client's queue reaches 100 events, additional events are dropped without error.
**Traces to**: AC-API-4
**Category**: API

**Preconditions**:
- Engine is initialized
- SSE client connected but NOT consuming events (stalled reader)

**Input data**: test-video (generates many events)

**Steps**:

| Step | Consumer Action | Expected System Response |
|------|----------------|------------------------|
| 1 | Open SSE connection but pause reading | Connection established |
| 2 | `POST /detect/test-media-007` with config that generates > 100 events | `{"status": "started"}` |
| 3 | Wait for processing to complete | No error on the detection side |
| 4 | Resume reading SSE | Receive ≤ 100 events (queue max depth) |

**Expected outcome**: No crash or error. Overflow events are silently dropped. Detection completes normally.
**Max execution time**: 120s
