Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bring code coverage back up and change pre-commit #350

Merged
merged 1 commit into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions amt/utils/path.py

This file was deleted.

38 changes: 0 additions & 38 deletions amt/utils/storage.py

This file was deleted.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ relative_files = true # needed for sonarcloud code coverage
omit = [
"tests/*"
]

concurrency = ["greenlet", "thread"]

[tool.coverage.report]
fail_under = 75
fail_under = 95

[tool.coverage.html]
directory = "htmlcov"
Expand Down
11 changes: 11 additions & 0 deletions tests/api/routes/test_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import pytest
from httpx import AsyncClient
from pytest_mock import MockerFixture


@pytest.mark.asyncio
async def test_auth_profile(client: AsyncClient, mocker: MockerFixture) -> None:
mocker.patch("amt.api.routes.auth.get_user", return_value="test_user_for_auth_profile")
response = await client.get("/auth/profile")

assert b"https://gravatar.com/" in response.content
192 changes: 191 additions & 1 deletion tests/api/routes/test_project.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
from typing import Any

import pytest
from amt.api.routes.project import set_path
from amt.api.routes.project import (
MeasureUpdate,
find_measure_task,
find_requirement_task,
find_requirement_tasks_by_measure_urn,
get_project_context,
get_project_or_error,
set_path,
)
from amt.core.exceptions import AMTNotFound, AMTRepositoryError
from amt.models import Project
from amt.schema.task import MovedTask
from httpx import AsyncClient
from pytest_httpx import HTTPXMock
from pytest_mock import MockFixture

