diff --git a/frontend/src/components/tools/ToolPanels.tsx b/frontend/src/components/tools/ToolPanels.tsx index e4427e83..9ce1c89f 100644 --- a/frontend/src/components/tools/ToolPanels.tsx +++ b/frontend/src/components/tools/ToolPanels.tsx @@ -173,6 +173,8 @@ function QrPanel() { {result.error ? ( + ) : !result.decoded ? ( +

{result.status_reason}

) : (
diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index 19103c20..087a557e 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -386,7 +386,7 @@ export interface DarkWebResult { error?: string; } -export interface QrResult { +export interface QrResult extends ModuleStatusFields { decoded?: string; type?: string; is_url?: boolean; diff --git a/modules/qr_decoder.py b/modules/qr_decoder.py index 6b77bfbf..999e7fa6 100644 --- a/modules/qr_decoder.py +++ b/modules/qr_decoder.py @@ -1,6 +1,7 @@ import requests from typing import Dict, Any, Optional, Tuple from modules import get_proxies +from modules.module_status import annotate, OK, ERROR class QRDecoder: @@ -37,7 +38,7 @@ def _decode_api(self, image_bytes: bytes, filename: str) -> Tuple[Optional[str], return None, "Unexpected API response" symbols = data[0].get("symbol", []) if not symbols: - return None, "No QR code detected in the image" + return None, None err = symbols[0].get("error") if err: return None, err @@ -80,13 +81,11 @@ def decode(self, image_bytes: bytes, filename: str = "qr.png") -> Dict[str, Any] decoded, err = self._decode_api(image_bytes, filename) result["source"] = "api" if err: - result["error"] = err - return result + return annotate(result, ERROR, err) if not decoded: - result["error"] = "No QR code detected in the image" - return result + return annotate(result, OK, "No QR code detected in the image") result["decoded"] = decoded result["type"], result["is_url"] = self._classify(decoded) - return result + return annotate(result, OK) diff --git a/tests/test_qr_decoder.py b/tests/test_qr_decoder.py new file mode 100644 index 00000000..87ebd50c --- /dev/null +++ b/tests/test_qr_decoder.py @@ -0,0 +1,69 @@ +from unittest.mock import MagicMock, patch + +from modules.module_status import ERROR, OK +from modules.qr_decoder import QRDecoder + + +def _api_response(payload, status_code=200): + response = MagicMock() + response.status_code = status_code + response.json.return_value = payload + return response + + +def test_local_decode_is_ok(): + decoder = QRDecoder() + with patch.object(decoder, "_decode_local", return_value="https://example.com"): + result = decoder.decode(b"img") + assert result["status"] == OK + assert result["error"] is None + assert result["decoded"] == "https://example.com" + assert result["is_url"] is True + assert result["source"] == "local" + + +def test_no_qr_code_is_ok_with_reason(): + decoder = QRDecoder() + with patch.object(decoder, "_decode_local", return_value=None), patch( + "modules.qr_decoder.requests.post", + return_value=_api_response([{"symbol": []}]), + ): + result = decoder.decode(b"img") + assert result["status"] == OK + assert result["status_reason"] == "No QR code detected in the image" + assert result["error"] is None + assert result["decoded"] is None + + +def test_api_http_error_is_error(): + decoder = QRDecoder() + with patch.object(decoder, "_decode_local", return_value=None), patch( + "modules.qr_decoder.requests.post", + return_value=_api_response(None, status_code=500), + ): + result = decoder.decode(b"img") + assert result["status"] == ERROR + assert result["error"] == "API returned HTTP 500" + + +def test_api_exception_is_error(): + decoder = QRDecoder() + with patch.object(decoder, "_decode_local", return_value=None), patch( + "modules.qr_decoder.requests.post", side_effect=ConnectionError("network down") + ): + result = decoder.decode(b"img") + assert result["status"] == ERROR + assert result["error"] == "network down" + + +def test_api_decode_is_ok(): + decoder = QRDecoder() + with patch.object(decoder, "_decode_local", return_value=None), patch( + "modules.qr_decoder.requests.post", + return_value=_api_response([{"symbol": [{"data": "hello", "error": None}]}]), + ): + result = decoder.decode(b"img") + assert result["status"] == OK + assert result["decoded"] == "hello" + assert result["type"] == "Plain text" + assert result["source"] == "api"