Skip to content

Commit 70994c0

Browse files
committed
update: pytest tests
1 parent 59a285f commit 70994c0

26 files changed

+363
-261
lines changed

tests/__init__.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +0,0 @@
1-
import json
2-
import os
3-
import unittest
4-
5-
6-
class EndpointBaseTest(unittest.TestCase):
7-
"""
8-
Base class for testing endpoints.
9-
"""
10-
11-
def __init__(self, *args, **kwargs):
12-
super(EndpointBaseTest, self).__init__(*args, **kwargs)
13-
14-
def get_file_path(self, filename):
15-
return os.path.join(os.path.dirname(__file__), "data", filename)
16-
17-
def get_content(self, filename):
18-
with open(self.get_file_path(filename)) as f:
19-
return f.read()
20-
21-
def get_content_in_json(self, filename):
22-
with open(self.get_file_path(filename)) as f:
23-
return json.loads(f.read())

tests/conftest.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""
2+
Shared test fixtures and base classes for pytest.
3+
"""
4+
import json
5+
import os
6+
import unittest
7+
8+
9+
class EndpointBaseTest(unittest.TestCase):
10+
"""
11+
Base class for testing endpoints.
12+
"""
13+
14+
def __init__(self, *args, **kwargs):
15+
super(EndpointBaseTest, self).__init__(*args, **kwargs)
16+
17+
def get_file_path(self, filename):
18+
return os.path.join(os.path.dirname(__file__), "py", "data", filename)
19+
20+
def get_content(self, filename):
21+
with open(self.get_file_path(filename)) as f:
22+
return f.read()
23+
24+
def get_content_in_json(self, filename):
25+
with open(self.get_file_path(filename)) as f:
26+
return json.loads(f.read())
27+

tests/integration/__init__.py

Lines changed: 0 additions & 20 deletions
This file was deleted.

tests/integration/entity.py

Lines changed: 0 additions & 80 deletions
This file was deleted.

tests/py/integration/__init__.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import os
2+
3+
import pytest
4+
5+
from tests.conftest import EndpointBaseTest
6+
7+
ENV_TEST_HOST = "TEST_HOST"
8+
ENV_TEST_PORT = "TEST_PORT"
9+
ENV_TEST_ACCOUNT_ID = "TEST_ACCOUNT_ID"
10+
ENV_TEST_AUTH_TOKEN = "TEST_AUTH_TOKEN"
11+
ENV_TEST_SECURE = "TEST_SECURE"
12+
ENV_TEST_VERSION = "TEST_VERSION"
13+
DEFAULT_TEST_SECURE = "False"
14+
DEFAULT_TEST_VERSION = "2018-10-01"
15+
16+
REQUIRED_ENV_VARS = [ENV_TEST_HOST, ENV_TEST_PORT, ENV_TEST_ACCOUNT_ID, ENV_TEST_AUTH_TOKEN]
17+
MISSING_ENV_VARS_MESSAGE = (
18+
"Integration tests require environment variables: TEST_HOST, TEST_PORT, "
19+
"TEST_ACCOUNT_ID, TEST_AUTH_TOKEN. Set them to run integration tests."
20+
)
21+
22+
23+
def _check_integration_env():
24+
"""Check if required integration test environment variables are set."""
25+
missing = [var for var in REQUIRED_ENV_VARS if var not in os.environ]
26+
if missing:
27+
pytest.skip(f"{MISSING_ENV_VARS_MESSAGE} Missing: {', '.join(missing)}")
28+
29+
30+
class BaseIntegrationTest(EndpointBaseTest):
31+
"""
32+
Base class for endpoints integration tests.
33+
"""
34+
35+
def __init__(self, *args, **kwargs):
36+
super(BaseIntegrationTest, self).__init__(*args, **kwargs)
37+
_check_integration_env()
38+
self.endpoint_kwargs = {
39+
"host": os.environ[ENV_TEST_HOST],
40+
"port": os.environ[ENV_TEST_PORT],
41+
"account_id": os.environ[ENV_TEST_ACCOUNT_ID],
42+
"auth_token": os.environ[ENV_TEST_AUTH_TOKEN],
43+
"secure": os.environ.get(ENV_TEST_SECURE, DEFAULT_TEST_SECURE).lower() == "true",
44+
"version": os.environ.get(ENV_TEST_VERSION, DEFAULT_TEST_VERSION),
45+
}

tests/py/integration/entity.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import time
2+
3+
from requests.exceptions import HTTPError
4+
5+
from tests.py.integration import BaseIntegrationTest
6+
7+
INTEGRATION_TEST_TAG = "INTEGRATION-TEST"
8+
ENTITY_ID_KEY = "_id"
9+
ENTITY_NAME_KEY = "name"
10+
ENTITY_TAGS_KEY = "tags"
11+
TEST_NAME_PREFIX = "test-"
12+
UPDATED_NAME = "UPDATED"
13+
WARNING_ENTITY_NOT_FOUND = "WARNING: Entity with ID {} not found in the list of tagged entities: {}"
14+
WARNING_DELETE_FAILED = "WARNING: Failed to delete entity with ID {}: {}"
15+
16+
17+
class EntityIntegrationTest(BaseIntegrationTest):
18+
"""
19+
Base entity integration tests class.
20+
"""
21+
22+
def __init__(self, *args, **kwargs):
23+
super(EntityIntegrationTest, self).__init__(*args, **kwargs)
24+
self.endpoints = None
25+
self.entity_id: str = ""
26+
27+
def entities_selector(self):
28+
"""
29+
Returns a selector to access entities created in tests.
30+
Override upon inheritance as necessary.
31+
"""
32+
return {ENTITY_TAGS_KEY: INTEGRATION_TEST_TAG}
33+
34+
def tearDown(self):
35+
"""Delete only the current test entity if it still exists after test.
36+
37+
Warn if the filtering fails, failsafe attempt to delete the entity anyways.
38+
"""
39+
tagged_test_entity_id_list = [e[ENTITY_ID_KEY] for e in self.endpoints.list(query=self.entities_selector())]
40+
try:
41+
if self.entity_id not in tagged_test_entity_id_list:
42+
print(WARNING_ENTITY_NOT_FOUND.format(self.entity_id, tagged_test_entity_id_list))
43+
self.endpoints.delete(self.entity_id)
44+
45+
except HTTPError as e:
46+
print(WARNING_DELETE_FAILED.format(self.entity_id, e))
47+
48+
def get_default_config(self):
49+
"""
50+
Returns the default entity config.
51+
Override upon inheritance.
52+
"""
53+
raise NotImplementedError
54+
55+
def create_entity(self, kwargs=None):
56+
entity = self.get_default_config()
57+
entity.update(kwargs or {})
58+
entity.setdefault(ENTITY_TAGS_KEY, []).append(INTEGRATION_TEST_TAG)
59+
created_entity = self.endpoints.create(entity)
60+
self.entity_id = created_entity[ENTITY_ID_KEY]
61+
return created_entity
62+
63+
def list_entities_test(self):
64+
entity = self.create_entity()
65+
self.assertIn(entity[ENTITY_ID_KEY], [e[ENTITY_ID_KEY] for e in self.endpoints.list(query=self.entities_selector())])
66+
67+
def get_entity_by_id_test(self):
68+
entity = self.create_entity()
69+
self.assertEqual(self.endpoints.get(entity[ENTITY_ID_KEY])[ENTITY_ID_KEY], entity[ENTITY_ID_KEY])
70+
71+
def create_entity_test(self):
72+
name = f"{TEST_NAME_PREFIX}{time.time()}"
73+
entity = self.create_entity({ENTITY_NAME_KEY: name})
74+
self.assertEqual(entity[ENTITY_NAME_KEY], name)
75+
self.assertIsNotNone(entity[ENTITY_ID_KEY])
76+
self.assertIn(INTEGRATION_TEST_TAG, entity[ENTITY_TAGS_KEY])
77+
78+
def delete_entity_test(self):
79+
entity = self.create_entity()
80+
self.endpoints.delete(entity[ENTITY_ID_KEY])
81+
self.assertNotIn(entity[ENTITY_ID_KEY], [e[ENTITY_ID_KEY] for e in self.endpoints.list(query=self.entities_selector())])
82+
83+
def update_entity_test(self):
84+
entity = self.create_entity()
85+
updated_entity = self.endpoints.update(entity[ENTITY_ID_KEY], {ENTITY_NAME_KEY: UPDATED_NAME})
86+
self.assertEqual(updated_entity[ENTITY_NAME_KEY], UPDATED_NAME)
Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,30 @@
22
import time
33

44
from exabyte_api_client.endpoints.jobs import JobEndpoints
5-
from tests.integration.entity import EntityIntegrationTest
5+
from tests.py.integration.entity import EntityIntegrationTest
6+
7+
KNOWN_COMPLETED_JOB_ID = "9gyhfncWDhnSyzALv"
8+
JOB_NAME_PREFIX = "API-CLIENT TEST JOB"
9+
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
10+
DEFAULT_NODES = 1
11+
DEFAULT_NOTIFY = "n"
12+
DEFAULT_PPN = 1
13+
DEFAULT_QUEUE = "D"
14+
DEFAULT_TIME_LIMIT = "00:05:00"
15+
TEST_TIME_LIMIT = "00:10:00"
16+
TEST_NOTIFY = "abe"
17+
JOB_STATUS_SUBMITTED = "submitted"
18+
JOB_STATUS_FINISHED = "finished"
19+
JOB_WAIT_TIMEOUT = 900
20+
JOB_WAIT_SLEEP_INTERVAL = 5
21+
JOB_TIMEOUT_ERROR = "job with ID {} did not finish within {} seconds"
622

723

824
class JobEndpointsIntegrationTest(EntityIntegrationTest):
925
"""
1026
Job endpoints integration tests.
1127
"""
1228

13-
KNOWN_COMPLETED_JOB_ID = "9gyhfncWDhnSyzALv"
14-
1529
def __init__(self, *args, **kwargs):
1630
super(JobEndpointsIntegrationTest, self).__init__(*args, **kwargs)
1731
self.endpoints = JobEndpoints(**self.endpoint_kwargs)
@@ -21,10 +35,10 @@ def get_default_config(self):
2135
Returns the default entity config.
2236
Override upon inheritance.
2337
"""
24-
now_time = datetime.datetime.today().strftime("%Y-%m-%d %H:%M:%S")
25-
return {"name": "API-CLIENT TEST JOB {}".format(now_time)}
38+
now_time = datetime.datetime.today().strftime(DATETIME_FORMAT)
39+
return {"name": f"{JOB_NAME_PREFIX} {now_time}"}
2640

