Files
stl-repo/tests/test_api.py
Consultoria AS 14b307110d feat: implementar 12 mejoras, tests, docs y optimizaciones
- Fase A: license templates, search history, cost estimator
- Fase B: import URL, bulk ZIP, batch download
- Fase C: comparison mode, mesh validation, measurement tool
- Fase D: cross-section clipping, overhang heatmap, layer animation
- Refactor Pydantic/SQLAlchemy warnings
- 24 tests pytest
- README actualizado
- WebP thumbnails, lazy loading, cache headers
2026-04-27 09:14:58 +00:00

275 lines
7.5 KiB
Python

import os
import tempfile
import zipfile
from unittest.mock import patch, AsyncMock
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool
from app.database import Base, get_db
from app.main import app
engine = create_engine(
"sqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def override_get_db():
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_get_db
Base.metadata.create_all(bind=engine)
client = TestClient(app)
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_DIR = os.path.dirname(TEST_DIR)
_upload_counter = 0
def _get_unique_stl():
global _upload_counter
_upload_counter += 1
fd, path = tempfile.mkstemp(suffix='.stl')
with os.fdopen(fd, 'w') as f:
f.write(f"""solid test{_upload_counter}
facet normal 0 0 1
outer loop
vertex 0 0 0
vertex {_upload_counter} 0 0
vertex {_upload_counter} {_upload_counter} 0
endloop
endfacet
endsolid test{_upload_counter}
""")
return path
def test_list_empty_models():
response = client.get("/api/models/")
assert response.status_code == 200
assert response.json() == []
def test_list_tags_empty():
response = client.get("/api/models/tags")
assert response.status_code == 200
assert response.json() == []
def _upload():
stl_path = _get_unique_stl()
with open(stl_path, 'rb') as f:
response = client.post(
"/api/models/",
data={
"title": "Test Cube",
"description": "A test cube",
"author": "Tester",
"tags": "test, cube",
"category": "Piezas"
},
files={"files": (os.path.basename(stl_path), f, "application/octet-stream")}
)
assert response.status_code == 200
return response.json()['id']
def test_upload_model():
model_id = _upload()
assert isinstance(model_id, int)
def test_get_model():
model_id = _upload()
response = client.get(f"/api/models/{model_id}")
assert response.status_code == 200
data = response.json()
assert data['id'] == model_id
assert data['title'] == "Test Cube"
def test_update_model():
model_id = _upload()
response = client.put(
f"/api/models/{model_id}",
json={"title": "Updated Cube", "tag_names": ["updated"]}
)
assert response.status_code == 200
data = response.json()
assert data['title'] == "Updated Cube"
assert len(data['tags']) == 1
def test_download_model():
model_id = _upload()
response = client.get(f"/api/models/{model_id}/download")
assert response.status_code == 200
def test_thumbnail():
model_id = _upload()
response = client.get(f"/api/models/{model_id}/thumbnail")
assert response.status_code == 200
def test_qr_endpoint():
model_id = _upload()
response = client.get(f"/api/models/{model_id}/qr")
assert response.status_code == 200
def test_pagination():
response = client.get("/api/models/?skip=0&limit=10")
assert response.status_code == 200
def test_search_filter():
response = client.get("/api/models/?search=Cube")
assert response.status_code == 200
def test_delete_model():
model_id = _upload()
response = client.delete(f"/api/models/{model_id}")
assert response.status_code == 200
response = client.get(f"/api/models/{model_id}")
assert response.status_code == 404
def test_backup_endpoint():
response = client.get("/api/models/system/backup")
assert response.status_code == 200
# Phase 5: Social features
def test_create_rating():
model_id = _upload()
response = client.post(f"/api/models/{model_id}/ratings?stars=5")
assert response.status_code == 200
data = response.json()
assert data['stars'] == 5
response = client.get(f"/api/models/{model_id}")
assert response.json()['avg_rating'] == 5.0
def test_create_comment():
model_id = _upload()
response = client.post(f"/api/models/{model_id}/comments?text=Great%20model&author_name=User1")
assert response.status_code == 200
data = response.json()
assert data['text'] == "Great model"
assert data['author_name'] == "User1"
def test_list_comments():
model_id = _upload()
client.post(f"/api/models/{model_id}/comments?text=Comment1")
client.post(f"/api/models/{model_id}/comments?text=Comment2")
response = client.get(f"/api/models/{model_id}/comments")
assert response.status_code == 200
assert len(response.json()) == 2
def _create_collection():
response = client.post("/api/models/collections", json={"name": "My Collection", "description": "Test"})
assert response.status_code == 200
data = response.json()
assert data['name'] == "My Collection"
return data['id']
def test_add_to_collection():
model_id = _upload()
coll_id = _create_collection()
response = client.post(f"/api/models/collections/{coll_id}/add/{model_id}")
assert response.status_code == 200
response = client.get(f"/api/models/collections/{coll_id}")
assert response.status_code == 200
assert len(response.json()['models']) == 1
# Phase 6: Import / Bulk / Batch / Validation / Estimate
def test_import_from_url():
stl_path = _get_unique_stl()
with open(stl_path, 'rb') as f:
stl_bytes = f.read()
from unittest.mock import Mock
mock_response = Mock()
mock_response.content = stl_bytes
mock_response.raise_for_status = Mock()
async_mock_client = AsyncMock()
async_mock_client.get = AsyncMock(return_value=mock_response)
with patch('app.routers.models.httpx.AsyncClient', return_value=async_mock_client):
async_mock_client.__aenter__ = AsyncMock(return_value=async_mock_client)
async_mock_client.__aexit__ = AsyncMock(return_value=False)
response = client.post(
"/api/models/import-url",
data={"url": "http://example.com/model.stl", "title": "Imported Model"}
)
assert response.status_code == 200
data = response.json()
assert data['title'] == "Imported Model"
def test_bulk_zip_upload():
stl_path = _get_unique_stl()
zip_path = tempfile.mktemp(suffix='.zip')
with zipfile.ZipFile(zip_path, 'w') as zf:
zf.write(stl_path, os.path.basename(stl_path))
with open(zip_path, 'rb') as f:
response = client.post(
"/api/models/bulk-zip",
data={"author": "BulkTester"},
files={"zip_file": ("models.zip", f, "application/zip")}
)
assert response.status_code == 200
data = response.json()
assert len(data) >= 1
os.remove(zip_path)
def test_batch_download():
model_id1 = _upload()
model_id2 = _upload()
response = client.post(
"/api/models/batch-download",
json=[model_id1, model_id2]
)
assert response.status_code == 200
assert response.headers['content-type'] == 'application/zip'
def test_validate_mesh():
model_id = _upload()
response = client.get(f"/api/models/{model_id}/validate")
assert response.status_code == 200
data = response.json()
assert 'is_watertight' in data
assert 'volume_cm3' in data
def test_estimate_print():
model_id = _upload()
response = client.get(f"/api/models/{model_id}/estimate")
assert response.status_code == 200
data = response.json()
assert 'volume_cm3' in data
assert 'cost' in data
assert 'grams' in data