Skip to content

Commit 87fa516

Browse files
committed
Add edge-case and validation tests
Add test_edge_cases.py covering boundary values, empty strings, None inputs, and special characters for fields, queries, responses, and client initialization. Closes #40
1 parent 50cf840 commit 87fa516

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed

tests/test_edge_cases.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
"""Edge-case and validation tests for fields, queries, responses, and client."""
2+
3+
from datetime import datetime
4+
from unittest.mock import Mock
5+
6+
import pytest
7+
import requests_mock
8+
9+
from leakix import (
10+
AgeField,
11+
Client,
12+
CountryField,
13+
CustomField,
14+
ErrorResponse,
15+
IPField,
16+
MustQuery,
17+
Operator,
18+
PortField,
19+
RawQuery,
20+
Scope,
21+
SuccessResponse,
22+
TimeField,
23+
)
24+
25+
26+
class TestPortFieldEdgeCases:
27+
@pytest.mark.parametrize(
28+
"port",
29+
[-1, -100, 65536, 70000, 100000],
30+
ids=["neg-1", "neg-100", "65536", "70000", "100000"],
31+
)
32+
def test_invalid_ports(self, port):
33+
with pytest.raises((ValueError, AssertionError)):
34+
PortField(port)
35+
36+
@pytest.mark.parametrize(
37+
"port",
38+
[0, 1, 80, 443, 8080, 65535],
39+
ids=["min", "one", "http", "https", "alt-http", "max"],
40+
)
41+
def test_valid_boundary_ports(self, port):
42+
field = PortField(port)
43+
assert field.serialize() == f"port:{port}"
44+
45+
46+
class TestCustomFieldEdgeCases:
47+
def test_empty_string_value(self):
48+
field = CustomField("", "test_field")
49+
assert field.serialize() == "test_field:"
50+
51+
def test_empty_string_field_name(self):
52+
field = CustomField("value", "")
53+
assert field.serialize() == ":value"
54+
55+
def test_special_characters_in_value(self):
56+
field = CustomField("test value with spaces", "field")
57+
assert field.serialize() == "field:test value with spaces"
58+
59+
60+
class TestCountryFieldEdgeCases:
61+
def test_empty_string(self):
62+
field = CountryField("")
63+
assert field.serialize() == "country:"
64+
65+
def test_single_char(self):
66+
field = CountryField("X")
67+
assert field.serialize() == "country:X"
68+
69+
70+
class TestIPFieldEdgeCases:
71+
def test_empty_string(self):
72+
field = IPField("")
73+
assert field.serialize() == "ip:"
74+
75+
def test_ipv6_format(self):
76+
field = IPField("::1")
77+
assert field.serialize() == "ip:::1"
78+
79+
80+
class TestAgeFieldEdgeCases:
81+
def test_zero_age(self):
82+
field = AgeField(0)
83+
assert field.serialize() == "age:0"
84+
85+
def test_negative_age(self):
86+
field = AgeField(-1)
87+
assert field.serialize() == "age:-1"
88+
89+
def test_large_age(self):
90+
field = AgeField(999999)
91+
assert field.serialize() == "age:999999"
92+
93+
94+
class TestTimeFieldEdgeCases:
95+
def test_epoch_date(self):
96+
field = TimeField(datetime(1970, 1, 1))
97+
assert field.serialize() == 'time:"1970-01-01"'
98+
99+
100+
class TestQueryEdgeCases:
101+
def test_raw_query_empty_string(self):
102+
query = RawQuery("")
103+
assert query.serialize() == ""
104+
105+
def test_raw_query_special_chars(self):
106+
query = RawQuery('+host:*.example.com -port:"22"')
107+
assert query.serialize() == '+host:*.example.com -port:"22"'
108+
109+
def test_must_query_with_all_operators(self):
110+
for op in Operator:
111+
field = CustomField("val", "f", op)
112+
query = MustQuery(field)
113+
result = query.serialize()
114+
assert result.startswith("+")
115+
116+
117+
class TestClientEdgeCases:
118+
def test_none_api_key(self):
119+
client = Client(api_key=None)
120+
assert "api-key" not in client.headers
121+
122+
def test_empty_string_api_key(self):
123+
client = Client(api_key="")
124+
assert "api-key" not in client.headers
125+
126+
def test_none_base_url_uses_default(self):
127+
client = Client(base_url=None)
128+
assert client.base_url == "https://leakix.net"
129+
130+
def test_empty_base_url_uses_default(self):
131+
client = Client(base_url="")
132+
assert client.base_url == "https://leakix.net"
133+
134+
def test_get_with_none_queries(self):
135+
client = Client()
136+
with requests_mock.Mocker() as m:
137+
m.get(f"{client.base_url}/search", json=[], status_code=200)
138+
response = client.get(Scope.SERVICE, queries=None)
139+
assert response.is_success()
140+
assert m.last_request.qs["q"] == ["*"]
141+
142+
def test_get_with_empty_query_list(self):
143+
client = Client()
144+
with requests_mock.Mocker() as m:
145+
m.get(f"{client.base_url}/search", json=[], status_code=200)
146+
response = client.get(Scope.SERVICE, queries=[])
147+
assert response.is_success()
148+
assert m.last_request.qs["q"] == ["*"]
149+
150+
def test_get_page_zero(self):
151+
client = Client()
152+
with requests_mock.Mocker() as m:
153+
m.get(f"{client.base_url}/search", json=[], status_code=200)
154+
response = client.get(Scope.SERVICE, page=0)
155+
assert response.is_success()
156+
157+
def test_get_negative_page(self):
158+
client = Client()
159+
with pytest.raises(ValueError):
160+
client.get(Scope.SERVICE, page=-1)
161+
162+
163+
class TestResponseEdgeCases:
164+
def test_success_with_none_json(self):
165+
mock = Mock()
166+
mock.status_code = 200
167+
mock.json.return_value = None
168+
response = SuccessResponse(mock)
169+
assert response.json() is None
170+
171+
def test_success_with_empty_list(self):
172+
mock = Mock()
173+
mock.status_code = 200
174+
response = SuccessResponse(mock, response_json=[])
175+
assert response.json() == []
176+
177+
def test_error_with_custom_status_code(self):
178+
mock = Mock()
179+
mock.status_code = 503
180+
mock.json.return_value = {"error": "unavailable"}
181+
response = ErrorResponse(mock, status_code=503)
182+
assert response.status_code() == 503

0 commit comments

Comments
 (0)