27-
def get_compute_params(self, nodes=1, notify="n", ppn=1, queue="D", time_limit="00:05:00"):
41+
def get_compute_params(self, nodes=DEFAULT_NODES, notify=DEFAULT_NOTIFY, ppn=DEFAULT_PPN, queue=DEFAULT_QUEUE, time_limit=DEFAULT_TIME_LIMIT):
2842
return {"nodes": nodes, "notify": notify, "ppn": ppn, "queue": queue, "timeLimit": time_limit}
2943

3044
def test_list_jobs(self):
@@ -42,34 +56,33 @@ def test_delete_job(self):
4256
def test_update_job(self):
4357
self.update_entity_test()
4458

45-
def _wait_for_job_to_finish(self, id_, timeout=900):
59+
def _wait_for_job_to_finish(self, id_, timeout=JOB_WAIT_TIMEOUT):
4660
end = time.time() + timeout
4761
while time.time() < end:
4862
job = self.endpoints.get(id_)
49-
if job["status"] == "finished":
63+
if job["status"] == JOB_STATUS_FINISHED:
5064
return
51-
time.sleep(5)
52-
raise BaseException("job with ID {} did not finish within {} seconds".format(id_, timeout))
65+
time.sleep(JOB_WAIT_SLEEP_INTERVAL)
66+
raise BaseException(JOB_TIMEOUT_ERROR.format(id_, timeout))
5367

