Project_phising_detection / tests /test_urlscan.py
Simon
Model evealuation (#10)
a9f0e5a
Raw
History Blame Contribute Delete
8.44 kB
"""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)
@responses.activate
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/"
@responses.activate
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
@responses.activate
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)
@responses.activate
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)
@responses.activate
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
@responses.activate
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)
@responses.activate
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
@responses.activate
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"
@responses.activate
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"
@responses.activate
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"
@responses.activate
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)