Spaces:
Sleeping
Sleeping
| """ | |
| 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 | |
| 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 | |