diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3876cfa..e2b4f82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,4 +34,4 @@ jobs: - name: Test run: | - python -m pytest + python -m pytest test/unittests diff --git a/.vscode/settings.json b/.vscode/settings.json index ceb3c0f..87542d3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -21,12 +21,17 @@ // cSpell custom dictionary "cSpell.words": [ "aiohttp", + "asyncio", + "asyncutils", "getattrs", "mccabe", "mypy", + "pydash", "pystrapi", + "pytest", "reqargs", "strapi", - "testdata" + "testdata", + "testserver" ], } diff --git a/README.md b/README.md index 8a20396..7992c72 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,10 @@ asyncio.run(main()) ``` ## Development +### Install environment: +``` +poetry install +``` ### Lint Run [prospector](https://prospector.landscape.io/): @@ -38,6 +42,17 @@ Run [prospector](https://prospector.landscape.io/): prospector ``` +### Unit tests +``` +pytest test/unittests +``` + +### Integration tests +Run Strapi test server (see [instructions](testserver/README.md)), and run integration tests: +``` +pytest test/integration +``` + ### Create new release Push changes to 'main' branch following [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). diff --git a/poetry.lock b/poetry.lock index 74882c2..8bf1944 100644 --- a/poetry.lock +++ b/poetry.lock @@ -568,6 +568,17 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pydash" +version = "5.1.0" +description = "The kitchen sink of Python utility libraries for doing \"stuff\" in a functional way. Based on the Lo-Dash Javascript library." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["black", "coverage", "docformatter", "flake8", "flake8-black", "flake8-bugbear", "flake8-isort", "invoke", "isort", "pylint", "pytest", "pytest-cov", "pytest-flake8", "pytest-pylint", "sphinx", "sphinx-rtd-theme", "tox", "twine", "wheel"] + [[package]] name = "pydocstyle" version = "6.1.1" @@ -702,6 +713,20 @@ tomli = ">=1.0.0" [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +[[package]] +name = "pytest-asyncio" +version = "0.18.3" +description = "Pytest support for asyncio" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pytest = ">=6.1.0" + +[package.extras] +testing = ["coverage (==6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (==0.931)", "pytest-trio (>=0.7.0)"] + [[package]] name = "python-gitlab" version = "2.10.1" @@ -1067,7 +1092,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = ">=3.8,<4.0" -content-hash = "11243ecd5a0f4fb49106e59ab693a55f0031c9a5db710b8d5e0eb92901ac8a05" +content-hash = "25b276289359b6dd81f1ab6dbf68622f204a89eaacb3907b7440a628c7834cb9" [metadata.files] aiohttp = [ @@ -1597,6 +1622,10 @@ pycparser = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] +pydash = [ + {file = "pydash-5.1.0-py3-none-any.whl", hash = "sha256:ced4fedb163eb07fbee376e474bca74029eb9fab215614449fe13164f71dd9e3"}, + {file = "pydash-5.1.0.tar.gz", hash = "sha256:1b2b050ac1bae049cd07f5920b14fabbe52638f485d9ada1eb115a9eebff6835"}, +] pydocstyle = [ {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"}, {file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"}, @@ -1635,6 +1664,11 @@ pytest = [ {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, ] +pytest-asyncio = [ + {file = "pytest-asyncio-0.18.3.tar.gz", hash = "sha256:7659bdb0a9eb9c6e3ef992eef11a2b3e69697800ad02fb06374a210d85b29f91"}, + {file = "pytest_asyncio-0.18.3-1-py3-none-any.whl", hash = "sha256:16cf40bdf2b4fb7fc8e4b82bd05ce3fbcd454cbf7b92afc445fe299dabb88213"}, + {file = "pytest_asyncio-0.18.3-py3-none-any.whl", hash = "sha256:8fafa6c52161addfd41ee7ab35f11836c5a16ec208f93ee388f752bea3493a84"}, +] python-gitlab = [ {file = "python-gitlab-2.10.1.tar.gz", hash = "sha256:7afa7d7c062fa62c173190452265a30feefb844428efc58ea5244f3b9fc0d40f"}, {file = "python_gitlab-2.10.1-py3-none-any.whl", hash = "sha256:581a219759515513ea9399e936ed7137437cfb681f52d2641626685c492c999d"}, diff --git a/pyproject.toml b/pyproject.toml index 46d711b..15edd5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,8 @@ prospector = {version = "^1.7.7", extras = ["with_mypy", "with_bandit"]} autopep8 = "^1.6.0" pytest = "^7.1.2" types-requests = "^2.27.30" +pytest-asyncio = "^0.18.3" +pydash = "^5.1.0" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..2f4c80e --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +asyncio_mode = auto diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/integration/__init__.py b/test/integration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/integration/conftest.py b/test/integration/conftest.py new file mode 100644 index 0000000..f91e257 --- /dev/null +++ b/test/integration/conftest.py @@ -0,0 +1,36 @@ +import pytest +from typing import Any, Type + +from test.types import AnyStrapiClient +from pystrapi.strapi_client import StrapiClient +from pystrapi.strapi_client_sync import StrapiClientSync + + +@pytest.fixture(params=[StrapiClientSync, StrapiClient]) +def client(request: Any) -> AnyStrapiClient: + client_type: Type[AnyStrapiClient] = request.param + return client_type() + + +@pytest.fixture +def post1() -> dict: + return { + "id": 1, + "attributes": { + "description": "The first post", + "content": "Hello", + "title": "First Post", + } + } + + +@pytest.fixture +def post2() -> dict: + return { + "id": 2, + "attributes": { + "description": "The second post", + "content": "Hello again", + "title": "Second Post" + } + } diff --git a/test/integration/test_strapi_client.py b/test/integration/test_strapi_client.py new file mode 100644 index 0000000..ed2aa85 --- /dev/null +++ b/test/integration/test_strapi_client.py @@ -0,0 +1,17 @@ +from test.types import AnyStrapiClient +from test.utils import asyncutils +from pystrapi.types import StrapiEntriesResponse, StrapiEntryResponse + +from pydash.predicates import is_match # type: ignore + + +async def test_get_entry(client: AnyStrapiClient, post1: dict) -> None: + res: StrapiEntryResponse = await asyncutils.value(client.get_entry('posts', 1)) + assert is_match(res['data'], post1) + + +async def test_get_entries(client: AnyStrapiClient, post1: dict, post2: dict) -> None: + res: StrapiEntriesResponse = await asyncutils.value(client.get_entries('posts')) + assert res['data'] + assert is_match(res['data'][0], post1) + assert is_match(res['data'][1], post2) diff --git a/test/types.py b/test/types.py new file mode 100644 index 0000000..6c67d7c --- /dev/null +++ b/test/types.py @@ -0,0 +1,6 @@ +from typing import Union + +from pystrapi.strapi_client import StrapiClient +from pystrapi.strapi_client_sync import StrapiClientSync + +AnyStrapiClient = Union[StrapiClientSync, StrapiClient] diff --git a/test/unittests/__init__.py b/test/unittests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/utils/asyncutils.py b/test/utils/asyncutils.py new file mode 100644 index 0000000..0258bfc --- /dev/null +++ b/test/utils/asyncutils.py @@ -0,0 +1,14 @@ +import inspect +from typing import Any, Coroutine, TypeVar, Union + + +V = TypeVar("V", bound="Any") + + +async def value(val: Union[V, Coroutine[Any, Any, V]]) -> V: + """ + Return the given value. await if it's awaitable. + """ + if inspect.isawaitable(val): + return await val # type: ignore + return val # type: ignore