verifile-x-api / backend /tests /test_robustness.py
abinazebinoy's picture
feat(robustness): adversarial robustness testing suite
7308a87
Raw
History Blame Contribute Delete
5.98 kB
"""
Tests for adversarial robustness testing suite.
Tests run without GPU and use small images for speed.
All attack functions must be testable without ML models.
"""
import pytest
import numpy as np
from PIL import Image
from io import BytesIO
def _make_image(width: int = 100, height: int = 100, seed: int = 42) -> bytes:
rng = np.random.default_rng(seed)
arr = rng.integers(50, 200, (height, width, 3), dtype=np.uint8)
buf = BytesIO()
Image.fromarray(arr, "RGB").save(buf, format="JPEG", quality=85)
return buf.getvalue()
def _img_array(image_bytes: bytes) -> np.ndarray:
return np.array(Image.open(BytesIO(image_bytes)).convert("RGB"))
# ── Attack function unit tests ────────────────────────────────────────────────
def test_jpeg_recompression_changes_image():
from backend.services.adversarial_tester import _apply_jpeg_recompression
arr = _img_array(_make_image())
result = _apply_jpeg_recompression(arr.copy(), 0.6)
assert result.shape == arr.shape
assert result.dtype == np.uint8
def test_gaussian_noise_bounded():
from backend.services.adversarial_tester import _apply_gaussian_noise
arr = _img_array(_make_image())
result = _apply_gaussian_noise(arr.copy(), 0.5)
assert result.shape == arr.shape
assert result.min() >= 0 and result.max() <= 255
def test_gaussian_blur_produces_valid_image():
from backend.services.adversarial_tester import _apply_gaussian_blur
arr = _img_array(_make_image())
result = _apply_gaussian_blur(arr.copy(), 0.5)
assert result.shape == arr.shape
assert result.dtype == np.uint8
def test_color_jitter_preserves_shape():
from backend.services.adversarial_tester import _apply_color_jitter
arr = _img_array(_make_image())
result = _apply_color_jitter(arr.copy(), 0.5)
assert result.shape == arr.shape
def test_downscale_upscale_preserves_dimensions():
from backend.services.adversarial_tester import _apply_downscale_upscale
arr = _img_array(_make_image())
result = _apply_downscale_upscale(arr.copy(), 0.5)
assert result.shape == arr.shape
def test_random_crop_preserves_dimensions():
from backend.services.adversarial_tester import _apply_random_crop
arr = _img_array(_make_image())
result = _apply_random_crop(arr.copy(), 0.5)
assert result.shape == arr.shape
def test_histogram_equalization_bounded():
from backend.services.adversarial_tester import _apply_histogram_equalization
arr = _img_array(_make_image())
result = _apply_histogram_equalization(arr.copy(), 0.5)
assert result.shape == arr.shape
assert result.min() >= 0 and result.max() <= 255
def test_pixel_shuffle_preserves_shape():
from backend.services.adversarial_tester import _apply_pixel_shuffle
arr = _img_array(_make_image())
result = _apply_pixel_shuffle(arr.copy(), 0.5)
assert result.shape == arr.shape
# ── Robustness test integration ───────────────────────────────────────────────
def test_robustness_result_structure():
"""robustness test returns correct schema."""
from backend.services.adversarial_tester import run_robustness_test
result = run_robustness_test(_make_image(), "test.jpg", attacks=["gaussian_noise"])
assert "overall_robustness" in result
assert "baseline_score" in result
assert "baseline_class" in result
assert "attack_results" in result
assert "robustness_level" in result
assert 0.0 <= result["overall_robustness"] <= 1.0
def test_robustness_level_valid():
from backend.services.adversarial_tester import run_robustness_test
result = run_robustness_test(_make_image(), "test.jpg", attacks=["gaussian_blur"])
assert result["robustness_level"] in {"high", "medium", "low"}
def test_robustness_attack_results_present():
from backend.services.adversarial_tester import run_robustness_test
result = run_robustness_test(
_make_image(), "test.jpg",
attacks=["jpeg_recompression", "gaussian_noise"]
)
assert "jpeg_recompression" in result["attack_results"]
assert "gaussian_noise" in result["attack_results"]
for attack in result["attack_results"].values():
assert "results" in attack
assert "mean_robustness" in attack
assert 0.0 <= attack["mean_robustness"] <= 1.0
def test_robustness_intensity_scores_bounded():
from backend.services.adversarial_tester import run_robustness_test
result = run_robustness_test(_make_image(), "test.jpg", attacks=["color_jitter"])
for r in result["attack_results"]["color_jitter"]["results"]:
assert 0.0 <= r["robustness_score"] <= 1.0
assert r["intensity"] in {0.3, 0.6, 1.0}
def test_robustness_api_endpoint(client):
"""API endpoint responds within timeout and returns correct schema."""
img = _make_image()
response = client.post(
"/api/v1/analyze/robustness",
files={"file": ("test.jpg", img, "image/jpeg")}
)
assert response.status_code == 200
data = response.json()
assert "overall_robustness" in data
assert "robustness_level" in data
assert "attack_results" in data
def test_robustness_api_rejects_non_image(client):
response = client.post(
"/api/v1/analyze/robustness",
files={"file": ("test.txt", b"text", "text/plain")}
)
assert response.status_code == 415
@pytest.mark.slow
def test_all_eight_attacks_run():
"""Full suite test β€” marked slow, skipped in CI fast run."""
from backend.services.adversarial_tester import run_robustness_test, _ATTACK_CONFIGS
result = run_robustness_test(_make_image(128, 128), "test.jpg")
assert result["attacks_tested"] == len(_ATTACK_CONFIGS)
assert "summary" in result
assert "recommendation" in result