diff --git a/.github/workflows/build-package.yaml b/.github/workflows/build-package.yaml index c94c6fd..31b8364 100644 --- a/.github/workflows/build-package.yaml +++ b/.github/workflows/build-package.yaml @@ -31,7 +31,7 @@ jobs: run: python -m pip install -e . - name: Run tests and show coverage on the command line - run: coverage run tests.py && coverage report -m && coverage xml + run: python3 -m pytest --cov-report term --cov-report xml:coverage.xml --cov=pyfdc - name: Upload reports to codecov env: diff --git a/changelog.md b/changelog.md index 900a7a6..940ccd8 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,9 @@ **Version 0.2.3** +* Tests are now written with the `pytest` framework which means that this is now a dependency. We also now use `pytest_cov` for coverage reports. This also +introduces yet another dependency. + * Fixed an issue in `get_food_details` due to a change in column naming in the API interface. This is now automated backend. **Version 0.2.2** diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 1e25356..532d705 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -5,7 +5,12 @@ Welcome to pyfdc's changelog **Version 0.2.3** -* Fixed an issue in ``get_food_details`` due to a change in column naming in the API interface. This is now automated backend. +* + Tests are now written with the ``pytest`` framework which means that this is now a dependency. We also now use ``pytest_cov`` for coverage reports. This also + introduces yet another dependency. + +* + Fixed an issue in ``get_food_details`` due to a change in column naming in the API interface. This is now automated backend. **Version 0.2.2** diff --git a/requirements.txt b/requirements.txt index 8ee5ce2..c9f36be 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,8 @@ sphinxcontrib-htmlhelp==2.0.0 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 -mistune==2.0.3 +# Known security issues but m2r won't work with anything above 0.8.4 for some reason +mistune==0.8.4 m2r==0.2.1 coverage==5.3.1 pytest==7.1.2 diff --git a/test_pyfdc.py b/test_pyfdc.py new file mode 100644 index 0000000..17db3f6 --- /dev/null +++ b/test_pyfdc.py @@ -0,0 +1,106 @@ +from pyfdc.pyfdc import FoodDataCentral +from pandas import DataFrame +import os +from unittest.mock import patch +import requests +from unittest import mock +from pyfdc.utils import set_api_key +import pytest + +# set API key from a users OS environ +# Only on Github actions +# TODO: Check that this only runs on GH Actions +set_api_key("EMgmhkxg9Jfp2N8zw6gQ29u5Oek1sHvsWmkFJycE") + +@pytest.fixture +def my_search(): + def _search(**kwargs): + return FoodDataCentral(**kwargs) + return _search + + +def test_instance(my_search): + assert isinstance(my_search(), FoodDataCentral) + assert isinstance(my_search().available_targets, dict) + with pytest.warns(Warning, match = "Providing an api_key is discouraged, please consider using set_api_key.") as wrng: + my_search(api_key = "fakekey") + + + +# This mocks what set_api_key does. +# TODO: use an actual mock, this looks like a poor mxn's mock? +def test_api_key_mocks(): + with patch.dict('os.environ', {"pyfdc_key": "EMgmhkxg9Jfp2N8zw6gQ29u5Oek1sHvsWmkFJycE"}): + assert os.environ["pyfdc_key"] == "EMgmhkxg9Jfp2N8zw6gQ29u5Oek1sHvsWmkFJycE" + assert "pyfdc_key" in os.environ + pass + +def test_get_food_info(my_search): + with pytest.warns(UserWarning, match = "No target_fields were provided, returning fdc_id, ingredients, and description.") as uwarn: + my_search().get_food_info(search_phrase="cheese") + + with pytest.raises(TypeError, match ="target should be a list or tuple not int") as err: + my_search().get_food_info(search_phrase="cheese", target_fields=1234) + + # Check that we indeed get cheese + res = my_search().get_food_info(search_phrase="cheese", target_fields=["description"]) + say_cheese = res["description"].iloc[0] + assert say_cheese == "CHEESE" + + # Check that we raise a KeyError + with pytest.raises(KeyError): + # Need to figure out how to test the actual error + my_search().get_food_info(search_phrase="cheese", target_fields=["not_in"]) + + # Check that we get an HTTPError if we have the wrong api_key + with pytest.raises(requests.exceptions.HTTPError): + FoodDataCentral(api_key="fakekey").get_food_info(search_phrase="cheese") + +def test_get_food_details(my_search): + with pytest.raises(AssertionError, match ="fdc_id should not be None") as err: + my_search().get_food_details() + + with pytest.raises(AssertionError, match = "fdc_id should be an int not str") as err: + my_search().get_food_details(fdc_id="string_id") + + with pytest.warns(UserWarning, match = "No target_field was provided, returning low level results.") as uwarn2: + my_search().get_food_details(fdc_id=496446) + + + # Check that we get the expected result + + food_details = my_search().get_food_details(fdc_id=496446, target_field="nutrients") + + assert isinstance(food_details, DataFrame) + + # Expect HTTP Errors if we have fake api keys for instance + # https://docs.pytest.org/en/6.2.x/reference.html#pytest._code.ExceptionInfo + with pytest.raises(requests.HTTPError) as exc_info: + FoodDataCentral(api_key="fake").get_food_details(fdc_id=496446, + target_field="description") + + # Fake API Key --> I will not let you in :) + assert exc_info.value.response.status_code == 403 + + with pytest.raises(KeyError): + my_search().get_food_details(fdc_id=496446, target_field="some_fake_key") + + # Check that we get the same fdc_id as we sent + + assert my_search().get_food_details(fdc_id=496446, target_field="fdcId") == 496446 + + assert isinstance(my_search().get_food_details(fdc_id=496446, target_field="label_nutrients"), + DataFrame) + # Check that we can raise a KeyError if no label nutrients are present + # print(my_search.get_food_details(fdc_id=168977, target_field="label_nutrients")) + with pytest.raises(KeyError, match="FDC ID 168977 has no label nutrients."): + my_search().get_food_details(fdc_id=168977, target_field="label_nutrients") + # @mock.patch("pyfdc.utils.input") + # def test_utils(self, mock_input): + # mock_input.side_effect = "False" + # with self.assertRaises(ValueError): + # set_api_key() + + + +