5468
def test_submit_job_and_wait_to_finish(self):
5569
job = self.create_entity()
5670
self.endpoints.submit(job["_id"])
57-
self.assertEqual("submitted", self.endpoints.get(job["_id"])["status"])
71+
self.assertEqual(JOB_STATUS_SUBMITTED, self.endpoints.get(job["_id"])["status"])
5872
self._wait_for_job_to_finish(job["_id"])
5973

6074
def test_create_job_timeLimit(self):
61-
time_limit = "00:10:00"
62-
job = self.create_entity({"compute": self.get_compute_params(time_limit=time_limit)})
75+
job = self.create_entity({"compute": self.get_compute_params(time_limit=TEST_TIME_LIMIT)})
6376
self.assertEqual(self.endpoints.get(job["_id"])["_id"], job["_id"])
64-
self.assertEqual(self.endpoints.get(job["_id"])["compute"]["timeLimit"], time_limit)
77+
self.assertEqual(self.endpoints.get(job["_id"])["compute"]["timeLimit"], TEST_TIME_LIMIT)
6578

6679
def test_create_job_notify(self):
67-
job = self.create_entity({"compute": self.get_compute_params(notify="abe")})
80+
job = self.create_entity({"compute": self.get_compute_params(notify=TEST_NOTIFY)})
6881
self.assertEqual(self.endpoints.get(job["_id"])["_id"], job["_id"])
69-
self.assertEqual(self.endpoints.get(job["_id"])["compute"]["notify"], "abe")
82+
self.assertEqual(self.endpoints.get(job["_id"])["compute"]["notify"], TEST_NOTIFY)
7083

7184
def test_list_files(self):
72-
http_response_data = self.endpoints.list_files(self.KNOWN_COMPLETED_JOB_ID)
85+
http_response_data = self.endpoints.list_files(KNOWN_COMPLETED_JOB_ID)
7386
self.assertIsInstance(http_response_data, list)
7487
self.assertGreater(len(http_response_data), 0)
7588
self.assertIsInstance(http_response_data[0], dict)

0 commit comments

Comments
 (0)