Skip to content

Commit

Permalink
test: increase unit test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
uniqueg committed Feb 12, 2023
1 parent a90cd3e commit 756d3c4
Show file tree
Hide file tree
Showing 9 changed files with 422 additions and 89 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,9 @@ jobs:
run: flake8 .

- name: Run unit tests
run: coverage run --source tes -m pytest -W ignore::DeprecationWarning
run: |
pytest \
--cov=tes/ \
--cov-branch \
--cov-report=term-missing \
--cov-fail-under=99
6 changes: 3 additions & 3 deletions tes/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,14 @@ def check_success(data: Task) -> bool:
time.sleep(0.5)

def _request_params(
self, data: Optional[str] = None,
params: Optional[Dict] = None
self, data: Optional[str] = None, params: Optional[Dict] = None
) -> Dict[str, Any]:
kwargs: Dict[str, Any] = {}
kwargs['timeout'] = self.timeout
kwargs['headers'] = {}
kwargs['headers']['Content-type'] = 'application/json'
kwargs['auth'] = (self.user, self.password)
if self.user is not None and self.password is not None:
kwargs['auth'] = (self.user, self.password)
if data:
kwargs['data'] = data
if params:
Expand Down
32 changes: 14 additions & 18 deletions tes/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,15 @@
from typing import Any, Dict, List, Optional, Tuple, Type, Union


@attrs
@attrs(repr=False)
class _ListOfValidator(object):
type: Type = attrib()

def __call__(self, inst, attr, value):
"""
We use a callable class to be able to change the ``__repr__``.
"""
def __call__(self, inst, attr, value) -> None:
if not all([isinstance(n, self.type) for n in value]):
raise TypeError(
"'{attr.name}' must be a list of {self.type!r} (got {value!r} "
"that is a list of {values[0].__class__!r}).",
attr, self.type, value,
f"'{attr.name}' must be a list of {self.type!r} (got "
f"{value!r}", attr
)

def __repr__(self) -> str:
Expand Down Expand Up @@ -60,15 +56,15 @@ def strconv(value: Any) -> Any:
# since an int64 value is encoded as a string in json we need to handle
# conversion
def int64conv(value: Optional[str]) -> Optional[int]:
if value is not None:
return int(value)
return value
if value is None:
return value
return int(value)


def timestampconv(value: Optional[str]) -> Optional[datetime]:
if value is not None:
return dateutil.parser.parse(value)
return value
if value is None:
return value
return dateutil.parser.parse(value)


def datetime_json_handler(x: Any) -> str:
Expand Down Expand Up @@ -294,7 +290,7 @@ def is_valid(self) -> Tuple[bool, Union[None, TypeError]]:
for e in self.executors:
if e.image is None:
errs.append("Executor image must be provided")
if len(e.command) == 0:
if e.command is None or len(e.command) == 0:
errs.append("Executor command must be provided")
if e.stdin is not None:
if not os.path.isabs(e.stdin):
Expand All @@ -306,8 +302,8 @@ def is_valid(self) -> Tuple[bool, Union[None, TypeError]]:
if not os.path.isabs(e.stderr):
errs.append("Executor stderr must be an absolute path")
if e.env is not None:
for k, v in e.env:
if not isinstance(k, str) and not isinstance(k, str):
for k, v in e.env.items():
if not isinstance(k, str) and not isinstance(v, str):
errs.append(
"Executor env keys and values must be StrType"
)
Expand Down Expand Up @@ -339,7 +335,7 @@ def is_valid(self) -> Tuple[bool, Union[None, TypeError]]:
errs.append("Volume paths must be absolute")

