From 113e787c1f6f7bde504e49f082448f16cff9d7e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bournhonesque?= Date: Fri, 9 Feb 2024 13:31:57 +0100 Subject: [PATCH] fix: run integration tests and unit tests in docker This way, we can test against PostgreSQL database. In the commit, we also: - removed labels_tags__like and origins_tags__like filters. These filters triggers an error in PostgreSQL, as we're using a JSONB field type, which does not support LIKE queries (this bug was in production too, see https://openfoodfacts.sentry.io/issues/4911496304/?project=4506270533091328&query=LIKE&referrer=issue-stream&sort=freq&statsPeriod=90d&stream_index=0) - fixed some integration tests to make it work with PostgreSQL --- Makefile | 13 +++++++++++ app/schemas.py | 2 -- docker-compose.yml | 4 ++++ docker/dev.yml | 4 +++- tests/integration/__init__.py | 0 tests/{ => integration}/test_api.py | 34 ++++++----------------------- 6 files changed, 27 insertions(+), 30 deletions(-) create mode 100644 tests/integration/__init__.py rename tests/{ => integration}/test_api.py (96%) diff --git a/Makefile b/Makefile index 594bbabc..4758a45f 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ export COMPOSE_DOCKER_CLI_BUILD=1 # take it form env, or from env file COMPOSE_PROJECT_NAME ?= $(shell grep COMPOSE_PROJECT_NAME ${ENV_FILE} | cut -d '=' -f 2) DOCKER_COMPOSE=docker-compose --env-file=${ENV_FILE} +DOCKER_COMPOSE_TEST=COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME}_test docker-compose --env-file=${ENV_FILE} # avoid target corresponding to file names, to depends on them .PHONY: * @@ -124,6 +125,18 @@ docs: checks: toml-check flake8 black-check mypy isort-check docs +unit-tests: + @echo "🥫 Running tests …" + # change project name to run in isolation + ${DOCKER_COMPOSE_TEST} run --rm api poetry run pytest tests/unit + +integration-tests: + @echo "🥫 Running integration tests …" + # change project name to run in isolation + ${DOCKER_COMPOSE_TEST} run --rm api poetry run pytest tests/integration + ( ${DOCKER_COMPOSE_TEST} down -v || true ) + + #------------# # Production # #------------# diff --git a/app/schemas.py b/app/schemas.py index 879cb067..a6d366a6 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -377,8 +377,6 @@ class PriceFilter(Filter): product_id: Optional[int] | None = None product_id__isnull: Optional[bool] | None = None category_tag: Optional[str] | None = None - labels_tags__like: Optional[str] | None = None - origins_tags__like: Optional[str] | None = None location_osm_id: Optional[int] | None = None location_osm_type: Optional[LocationOSMEnum] | None = None location_id: Optional[int] | None = None diff --git a/docker-compose.yml b/docker-compose.yml index 95e134a9..79d96ed9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,12 +21,16 @@ services: <<: *api-common volumes: - ./static:/opt/open-prices/static + depends_on: + - postgres scheduler: <<: *api-common command: ["python", "-m", "app", "run-scheduler"] volumes: - ./static:/opt/open-prices/static + depends_on: + - postgres postgres: restart: $RESTART_POLICY diff --git a/docker/dev.yml b/docker/dev.yml index 30c7159a..0b148716 100644 --- a/docker/dev.yml +++ b/docker/dev.yml @@ -17,8 +17,10 @@ x-api-base: &api-base - ./README.md:/opt/open-prices/README.md - ./docs:/opt/open-prices/docs - ./gh_pages:/opt/open-prices/gh_pages - # Make migrations available so that we can run them easily + # make migrations available so that we can run them easily - ./alembic:/opt/open-prices/alembic + # mount tests + - ./tests:/opt/open-prices/tests services: api: diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_api.py b/tests/integration/test_api.py similarity index 96% rename from tests/test_api.py rename to tests/integration/test_api.py index da28d24c..0a204f73 100644 --- a/tests/test_api.py +++ b/tests/integration/test_api.py @@ -4,13 +4,10 @@ import pytest from fastapi.encoders import jsonable_encoder from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from sqlalchemy.pool import StaticPool from app import crud from app.api import app, get_db -from app.db import Base +from app.db import Base, engine, session from app.models import Session as SessionModel from app.schemas import ( LocationCreate, @@ -20,23 +17,12 @@ UserCreate, ) -# database setup -# ------------------------------------------------------------------------------ -SQLALCHEMY_DATABASE_URL = "sqlite://" - -engine = create_engine( - SQLALCHEMY_DATABASE_URL, - connect_args={"check_same_thread": False}, - poolclass=StaticPool, -) -TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - Base.metadata.create_all(bind=engine) def override_get_db(): try: - db = TestingSessionLocal() + db = session() yield db finally: db.close() @@ -166,6 +152,7 @@ def location(db_session): @pytest.fixture(scope="function") def clean_users(db_session): + db_session.query(SessionModel).delete() db_session.query(crud.User).delete() db_session.commit() @@ -550,14 +537,6 @@ def test_get_prices_filters(db_session, user_session: SessionModel, clean_prices response = client.get("/api/v1/prices?category_tag=en:tomatoes") assert response.status_code == 200 assert len(response.json()["items"]) == 1 - # 1 price with labels_tags - response = client.get("/api/v1/prices?labels_tags__like=en:organic") - assert response.status_code == 200 - assert len(response.json()["items"]) == 1 - # 1 price with origins_tags - response = client.get("/api/v1/prices?origins_tags__like=en:spain") - assert response.status_code == 200 - assert len(response.json()["items"]) == 1 # 1 price with price > 5 response = client.get("/api/v1/prices?price__gt=5") assert response.status_code == 200 @@ -573,6 +552,7 @@ def test_get_prices_filters(db_session, user_session: SessionModel, clean_prices def test_get_prices_orders(db_session, user_session: SessionModel, clean_prices): + # PRICE_1 date is "2023-10-31" crud.create_price(db_session, PRICE_1, user_session.user) crud.create_price( db_session, @@ -583,8 +563,8 @@ def test_get_prices_orders(db_session, user_session: SessionModel, clean_prices) ) response = client.get("/api/v1/prices") assert response.status_code == 200 - assert (response.json()["items"][0]["date"]) == "2023-10-31" - response = client.get("/api/v1/prices?order_by=date") # ASC + assert len(response.json()["items"]) == 2 + response = client.get("/api/v1/prices?order_by=%2Bdate") # +date, ASC assert response.status_code == 200 assert (response.json()["items"][0]["date"]) == "2023-10-01" response = client.get("/api/v1/prices?order_by=-date") # DESC @@ -707,7 +687,7 @@ def test_get_proofs(user_session: SessionModel): } for i, item in enumerate(data): - assert item["id"] == i + 1 + assert isinstance(item["id"], int) assert item["file_path"].startswith("0001/") assert item["file_path"].endswith(".webp") assert item["type"] == ("PRICE_TAG" if i == 0 else "RECEIPT")