from tests.api.routes.test_projects import MockRequest
from tests.constants import (
TASK_REGISTRY_AIIA_CONTENT_PAYLOAD,
TASK_REGISTRY_CONTENT_PAYLOAD,
Expand Down Expand Up @@ -43,6 +54,96 @@ async def test_get_project_tasks(client: AsyncClient, db: DatabaseTestUtils) ->
assert b"Default Task" in response.content


@pytest.mark.asyncio
async def test_get_project_inference(client: AsyncClient, db: DatabaseTestUtils) -> None:
# given
await db.given([default_project("testproject1"), default_task(project_id=1, status_id=1)])

# when
response = await client.get("/algorithm-system/1/details/model/inference")

# then
assert response.status_code == 200
assert response.headers["content-type"] == "text/html; charset=utf-8"
assert b'<button id="runInference"' in response.content


@pytest.mark.asyncio
async def test_move_task(client: AsyncClient, db: DatabaseTestUtils, mocker: MockFixture) -> None:
# given
await db.given(
[
default_project("testproject1"),
default_task(project_id=1, status_id=1),
default_task(project_id=1, status_id=1),
]
)
mocker.patch("fastapi_csrf_protect.CsrfProtect.validate_csrf", new_callable=mocker.AsyncMock)
client.cookies["fastapi-csrf-token"] = "1"

# All -1 flow
move_task_json = MovedTask(taskId=1, statusId=1, previousSiblingId=-1, nextSiblingId=-1).model_dump(by_alias=True)
response = await client.patch("/algorithm-system/move_task", json=move_task_json, headers={"X-CSRF-Token": "1"})

# then
assert response.status_code == 200
assert response.headers["content-type"] == "text/html; charset=utf-8"
assert b"Default Task" in response.content

# All 1 flow
move_task_json = MovedTask(taskId=1, statusId=1, previousSiblingId=1, nextSiblingId=1).model_dump(by_alias=True)
response = await client.patch("/algorithm-system/move_task", json=move_task_json, headers={"X-CSRF-Token": "1"})

# then
assert response.status_code == 200
assert response.headers["content-type"] == "text/html; charset=utf-8"
assert b"Default Task" in response.content


@pytest.mark.asyncio
async def test_get_project_context(client: AsyncClient, db: DatabaseTestUtils, mocker: MockFixture) -> None:
# given
test_project = default_project_with_system_card("testproject1")
project_service = mocker.AsyncMock()
project_service.get.return_value = test_project

project, project_context = await get_project_context(
project_id=1, projects_service=project_service, request=MockRequest("nl", url="/")
)
assert project_context["last_edited"] is None
assert project == test_project


@pytest.mark.asyncio
async def test_get_project_non_existing_project(client: AsyncClient, db: DatabaseTestUtils) -> None:
# given
await db.given([default_project("testproject1"), default_task(project_id=1, status_id=1)])

# when
response = await client.get("/algorithm-system/99/details/tasks")

# then
assert response.status_code == 404
assert b"The requested page or resource could not be found." in response.content


@pytest.mark.asyncio
async def test_get_project_or_error(client: AsyncClient, db: DatabaseTestUtils, mocker: MockFixture) -> None:
# given
test_project = default_project("testproject1")
project_service = mocker.AsyncMock()
project_service.get.return_value = test_project

# happy flow
project = await get_project_or_error(project_id=1, projects_service=project_service, request=mocker.AsyncMock())
assert project == test_project

# unhappy flow
project_service.get.side_effect = AMTRepositoryError
with pytest.raises(AMTNotFound):
_ = await get_project_or_error(project_id=99, projects_service=project_service, request=mocker.AsyncMock())


# TODO: Test are now have hard coded URL paths because the system card
# is fixed for now. Tests need to be refactored and made proper once
# the actual stored system card in a project is being rendered.
Expand Down Expand Up @@ -364,3 +465,92 @@ def test_set_path(): # type: ignore
project_dict: dict[str, Any] = {}
set_path(project_dict, "/root", "value5")
assert project_dict == {"root": "value5"}


@pytest.mark.asyncio
async def test_find_measure_task() -> None:
test_project = default_project_with_system_card("testproject1")

# no matched measure
measure = find_measure_task(test_project.system_card, "")
assert measure is None

# matches measure
measure = find_measure_task(test_project.system_card, "urn:nl:ak:mtr:bnd-01")
assert measure.urn == "urn:nl:ak:mtr:bnd-01" # pyright: ignore [reportOptionalMemberAccess]
assert measure.value is not None # pyright: ignore [reportOptionalMemberAccess]

# no measures in system_card
test_project.system_card.measures = []
measure = find_measure_task(test_project.system_card, "")
assert measure is None


@pytest.mark.asyncio
async def test_find_requirement_task() -> None:
test_project = default_project_with_system_card("testproject1")

# no matched requirement
requirement = find_requirement_task(test_project.system_card, "")
assert requirement is None

# matches measure
requirement = find_requirement_task(test_project.system_card, "urn:nl:ak:ver:aia-05")
assert requirement.urn == "urn:nl:ak:ver:aia-05" # pyright: ignore [reportOptionalMemberAccess]
assert requirement.state is not None # pyright: ignore [reportOptionalMemberAccess]

# no measures in system_card
test_project.system_card.requirements = []
requirement = find_measure_task(test_project.system_card, "")
assert requirement is None


@pytest.mark.asyncio
async def test_find_requirement_tasks_by_measure_urn() -> None:
test_project = default_project_with_system_card("testproject1")

# no matched requirement
with pytest.raises(IndexError):
# TODO: this is because it is not coded well change later
requirement_tasks = find_requirement_tasks_by_measure_urn(test_project.system_card, "")

# matches measure
requirement_tasks = find_requirement_tasks_by_measure_urn(test_project.system_card, "urn:nl:ak:mtr:bnd-01")
assert len(requirement_tasks) == 3

# empty requirements
test_project.system_card.requirements = []
with pytest.raises(KeyError):
# TODO: this is because it is not coded well change later
requirement_tasks = find_requirement_tasks_by_measure_urn(test_project.system_card, "urn:nl:ak:mtr:bnd-01")


@pytest.mark.asyncio
async def test_get_measure(client: AsyncClient, db: DatabaseTestUtils) -> None:
# given
await db.given([default_project_with_system_card("testproject1")])

# when
response = await client.get("/algorithm-system/1/measure/urn:nl:ak:mtr:bnd-01")

# then
assert response.status_code == 200
assert response.headers["content-type"] == "text/html; charset=utf-8"
assert b"Gebruik aselecte steekproeven" in response.content


@pytest.mark.asyncio
async def test_update_measure_value(client: AsyncClient, mocker: MockFixture, db: DatabaseTestUtils) -> None:
# given
await db.given([default_project_with_system_card("testproject1")])
client.cookies["fastapi-csrf-token"] = "1"
mocker.patch("fastapi_csrf_protect.CsrfProtect.validate_csrf", new_callable=mocker.AsyncMock)

# happy flow
response = await client.post(
"/algorithm-system/1/measure/urn:nl:ak:mtr:bnd-01",
json={"measure_update": MeasureUpdate(measure_state="done", measure_value="something").model_dump()},
headers={"X-CSRF-Token": "1"},
)
assert response.status_code == 200
assert response.headers["content-type"] == "text/html; charset=utf-8"
11 changes: 9 additions & 2 deletions tests/api/routes/test_projects.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from collections.abc import MutableMapping
from datetime import UTC, datetime
from typing import cast
from typing import Any, cast

import pytest
from amt.api.routes.projects import get_localized_value
Expand All @@ -12,6 +13,7 @@
from fastapi.requests import Request
from httpx import AsyncClient
from pytest_mock import MockFixture
from starlette.datastructures import URL

from tests.constants import default_instrument, default_project
from tests.database_test_utils import DatabaseTestUtils
Expand Down Expand Up @@ -198,8 +200,13 @@ async def test_post_new_projects_write_system_card(


class MockRequest(Request):
def __init__(self, lang: str) -> None:
def __init__(self, lang: str, scope: MutableMapping[str, Any] | None = None, url: str | None = None) -> None:
if scope is None:
scope = {}
if url:
self._url = URL(url=url)
self.lang = lang
self.scope = scope

@property
def headers(self): # type: ignore
Expand Down
13 changes: 13 additions & 0 deletions tests/api/test_ai_act_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from amt.api.ai_act_profile import (
AiActProfileItem,
get_translation,
)
from babel.support import Translations


def test_get_translation():
translation = Translations.load("amt/locale", locales="en")
for item in AiActProfileItem:
assert get_translation(item, translation) is not None
# empty match
assert get_translation(None, translation) is None # pyright: ignore
12 changes: 12 additions & 0 deletions tests/api/test_deps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from amt.api.deps import custom_context_processor

from tests.constants import default_fastapi_request


def test_custom_context_processor():
result = custom_context_processor(default_fastapi_request())
assert result is not None
assert result["version"] == "0.1.0"
assert result["available_translations"] == ["en", "nl"]
assert result["language"] == "en"
assert result["translations"] is None
13 changes: 13 additions & 0 deletions tests/core/test_authorization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import pytest
from amt.core.authorization import get_user
from pytest_mock import MockerFixture


@pytest.mark.asyncio
def test_get_user(mocker: MockerFixture) -> None:
mock_request = mocker.Mock(scope=["session"])
mock_request.session = {"user": {"name": "user"}}
mock_get_requested_language = mocker.patch("amt.core.authorization.get_requested_language", return_value="nl")
user = get_user(mock_request)
assert user == {"name": "user", "locale": "nl"}
mock_get_requested_language.assert_called_once_with(mock_request)
4 changes: 3 additions & 1 deletion tests/core/test_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pytest
from amt.core.db import (
check_db,
init_db,
)
from pytest_mock import MockFixture
from sqlalchemy import select
Expand All @@ -13,12 +14,13 @@


@pytest.mark.asyncio
async def test_check_database(monkeypatch: pytest.MonkeyPatch, tmp_path: Path, mocker: MockFixture):
async def test_check_and_init_database(monkeypatch: pytest.MonkeyPatch, tmp_path: Path, mocker: MockFixture):
database_file = tmp_path / "database.sqlite3"
monkeypatch.setenv("APP_DATABASE_FILE", str(database_file))
org_exec = AsyncSession.execute
AsyncSession.execute = mocker.AsyncMock()
await check_db()
await init_db()

assert AsyncSession.execute.call_args is not None
assert str(select(1)) == str(AsyncSession.execute.call_args.args[0])
Expand Down
Loading
Loading