test_algorithms.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. """真实图片计算、HTTP 契约及计算隔离回归。"""
  2. import io
  3. import json
  4. from pathlib import Path
  5. import subprocess
  6. import zipfile
  7. import threading
  8. import cv2
  9. import numpy as np
  10. import pytest
  11. from fastapi import FastAPI
  12. from fastapi.testclient import TestClient
  13. from algorithms.characteristic_curve_recognition import service
  14. from api.algorithm_routes import router
  15. @pytest.fixture
  16. def client(tmp_path, monkeypatch):
  17. monkeypatch.setattr("config.ALGORITHM_JOB_DIR", str(tmp_path / "jobs"))
  18. app = FastAPI()
  19. app.include_router(router, prefix="/api")
  20. with TestClient(app) as value:
  21. yield value
  22. @pytest.fixture
  23. def chart():
  24. image = np.full((320, 420, 3), 255, np.uint8)
  25. cv2.line(image, (45, 25), (45, 275), (0, 0, 0), 2)
  26. cv2.line(image, (45, 275), (390, 275), (0, 0, 0), 2)
  27. for offset in (0, 30):
  28. points = np.array([[x, int(45 + offset + .0012 * (x - 50) ** 2)]
  29. for x in range(60, 380)], np.int32)
  30. cv2.polylines(image, [points], False, (0, 0, 0), 2)
  31. return cv2.imencode(".png", image)[1].tobytes()
  32. def test_real_json_and_zip(client, chart):
  33. response = client.post("/api/algorithms/characteristic-curve-recognition", files={"file": ("../../图.png", chart)})
  34. assert response.status_code == 200, response.text
  35. result = response.json()
  36. assert result["algorithm"] == "characteristic_curve_recognition"
  37. assert result["model"]["execution_status"] == "completed"
  38. assert result["model"]["quality_status"] == "needs_review"
  39. assert result["model"]["physical_coordinate_status"] == "not_calibrated"
  40. curves = result["model"]["curves"]
  41. assert len(curves) == 2
  42. assert all(1 <= curve["segment_count"] <= 3 for curve in curves)
  43. assert all(not curve["model_samples_used_as_observations"] for curve in curves)
  44. assert result["geometry"]["grid"]["cells"]
  45. assert "vent-characteristic-curve-" not in response.text
  46. response = client.post("/api/algorithms/characteristic-curve-recognition?output=zip&detailed=true",
  47. files={"file": ("图.png", chart)})
  48. assert response.status_code == 200
  49. assert response.headers["content-type"] == "application/zip"
  50. with zipfile.ZipFile(io.BytesIO(response.content)) as archive:
  51. names = archive.namelist()
  52. assert {"index.html", "result.json", "final_overlay.png", "final_redraw.png",
  53. "review/pipeline_overview.png"} <= set(names)
  54. assert all(not name.startswith("/") and ".." not in Path(name).parts for name in names)
  55. assert json.loads(archive.read("final_model.json")) == result["model"]
  56. @pytest.mark.parametrize("content,code", [(b"", "invalid_image"),
  57. (b"hello", "invalid_image"), (b"\x89PNG\r\n\x1a\ninvalid", "invalid_image")])
  58. def test_invalid_image(client, content, code):
  59. response = client.post("/api/algorithms/characteristic-curve-recognition", files={"file": ("a.png", content)})
  60. assert response.status_code == 422
  61. assert response.json()["detail"]["code"] == code
  62. def test_blank_chart(client):
  63. content = cv2.imencode(".png", np.full((200, 200, 3), 255, np.uint8))[1].tobytes()
  64. response = client.post("/api/algorithms/characteristic-curve-recognition", files={"file": ("a.png", content)})
  65. assert response.status_code == 422
  66. assert response.json()["detail"]["code"] == "detection_failed"
  67. @pytest.mark.parametrize("shape,extension", [((16, 48, 3), ".jpg"), ((4001, 4000), ".png")])
  68. def test_image_dimensions(client, shape, extension):
  69. content = cv2.imencode(extension, np.full(shape, 255, np.uint8))[1].tobytes()
  70. response = client.post("/api/algorithms/characteristic-curve-recognition", files={"file": ("a" + extension, content)})
  71. assert response.status_code == 422
  72. assert response.json()["detail"]["code"] == "invalid_image"
  73. def test_limits_and_parameters(client, chart, monkeypatch):
  74. monkeypatch.setattr("api.algorithm_routes.MAX_UPLOAD_BYTES", 8)
  75. monkeypatch.setattr(service, "MAX_UPLOAD_BYTES", 8)
  76. response = client.post("/api/algorithms/characteristic-curve-recognition", files={"file": ("a.png", chart)})
  77. assert response.status_code == 413
  78. assert client.post("/api/algorithms/characteristic-curve-recognition").status_code == 422
  79. assert client.post("/api/algorithms/characteristic-curve-recognition?output=exe", files={"file": ("a.png", chart)}).status_code == 422
  80. def test_busy(client, chart, monkeypatch):
  81. monkeypatch.setattr(service, "_slots", threading.BoundedSemaphore(1))
  82. assert service._slots.acquire(blocking=False)
  83. try:
  84. response = client.post("/api/algorithms/characteristic-curve-recognition", files={"file": ("a.png", chart)})
  85. assert response.status_code == 429
  86. assert response.headers["retry-after"] == "5"
  87. finally:
  88. service._slots.release()
  89. def test_timeout_cleans_temp_and_releases_slot(client, chart, monkeypatch):
  90. paths = []
  91. def timeout(command, **kwargs):
  92. paths.append(Path(command[4]).parent)
  93. assert Path(command[4]).is_file()
  94. raise subprocess.TimeoutExpired(command, kwargs["timeout"])
  95. monkeypatch.setattr(service.subprocess, "run", timeout)
  96. for _ in range(2):
  97. response = client.post("/api/algorithms/characteristic-curve-recognition", files={"file": ("a.png", chart)})
  98. assert response.status_code == 504
  99. assert all(not path.exists() for path in paths)
  100. def test_missing_dependencies(client, chart, monkeypatch):
  101. monkeypatch.setattr(service.importlib.util, "find_spec", lambda name: None)
  102. response = client.post("/api/algorithms/characteristic-curve-recognition", files={"file": ("a.png", chart)})
  103. assert response.status_code == 503
  104. def test_registered_in_main_app():
  105. from main import app
  106. assert "/api/algorithms/characteristic-curve-recognition" in app.openapi()["paths"]