if self.tags is not None:
for k, v in self.tags:
for k, v in self.tags.items():
if not isinstance(k, str) and not isinstance(k, str):
errs.append(
"Tag keys and values must be StrType"
Expand Down
26 changes: 12 additions & 14 deletions tes/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,20 @@ def __init__(self, *args, **kwargs):


def unmarshal(j: Any, o: Type, convert_camel_case=True) -> Any:
m: Any = None
if isinstance(j, str):
m = json.loads(j)
elif isinstance(j, dict):
m = j
try:
m = json.loads(j)
except json.decoder.JSONDecodeError:
pass
elif j is None:
return None
else:
raise TypeError("j must be a str, a dict or None")
m = j

if not isinstance(m, dict):
raise TypeError("j must be a dictionary, a JSON string evaluation to "
"a dictionary, or None")

d: Dict[str, Any] = {}
if convert_camel_case:
Expand Down Expand Up @@ -77,16 +83,8 @@ def _unmarshal(v: Any, obj: Type) -> Any:
field = v
omap = fullOmap.get(o.__name__, {})
if k in omap:
if isinstance(omap[k], tuple):
try:
obj = omap[k][0]
field = _unmarshal(v, obj)
except Exception:
obj = omap[k][1]
field = _unmarshal(v, obj)
else:
obj = omap[k]
field = _unmarshal(v, obj)
obj = omap[k]
field = _unmarshal(v, obj)
r[k] = field

try:
Expand Down
Empty file added tests/__init__.py
Empty file.
1 change: 1 addition & 0 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ coverage>=6.5.0
coveralls>=3.3.1
flake8>=5.0.4
pytest>=7.2.1
pytest-cov>=4.0.0
requests_mock>=1.10.0
39 changes: 32 additions & 7 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ class TestHTTPClient(unittest.TestCase):
def test_cli(self):
cli = HTTPClient(url="http://fakehost:8000//", timeout=5)
self.assertEqual(cli.url, "http://fakehost:8000")
self.assertEqual(cli.timeout, 5)
self.assertAlmostEqual(cli.timeout, 5)

with self.assertRaises(ValueError):
HTTPClient(url="fakehost:8000", timeout=5)

with self.assertRaises(ValueError):
HTTPClient(url="htpp://fakehost:8000", timeout="5")
HTTPClient(url="htpp://fakehost:8000", timeout="5") # type: ignore

def test_create_task(self):
with requests_mock.Mocker() as m:
Expand All @@ -41,7 +41,7 @@ def test_create_task(self):
)
self.cli.create_task(self.task)
self.assertEqual(m.last_request.text, self.task.as_json())
self.assertEqual(m.last_request.timeout, self.cli.timeout)
self.assertAlmostEqual(m.last_request.timeout, self.cli.timeout)

m.post(
"%s/v1/tasks" % (self.mock_url),
Expand All @@ -50,6 +50,9 @@ def test_create_task(self):
with self.assertRaises(requests.HTTPError):
self.cli.create_task(self.task)

with self.assertRaises(TypeError):
self.cli.create_task('not_a_task_object') # type: ignore

def test_get_task(self):
with requests_mock.Mocker() as m:
m.get(
Expand All @@ -65,7 +68,7 @@ def test_get_task(self):
m.last_request.url,
"%s/v1/tasks/%s?view=MINIMAL" % (self.mock_url, self.mock_id)
)
self.assertEqual(m.last_request.timeout, self.cli.timeout)
self.assertAlmostEqual(m.last_request.timeout, self.cli.timeout)

m.get(
"%s/v1/tasks/%s" % (self.mock_url, self.mock_id),
Expand All @@ -88,7 +91,7 @@ def test_list_tasks(self):
m.last_request.url,
"%s/v1/tasks?view=MINIMAL" % (self.mock_url)
)
self.assertEqual(m.last_request.timeout, self.cli.timeout)
self.assertAlmostEqual(m.last_request.timeout, self.cli.timeout)

# empty response
m.get(
Expand Down Expand Up @@ -121,7 +124,7 @@ def test_cancel_task(self):
m.last_request.url,
"%s/v1/tasks/%s:cancel" % (self.mock_url, self.mock_id)
)
self.assertEqual(m.last_request.timeout, self.cli.timeout)
self.assertAlmostEqual(m.last_request.timeout, self.cli.timeout)

m.post(
"%s/v1/tasks/%s:cancel" % (self.mock_url, self.mock_id),
Expand All @@ -142,7 +145,7 @@ def test_get_service_info(self):
m.last_request.url,
"%s/v1/tasks/service-info" % (self.mock_url)
)
self.assertEqual(m.last_request.timeout, self.cli.timeout)
self.assertAlmostEqual(m.last_request.timeout, self.cli.timeout)

m.get(
"%s/v1/tasks/service-info" % (self.mock_url),
Expand Down Expand Up @@ -177,3 +180,25 @@ def test_wait(self):
]
)
self.cli.wait(self.mock_id, timeout=2)

def test_request_params(self):

cli = HTTPClient(url="http://fakehost:8000", timeout=5)
vals = cli._request_params()
self.assertAlmostEqual(vals["timeout"], 5)
self.assertEqual(vals["headers"]["Content-type"], "application/json")
self.assertRaises(KeyError, lambda: vals["headers"]["Authorization"])
self.assertRaises(KeyError, lambda: vals["auth"])
self.assertRaises(KeyError, lambda: vals["data"])
self.assertRaises(KeyError, lambda: vals["params"])

cli = HTTPClient(url="http://fakehost:8000", user="user",
password="password", token="token")
vals = cli._request_params(data='{"json": "string"}',
params={"query_param": "value"})
self.assertAlmostEqual(vals["timeout"], 10)
self.assertEqual(vals["headers"]["Content-type"], "application/json")
self.assertEqual(vals["headers"]["Authorization"], "Bearer token")
self.assertEqual(vals["auth"], ("user", "password"))
self.assertEqual(vals["data"], '{"json": "string"}')
self.assertEqual(vals["params"], {"query_param": "value"})
Loading

0 comments on commit 756d3c4

Please sign in to comment.