3cf4a9a40a
python / test (push) Failing after 8s
- Updated README.md to reflect new features and API changes. - Introduced versioning in app initialization. - Enhanced configuration management in app/config.py with new validation functions. - Refactored main.py to improve request handling and response generation. - Added new models in app/models.py for structured API responses. - Implemented a dedicated UI rendering function in app/ui.py. - Improved Docker configuration for better security and health checks. - Updated tests to cover new validation rules and response formats. - Added CLAUDE.md for project guidelines and working rules.
217 lines
6.6 KiB
Python
217 lines
6.6 KiB
Python
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
import app.main as main
|
|
from app import __version__
|
|
|
|
|
|
class DummySettings(SimpleNamespace):
|
|
@property
|
|
def auth_enabled(self):
|
|
return bool(self.auth_token)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_settings(monkeypatch):
|
|
monkeypatch.setattr(
|
|
main,
|
|
"settings",
|
|
DummySettings(
|
|
env="test",
|
|
docs_enabled=True,
|
|
test_ui_enabled=True,
|
|
auth_token="",
|
|
auth_header_name="X-API-Key",
|
|
max_upload_bytes=1024 * 1024,
|
|
allowed_mime_types=("image/jpeg", "image/png", "image/webp", "image/gif"),
|
|
),
|
|
)
|
|
|
|
|
|
@pytest.fixture()
|
|
def client():
|
|
return TestClient(main.app)
|
|
|
|
|
|
def test_health_reflects_settings(client):
|
|
resp = client.get("/health")
|
|
assert resp.status_code == 200
|
|
assert resp.json() == {
|
|
"ok": True,
|
|
"env": "test",
|
|
"version": __version__,
|
|
"docs_enabled": True,
|
|
"test_ui_enabled": True,
|
|
"auth_enabled": False,
|
|
"auth_header": None,
|
|
"max_upload_bytes": 1024 * 1024,
|
|
"allowed_mime_types": ["image/jpeg", "image/png", "image/webp", "image/gif"],
|
|
}
|
|
|
|
|
|
def test_index_shows_disabled_message_when_ui_off(monkeypatch):
|
|
monkeypatch.setattr(
|
|
main,
|
|
"settings",
|
|
DummySettings(
|
|
env="test",
|
|
docs_enabled=True,
|
|
test_ui_enabled=False,
|
|
auth_token="",
|
|
auth_header_name="X-API-Key",
|
|
max_upload_bytes=1024 * 1024,
|
|
allowed_mime_types=("image/jpeg", "image/png", "image/webp", "image/gif"),
|
|
),
|
|
)
|
|
resp = TestClient(main.app).get("/")
|
|
assert resp.status_code == 200
|
|
assert "Test UI is disabled." in resp.text
|
|
assert resp.headers["cache-control"] == "no-store"
|
|
|
|
|
|
def test_focus_accepts_bearer_auth_and_returns_payload(monkeypatch, client):
|
|
monkeypatch.setattr(
|
|
main,
|
|
"settings",
|
|
DummySettings(
|
|
env="test",
|
|
docs_enabled=True,
|
|
test_ui_enabled=True,
|
|
auth_token="secret",
|
|
auth_header_name="X-API-Key",
|
|
max_upload_bytes=1024 * 1024,
|
|
allowed_mime_types=("image/jpeg", "image/png", "image/webp", "image/gif"),
|
|
),
|
|
)
|
|
|
|
def fake_process_image(image_bytes, filename, buffer_ratio=0.15, detector="subject"):
|
|
assert image_bytes == b"image-bytes"
|
|
assert filename == "sample.jpg"
|
|
assert buffer_ratio == 0.2
|
|
assert detector == "face"
|
|
return {
|
|
"filename": filename,
|
|
"detector": detector,
|
|
"method": "face_cascade",
|
|
"buffer_ratio": buffer_ratio,
|
|
"detected_bbox": {"x": 1, "y": 2, "w": 3, "h": 4},
|
|
"square_bbox": {"x": 0, "y": 0, "w": 8, "h": 8},
|
|
"source_size": {"width": 10, "height": 12},
|
|
"crop_data_url": "data:image/jpeg;base64,AAA=",
|
|
"annotated_data_url": "data:image/jpeg;base64,BBB=",
|
|
"mime_type": "image/jpeg",
|
|
"crop_bytes": b"crop",
|
|
}
|
|
|
|
monkeypatch.setattr("app.vision.process_image", fake_process_image)
|
|
|
|
resp = client.post(
|
|
"/api/focus",
|
|
headers={"Authorization": "Bearer secret"},
|
|
files={"file": ("sample.jpg", b"image-bytes", "image/jpeg")},
|
|
data={"buffer_ratio": "0.2", "detector": "face"},
|
|
)
|
|
assert resp.status_code == 200
|
|
assert resp.json()["method"] == "face_cascade"
|
|
assert resp.json()["detected_bbox"] == {"x": 1, "y": 2, "w": 3, "h": 4}
|
|
assert resp.json()["mime_type"] == "image/jpeg"
|
|
|
|
|
|
def test_focus_rejects_unauthorized_request(monkeypatch, client):
|
|
monkeypatch.setattr(
|
|
main,
|
|
"settings",
|
|
DummySettings(
|
|
env="test",
|
|
docs_enabled=True,
|
|
test_ui_enabled=True,
|
|
auth_token="secret",
|
|
auth_header_name="X-API-Key",
|
|
max_upload_bytes=1024 * 1024,
|
|
allowed_mime_types=("image/jpeg", "image/png", "image/webp", "image/gif"),
|
|
),
|
|
)
|
|
|
|
resp = client.post(
|
|
"/api/focus",
|
|
files={"file": ("sample.jpg", b"image-bytes", "image/jpeg")},
|
|
)
|
|
assert resp.status_code == 401
|
|
assert resp.json() == {"detail": "unauthorized"}
|
|
|
|
|
|
def test_focus_rejects_unsupported_content_type(monkeypatch, client):
|
|
monkeypatch.setattr(
|
|
main,
|
|
"settings",
|
|
DummySettings(
|
|
env="test",
|
|
docs_enabled=True,
|
|
test_ui_enabled=True,
|
|
auth_token="",
|
|
auth_header_name="X-API-Key",
|
|
max_upload_bytes=1024 * 1024,
|
|
allowed_mime_types=("image/jpeg",),
|
|
),
|
|
)
|
|
|
|
resp = client.post(
|
|
"/api/focus",
|
|
files={"file": ("sample.txt", b"image-bytes", "text/plain")},
|
|
)
|
|
assert resp.status_code == 415
|
|
assert resp.json() == {"detail": "unsupported content type: text/plain"}
|
|
|
|
|
|
def test_focus_rejects_large_upload(monkeypatch, client):
|
|
monkeypatch.setattr(
|
|
main,
|
|
"settings",
|
|
DummySettings(
|
|
env="test",
|
|
docs_enabled=True,
|
|
test_ui_enabled=True,
|
|
auth_token="",
|
|
auth_header_name="X-API-Key",
|
|
max_upload_bytes=4,
|
|
allowed_mime_types=("image/jpeg",),
|
|
),
|
|
)
|
|
|
|
resp = client.post(
|
|
"/api/focus",
|
|
files={"file": ("sample.jpg", b"image-bytes", "image/jpeg")},
|
|
)
|
|
assert resp.status_code == 413
|
|
assert resp.json() == {"detail": "upload exceeds FACE_LOCK_MAX_UPLOAD_BYTES (4 bytes)"}
|
|
|
|
|
|
def test_focus_image_returns_jpeg_stream(monkeypatch, client):
|
|
def fake_process_image(image_bytes, filename, buffer_ratio=0.15, detector="subject"):
|
|
return {
|
|
"filename": filename,
|
|
"detector": detector,
|
|
"method": "subject_contour",
|
|
"buffer_ratio": buffer_ratio,
|
|
"detected_bbox": {"x": 1, "y": 2, "w": 3, "h": 4},
|
|
"square_bbox": {"x": 0, "y": 0, "w": 8, "h": 8},
|
|
"source_size": {"width": 10, "height": 12},
|
|
"crop_data_url": "data:image/jpeg;base64,AAA=",
|
|
"annotated_data_url": "data:image/jpeg;base64,BBB=",
|
|
"mime_type": "image/jpeg",
|
|
"crop_bytes": b"crop-bytes",
|
|
}
|
|
|
|
monkeypatch.setattr("app.vision.process_image", fake_process_image)
|
|
|
|
resp = client.post(
|
|
"/api/focus/image",
|
|
files={"file": ("sample.jpg", b"image-bytes", "image/jpeg")},
|
|
)
|
|
assert resp.status_code == 200
|
|
assert resp.headers["content-type"] == "image/jpeg"
|
|
assert resp.headers["content-disposition"] == 'inline; filename="sample-crop.jpg"'
|
|
assert resp.content == b"crop-bytes"
|