Skip to content

Commit

Permalink
Using pytest move route test class to functions, where fixtures can b…
Browse files Browse the repository at this point in the history
…e used to set variables (#330)

Change pytest class to functions and use parameterize and fixture features of pytest to make the unit test more concise.

Co-authored-by: Casper Welzel Andersen <[email protected]>
  • Loading branch information
unkcpz and CasperWA authored Jul 27, 2022
1 parent 14a470d commit 0e5be8d
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 193 deletions.
25 changes: 22 additions & 3 deletions tests/server/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# pylint: disable=redefined-outer-name
import re
from typing import Any, Dict, List
from typing import Any, Dict, List, Iterable

import pytest

Expand All @@ -25,7 +25,7 @@ def remote_client():
def get_good_response(client, caplog):
"""Get OPTIMADE response with some sanity checks"""

def inner(request: str) -> Dict[str, Any]:
def inner(request: str, raw=False) -> Dict[str, Any]:
try:
response = client.get(request)

Expand All @@ -38,7 +38,10 @@ def inner(request: str) -> Dict[str, Any]:
), caplog.text

assert response.status_code == 200, f"Request failed: {response.json()}"
response = response.json()

if not raw:
response = response.json()

except Exception:
print("Request attempted:")
print(f"{client.base_url}{client.version}{request}")
Expand All @@ -49,6 +52,22 @@ def inner(request: str) -> Dict[str, Any]:
return inner


@pytest.fixture
def check_keys():
"""Utility function to help validate dict keys"""

def inner(
keys: list,
response_subset: Iterable,
):
for key in keys:
assert (
key in response_subset
), f"{key} missing from response {response_subset}"

return inner


@pytest.fixture
def check_response(get_good_response):
"""Fixture to check response using client fixture"""
Expand Down
179 changes: 83 additions & 96 deletions tests/server/routers/test_info.py
Original file line number Diff line number Diff line change
@@ -1,119 +1,106 @@
import pytest

from optimade.models import (
InfoResponse,
EntryInfoResponse,
BaseInfoAttributes,
EntryInfoResource,
)

from ..utils import EndpointTests

def test_info_endpoint_attributes(get_good_response, check_keys):
"""Check known properties/attributes for successful response"""
response = get_good_response("/info")

class TestInfoEndpoint(EndpointTests):
"""Tests for /info"""
assert "data" in response
assert response["data"]["type"] == "info"
assert response["data"]["id"] == "/"
assert "attributes" in response["data"]
attributes = list(BaseInfoAttributes.schema()["properties"].keys())
check_keys(attributes, response["data"]["attributes"])

request_str = "/info"
response_cls = InfoResponse

def test_info_endpoint_attributes(self):
"""Check known properties/attributes for successful response"""
assert "data" in self.json_response
assert self.json_response["data"]["type"] == "info"
assert self.json_response["data"]["id"] == "/"
assert "attributes" in self.json_response["data"]
attributes = list(BaseInfoAttributes.schema()["properties"].keys())
self.check_keys(attributes, self.json_response["data"]["attributes"])
def test_info_structures_endpoint_data(get_good_response, check_keys):
"""Check known properties/attributes for successful response"""
response = get_good_response("/info/structures")

assert "data" in response
data = EntryInfoResource.schema()["required"]
check_keys(data, response["data"])

class TestInfoStructuresEndpoint(EndpointTests):
"""Tests for /info/structures"""

request_str = "/info/structures"
response_cls = EntryInfoResponse
def test_info_structures_sortable(get_good_response):
"""Check the sortable key is present for all properties"""
response = get_good_response("/info/structures")

def test_info_structures_endpoint_data(self):
"""Check known properties/attributes for successful response"""
assert "data" in self.json_response
data = EntryInfoResource.schema()["required"]
self.check_keys(data, self.json_response["data"])
for info_keys in response.get("data", {}).get("properties", {}).values():
assert "sortable" in info_keys

def test_info_structures_sortable(self):
"""Check the sortable key is present for all properties"""
for info_keys in (
self.json_response.get("data", {}).get("properties", {}).values()
):
assert "sortable" in info_keys

def test_sortable_values(self):
"""Make sure certain properties are and are not sortable"""
sortable = ["id", "nelements", "nsites"]
non_sortable = ["species", "lattice_vectors", "dimension_types"]
def test_sortable_values(get_good_response):
"""Make sure certain properties are and are not sortable"""
response = get_good_response("/info/structures")
sortable = ["id", "nelements", "nsites"]
non_sortable = ["species", "lattice_vectors", "dimension_types"]

for field in sortable:
sortable_info_value = (
self.json_response.get("data", {})
.get("properties", {})
.get(field, {})
.get("sortable", None)
)
assert sortable_info_value is not None
assert sortable_info_value is True

for field in non_sortable:
sortable_info_value = (
self.json_response.get("data", {})
.get("properties", {})
.get(field, {})
.get("sortable", None)
)
assert sortable_info_value is not None
assert sortable_info_value is False

def test_info_structures_unit(self):
"""Check the unit key is present for certain properties"""
unit_fields = ["lattice_vectors", "cartesian_site_positions"]
for field, info_keys in (
self.json_response.get("data", {}).get("properties", {}).items()
):
if field in unit_fields:
assert "unit" in info_keys, f"Field: {field}"
else:
assert "unit" not in info_keys, f"Field: {field}"

