Spaces:
Sleeping
Sleeping
| """Tests for URLScan.io API client.""" | |
| import pytest | |
| import responses | |
| from unittest.mock import patch | |
| import sys | |
| from pathlib import Path | |
| from src.phising_detection.utils.urlscan import URLScanClient, URLScanError | |
| class TestURLScanClient: | |
| """Tests for URLScanClient class.""" | |
| def test_init_with_api_key(self): | |
| """Test initialization with API key provided.""" | |
| client = URLScanClient(api_key="test_key") | |
| assert client.api_key == "test_key" | |
| def test_init_with_env_var(self, monkeypatch): | |
| """Test initialization with API key from environment.""" | |
| monkeypatch.setenv("URLSCAN_API_KEY", "env_key") | |
| client = URLScanClient() | |
| assert client.api_key == "env_key" | |
| def test_init_without_api_key(self, monkeypatch): | |
| """Test initialization fails without API key.""" | |
| monkeypatch.delenv("URLSCAN_API_KEY", raising=False) | |
| with pytest.raises(URLScanError) as exc_info: | |
| URLScanClient() | |
| assert "No API key provided" in str(exc_info.value) | |
| def test_submit_url_success(self): | |
| """Test successful URL submission.""" | |
| # Mock API response | |
| responses.add( | |
| responses.POST, | |
| "https://urlscan.io/api/v1/scan/", | |
| json={ | |
| "uuid": "test-uuid-123", | |
| "result": "https://urlscan.io/result/test-uuid-123/", | |
| "api": "https://urlscan.io/api/v1/result/test-uuid-123/" | |
| }, | |
| status=200 | |
| ) | |
| client = URLScanClient(api_key="test_key") | |
| result = client.submit_url("https://example.com") | |
| assert result["uuid"] == "test-uuid-123" | |
| assert "result" in result | |
| assert len(responses.calls) == 1 | |
| assert responses.calls[0].request.url == "https://urlscan.io/api/v1/scan/" | |
| def test_submit_url_with_tags(self): | |
| """Test URL submission with tags.""" | |
| responses.add( | |
| responses.POST, | |
| "https://urlscan.io/api/v1/scan/", | |
| json={"uuid": "test-uuid", "result": "url"}, | |
| status=200 | |
| ) | |
| client = URLScanClient(api_key="test_key") | |
| client.submit_url( | |
| "https://example.com", | |
| visibility="unlisted", | |
| tags=["phishing", "test"] | |
| ) | |
| request_body = responses.calls[0].request.body | |
| assert b"phishing" in request_body | |
| assert b"test" in request_body | |
| def test_submit_url_rate_limit(self): | |
| """Test rate limit error handling.""" | |
| responses.add( | |
| responses.POST, | |
| "https://urlscan.io/api/v1/scan/", | |
| status=429 | |
| ) | |
| client = URLScanClient(api_key="test_key") | |
| with pytest.raises(URLScanError) as exc_info: | |
| client.submit_url("https://example.com") | |
| assert "Rate limit exceeded" in str(exc_info.value) | |
| def test_submit_url_bad_request(self): | |
| """Test bad request error handling.""" | |
| responses.add( | |
| responses.POST, | |
| "https://urlscan.io/api/v1/scan/", | |
| body="Invalid URL", | |
| status=400 | |
| ) | |
| client = URLScanClient(api_key="test_key") | |
| with pytest.raises(URLScanError) as exc_info: | |
| client.submit_url("not-a-valid-url") | |
| assert "Bad request" in str(exc_info.value) | |
| def test_get_result_success(self): | |
| """Test successful result retrieval.""" | |
| responses.add( | |
| responses.GET, | |
| "https://urlscan.io/api/v1/result/test-uuid/", | |
| json={ | |
| "page": { | |
| "url": "https://example.com", | |
| "title": "Example Domain" | |
| }, | |
| "verdicts": { | |
| "overall": { | |
| "score": 0, | |
| "malicious": False | |
| } | |
| }, | |
| "task": {"uuid": "test-uuid"} | |
| }, | |
| status=200 | |
| ) | |
| client = URLScanClient(api_key="test_key") | |
| result = client.get_result("test-uuid") | |
| assert result["page"]["url"] == "https://example.com" | |
| assert result["verdicts"]["overall"]["malicious"] is False | |
| def test_get_result_not_found(self): | |
| """Test result not found error.""" | |
| responses.add( | |
| responses.GET, | |
| "https://urlscan.io/api/v1/result/test-uuid/", | |
| status=404 | |
| ) | |
| client = URLScanClient(api_key="test_key") | |
| with pytest.raises(URLScanError) as exc_info: | |
| client.get_result("test-uuid") | |
| assert "not found or not ready" in str(exc_info.value) | |
| def test_search_success(self): | |
| """Test search functionality.""" | |
| responses.add( | |
| responses.GET, | |
| "https://urlscan.io/api/v1/search/", | |
| json={ | |
| "total": 2, | |
| "results": [ | |
| {"task": {"url": "https://example.com"}}, | |
| {"task": {"url": "https://example.org"}} | |
| ] | |
| }, | |
| status=200 | |
| ) | |
| client = URLScanClient(api_key="test_key") | |
| results = client.search(query="domain:example.com", size=10) | |
| assert results["total"] == 2 | |
| assert len(results["results"]) == 2 | |
| def test_get_verdict_malicious(self): | |
| """Test verdict extraction for malicious URL.""" | |
| responses.add( | |
| responses.GET, | |
| "https://urlscan.io/api/v1/result/test-uuid/", | |
| json={ | |
| "verdicts": { | |
| "overall": { | |
| "score": 100, | |
| "malicious": True | |
| } | |
| } | |
| }, | |
| status=200 | |
| ) | |
| client = URLScanClient(api_key="test_key") | |
| verdict = client.get_verdict("test-uuid") | |
| assert verdict == "malicious" | |
| def test_get_verdict_safe(self): | |
| """Test verdict extraction for safe URL.""" | |
| responses.add( | |
| responses.GET, | |
| "https://urlscan.io/api/v1/result/test-uuid/", | |
| json={ | |
| "verdicts": { | |
| "overall": { | |
| "score": 0, | |
| "malicious": False | |
| } | |
| } | |
| }, | |
| status=200 | |
| ) | |
| client = URLScanClient(api_key="test_key") | |
| verdict = client.get_verdict("test-uuid") | |
| assert verdict == "safe" | |
| def test_submit_and_wait_success(self): | |
| """Test submit and wait for results.""" | |
| # Mock submission | |
| responses.add( | |
| responses.POST, | |
| "https://urlscan.io/api/v1/scan/", | |
| json={ | |
| "uuid": "test-uuid", | |
| "result": "https://urlscan.io/result/test-uuid/" | |
| }, | |
| status=200 | |
| ) | |
| # Mock result retrieval | |
| responses.add( | |
| responses.GET, | |
| "https://urlscan.io/api/v1/result/test-uuid/", | |
| json={ | |
| "page": {"title": "Example"}, | |
| "task": {"uuid": "test-uuid"} | |
| }, | |
| status=200 | |
| ) | |
| client = URLScanClient(api_key="test_key") | |
| result = client.submit_and_wait( | |
| "https://example.com", | |
| max_wait=10, | |
| poll_interval=1 | |
| ) | |
| assert result["page"]["title"] == "Example" | |
| def test_submit_and_wait_timeout(self): | |
| """Test timeout in submit_and_wait.""" | |
| # Mock submission | |
| responses.add( | |
| responses.POST, | |
| "https://urlscan.io/api/v1/scan/", | |
| json={"uuid": "test-uuid"}, | |
| status=200 | |
| ) | |
| # Mock result always returning 404 | |
| responses.add( | |
| responses.GET, | |
| "https://urlscan.io/api/v1/result/test-uuid/", | |
| status=404 | |
| ) | |
| client = URLScanClient(api_key="test_key") | |
| with pytest.raises(URLScanError) as exc_info: | |
| client.submit_and_wait( | |
| "https://example.com", | |
| max_wait=3, | |
| poll_interval=1 | |
| ) | |
| assert "Timeout" in str(exc_info.value) | |