From ac7495df8531e8bc21c7a024d865f0cbc31b678c Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 17 Jul 2025 15:11:25 +0530 Subject: [PATCH 01/37] overriding env with test env --- backend/app/tests/conftest.py | 9 ++++++++- backend/app/tests/utils/utils.py | 13 +++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/backend/app/tests/conftest.py b/backend/app/tests/conftest.py index 5d68c3f1c..4c4349079 100644 --- a/backend/app/tests/conftest.py +++ b/backend/app/tests/conftest.py @@ -1,19 +1,26 @@ from collections.abc import Generator import pytest +import os from fastapi.testclient import TestClient from sqlmodel import Session from sqlalchemy import event +from dotenv import load_dotenv from app.core.config import settings from app.core.db import engine from app.api.deps import get_db from app.main import app from app.tests.utils.user import authentication_token_from_email -from app.tests.utils.utils import get_superuser_token_headers +from app.tests.utils.utils import get_superuser_token_headers, load_environment from app.seed_data.seed_data import seed_database +@pytest.fixture(scope="session", autouse=True) +def load_test_env(): + load_environment("../.env.test") + + @pytest.fixture(scope="function") def db() -> Generator[Session, None, None]: connection = engine.connect() diff --git a/backend/app/tests/utils/utils.py b/backend/app/tests/utils/utils.py index d48124241..3ba8b4e39 100644 --- a/backend/app/tests/utils/utils.py +++ b/backend/app/tests/utils/utils.py @@ -2,6 +2,8 @@ import string from uuid import UUID from typing import Type, TypeVar +import os +from dotenv import load_dotenv import pytest from fastapi.testclient import TestClient @@ -85,6 +87,17 @@ def get_project(session: Session, name: str | None = None) -> Project: return project +def load_environment(env_test_path: str = "../.env.test"): + """Loads the test environment variables if the .env.test file exists.""" + if os.path.exists(env_test_path): + load_dotenv(dotenv_path=env_test_path, override=True) + settings.__init__() + else: + print( + f"Warning: {env_test_path} not found. Using default environment settings." + ) + + class SequentialUuidGenerator: def __init__(self, start=0): self.start = start From bdb045920099bd04879ef9d083d872471af0b01d Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 17 Jul 2025 15:18:03 +0530 Subject: [PATCH 02/37] env file example --- .env.test.example | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .env.test.example diff --git a/.env.test.example b/.env.test.example new file mode 100644 index 000000000..accbd6c0c --- /dev/null +++ b/.env.test.example @@ -0,0 +1,45 @@ +# Environment: local, staging, production + +ENVIRONMENT=local + +PROJECT_NAME="AI Platform" +STACK_NAME=ai-platform + +#Backend +SECRET_KEY=changethis +FIRST_SUPERUSER=superuser@example.com +FIRST_SUPERUSER_PASSWORD=changethis +EMAIL_TEST_USER="test@example.com" + +# Postgres + +POSTGRES_SERVER=localhost +POSTGRES_PORT=5432 +POSTGRES_DB=ai_platform +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres + +SENTRY_DSN= + +# Configure these with your own Docker registry images + +DOCKER_IMAGE_BACKEND=backend +DOCKER_IMAGE_FRONTEND=frontend + +# AWS + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=ap-south-1 +AWS_S3_BUCKET_PREFIX = "bucket-prefix-name" + +# OpenAI + +OPENAI_API_KEY="this_is_not_a_secret" +LANGFUSE_PUBLIC_KEY="this_is_not_a_secret" +LANGFUSE_SECRET_KEY="this_is_not_a_secret" +LANGFUSE_HOST="this_is_not_a_secret" + +# Misc + +CI="" From 4703a57915340e31257accb025c6afa98dbbdac6 Mon Sep 17 00:00:00 2001 From: nishika26 Date: Tue, 22 Jul 2025 20:33:45 +0530 Subject: [PATCH 03/37] stricter overriding for test db --- .env.test.example | 20 ------------------- backend/app/tests/conftest.py | 34 ++++++++++++++++++++------------ backend/app/tests/utils/utils.py | 34 +++++++++++++++++++++++++++----- 3 files changed, 50 insertions(+), 38 deletions(-) diff --git a/.env.test.example b/.env.test.example index accbd6c0c..2d80fb760 100644 --- a/.env.test.example +++ b/.env.test.example @@ -1,9 +1,3 @@ -# Environment: local, staging, production - -ENVIRONMENT=local - -PROJECT_NAME="AI Platform" -STACK_NAME=ai-platform #Backend SECRET_KEY=changethis @@ -19,13 +13,6 @@ POSTGRES_DB=ai_platform POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres -SENTRY_DSN= - -# Configure these with your own Docker registry images - -DOCKER_IMAGE_BACKEND=backend -DOCKER_IMAGE_FRONTEND=frontend - # AWS AWS_ACCESS_KEY_ID= @@ -36,10 +23,3 @@ AWS_S3_BUCKET_PREFIX = "bucket-prefix-name" # OpenAI OPENAI_API_KEY="this_is_not_a_secret" -LANGFUSE_PUBLIC_KEY="this_is_not_a_secret" -LANGFUSE_SECRET_KEY="this_is_not_a_secret" -LANGFUSE_HOST="this_is_not_a_secret" - -# Misc - -CI="" diff --git a/backend/app/tests/conftest.py b/backend/app/tests/conftest.py index 1752b036e..4a4852262 100644 --- a/backend/app/tests/conftest.py +++ b/backend/app/tests/conftest.py @@ -1,19 +1,18 @@ from collections.abc import Generator - import pytest -import time - from fastapi.testclient import TestClient -from sqlmodel import Session +from sqlmodel import Session, create_engine from sqlalchemy import event -from dotenv import load_dotenv from app.core.config import settings -from app.core.db import engine from app.api.deps import get_db from app.main import app from app.tests.utils.user import authentication_token_from_email -from app.tests.utils.utils import get_superuser_token_headers, get_api_key_by_email +from app.tests.utils.utils import ( + get_superuser_token_headers, + get_api_key_by_email, + load_environment, +) from app.seed_data.seed_data import seed_database @@ -22,14 +21,21 @@ def load_test_env(): load_environment("../.env.test") -@pytest.fixture(scope="function") -def db() -> Generator[Session, None, None]: +@pytest.fixture(scope="session") +def engine_connection(): + engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI)) connection = engine.connect() + yield engine, connection + connection.close() + + +@pytest.fixture(scope="function") +def db(engine_connection) -> Generator[Session, None, None]: + engine, connection = engine_connection + print("enginer_1", engine) transaction = connection.begin() session = Session(bind=connection) - nested = session.begin_nested() - @event.listens_for(session, "after_transaction_end") def restart_savepoint(sess, trans): if trans.nested and not trans._parent.nested: @@ -40,12 +46,14 @@ def restart_savepoint(sess, trans): finally: session.close() transaction.rollback() - connection.close() @pytest.fixture(scope="session", autouse=True) -def seed_baseline(): +def seed_baseline(engine_connection): + engine, connection = engine_connection + print("enginer_2", engine) with Session(engine) as session: + print("Seeding baseline data...") seed_database(session) # deterministic baseline yield diff --git a/backend/app/tests/utils/utils.py b/backend/app/tests/utils/utils.py index 944b21bf0..1cf2c65ed 100644 --- a/backend/app/tests/utils/utils.py +++ b/backend/app/tests/utils/utils.py @@ -10,7 +10,7 @@ from fastapi.testclient import TestClient from sqlmodel import Session, select -from app.core.config import settings +from app.core.config import settings, Settings from app.crud import get_user_by_email, get_api_key_by_user_id from app.models import APIKeyPublic, Project from app.crud import get_api_key_by_value @@ -95,16 +95,40 @@ def get_project(session: Session, name: str | None = None) -> Project: return project -def load_environment(env_test_path: str = "../.env.test"): - """Loads the test environment variables if the .env.test file exists.""" +def load_environment( + env_test_path: str = "./Users/nishikayadav/Desktop/platform/.env.test", +): + """Loads the test environment variables if the .env.test file exists. + + If any of the required PostgreSQL credentials are missing in the env test, raises an error. + """ + if os.path.exists(env_test_path): load_dotenv(dotenv_path=env_test_path, override=True) settings.__init__() + + required_vars = [ + "POSTGRES_USER", + "POSTGRES_PASSWORD", + "POSTGRES_SERVER", + "POSTGRES_PORT", + "POSTGRES_DB", + ] + + missing_vars = [var for var in required_vars if not os.getenv(var)] + + if missing_vars: + raise ValueError( + f"Missing the following PostgreSQL credentials in {env_test_path}: {', '.join(missing_vars)}" + ) + else: - print( - f"Warning: {env_test_path} not found. Using default environment settings." + raise FileNotFoundError( + f"Warning: {env_test_path} not found. No environment variables loaded." ) + return settings + class SequentialUuidGenerator: def __init__(self, start=0): From e290dd731310cfa3291ebd91302c3b7d7f8f42b1 Mon Sep 17 00:00:00 2001 From: nishika26 Date: Tue, 22 Jul 2025 20:39:50 +0530 Subject: [PATCH 04/37] ci --- .github/workflows/continuous_integration.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 5b40cdd3e..0ee2944be 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -58,6 +58,9 @@ jobs: uv run pre-commit run --all-files working-directory: backend + - name: Making env file + run: cp .env.test.example .env.test + - name: Run tests run: uv run bash scripts/tests-start.sh "Coverage for ${{ github.sha }}" working-directory: backend From bbdf4de12d5f5db264fb9f374a31433acacc030c Mon Sep 17 00:00:00 2001 From: nishika26 Date: Tue, 22 Jul 2025 20:54:38 +0530 Subject: [PATCH 05/37] ci clarification --- .github/workflows/continuous_integration.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 0ee2944be..15a18a9fb 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -33,7 +33,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Making env file + - name: Making test-env file run: cp .env.example .env - name: Install uv @@ -58,7 +58,7 @@ jobs: uv run pre-commit run --all-files working-directory: backend - - name: Making env file + - name: Making env file for testing run: cp .env.test.example .env.test - name: Run tests From 0bc016d10f5a414d57d191d5017d79293fcb4247 Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 24 Jul 2025 20:32:46 +0530 Subject: [PATCH 06/37] updating env variables before importing --- backend/app/tests/conftest.py | 40 +++++++++++++------------------- backend/app/tests/utils/utils.py | 23 ++++++++++++------ backend/app/tests_pre_start.py | 8 ++++++- 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/backend/app/tests/conftest.py b/backend/app/tests/conftest.py index 4a4852262..21e7d4daf 100644 --- a/backend/app/tests/conftest.py +++ b/backend/app/tests/conftest.py @@ -4,35 +4,37 @@ from sqlmodel import Session, create_engine from sqlalchemy import event -from app.core.config import settings from app.api.deps import get_db from app.main import app from app.tests.utils.user import authentication_token_from_email + + +@pytest.fixture(scope="session", autouse=True) +def load_test_env(): + load_environment("../.env.test") + + +from app.core.config import settings from app.tests.utils.utils import ( get_superuser_token_headers, get_api_key_by_email, load_environment, ) from app.seed_data.seed_data import seed_database +from app.core.db import engine @pytest.fixture(scope="session", autouse=True) -def load_test_env(): - load_environment("../.env.test") - - -@pytest.fixture(scope="session") -def engine_connection(): - engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI)) - connection = engine.connect() - yield engine, connection - connection.close() +def seed_baseline(): + with Session(engine) as session: + print("Seeding baseline data...") + seed_database(session) # deterministic baseline + yield @pytest.fixture(scope="function") -def db(engine_connection) -> Generator[Session, None, None]: - engine, connection = engine_connection - print("enginer_1", engine) +def db() -> Generator[Session, None, None]: + connection = engine.connect() transaction = connection.begin() session = Session(bind=connection) @@ -48,16 +50,6 @@ def restart_savepoint(sess, trans): transaction.rollback() -@pytest.fixture(scope="session", autouse=True) -def seed_baseline(engine_connection): - engine, connection = engine_connection - print("enginer_2", engine) - with Session(engine) as session: - print("Seeding baseline data...") - seed_database(session) # deterministic baseline - yield - - @pytest.fixture(scope="function") def client(db: Session): app.dependency_overrides[get_db] = lambda: db diff --git a/backend/app/tests/utils/utils.py b/backend/app/tests/utils/utils.py index 677f70b55..3394e7073 100644 --- a/backend/app/tests/utils/utils.py +++ b/backend/app/tests/utils/utils.py @@ -95,12 +95,15 @@ def get_project(session: Session, name: str | None = None) -> Project: return project -def load_environment( - env_test_path: str = "./Users/nishikayadav/Desktop/platform/.env.test", -): +from sqlalchemy.engine.url import make_url +from sqlalchemy import create_engine + + +def load_environment(env_test_path: str = "../.env.test"): """Loads the test environment variables if the .env.test file exists. - If any of the required PostgreSQL credentials are missing in the env test, raises an error. + Raises an error if any required PostgreSQL credentials are missing or if the + POSTGRES_DB value does not contain the word 'test', to ensure a safe test database is used. """ if os.path.exists(env_test_path): @@ -122,15 +125,21 @@ def load_environment( f"Missing the following PostgreSQL credentials in {env_test_path}: {', '.join(missing_vars)}" ) + db_name = os.getenv("POSTGRES_DB", "").lower() + if "test" not in db_name: + raise RuntimeError( + f"Connected to database '{db_name}', which doesn't appear to be a test database" + ) + else: raise FileNotFoundError( - f"Warning: {env_test_path} not found. No environment variables loaded." + f"{env_test_path} not found. No environment variables loaded." ) return settings - - def get_assistant(session: Session, name: str | None = None) -> Assistant: + +def get_assistant(session: Session, name: str | None = None) -> Assistant: """ Retrieve an active assistant from the database. diff --git a/backend/app/tests_pre_start.py b/backend/app/tests_pre_start.py index 0ce604563..11530832b 100644 --- a/backend/app/tests_pre_start.py +++ b/backend/app/tests_pre_start.py @@ -4,6 +4,10 @@ from sqlmodel import Session, select from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed +from app.tests.utils.utils import load_environment + +load_environment("../.env.test") + from app.core.db import engine logging.basicConfig(level=logging.INFO) @@ -21,7 +25,6 @@ ) def init(db_engine: Engine) -> None: try: - # Try to create session to check if DB is awake with Session(db_engine) as session: session.exec(select(1)) except Exception as e: @@ -33,6 +36,9 @@ def main() -> None: logger.info("Initializing service") init(engine) logger.info("Service finished initializing") + logger.info( + f"Using database URL: {engine.url.render_as_string(hide_password=True)}" + ) if __name__ == "__main__": From c0e37f0913cde29aa80fab8233be506c2d8eb75d Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 24 Jul 2025 20:36:31 +0530 Subject: [PATCH 07/37] adding test db to ci --- .github/workflows/continuous_integration.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 15a18a9fb..56058fbd6 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -10,7 +10,7 @@ jobs: checks: runs-on: ubuntu-latest services: - postgres: + postgres_main: image: postgres:16 env: POSTGRES_USER: postgres @@ -20,6 +20,16 @@ jobs: - 5432:5432 options: --health-cmd "pg_isready -U postgres" --health-interval 10s --health-timeout 5s --health-retries 5 + postgres_test: + image: postgres:16 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: ai_platform_test + ports: + - 5433:5432 + options: --health-cmd "pg_isready -U postgres" --health-interval 10s --health-timeout 5s --health-retries 5 + strategy: matrix: python-version: ["3.11.7"] From cc299695872db4bfab83e7c45fee44ad713736ac Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 24 Jul 2025 20:42:00 +0530 Subject: [PATCH 08/37] adding test db to ci --- .github/workflows/continuous_integration.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 56058fbd6..fa3bf24c7 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -68,8 +68,20 @@ jobs: uv run pre-commit run --all-files working-directory: backend - - name: Making env file for testing - run: cp .env.test.example .env.test + - name: Load environment variables for testing + run: cp .env.test.example .env.test # Copy the test env file + working-directory: backend + + - name: Set environment for test DB + run: | + echo "SQLALCHEMY_DATABASE_URI=postgresql://postgres:postgres@localhost:5433/ai_platform_test" >> .env.test + working-directory: backend + + - name: Activate virtual environment and run Alembic migrations for test DB + run: | + source .venv/bin/activate + alembic upgrade head + working-directory: backend - name: Run tests run: uv run bash scripts/tests-start.sh "Coverage for ${{ github.sha }}" From be834e791aa5c7c9761adb74fe8e787fd10418da Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 24 Jul 2025 20:44:21 +0530 Subject: [PATCH 09/37] adding test db to ci --- .github/workflows/continuous_integration.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index fa3bf24c7..231203590 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -68,9 +68,8 @@ jobs: uv run pre-commit run --all-files working-directory: backend - - name: Load environment variables for testing - run: cp .env.test.example .env.test # Copy the test env file - working-directory: backend + - name: Making env file for testing + run: cp .env.test.example .env.test - name: Set environment for test DB run: | From 07cfecdc8ba9971d60b2969338753413d3c27a87 Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 24 Jul 2025 20:52:30 +0530 Subject: [PATCH 10/37] adding test db to ci --- .github/workflows/continuous_integration.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 231203590..5d35a2877 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -81,6 +81,8 @@ jobs: source .venv/bin/activate alembic upgrade head working-directory: backend + env: + SQLALCHEMY_DATABASE_URI: postgresql://postgres:postgres@localhost:5433/ai_platform_test - name: Run tests run: uv run bash scripts/tests-start.sh "Coverage for ${{ github.sha }}" From 1516fb10c3e142b489e0cad71976ae1fb15019ee Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 24 Jul 2025 20:55:42 +0530 Subject: [PATCH 11/37] adding test db to ci --- .github/workflows/continuous_integration.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 5d35a2877..b86bb4073 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -87,6 +87,8 @@ jobs: - name: Run tests run: uv run bash scripts/tests-start.sh "Coverage for ${{ github.sha }}" working-directory: backend + env: + SQLALCHEMY_DATABASE_URI: postgresql://postgres:postgres@localhost:5433/ai_platform_test - name: Upload coverage reports to codecov uses: codecov/codecov-action@v5.4.3 From 3a2bf4cc9fe3790b7b39d29adc81a67f4c5f4b10 Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 24 Jul 2025 21:17:22 +0530 Subject: [PATCH 12/37] adding test db to ci --- .github/workflows/continuous_integration.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index b86bb4073..f37212615 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -85,11 +85,14 @@ jobs: SQLALCHEMY_DATABASE_URI: postgresql://postgres:postgres@localhost:5433/ai_platform_test - name: Run tests - run: uv run bash scripts/tests-start.sh "Coverage for ${{ github.sha }}" + run: | + source .env.test + uv run bash scripts/tests-start.sh "Coverage for ${{ github.sha }}" working-directory: backend env: SQLALCHEMY_DATABASE_URI: postgresql://postgres:postgres@localhost:5433/ai_platform_test + - name: Upload coverage reports to codecov uses: codecov/codecov-action@v5.4.3 with: From 0c680fa358c48cced7951579d228fdffd90ae082 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Fri, 1 Aug 2025 09:46:01 +0530 Subject: [PATCH 13/37] coderabbit suggestions --- backend/app/tests/conftest.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/backend/app/tests/conftest.py b/backend/app/tests/conftest.py index e031e4f81..fde000240 100644 --- a/backend/app/tests/conftest.py +++ b/backend/app/tests/conftest.py @@ -1,13 +1,21 @@ -from collections.abc import Generator import pytest +from collections.abc import Generator from fastapi.testclient import TestClient -from sqlmodel import Session, create_engine +from sqlmodel import Session from sqlalchemy import event from app.api.deps import get_db from app.main import app from app.models import APIKeyPublic +from app.core.config import settings +from app.core.db import engine from app.tests.utils.user import authentication_token_from_email +from app.tests.utils.utils import ( + get_superuser_token_headers, + get_api_key_by_email, + load_environment, +) +from app.seed_data.seed_data import seed_database @pytest.fixture(scope="session", autouse=True) From 945bf3d7ba2f22f74794aab3d6fec80a801145ce Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Fri, 1 Aug 2025 09:46:14 +0530 Subject: [PATCH 14/37] coderabbit suggestions --- .env.test.example | 4 +-- .github/workflows/continuous_integration.yml | 37 ++------------------ .gitignore | 2 +- 3 files changed, 6 insertions(+), 37 deletions(-) diff --git a/.env.test.example b/.env.test.example index 2d80fb760..7bcbdbd3d 100644 --- a/.env.test.example +++ b/.env.test.example @@ -9,7 +9,7 @@ EMAIL_TEST_USER="test@example.com" POSTGRES_SERVER=localhost POSTGRES_PORT=5432 -POSTGRES_DB=ai_platform +POSTGRES_DB=ai_platform_test POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres @@ -18,7 +18,7 @@ POSTGRES_PASSWORD=postgres AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION=ap-south-1 -AWS_S3_BUCKET_PREFIX = "bucket-prefix-name" +AWS_S3_BUCKET_PREFIX ="bucket-prefix-name" # OpenAI diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index f37212615..5b40cdd3e 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -10,7 +10,7 @@ jobs: checks: runs-on: ubuntu-latest services: - postgres_main: + postgres: image: postgres:16 env: POSTGRES_USER: postgres @@ -20,16 +20,6 @@ jobs: - 5432:5432 options: --health-cmd "pg_isready -U postgres" --health-interval 10s --health-timeout 5s --health-retries 5 - postgres_test: - image: postgres:16 - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: ai_platform_test - ports: - - 5433:5432 - options: --health-cmd "pg_isready -U postgres" --health-interval 10s --health-timeout 5s --health-retries 5 - strategy: matrix: python-version: ["3.11.7"] @@ -43,7 +33,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Making test-env file + - name: Making env file run: cp .env.example .env - name: Install uv @@ -68,30 +58,9 @@ jobs: uv run pre-commit run --all-files working-directory: backend - - name: Making env file for testing - run: cp .env.test.example .env.test - - - name: Set environment for test DB - run: | - echo "SQLALCHEMY_DATABASE_URI=postgresql://postgres:postgres@localhost:5433/ai_platform_test" >> .env.test - working-directory: backend - - - name: Activate virtual environment and run Alembic migrations for test DB - run: | - source .venv/bin/activate - alembic upgrade head - working-directory: backend - env: - SQLALCHEMY_DATABASE_URI: postgresql://postgres:postgres@localhost:5433/ai_platform_test - - name: Run tests - run: | - source .env.test - uv run bash scripts/tests-start.sh "Coverage for ${{ github.sha }}" + run: uv run bash scripts/tests-start.sh "Coverage for ${{ github.sha }}" working-directory: backend - env: - SQLALCHEMY_DATABASE_URI: postgresql://postgres:postgres@localhost:5433/ai_platform_test - - name: Upload coverage reports to codecov uses: codecov/codecov-action@v5.4.3 diff --git a/.gitignore b/.gitignore index 0d8e46df9..ad2127f45 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ node_modules/ /playwright/.cache/ # Environments -.env +.env* .venv env/ venv/ From bc4d30e4f633b58ebc5f9cfe414b5c61d92316d0 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Fri, 1 Aug 2025 11:36:45 +0530 Subject: [PATCH 15/37] cleanups --- backend/app/tests/conftest.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/backend/app/tests/conftest.py b/backend/app/tests/conftest.py index ea68862cf..343f6b9cf 100644 --- a/backend/app/tests/conftest.py +++ b/backend/app/tests/conftest.py @@ -1,9 +1,9 @@ import pytest +import logging from collections.abc import Generator from fastapi.testclient import TestClient from sqlmodel import Session from sqlalchemy import event -from collections.abc import Generator from app.api.deps import get_db from app.main import app @@ -18,26 +18,14 @@ ) from app.seed_data.seed_data import seed_database - -@pytest.fixture(scope="session", autouse=True) -def load_test_env(): - load_environment("../.env.test") - - -from app.core.config import settings -from app.tests.utils.utils import ( - get_superuser_token_headers, - get_api_key_by_email, - load_environment, -) -from app.seed_data.seed_data import seed_database -from app.core.db import engine +logger = logging.getLogger(__name__) @pytest.fixture(scope="session", autouse=True) def seed_baseline(): + load_environment("../.env.test") with Session(engine) as session: - print("Seeding baseline data...") + logger.info("Seeding baseline data...") seed_database(session) # deterministic baseline yield From 1157fe2f725299b5f20f4340dc1d04b8e1e015dc Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Fri, 1 Aug 2025 12:34:24 +0530 Subject: [PATCH 16/37] fixing few testcases --- backend/app/core/cloud/storage.py | 6 +++++- .../api/routes/documents/test_route_document_upload.py | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/backend/app/core/cloud/storage.py b/backend/app/core/cloud/storage.py index 0c225d912..8f42750ce 100644 --- a/backend/app/core/cloud/storage.py +++ b/backend/app/core/cloud/storage.py @@ -83,7 +83,11 @@ def create(self): @dataclass(frozen=True) class SimpleStorageName: Key: str - Bucket: str = settings.AWS_S3_BUCKET + Bucket: str = None + + def __post_init__(self): + if self.Bucket is None: + object.__setattr__(self, "Bucket", settings.AWS_S3_BUCKET) def __str__(self): return urlunparse(self.to_url()) diff --git a/backend/app/tests/api/routes/documents/test_route_document_upload.py b/backend/app/tests/api/routes/documents/test_route_document_upload.py index 4c6abaaa9..205e58777 100644 --- a/backend/app/tests/api/routes/documents/test_route_document_upload.py +++ b/backend/app/tests/api/routes/documents/test_route_document_upload.py @@ -17,6 +17,7 @@ WebCrawler, httpx_to_standard, ) +from app.tests.utils.utils import load_environment class WebUploader(WebCrawler): @@ -51,6 +52,10 @@ def uploader(client: TestClient, user_api_key_header: dict[str, str]): @pytest.fixture(scope="class") def aws_credentials(): + # Load test environment to ensure correct bucket name + load_environment("../.env.test") + + # Set AWS credentials for moto mock os.environ["AWS_ACCESS_KEY_ID"] = "testing" os.environ["AWS_SECRET_ACCESS_KEY"] = "testing" os.environ["AWS_SECURITY_TOKEN"] = "testing" @@ -58,8 +63,8 @@ def aws_credentials(): os.environ["AWS_DEFAULT_REGION"] = settings.AWS_DEFAULT_REGION -@mock_aws @pytest.mark.usefixtures("aws_credentials") +@mock_aws class TestDocumentRouteUpload: def test_adds_to_database( self, From 75286127a6a9adc98d3afe82cebcb212c2781d24 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Fri, 1 Aug 2025 13:15:38 +0530 Subject: [PATCH 17/37] few more cleanups --- .env.test.example | 2 +- .github/workflows/continuous_integration.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.test.example b/.env.test.example index 7bcbdbd3d..eae7903f8 100644 --- a/.env.test.example +++ b/.env.test.example @@ -18,7 +18,7 @@ POSTGRES_PASSWORD=postgres AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION=ap-south-1 -AWS_S3_BUCKET_PREFIX ="bucket-prefix-name" +AWS_S3_BUCKET_PREFIX="bucket-prefix-name" # OpenAI diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 5b40cdd3e..d09184dd0 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -34,7 +34,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Making env file - run: cp .env.example .env + run: cp .env.example .env.test - name: Install uv uses: astral-sh/setup-uv@v6 From 0fcd10c29a61fc0b08e08b959a84c8dc9d92e6a7 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Fri, 1 Aug 2025 13:24:17 +0530 Subject: [PATCH 18/37] creating both env files --- .github/workflows/continuous_integration.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index d09184dd0..34f6f3032 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -34,7 +34,8 @@ jobs: python-version: ${{ matrix.python-version }} - name: Making env file - run: cp .env.example .env.test + run: cp .env.example .env + run: cp .env.test.example .env.test - name: Install uv uses: astral-sh/setup-uv@v6 From 4f2375c0e778504cb1586e62c3854c3ee96cd827 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Fri, 1 Aug 2025 13:33:06 +0530 Subject: [PATCH 19/37] cleanup CI --- .github/workflows/continuous_integration.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 34f6f3032..a47c672f4 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -34,8 +34,9 @@ jobs: python-version: ${{ matrix.python-version }} - name: Making env file - run: cp .env.example .env - run: cp .env.test.example .env.test + run: | + cp .env.example .env + cp .env.test.example .env.test - name: Install uv uses: astral-sh/setup-uv@v6 From f8615e7bb417b9cddb9a6ed6127072ff08a5b0e2 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Fri, 1 Aug 2025 14:35:11 +0530 Subject: [PATCH 20/37] using testdb --- .github/workflows/continuous_integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index a47c672f4..04d5a7510 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -15,7 +15,7 @@ jobs: env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres - POSTGRES_DB: ai_platform + POSTGRES_DB: ai_platform_test ports: - 5432:5432 options: --health-cmd "pg_isready -U postgres" --health-interval 10s --health-timeout 5s --health-retries 5 From 3c428754e9e5c158cc233ee978ad95e30af2d99b Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Fri, 1 Aug 2025 14:43:02 +0530 Subject: [PATCH 21/37] updating DB --- .github/workflows/continuous_integration.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 04d5a7510..e4a7d178a 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -51,6 +51,8 @@ jobs: - name: Activate virtual environment and run Alembic migrations run: | source .venv/bin/activate + # Use test database for migrations in CI + export POSTGRES_DB=ai_platform_test alembic upgrade head working-directory: backend From d66340629017edc0bc95ab1f27c0cc224043cd24 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Fri, 1 Aug 2025 14:53:59 +0530 Subject: [PATCH 22/37] using env.test as .env --- .github/workflows/continuous_integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index e4a7d178a..1ef7bd5ce 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -35,7 +35,7 @@ jobs: - name: Making env file run: | - cp .env.example .env + cp .env.test.example .env cp .env.test.example .env.test - name: Install uv From 0ec01d76bbb616db44ef8e4cd3e37a1878e863f6 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Fri, 1 Aug 2025 15:09:24 +0530 Subject: [PATCH 23/37] cleaning up env variable --- .env.test.example | 4 ---- .github/workflows/benchmark.yml | 4 ---- backend/app/core/config.py | 4 ---- docker-compose.yml | 8 -------- 4 files changed, 20 deletions(-) diff --git a/.env.test.example b/.env.test.example index eae7903f8..ad3934f2d 100644 --- a/.env.test.example +++ b/.env.test.example @@ -19,7 +19,3 @@ AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION=ap-south-1 AWS_S3_BUCKET_PREFIX="bucket-prefix-name" - -# OpenAI - -OPENAI_API_KEY="this_is_not_a_secret" diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 917203f2c..daccfd6e6 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -18,10 +18,6 @@ jobs: count: [100] env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - LANGFUSE_PUBLIC_KEY: ${{ secrets.LANGFUSE_PUBLIC_KEY }} - LANGFUSE_SECRET_KEY: ${{ secrets.LANGFUSE_SECRET_KEY }} - LANGFUSE_HOST: ${{ secrets.LANGFUSE_HOST }} LOCAL_CREDENTIALS_ORG_OPENAI_API_KEY: ${{ secrets.LOCAL_CREDENTIALS_ORG_OPENAI_API_KEY }} LOCAL_CREDENTIALS_API_KEY: ${{ secrets.LOCAL_CREDENTIALS_API_KEY }} diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 24779bf33..0c829ed26 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -30,10 +30,6 @@ class Settings(BaseSettings): env_ignore_empty=True, extra="ignore", ) - LANGFUSE_PUBLIC_KEY: str - LANGFUSE_SECRET_KEY: str - LANGFUSE_HOST: str # 🇪🇺 EU region - OPENAI_API_KEY: str API_V1_STR: str = "/api/v1" SECRET_KEY: str = secrets.token_urlsafe(32) # 60 minutes * 24 hours * 1 days = 1 days diff --git a/docker-compose.yml b/docker-compose.yml index e8e1330dc..78a4af528 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -72,10 +72,6 @@ services: - POSTGRES_USER=${POSTGRES_USER?Variable not set} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} - SENTRY_DSN=${SENTRY_DSN} - - OPENAI_API_KEY=${OPENAI_API_KEY} - - LANGFUSE_PUBLIC_KEY=${LANGFUSE_PUBLIC_KEY} - - LANGFUSE_SECRET_KEY=${LANGFUSE_SECRET_KEY} - - LANGFUSE_HOST=${LANGFUSE_HOST} - LOCAL_CREDENTIALS_ORG_OPENAI_API_KEY=${LOCAL_CREDENTIALS_ORG_OPENAI_API_KEY} - LOCAL_CREDENTIALS_API_KEY=${LOCAL_CREDENTIALS_API_KEY} - EMAIL_TEST_USER=${EMAIL_TEST_USER} @@ -112,10 +108,6 @@ services: - POSTGRES_USER=${POSTGRES_USER?Variable not set} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} - SENTRY_DSN=${SENTRY_DSN} - - OPENAI_API_KEY=${OPENAI_API_KEY} - - LANGFUSE_PUBLIC_KEY=${LANGFUSE_PUBLIC_KEY} - - LANGFUSE_SECRET_KEY=${LANGFUSE_SECRET_KEY} - - LANGFUSE_HOST=${LANGFUSE_HOST} - LOCAL_CREDENTIALS_ORG_OPENAI_API_KEY=${LOCAL_CREDENTIALS_ORG_OPENAI_API_KEY} - LOCAL_CREDENTIALS_API_KEY=${LOCAL_CREDENTIALS_API_KEY} - EMAIL_TEST_USER=${EMAIL_TEST_USER} From ae259aa8f22b5ddca8d06634280151371323b484 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Fri, 1 Aug 2025 15:18:31 +0530 Subject: [PATCH 24/37] updating env.example --- .env.test.example | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.env.test.example b/.env.test.example index ad3934f2d..79bc08147 100644 --- a/.env.test.example +++ b/.env.test.example @@ -1,3 +1,7 @@ +ENVIRONMENT=local + +PROJECT_NAME="AI Platform" +STACK_NAME=ai-platform #Backend SECRET_KEY=changethis From de5a19cdd0d43ec4ef05a96c676ce3f5869e9236 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Fri, 1 Aug 2025 16:08:53 +0530 Subject: [PATCH 25/37] increasing pool size --- backend/app/core/db.py | 13 ++++++++++++- backend/app/tests/api/routes/test_onboarding.py | 6 +----- backend/app/tests/conftest.py | 9 +++++++++ backend/app/tests/crud/test_thread_result.py | 5 ++--- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/backend/app/core/db.py b/backend/app/core/db.py index ba991fb36..b35dfcc74 100644 --- a/backend/app/core/db.py +++ b/backend/app/core/db.py @@ -4,7 +4,18 @@ from app.core.config import settings from app.models import User, UserCreate -engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI)) +# Configure connection pool settings +# For testing, we need more connections since tests run in parallel +pool_size = 20 if settings.ENVIRONMENT == "local" else 5 +max_overflow = 30 if settings.ENVIRONMENT == "local" else 10 + +engine = create_engine( + str(settings.SQLALCHEMY_DATABASE_URI), + pool_size=pool_size, + max_overflow=max_overflow, + pool_pre_ping=True, + pool_recycle=300, # Recycle connections after 5 minutes +) # make sure all SQLModel models are imported (app.models) before initializing DB diff --git a/backend/app/tests/api/routes/test_onboarding.py b/backend/app/tests/api/routes/test_onboarding.py index 6f0327003..3a7ce037e 100644 --- a/backend/app/tests/api/routes/test_onboarding.py +++ b/backend/app/tests/api/routes/test_onboarding.py @@ -1,11 +1,7 @@ -import pytest from fastapi.testclient import TestClient from app.main import app # Assuming your FastAPI app is in app/main.py from app.models import Organization, Project, User, APIKey -from app.crud import create_organization, create_project, create_user, create_api_key -from app.api.deps import SessionDep -from sqlalchemy import create_engine -from sqlmodel import Session, SQLModel +from sqlmodel import Session from app.core.config import settings from app.tests.utils.utils import random_email, random_lower_string from app.core.security import decrypt_api_key diff --git a/backend/app/tests/conftest.py b/backend/app/tests/conftest.py index 343f6b9cf..dd60df11e 100644 --- a/backend/app/tests/conftest.py +++ b/backend/app/tests/conftest.py @@ -30,6 +30,14 @@ def seed_baseline(): yield +@pytest.fixture(scope="function", autouse=True) +def cleanup_sessions(): + """Clean up any lingering sessions after each test.""" + yield + # Force cleanup of any remaining connections in the pool + engine.dispose() + + @pytest.fixture(scope="function") def db() -> Generator[Session, None, None]: connection = engine.connect() @@ -46,6 +54,7 @@ def restart_savepoint(sess, trans): finally: session.close() transaction.rollback() + connection.close() # Explicitly close the connection @pytest.fixture(scope="function") diff --git a/backend/app/tests/crud/test_thread_result.py b/backend/app/tests/crud/test_thread_result.py index 00c581dea..b82a5a8d5 100644 --- a/backend/app/tests/crud/test_thread_result.py +++ b/backend/app/tests/crud/test_thread_result.py @@ -1,7 +1,6 @@ -import pytest -from sqlmodel import SQLModel, Session, create_engine +from sqlmodel import Session -from app.models import OpenAI_Thread, OpenAIThreadCreate +from app.models import OpenAIThreadCreate from app.crud import upsert_thread_result, get_thread_result From 21497d1b4185ef18741c44912016548e598a3ad5 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Mon, 4 Aug 2025 10:57:49 +0530 Subject: [PATCH 26/37] cleanup file --- .../api/routes/documents/test_route_document_upload.py | 10 ++++------ backend/app/tests/utils/utils.py | 7 +++---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/backend/app/tests/api/routes/documents/test_route_document_upload.py b/backend/app/tests/api/routes/documents/test_route_document_upload.py index 205e58777..38c41ce51 100644 --- a/backend/app/tests/api/routes/documents/test_route_document_upload.py +++ b/backend/app/tests/api/routes/documents/test_route_document_upload.py @@ -8,15 +8,12 @@ from moto import mock_aws from sqlmodel import Session, select from fastapi.testclient import TestClient +from dotenv import find_dotenv from app.core.cloud import AmazonCloudStorageClient from app.core.config import settings from app.models import Document -from app.tests.utils.document import ( - Route, - WebCrawler, - httpx_to_standard, -) +from app.tests.utils.document import Route, WebCrawler, httpx_to_standard from app.tests.utils.utils import load_environment @@ -53,7 +50,8 @@ def uploader(client: TestClient, user_api_key_header: dict[str, str]): @pytest.fixture(scope="class") def aws_credentials(): # Load test environment to ensure correct bucket name - load_environment("../.env.test") + path = find_dotenv(".env.test") + load_environment(path) # Set AWS credentials for moto mock os.environ["AWS_ACCESS_KEY_ID"] = "testing" diff --git a/backend/app/tests/utils/utils.py b/backend/app/tests/utils/utils.py index d9d1febbb..47114fd20 100644 --- a/backend/app/tests/utils/utils.py +++ b/backend/app/tests/utils/utils.py @@ -91,11 +91,10 @@ def get_project(session: Session, name: str | None = None) -> Project: return project -from sqlalchemy.engine.url import make_url -from sqlalchemy import create_engine +from dotenv import load_dotenv, find_dotenv -def load_environment(env_test_path: str = "../.env.test"): +def load_environment(env_test_path: str): """Loads the test environment variables if the .env.test file exists. Raises an error if any required PostgreSQL credentials are missing or if the @@ -103,7 +102,7 @@ def load_environment(env_test_path: str = "../.env.test"): """ if os.path.exists(env_test_path): - load_dotenv(dotenv_path=env_test_path, override=True) + load_dotenv(env_test_path, override=True) settings.__init__() required_vars = [ From aa14f7eef223a8d330ea1379b6b26c535549f372 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Mon, 4 Aug 2025 10:57:55 +0530 Subject: [PATCH 27/37] cleanup file --- .env.example | 11 ----------- .github/workflows/continuous_integration.yml | 2 -- 2 files changed, 13 deletions(-) diff --git a/.env.example b/.env.example index abea3f771..b5ca0ba9b 100644 --- a/.env.example +++ b/.env.example @@ -44,14 +44,3 @@ AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION=ap-south-1 AWS_S3_BUCKET_PREFIX="bucket-prefix-name" - -# OpenAI - -OPENAI_API_KEY="this_is_not_a_secret" -LANGFUSE_PUBLIC_KEY="this_is_not_a_secret" -LANGFUSE_SECRET_KEY="this_is_not_a_secret" -LANGFUSE_HOST="this_is_not_a_secret" - -# Misc - -CI="" diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 1ef7bd5ce..b617c59e2 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -51,8 +51,6 @@ jobs: - name: Activate virtual environment and run Alembic migrations run: | source .venv/bin/activate - # Use test database for migrations in CI - export POSTGRES_DB=ai_platform_test alembic upgrade head working-directory: backend From f69f4284725f1ddcae898a76f2ebe0a3e11b5470 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Mon, 4 Aug 2025 11:20:56 +0530 Subject: [PATCH 28/37] cleanup env --- backend/app/tests/utils/utils.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/app/tests/utils/utils.py b/backend/app/tests/utils/utils.py index 47114fd20..0ce37c7fb 100644 --- a/backend/app/tests/utils/utils.py +++ b/backend/app/tests/utils/utils.py @@ -1,21 +1,25 @@ import random import string +import logging + from uuid import UUID from typing import Type, TypeVar import os from dotenv import load_dotenv - -import pytest from pydantic import EmailStr from fastapi.testclient import TestClient from sqlmodel import Session, select +from dotenv import load_dotenv + from app.core.config import settings from app.crud.user import get_user_by_email from app.crud.api_key import get_api_key_by_value, get_api_key_by_user_id from app.models import APIKeyPublic, Project, Assistant, Organization +logger = logging.getLogger(__name__) + T = TypeVar("T") @@ -91,10 +95,7 @@ def get_project(session: Session, name: str | None = None) -> Project: return project -from dotenv import load_dotenv, find_dotenv - - -def load_environment(env_test_path: str): +def load_environment(env_test_path: str = "../.env.test"): """Loads the test environment variables if the .env.test file exists. Raises an error if any required PostgreSQL credentials are missing or if the @@ -103,7 +104,6 @@ def load_environment(env_test_path: str): if os.path.exists(env_test_path): load_dotenv(env_test_path, override=True) - settings.__init__() required_vars = [ "POSTGRES_USER", @@ -127,8 +127,8 @@ def load_environment(env_test_path: str): ) else: - raise FileNotFoundError( - f"{env_test_path} not found. No environment variables loaded." + logger.warning( + f"{env_test_path} not found. Using default environment settings." ) return settings From 9ecc5cb4ee601b580d9dd11719000eb65f60d914 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Mon, 4 Aug 2025 12:07:17 +0530 Subject: [PATCH 29/37] cleaning up imports --- backend/app/tests/utils/utils.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/backend/app/tests/utils/utils.py b/backend/app/tests/utils/utils.py index 0ce37c7fb..9f97bd24d 100644 --- a/backend/app/tests/utils/utils.py +++ b/backend/app/tests/utils/utils.py @@ -1,18 +1,15 @@ import random import string import logging - -from uuid import UUID -from typing import Type, TypeVar import os -from dotenv import load_dotenv +from uuid import UUID +from typing import TypeVar from pydantic import EmailStr from fastapi.testclient import TestClient from sqlmodel import Session, select from dotenv import load_dotenv - from app.core.config import settings from app.crud.user import get_user_by_email from app.crud.api_key import get_api_key_by_value, get_api_key_by_user_id @@ -68,7 +65,7 @@ def get_user_from_api_key(db: Session, api_key_headers: dict[str, str]) -> APIKe return api_key -def get_non_existent_id(session: Session, model: Type[T]) -> int: +def get_non_existent_id(session: Session, model: type[T]) -> int: result = session.exec(select(model.id).order_by(model.id.desc())).first() return (result or 0) + 1 From 5398d214a842460f8c7ce899094ecf8426c060e6 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Tue, 5 Aug 2025 12:32:46 +0530 Subject: [PATCH 30/37] revert changes for storage.py --- backend/app/core/cloud/storage.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/backend/app/core/cloud/storage.py b/backend/app/core/cloud/storage.py index 8f42750ce..0c225d912 100644 --- a/backend/app/core/cloud/storage.py +++ b/backend/app/core/cloud/storage.py @@ -83,11 +83,7 @@ def create(self): @dataclass(frozen=True) class SimpleStorageName: Key: str - Bucket: str = None - - def __post_init__(self): - if self.Bucket is None: - object.__setattr__(self, "Bucket", settings.AWS_S3_BUCKET) + Bucket: str = settings.AWS_S3_BUCKET def __str__(self): return urlunparse(self.to_url()) From 724a2294099b019bbfff52bf820a8ca78f55680d Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Tue, 5 Aug 2025 13:02:46 +0530 Subject: [PATCH 31/37] removing redundant code --- .../api/routes/documents/test_route_document_upload.py | 6 ------ backend/app/tests/conftest.py | 7 ++++++- backend/app/tests/utils/utils.py | 4 +--- backend/app/tests_pre_start.py | 4 ---- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/backend/app/tests/api/routes/documents/test_route_document_upload.py b/backend/app/tests/api/routes/documents/test_route_document_upload.py index 38c41ce51..b4ec4c5f3 100644 --- a/backend/app/tests/api/routes/documents/test_route_document_upload.py +++ b/backend/app/tests/api/routes/documents/test_route_document_upload.py @@ -8,13 +8,11 @@ from moto import mock_aws from sqlmodel import Session, select from fastapi.testclient import TestClient -from dotenv import find_dotenv from app.core.cloud import AmazonCloudStorageClient from app.core.config import settings from app.models import Document from app.tests.utils.document import Route, WebCrawler, httpx_to_standard -from app.tests.utils.utils import load_environment class WebUploader(WebCrawler): @@ -49,10 +47,6 @@ def uploader(client: TestClient, user_api_key_header: dict[str, str]): @pytest.fixture(scope="class") def aws_credentials(): - # Load test environment to ensure correct bucket name - path = find_dotenv(".env.test") - load_environment(path) - # Set AWS credentials for moto mock os.environ["AWS_ACCESS_KEY_ID"] = "testing" os.environ["AWS_SECRET_ACCESS_KEY"] = "testing" diff --git a/backend/app/tests/conftest.py b/backend/app/tests/conftest.py index dd60df11e..5356baab5 100644 --- a/backend/app/tests/conftest.py +++ b/backend/app/tests/conftest.py @@ -1,9 +1,11 @@ import pytest import logging + from collections.abc import Generator from fastapi.testclient import TestClient from sqlmodel import Session from sqlalchemy import event +from dotenv import find_dotenv from app.api.deps import get_db from app.main import app @@ -23,7 +25,10 @@ @pytest.fixture(scope="session", autouse=True) def seed_baseline(): - load_environment("../.env.test") + # Load test environment to ensure correct bucket name + path = find_dotenv(".env.test") + load_environment(path) + with Session(engine) as session: logger.info("Seeding baseline data...") seed_database(session) # deterministic baseline diff --git a/backend/app/tests/utils/utils.py b/backend/app/tests/utils/utils.py index 9f97bd24d..5d3208dd7 100644 --- a/backend/app/tests/utils/utils.py +++ b/backend/app/tests/utils/utils.py @@ -92,7 +92,7 @@ def get_project(session: Session, name: str | None = None) -> Project: return project -def load_environment(env_test_path: str = "../.env.test"): +def load_environment(env_test_path: str = "../.env.test") -> None: """Loads the test environment variables if the .env.test file exists. Raises an error if any required PostgreSQL credentials are missing or if the @@ -128,8 +128,6 @@ def load_environment(env_test_path: str = "../.env.test"): f"{env_test_path} not found. Using default environment settings." ) - return settings - def get_assistant(session: Session, name: str | None = None) -> Assistant: """ diff --git a/backend/app/tests_pre_start.py b/backend/app/tests_pre_start.py index 11530832b..a329e17be 100644 --- a/backend/app/tests_pre_start.py +++ b/backend/app/tests_pre_start.py @@ -4,10 +4,6 @@ from sqlmodel import Session, select from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed -from app.tests.utils.utils import load_environment - -load_environment("../.env.test") - from app.core.db import engine logging.basicConfig(level=logging.INFO) From 074e7532f044e9f96d0583111f12572e02d81b04 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Tue, 5 Aug 2025 13:43:56 +0530 Subject: [PATCH 32/37] fixing engine --- backend/app/tests/conftest.py | 20 +++++++++++++------- backend/app/tests/utils/utils.py | 28 +++++++++++++++++++++++++++- backend/app/tests_pre_start.py | 8 ++++++++ 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/backend/app/tests/conftest.py b/backend/app/tests/conftest.py index 5356baab5..939f3f303 100644 --- a/backend/app/tests/conftest.py +++ b/backend/app/tests/conftest.py @@ -1,24 +1,23 @@ -import pytest import logging - from collections.abc import Generator + +import pytest +from dotenv import find_dotenv from fastapi.testclient import TestClient -from sqlmodel import Session from sqlalchemy import event -from dotenv import find_dotenv +from sqlmodel import Session from app.api.deps import get_db +from app.core.config import settings from app.main import app from app.models import APIKeyPublic -from app.core.config import settings -from app.core.db import engine +from app.seed_data.seed_data import seed_database from app.tests.utils.user import authentication_token_from_email from app.tests.utils.utils import ( get_superuser_token_headers, get_api_key_by_email, load_environment, ) -from app.seed_data.seed_data import seed_database logger = logging.getLogger(__name__) @@ -29,6 +28,9 @@ def seed_baseline(): path = find_dotenv(".env.test") load_environment(path) + # Import engine after environment is loaded + from app.core.db import engine + with Session(engine) as session: logger.info("Seeding baseline data...") seed_database(session) # deterministic baseline @@ -40,11 +42,15 @@ def cleanup_sessions(): """Clean up any lingering sessions after each test.""" yield # Force cleanup of any remaining connections in the pool + from app.core.db import engine + engine.dispose() @pytest.fixture(scope="function") def db() -> Generator[Session, None, None]: + from app.core.db import engine + connection = engine.connect() transaction = connection.begin() session = Session(bind=connection) diff --git a/backend/app/tests/utils/utils.py b/backend/app/tests/utils/utils.py index 5d3208dd7..2dfba7a2a 100644 --- a/backend/app/tests/utils/utils.py +++ b/backend/app/tests/utils/utils.py @@ -2,6 +2,7 @@ import string import logging import os +import importlib from uuid import UUID from typing import TypeVar @@ -10,6 +11,7 @@ from sqlmodel import Session, select from dotenv import load_dotenv +from app.core import config from app.core.config import settings from app.crud.user import get_user_by_email from app.crud.api_key import get_api_key_by_value, get_api_key_by_user_id @@ -92,7 +94,7 @@ def get_project(session: Session, name: str | None = None) -> Project: return project -def load_environment(env_test_path: str = "../.env.test") -> None: +def load_environment(env_test_path: str) -> None: """Loads the test environment variables if the .env.test file exists. Raises an error if any required PostgreSQL credentials are missing or if the @@ -123,6 +125,30 @@ def load_environment(env_test_path: str = "../.env.test") -> None: f"Connected to database '{db_name}', which doesn't appear to be a test database" ) + # Reload settings to reflect the new environment variables + importlib.reload(config) + + # Recreate the engine with the updated settings + from sqlmodel import create_engine + from app.core.db import engine + + # Dispose of the old engine + engine.dispose() + + # Create a new engine with updated settings + new_engine = create_engine( + str(config.settings.SQLALCHEMY_DATABASE_URI), + pool_size=20 if config.settings.ENVIRONMENT == "local" else 5, + max_overflow=30 if config.settings.ENVIRONMENT == "local" else 10, + pool_pre_ping=True, + pool_recycle=300, + ) + + # Replace the engine in the db module + import app.core.db + + app.core.db.engine = new_engine + else: logger.warning( f"{env_test_path} not found. Using default environment settings." diff --git a/backend/app/tests_pre_start.py b/backend/app/tests_pre_start.py index a329e17be..2fc5a2188 100644 --- a/backend/app/tests_pre_start.py +++ b/backend/app/tests_pre_start.py @@ -1,9 +1,17 @@ import logging +from dotenv import find_dotenv from sqlalchemy import Engine from sqlmodel import Session, select from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed +from app.tests.utils.utils import load_environment + +# Load test environment before importing engine +path = find_dotenv(".env.test") +load_environment(path) + +# Import engine after environment is loaded from app.core.db import engine logging.basicConfig(level=logging.INFO) From 72847f8c2fa3f5da7d55f1a60e8ab8a5fa6c243f Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Tue, 5 Aug 2025 13:50:27 +0530 Subject: [PATCH 33/37] cleanups for PEP8 --- backend/app/tests/utils/utils.py | 63 ++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/backend/app/tests/utils/utils.py b/backend/app/tests/utils/utils.py index 2dfba7a2a..a71a330d8 100644 --- a/backend/app/tests/utils/utils.py +++ b/backend/app/tests/utils/utils.py @@ -1,41 +1,44 @@ -import random -import string +import importlib import logging import os -import importlib - -from uuid import UUID +import random +import string from typing import TypeVar -from pydantic import EmailStr -from fastapi.testclient import TestClient -from sqlmodel import Session, select +from uuid import UUID + from dotenv import load_dotenv +from fastapi.testclient import TestClient +from pydantic import EmailStr +from sqlmodel import Session, create_engine, select from app.core import config from app.core.config import settings +from app.crud.api_key import get_api_key_by_user_id, get_api_key_by_value from app.crud.user import get_user_by_email -from app.crud.api_key import get_api_key_by_value, get_api_key_by_user_id -from app.models import APIKeyPublic, Project, Assistant, Organization +from app.models import APIKeyPublic, Assistant, Organization, Project logger = logging.getLogger(__name__) - T = TypeVar("T") def random_lower_string() -> str: + """Generate a random lowercase string of 32 characters.""" return "".join(random.choices(string.ascii_lowercase, k=32)) -def generate_random_string(length=10): +def generate_random_string(length: int = 10) -> str: + """Generate a random string of specified length.""" return "".join(random.choices(string.ascii_letters + string.digits, k=length)) def random_email() -> str: + """Generate a random email address.""" return f"{random_lower_string()}@{random_lower_string()}.com" def get_superuser_token_headers(client: TestClient) -> dict[str, str]: + """Get authentication headers for superuser.""" login_data = { "username": settings.FIRST_SUPERUSER, "password": settings.FIRST_SUPERUSER_PASSWORD, @@ -48,18 +51,20 @@ def get_superuser_token_headers(client: TestClient) -> dict[str, str]: def get_api_key_by_email(db: Session, email: EmailStr) -> APIKeyPublic: + """Get API key for a user by their email address.""" user = get_user_by_email(session=db, email=email) api_key = get_api_key_by_user_id(db, user_id=user.id) - return api_key def get_user_id_by_email(db: Session) -> int: + """Get user ID for the test user email.""" user = get_user_by_email(session=db, email=settings.EMAIL_TEST_USER) return user.id def get_user_from_api_key(db: Session, api_key_headers: dict[str, str]) -> APIKeyPublic: + """Get API key object from API key headers.""" key_value = api_key_headers["X-API-KEY"] api_key = get_api_key_by_value(db, api_key_value=key_value) if api_key is None: @@ -68,6 +73,7 @@ def get_user_from_api_key(db: Session, api_key_headers: dict[str, str]) -> APIKe def get_non_existent_id(session: Session, model: type[T]) -> int: + """Get an ID that doesn't exist in the database for the given model.""" result = session.exec(select(model.id).order_by(model.id.desc())).first() return (result or 0) + 1 @@ -95,12 +101,16 @@ def get_project(session: Session, name: str | None = None) -> Project: def load_environment(env_test_path: str) -> None: - """Loads the test environment variables if the .env.test file exists. - - Raises an error if any required PostgreSQL credentials are missing or if the - POSTGRES_DB value does not contain the word 'test', to ensure a safe test database is used. """ + Load test environment variables from the specified file. + + Args: + env_test_path: Path to the test environment file. + Raises: + ValueError: If required PostgreSQL credentials are missing. + RuntimeError: If the database name doesn't contain 'test'. + """ if os.path.exists(env_test_path): load_dotenv(env_test_path, override=True) @@ -116,20 +126,21 @@ def load_environment(env_test_path: str) -> None: if missing_vars: raise ValueError( - f"Missing the following PostgreSQL credentials in {env_test_path}: {', '.join(missing_vars)}" + f"Missing the following PostgreSQL credentials in {env_test_path}: " + f"{', '.join(missing_vars)}" ) db_name = os.getenv("POSTGRES_DB", "").lower() if "test" not in db_name: raise RuntimeError( - f"Connected to database '{db_name}', which doesn't appear to be a test database" + f"Connected to database '{db_name}', which doesn't appear to be a " + f"test database" ) # Reload settings to reflect the new environment variables importlib.reload(config) # Recreate the engine with the updated settings - from sqlmodel import create_engine from app.core.db import engine # Dispose of the old engine @@ -159,7 +170,7 @@ def get_assistant(session: Session, name: str | None = None) -> Assistant: """ Retrieve an active assistant from the database. - If a assistant name is provided, fetch the active assistant with that name. + If an assistant name is provided, fetch the active assistant with that name. If no name is provided, fetch any random assistant. """ if name: @@ -204,16 +215,22 @@ def get_organization(session: Session, name: str | None = None) -> Organization: class SequentialUuidGenerator: - def __init__(self, start=0): + """Generate sequential UUIDs for testing purposes.""" + + def __init__(self, start: int = 0) -> None: + """Initialize the generator with a starting value.""" self.start = start - def __iter__(self): + def __iter__(self) -> "SequentialUuidGenerator": + """Return self as an iterator.""" return self def __next__(self) -> UUID: + """Generate the next UUID in sequence.""" uu_id = UUID(int=self.start) self.start += 1 return uu_id def peek(self) -> UUID: + """Peek at the next UUID without advancing the sequence.""" return UUID(int=self.start) From bf6c039869d69307f4c1a1a702211ea7b9d49e77 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Tue, 5 Aug 2025 13:54:33 +0530 Subject: [PATCH 34/37] cleanups --- .../tests/api/routes/documents/test_route_document_upload.py | 3 +-- backend/app/tests/utils/utils.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/app/tests/api/routes/documents/test_route_document_upload.py b/backend/app/tests/api/routes/documents/test_route_document_upload.py index b4ec4c5f3..17c37766e 100644 --- a/backend/app/tests/api/routes/documents/test_route_document_upload.py +++ b/backend/app/tests/api/routes/documents/test_route_document_upload.py @@ -47,7 +47,6 @@ def uploader(client: TestClient, user_api_key_header: dict[str, str]): @pytest.fixture(scope="class") def aws_credentials(): - # Set AWS credentials for moto mock os.environ["AWS_ACCESS_KEY_ID"] = "testing" os.environ["AWS_SECRET_ACCESS_KEY"] = "testing" os.environ["AWS_SECURITY_TOKEN"] = "testing" @@ -55,8 +54,8 @@ def aws_credentials(): os.environ["AWS_DEFAULT_REGION"] = settings.AWS_DEFAULT_REGION -@pytest.mark.usefixtures("aws_credentials") @mock_aws +@pytest.mark.usefixtures("aws_credentials") class TestDocumentRouteUpload: def test_adds_to_database( self, diff --git a/backend/app/tests/utils/utils.py b/backend/app/tests/utils/utils.py index a71a330d8..761754a37 100644 --- a/backend/app/tests/utils/utils.py +++ b/backend/app/tests/utils/utils.py @@ -141,6 +141,7 @@ def load_environment(env_test_path: str) -> None: importlib.reload(config) # Recreate the engine with the updated settings + # need import here to avoid circular imports & ensures proper timing of engine creation from app.core.db import engine # Dispose of the old engine From ca60538da858317d47f14704eacd430fc16134d9 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Tue, 5 Aug 2025 13:56:18 +0530 Subject: [PATCH 35/37] removing duplicate setting engine --- backend/app/core/db.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/backend/app/core/db.py b/backend/app/core/db.py index b35dfcc74..aba531198 100644 --- a/backend/app/core/db.py +++ b/backend/app/core/db.py @@ -4,20 +4,6 @@ from app.core.config import settings from app.models import User, UserCreate -# Configure connection pool settings -# For testing, we need more connections since tests run in parallel -pool_size = 20 if settings.ENVIRONMENT == "local" else 5 -max_overflow = 30 if settings.ENVIRONMENT == "local" else 10 - -engine = create_engine( - str(settings.SQLALCHEMY_DATABASE_URI), - pool_size=pool_size, - max_overflow=max_overflow, - pool_pre_ping=True, - pool_recycle=300, # Recycle connections after 5 minutes -) - - # make sure all SQLModel models are imported (app.models) before initializing DB # otherwise, SQLModel might fail to initialize relationships properly # for more details: https://github.com/fastapi/full-stack-fastapi-template/issues/28 From 1239e2a9944792cf0750bb5c256238b664e98a28 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Tue, 5 Aug 2025 13:58:28 +0530 Subject: [PATCH 36/37] removing duplicate setting engine --- backend/app/core/db.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/backend/app/core/db.py b/backend/app/core/db.py index aba531198..c923b0b47 100644 --- a/backend/app/core/db.py +++ b/backend/app/core/db.py @@ -4,6 +4,22 @@ from app.core.config import settings from app.models import User, UserCreate +engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI)) + +# Configure connection pool settings +# For testing, we need more connections since tests run in parallel +pool_size = 20 if settings.ENVIRONMENT == "local" else 5 +max_overflow = 30 if settings.ENVIRONMENT == "local" else 10 + +engine = create_engine( + str(settings.SQLALCHEMY_DATABASE_URI), + pool_size=pool_size, + max_overflow=max_overflow, + pool_pre_ping=True, + pool_recycle=300, # Recycle connections after 5 minutes +) + + # make sure all SQLModel models are imported (app.models) before initializing DB # otherwise, SQLModel might fail to initialize relationships properly # for more details: https://github.com/fastapi/full-stack-fastapi-template/issues/28 From 874f17708f3a1d0539e80b80012e650079be8415 Mon Sep 17 00:00:00 2001 From: Akhilesh Negi Date: Wed, 6 Aug 2025 09:46:26 +0530 Subject: [PATCH 37/37] added few comments --- backend/app/tests/utils/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/app/tests/utils/utils.py b/backend/app/tests/utils/utils.py index 761754a37..b66ef9fe0 100644 --- a/backend/app/tests/utils/utils.py +++ b/backend/app/tests/utils/utils.py @@ -103,6 +103,9 @@ def get_project(session: Session, name: str | None = None) -> Project: def load_environment(env_test_path: str) -> None: """ Load test environment variables from the specified file. + 1. Application starts + 2. .env file (loaded by Settings class) + 3. for testcases only .env.test file (loaded by load_environment_file function) Args: env_test_path: Path to the test environment file.