def test_provider_fields(self):
"""Check the presence of AiiDA-specific fields"""
from optimade.server.config import CONFIG

provider_fields = CONFIG.provider_fields.get("structures", [])

if not provider_fields:
import warnings

warnings.warn("No provider-specific fields found for 'structures'!")
return

for field in provider_fields:
updated_field_name = f"_{CONFIG.provider.prefix}_{field}"
assert updated_field_name in self.json_response.get("data", {}).get(
"properties", {}
)
for field in sortable:
sortable_info_value = (
response.get("data", {})
.get("properties", {})
.get(field, {})
.get("sortable", None)
)
assert sortable_info_value is not None
assert sortable_info_value is True

for static_key in ["description", "sortable"]:
assert static_key in self.json_response.get("data", {}).get(
"properties", {}
).get(updated_field_name, {})
for field in non_sortable:
sortable_info_value = (
response.get("data", {})
.get("properties", {})
.get(field, {})
.get("sortable", None)
)
assert sortable_info_value is not None
assert sortable_info_value is False


@pytest.mark.skip("References has not yet been implemented")
class TestInfoReferencesEndpoint(EndpointTests):
"""Tests for /info/references"""
def test_info_structures_unit(get_good_response):
"""Check the unit key is present for certain properties"""
response = get_good_response("/info/structures")
unit_fields = ["lattice_vectors", "cartesian_site_positions"]
for field, info_keys in response.get("data", {}).get("properties", {}).items():
if field in unit_fields:
assert "unit" in info_keys, f"Field: {field}"
else:
assert "unit" not in info_keys, f"Field: {field}"


def test_provider_fields(get_good_response):
"""Check the presence of AiiDA-specific fields"""
from optimade.server.config import CONFIG

response = get_good_response("/info/structures")
provider_fields = CONFIG.provider_fields.get("structures", [])

request_str = "/info/references"
response_cls = EntryInfoResponse
if not provider_fields:
import warnings

warnings.warn("No provider-specific fields found for 'structures'!")
return

for field in provider_fields:
updated_field_name = f"_{CONFIG.provider.prefix}_{field}"
assert updated_field_name in response.get("data", {}).get("properties", {})

for static_key in ["description", "sortable"]:
assert static_key in response.get("data", {}).get("properties", {}).get(
updated_field_name, {}
)


@pytest.mark.skip("References has not yet been implemented")
def test_info_references_endpoint_data(get_good_response, check_keys):
"""Check known properties/attributes for successful response"""
response = get_good_response("/info/reference")

def test_info_references_endpoint_data(self):
"""Check known properties/attributes for successful response"""
assert "data" in self.json_response
data = EntryInfoResource.schema()["required"]
self.check_keys(data, self.json_response["data"])
assert "data" in response
data = EntryInfoResource.schema()["required"]
check_keys(data, response["data"])
13 changes: 4 additions & 9 deletions tests/server/routers/test_links.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
from optimade.models import LinksResponse
def test_links(get_good_response):
"""Check /links for successful response"""
response = get_good_response("/links")

from ..utils import EndpointTests


class TestLinksEndpoint(EndpointTests):
"""Tests for /links"""

request_str = "/links"
response_cls = LinksResponse
assert "data" in response
73 changes: 73 additions & 0 deletions tests/server/routers/test_response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from optimade.models import (
InfoResponse,
LinksResponse,
EntryInfoResponse,
ReferenceResponseMany,
ReferenceResponseOne,
StructureResponseMany,
StructureResponseOne,
)
from optimade.models import ResponseMeta

import pytest


@pytest.mark.parametrize(
"request_str, ResponseType",
[
("/info", InfoResponse),
("/info/structures", EntryInfoResponse),
("/links", LinksResponse),
("/structures", StructureResponseMany),
("/structures/0", StructureResponseOne),
pytest.param(
"/references",
ReferenceResponseMany,
marks=pytest.mark.xfail(reason="References has not yet been implemented"),
),
pytest.param(
"/references/dijkstra1968",
ReferenceResponseOne,
marks=pytest.mark.xfail(reason="References has not yet been implemented"),
),
pytest.param(
"/references/dummy/20.19",
ReferenceResponseOne,
marks=pytest.mark.xfail(reason="References has not yet been implemented"),
),
],
)
def test_serialize_response(get_good_response, request_str, ResponseType):
response = get_good_response(request_str)

ResponseType(**response)


@pytest.mark.parametrize(
"request_str",
[
"/info",
"/info/structures",
"/links",
"/structures",
"structures/0",
pytest.param(
"/references",
marks=pytest.mark.xfail(reason="References has not yet been implemented"),
),
],
)
def test_meta_response(request_str, get_good_response, check_keys):
"""Check `meta` property in response"""
response = get_good_response(request_str)

assert "meta" in response
meta_required_keys = ResponseMeta.schema()["required"]
meta_optional_keys = list(
set(ResponseMeta.schema()["properties"].keys()) - set(meta_required_keys)
)
implemented_optional_keys = ["data_available", "implementation"]

check_keys(meta_required_keys, response["meta"])
check_keys(implemented_optional_keys, meta_optional_keys)
check_keys(implemented_optional_keys, response["meta"])
Loading

0 comments on commit 0e5be8d

Please sign in to comment.