Skip to content

Commit

Permalink
Optimize the testing scopes
Browse files Browse the repository at this point in the history
  • Loading branch information
Cito committed Mar 12, 2024
1 parent 3908a11 commit ffa90ed
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 104 deletions.
3 changes: 2 additions & 1 deletion src/ars/inject.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ async def prepare_rest_app(
core_override: Optional[AccessRequestRepositoryPort] = None,
) -> AsyncGenerator[FastAPI, None]:
"""Construct and initialize a REST API app along with all its dependencies.
By default, the core dependencies are automatically prepared but you can also
By default, the core dependencies are automatically prepared, but you can also
provide them using the core_override parameter.
"""
app = get_configured_app(config=config)
Expand Down
37 changes: 37 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright 2021 - 2023 Universität Tübingen, DKFZ, EMBL, and Universität zu Köln
# for the German Human Genome-Phenome Archive (GHGA)
#
# 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.
#

"""Setup for testing the access request service."""

import pytest
from hexkit.providers.akafka.testutils import get_kafka_fixture
from hexkit.providers.mongodb.testutils import get_mongodb_fixture

from .fixtures import JointFixture, get_joint_fixture


@pytest.fixture(autouse=True)
def reset_state(joint_fixture: JointFixture):
"""Clear joint_fixture state before tests.
This is a function-level fixture because it needs to run in each test.
"""
joint_fixture.mongodb.empty_collections()


kafka_fixture = get_kafka_fixture("session")
mongodb_fixture = get_mongodb_fixture("session")
joint_fixture = get_joint_fixture("session")
57 changes: 37 additions & 20 deletions tests/fixtures/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,21 @@
"""Fixtures that are used in both integration and unit tests"""

from collections.abc import AsyncGenerator
from typing import NamedTuple

import pytest_asyncio
from ghga_service_commons.api.testing import AsyncTestClient
from ghga_service_commons.utils.jwt_helpers import (
generate_jwk,
sign_and_serialize_token,
)
from hexkit.custom_types import PytestScope
from hexkit.providers.akafka.testutils import KafkaFixture
from hexkit.providers.mongodb.testutils import MongoDbFixture
from pytest import fixture
from pytest_asyncio import fixture as async_fixture

from ars.config import Config
from ars.inject import prepare_rest_app
from ars.inject import prepare_core, prepare_rest_app

__all__ = [
"AUTH_KEY_PAIR",
Expand All @@ -38,9 +40,9 @@
"fixture_auth_headers_steward",
"fixture_auth_headers_doe_inactive",
"fixture_auth_headers_steward_inactive",
"fixture_client",
"get_joint_fixture",
"JointFixture",
"headers_for_token",
"non_mocked_hosts",
]


Expand Down Expand Up @@ -98,27 +100,42 @@ def fixture_auth_headers_steward() -> dict[str, str]:
return headers_for_token(token)


@async_fixture(name="client")
async def fixture_client(
kafka_fixture: KafkaFixture,
mongodb_fixture: MongoDbFixture,
) -> AsyncGenerator[AsyncTestClient, None]:
"""Get test client for the access request service"""
class JointFixture(NamedTuple):
"""Joint fixture object."""

config: Config
kafka: KafkaFixture
mongodb: MongoDbFixture
rest_client: AsyncTestClient


async def joint_fixture_function(
mongodb_fixture: MongoDbFixture, kafka_fixture: KafkaFixture
) -> AsyncGenerator[JointFixture, None]:
"""A fixture that embeds all other fixtures for API-level integration testing
**Do not call directly** Instead, use get_joint_fixture().
"""
config = Config(
auth_key=AUTH_KEY_PAIR.export_public(), # pyright: ignore
download_access_url="http://access",
data_steward_email="[email protected]",
**kafka_fixture.config.model_dump(),
**mongodb_fixture.config.model_dump(),
)

async with prepare_rest_app(config=config) as app:
async with AsyncTestClient(app=app) as client:
yield client


@fixture
def non_mocked_hosts() -> list[str]:
"""Get hosts that are not mocked by pytest-httpx."""
return ["test", "localhost"]
async with prepare_core(config=config) as core:
async with (
prepare_rest_app(config=config, core_override=core) as app,
):
async with AsyncTestClient(app=app) as rest_client:
yield JointFixture(
config=config,
kafka=kafka_fixture,
mongodb=mongodb_fixture,
rest_client=rest_client,
)


def get_joint_fixture(scope: PytestScope = "function"):
"""Produce a joint fixture with desired scope"""
return pytest_asyncio.fixture(joint_fixture_function, scope=scope)
19 changes: 10 additions & 9 deletions tests/test_access_grants.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@
from collections.abc import AsyncGenerator

import httpx
import pytest
from ghga_service_commons.utils.utc_dates import utc_datetime
from pytest import mark, raises
from pytest_asyncio import fixture as async_fixture
from pytest_httpx import HTTPXMock

from ars.adapters.outbound.http import AccessGrantsAdapter, AccessGrantsConfig

pytestmark = pytest.mark.asyncio(scope="session")


DOWNLOAD_ACCESS_URL = "http://test-access:1234"

USER_ID = "some-user-id"
Expand All @@ -37,15 +40,14 @@
URL = f"{DOWNLOAD_ACCESS_URL}/users/{USER_ID}/datasets/{DATASET_ID}"


@async_fixture(name="access_grant")
@async_fixture(name="access_grant", scope="session")
async def fixture_access_grant() -> AsyncGenerator[AccessGrantsAdapter, None]:
"""Get configured access grant test adapter."""
config = AccessGrantsConfig(download_access_url=DOWNLOAD_ACCESS_URL)
async with AccessGrantsAdapter.construct(config=config) as adapter:
yield adapter


@mark.asyncio
async def test_grant_download_access(
access_grant: AccessGrantsAdapter, httpx_mock: HTTPXMock
):
Expand All @@ -68,14 +70,13 @@ async def test_grant_download_access(
}


@mark.asyncio
async def test_grant_download_access_with_invalid_dates(
access_grant: AccessGrantsAdapter,
):
"""Test granting download access for invalid dates"""
grant_access = access_grant.grant_download_access

with raises(
with pytest.raises(
access_grant.AccessGrantsInvalidPeriodError, match="Invalid validity period"
):
await grant_access(
Expand All @@ -86,15 +87,14 @@ async def test_grant_download_access_with_invalid_dates(
)


@mark.asyncio
async def test_grant_download_access_with_server_error(
access_grant: AccessGrantsAdapter, httpx_mock: HTTPXMock
):
"""Test granting download access when there is a server error"""
grant_access = access_grant.grant_download_access
httpx_mock.add_response(method="POST", url=URL, status_code=500)

with raises(
with pytest.raises(
access_grant.AccessGrantsError, match="Unexpected HTTP response status code 500"
):
await grant_access(
Expand All @@ -105,15 +105,16 @@ async def test_grant_download_access_with_server_error(
)


@mark.asyncio
async def test_grant_download_access_with_timeout(
access_grant: AccessGrantsAdapter, httpx_mock: HTTPXMock
):
"""Test granting download access when there is a network timeout"""
grant_access = access_grant.grant_download_access
httpx_mock.add_exception(httpx.ReadTimeout("Simulated network problem"))

with raises(access_grant.AccessGrantsError, match="Simulated network problem"):
with pytest.raises(
access_grant.AccessGrantsError, match="Simulated network problem"
):
await grant_access(
user_id=USER_ID,
dataset_id=DATASET_ID,
Expand Down
Loading

0 comments on commit ffa90ed

Please sign in to comment.