Skip to content

Commit

Permalink
updated tests for conjunctions
Browse files Browse the repository at this point in the history
  • Loading branch information
dchaddock committed Feb 5, 2025
1 parent 05e0d97 commit 381763d
Show file tree
Hide file tree
Showing 14 changed files with 953 additions and 191 deletions.
20 changes: 10 additions & 10 deletions COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,18 @@ pyaurorax/models/atm/__init__.py 21 4 8
pyaurorax/pyaurorax.py 204 2 99% 180, 404
pyaurorax/search/__init__.py 93 0 100%
pyaurorax/search/api/__init__.py 20 0 100%
pyaurorax/search/api/classes/request.py 73 25 66% 70-71, 79, 111-112, 116-123, 133, 139, 143-147, 151, 155-159, 165, 169, 183, 186
pyaurorax/search/api/classes/response.py 11 2 82% 43, 46
pyaurorax/search/api/classes/request.py 33 0 100%
pyaurorax/search/api/classes/response.py 9 0 100%
pyaurorax/search/availability/__init__.py 14 0 100%
pyaurorax/search/availability/_availability.py 21 0 100%
pyaurorax/search/availability/classes/availability_result.py 8 0 100%
pyaurorax/search/conjunctions/__init__.py 29 2 93% 190, 258
pyaurorax/search/conjunctions/_conjunctions.py 94 56 40% 57, 63, 67, 72, 84-147, 154-157, 175-191
pyaurorax/search/conjunctions/classes/conjunction.py 24 0 100%
pyaurorax/search/conjunctions/classes/criteria_block.py 84 60 29% 45-48, 51, 54, 66-76, 107-111, 114, 117, 130-141, 144-147, 166-168, 171, 174, 185-194, 209, 212, 215, 221-222, 225-233
pyaurorax/search/conjunctions/classes/search.py 215 70 67% 40, 148, 151, 162-203, 271, 315-320, 327-334, 343-349, 358-359, 377, 419, 423, 445-446, 457-458, 530-535
pyaurorax/search/conjunctions/swarmaurora/__init__.py 15 1 93% 65
pyaurorax/search/conjunctions/swarmaurora/_swarmaurora.py 25 8 68% 29-35, 47
pyaurorax/search/conjunctions/__init__.py 29 0 100%
pyaurorax/search/conjunctions/_conjunctions.py 91 0 100%
pyaurorax/search/conjunctions/classes/conjunction.py 39 0 100%
pyaurorax/search/conjunctions/classes/criteria_block.py 84 51 39% 51, 54, 66-76, 114, 117, 130-141, 144-147, 166-168, 171, 174, 185-194, 209, 212, 215, 221-222, 225-233
pyaurorax/search/conjunctions/classes/search.py 215 29 87% 40, 173, 175, 179, 184, 186, 190, 319, 333, 343-349, 358-359, 377, 419, 423, 445-446, 457-458, 530-535
pyaurorax/search/conjunctions/swarmaurora/__init__.py 15 0 100%
pyaurorax/search/conjunctions/swarmaurora/_swarmaurora.py 25 4 84% 33-35, 47
pyaurorax/search/data_products/__init__.py 33 2 94% 124, 234
pyaurorax/search/data_products/_data_products.py 100 27 73% 40-44, 86, 92, 96, 101, 128, 143-145, 154, 169, 177-204, 211-214
pyaurorax/search/data_products/classes/data_product.py 47 1 98% 130
Expand Down Expand Up @@ -94,7 +94,7 @@ pyaurorax/tools/mosaic/_prep_skymaps.py 119 112
pyaurorax/tools/spectra/__init__.py 10 1 90% 129
pyaurorax/tools/spectra/_plot.py 114 108 5% 42-233
--------------------------------------------------------------------------------------------
TOTAL 6114 2819 54%
TOTAL 6084 2679 56%
13 empty files skipped.
```
30 changes: 18 additions & 12 deletions pyaurorax/search/api/classes/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,21 @@ def __init__(self,
self.null_response = null_response

def __json_converter(self, o):
if (isinstance(o, datetime.datetime) is True):
# NOTE: this method is a last-ditch catch for any datetimes that snuck
# into the request body without already being converted to string format.
# Since this should never happen, we exclude it from the test suite
if (isinstance(o, datetime.datetime) is True): # pragma: nocover
return str(o)

def __merge_headers(self):
# set initial headers
all_headers = self.__aurorax_obj.api_headers

# add headers passed into the class
for key, value in self.headers.items():
#
# NOTE: we don't usually pass any extra headers, so exclude this from
# the test suite
for key, value in self.headers.items(): # pragma: nocover
all_headers[key.lower()] = value

# add api key
Expand Down Expand Up @@ -108,11 +114,11 @@ def execute(self) -> AuroraXAPIResponse:
params=self.params,
data=body_santized,
timeout=self.__aurorax_obj.api_timeout)
except requests.exceptions.Timeout:
except requests.exceptions.Timeout: # pragma: nocover
raise AuroraXAPIError("API request timeout reached") from None

# check if authorization worked (raised by API or Nginx)
if (req.status_code == 401):
if (req.status_code == 401): # pragma: nocover
if (req.headers["Content-Type"] == "application/json"):
if ("error_message" in req.json()):
# this will be an error message that the API meant to send
Expand All @@ -123,7 +129,7 @@ def execute(self) -> AuroraXAPIResponse:
raise AuroraXUnauthorizedError("API error code 401: unauthorized")

# check for 404 error (raised by API or by Nginx)
if (req.status_code == 404):
if (req.status_code == 404): # pragma: nocover
if (req.headers["Content-Type"] == "application/json"):
if ("error_message" in req.json()):
# this will be an error message that the API meant to send
Expand All @@ -135,31 +141,31 @@ def execute(self) -> AuroraXAPIResponse:
raise AuroraXAPIError("API error code 404: not found")

# check for 400
if (req.status_code == 400):
if (req.status_code == 400): # pragma: nocover
raise AuroraXAPIError("API error code %d: %s" % (req.status_code, req.content.decode()))

# check for server error
if (req.status_code == 500):
if (req.status_code == 500): # pragma: nocover
response_json = req.json()
if ("error_message" in response_json):
raise AuroraXAPIError("API error code %d: %s" % (req.status_code, response_json["error_message"]))
else:
raise AuroraXAPIError("API error code %d: %s" % (req.status_code, response_json))

# check for maintenance mode error
if (req.status_code == 502):
if (req.status_code == 502): # pragma: nocover
raise AuroraXAPIError("API error code %d: API inaccessible, bad gateway" % (req.status_code))

# check for maintenance mode error
if (req.status_code == 503):
if (req.status_code == 503): # pragma: nocover
response_json = req.json()
if ("maintenance mode" in response_json["error_message"].lower()):
raise AuroraXMaintenanceError(response_json["error_message"])
else:
raise AuroraXAPIError("API error code %d: %s" % (req.status_code, response_json["error_message"]))

# check content type
if (self.null_response is False):
if (self.null_response is False): # pragma: nocover
if (req.headers["Content-Type"] == "application/json"):
if (len(req.content) == 0):
raise AuroraXAPIError("API error code %d: no response received" % (req.status_code))
Expand All @@ -180,7 +186,7 @@ def execute(self) -> AuroraXAPIResponse:
return res

def __str__(self) -> str:
return self.__repr__()
return self.__repr__() # pragma: nocover

def __repr__(self) -> str:
return f"AuroraXAPIRequest(method='{self.method}', url='{self.url}')"
return f"AuroraXAPIRequest(method='{self.method}', url='{self.url}')" # pragma: nocover
4 changes: 2 additions & 2 deletions pyaurorax/search/api/classes/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def __init__(self, request: Any, data: Any, status_code: int):
self.status_code = status_code

def __str__(self) -> str:
return self.__repr__()
return self.__repr__() # pragma: nocover

def __repr__(self) -> str:
return f"AuroraXAPIResponse [{self.status_code}] ({responses[self.status_code]})"
return f"AuroraXAPIResponse [{self.status_code}] ({responses[self.status_code]})" # pragma: nocover
7 changes: 5 additions & 2 deletions pyaurorax/search/conjunctions/_conjunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def search(aurorax_obj, start, end, distance, ground, space, events, custom_loca
s.wait(poll_interval=poll_interval, verbose=verbose)

# check if error condition encountered
if (s.status["search_result"]["error_condition"] is True):
if (s.status["search_result"]["error_condition"] is True): # pragma: nocover
# error encountered
raise AuroraXSearchError(s.logs[-1]["summary"])

Expand All @@ -80,6 +80,9 @@ def search(aurorax_obj, start, end, distance, ground, space, events, custom_loca


def search_from_raw_query(aurorax_obj, query, poll_interval, return_immediately, verbose):
# deep copy the query first
query = deepcopy(query)

# convert to dict
if (isinstance(query, str) is True):
# query is a string, so presumably it is a JSON-valid string; convert it to dict
Expand Down Expand Up @@ -126,7 +129,7 @@ def search_from_raw_query(aurorax_obj, query, poll_interval, return_immediately,
s.wait(poll_interval=poll_interval, verbose=verbose)

# check if error condition encountered
if (s.status["search_result"]["error_condition"] is True):
if (s.status["search_result"]["error_condition"] is True): # pragma: nocover
# error encountered
raise AuroraXSearchError(s.logs[-1]["summary"])

Expand Down
26 changes: 26 additions & 0 deletions pyaurorax/search/conjunctions/classes/conjunction.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,29 @@ def __str__(self) -> str:
def __repr__(self) -> str:
return f"Conjunction(start={repr(self.start)}, end={repr(self.end)}, min_distance={self.min_distance:.2f}, " \
f"max_distance={self.max_distance:.2f}, data_sources=[...], events=[...])"

def pretty_print(self):
"""
A special print output for this class.
"""
# set special strings
if (len(self.data_sources) == 1): # pragma: nocover
data_sources_str = "[%d data source]" % (len(self.data_sources))
else:
data_sources_str = "[%d data sources]" % (len(self.data_sources))
if (len(self.events) == 1):
events_str = "[%d event]" % (len(self.events))
else:
events_str = "[%d events]" % (len(self.events))

# print
print("Conjunction:")
print(" %-18s: %s" % ("conjunction_type", self.conjunction_type))
print(" %-18s: %s" % ("start", self.start))
print(" %-18s: %s" % ("end", self.end))
print(" %-18s: %s" % ("data_sources", data_sources_str))
print(" %-18s: %s" % ("min_distance", self.min_distance))
print(" %-18s: %s" % ("max_distance", self.max_distance))
print(" %-18s: %s" % ("events", events_str))
print(" %-18s: %s" % ("closest_epoch", self.closest_epoch))
print(" %-18s: %s" % ("farthest_epoch", self.farthest_epoch))
44 changes: 44 additions & 0 deletions tests/test_suite/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import glob
import shutil
import pytest
import datetime
import pyaurorax
from click.testing import CliRunner
from pathlib import Path
Expand Down Expand Up @@ -148,3 +149,46 @@ def find_dataset(datasets, dataset_name):
if (d.name == dataset_name):
return copy.deepcopy(d)
return None


@pytest.fixture(scope="session")
def conjunction_search_obj():
# init
aurorax = pyaurorax.PyAuroraX()

# create search object
start = datetime.datetime(2020, 1, 1, 0, 0, 0)
end = datetime.datetime(2020, 1, 1, 6, 59, 59)
distance = 500
ground = [aurorax.search.GroundCriteriaBlock(programs=["themis-asi"])]
space = [aurorax.search.SpaceCriteriaBlock(programs=["swarm"])]
s = aurorax.search.conjunctions.search(start, end, distance, ground=ground, space=space, return_immediately=True)

# return
return s


@pytest.fixture(scope="function")
def conjunction_search_dict():
return {
"start": "2019-01-01T00:00:00.000Z",
"end": "2019-01-03T23:59:59.000Z",
"conjunction_types": ["nbtrace"],
"ground": [{
"programs": ["themis-asi"],
"platforms": ["fort smith", "gillam"],
"instrument_types": ["panchromatic ASI"],
"ephemeris_metadata_filters": {}
}],
"space": [{
"programs": ["swarm"],
"platforms": [],
"instrument_types": ["footprint"],
"ephemeris_metadata_filters": {},
"hemisphere": ["northern"]
}],
"events": [],
"max_distances": {
"ground1-space1": 500
}
}
13 changes: 13 additions & 0 deletions tests/test_suite/search/conjunctions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2024 University of Calgary
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Copyright 2024 University of Calgary
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pytest


@pytest.mark.search_ro
def test_simple(aurorax):
distances = aurorax.search.conjunctions.create_advanced_distance_combos(500, ground=1, space=1)
for _, val in distances.items():
assert val == 500


@pytest.mark.search_ro
@pytest.mark.parametrize("ground,space,events,custom", [
[0, 0, 0, 0],
[0, 0, 0, 1],
[0, 0, 1, 0],
[0, 0, 1, 1],
[0, 1, 0, 0],
[0, 1, 0, 1],
[0, 1, 1, 0],
[0, 1, 1, 1],
[1, 0, 0, 0],
[1, 0, 0, 1],
[1, 0, 1, 0],
[1, 0, 1, 1],
[1, 1, 0, 0],
[1, 1, 0, 1],
[1, 1, 1, 0],
[1, 1, 1, 1],
])
def test_complex(aurorax, ground, space, events, custom):
distances = aurorax.search.conjunctions.create_advanced_distance_combos(
500,
ground=ground,
space=space,
events=events,
custom=custom,
)
for _, val in distances.items():
assert val == 500
50 changes: 50 additions & 0 deletions tests/test_suite/search/conjunctions/test_describe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Copyright 2024 University of Calgary
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pytest
import pyaurorax


@pytest.mark.search_ro
def test_simple(aurorax, conjunction_search_obj):
# get describe string
describe_str = aurorax.search.conjunctions.describe(conjunction_search_obj)

# test response
assert describe_str is not None and describe_str != ""


@pytest.mark.search_ro
def test_search_object(aurorax, conjunction_search_obj):
# get describe string
describe_str = aurorax.search.conjunctions.describe(search_obj=conjunction_search_obj)

# test response
assert describe_str is not None and describe_str != ""


@pytest.mark.search_ro
def test_search_dict(aurorax, conjunction_search_dict):
# get describe string
describe_str = aurorax.search.conjunctions.describe(query_dict=conjunction_search_dict)

# test response
assert describe_str is not None and describe_str != ""


@pytest.mark.search_ro
def test_bad(aurorax):
with pytest.raises(pyaurorax.AuroraXError) as e_info:
aurorax.search.conjunctions.describe()
assert "One of 'search_obj' or 'query_dict' must be supplied" in str(e_info)
Loading

0 comments on commit 381763d

Please sign in to comment.