Skip to content
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
68 changes: 68 additions & 0 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: unit tests
permissions:
contents: read
pull-requests: write

on:
push:
branches:
- main
paths:
- .pre-commit-config.yaml
- .github/workflows/code_checks.yml
- .github/workflows/docs.yml
- .github/workflows/unit_tests.yml
- '**.py'
- '**.ipynb'
- uv.lock
- pyproject.toml
- '**.rst'
- '**.md'
pull_request:
branches:
- main
paths:
- .pre-commit-config.yaml
- .github/workflows/code_checks.yml
- .github/workflows/docs.yml
- .github/workflows/unit_tests.yml
- '**.py'
- '**.ipynb'
- uv.lock
- pyproject.toml
- '**.rst'
- '**.md'

jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/[email protected]

- name: Install uv
uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4
with:
# Install a specific version of uv.
version: "0.9.7"
enable-cache: true

- name: "Set up Python"
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c
with:
python-version-file: ".python-version"

- name: Install the project
run: uv sync --all-extras --dev

- name: Install dependencies and check code
run: |
uv run pytest -m "not integration_test" --cov src/aieng_platform_onboard --cov-report=xml tests

# Uncomment this once this repo is configured on Codecov
- name: Upload coverage to Codecov
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7
with:
token: ${{ secrets.CODECOV_TOKEN }}
slug: VectorInstitute/aieng-platform
fail_ci_if_error: true
verbose: true
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ terraform.tfvars

.coder
keys/
.coverage
22 changes: 22 additions & 0 deletions src/aieng_platform_onboard/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import argparse
import subprocess
import sys
from importlib.metadata import version
from pathlib import Path
from typing import Any

Expand All @@ -34,6 +35,21 @@
)


def get_version() -> str:
"""
Get the installed version of the package.

Returns
-------
str
Version string from package metadata.
"""
try:
return version("aieng-platform-onboard")
except Exception:
return "unknown"


def run_integration_test(test_script: Path) -> tuple[bool, str]:
"""
Execute integration test script to verify API keys.
Expand Down Expand Up @@ -455,6 +471,12 @@ def main() -> int: # noqa: PLR0911
description="Bootcamp participant onboarding script",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"--version",
action="version",
version=f"%(prog)s {get_version()}",
help="Show version number and exit",
)
parser.add_argument(
"--bootcamp-name",
type=str,
Expand Down
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for aieng_platform_onboard package."""
202 changes: 202 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
"""Pytest configuration and fixtures for aieng_platform_onboard tests."""

from datetime import datetime, timezone
from typing import Any
from unittest.mock import Mock

import pytest
from google.cloud import firestore


@pytest.fixture
def mock_firestore_client() -> Mock:
"""
Create a mock Firestore client for testing.

Returns
-------
Mock
Mock Firestore client instance.
"""
return Mock(spec=firestore.Client)


@pytest.fixture
def mock_firestore_document() -> Mock:
"""
Create a mock Firestore document for testing.

Returns
-------
Mock
Mock Firestore document instance.
"""
mock_doc = Mock()
mock_doc.exists = True
mock_doc.id = "test-handle"
mock_doc.to_dict.return_value = {
"github_handle": "test-handle",
"team_name": "test-team",
"onboarded": False,
"email": "[email protected]",
}
return mock_doc


@pytest.fixture
def sample_participant_data() -> dict[str, Any]:
"""
Sample participant data for testing.

Returns
-------
dict[str, Any]
Sample participant data dictionary.
"""
return {
"github_handle": "test-user",
"team_name": "test-team",
"onboarded": False,
"email": "[email protected]",
"created_at": datetime.now(timezone.utc),
}


@pytest.fixture
def sample_team_data() -> dict[str, Any]:
"""
Sample team data for testing.

Returns
-------
dict[str, Any]
Sample team data dictionary.
"""
return {
"team_name": "test-team",
"openai_api_key": "test-openai-key",
"langfuse_secret_key": "test-langfuse-secret",
"langfuse_public_key": "test-langfuse-public",
"langfuse_url": "https://test-langfuse.example.com",
"web_search_api_key": "test-search-key",
"participants": ["test-user", "test-user-2"],
}


@pytest.fixture
def sample_global_keys() -> dict[str, Any]:
"""
Sample global keys for testing.

Returns
-------
dict[str, Any]
Sample global keys dictionary.
"""
return {
"EMBEDDING_BASE_URL": "https://embedding.example.com",
"EMBEDDING_API_KEY": "test-embedding-key",
"WEAVIATE_HTTP_HOST": "weaviate.example.com",
"WEAVIATE_GRPC_HOST": "weaviate-grpc.example.com",
"WEAVIATE_API_KEY": "test-weaviate-key",
"WEAVIATE_HTTP_PORT": "443",
"WEAVIATE_GRPC_PORT": "50051",
"WEAVIATE_HTTP_SECURE": "true",
"WEAVIATE_GRPC_SECURE": "true",
}


@pytest.fixture
def mock_requests_post(monkeypatch: pytest.MonkeyPatch) -> Mock:
"""
Mock requests.post for testing HTTP requests.

Parameters
----------
monkeypatch : pytest.MonkeyPatch
Pytest monkeypatch fixture.

Returns
-------
Mock
Mock requests.post function.
"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"token": "test-token"}

mock_post = Mock(return_value=mock_response)
monkeypatch.setattr("requests.post", mock_post)
return mock_post


@pytest.fixture
def mock_google_auth(monkeypatch: pytest.MonkeyPatch) -> tuple[Mock, str]:
"""
Mock google.auth.default for testing authentication.

Parameters
----------
monkeypatch : pytest.MonkeyPatch
Pytest monkeypatch fixture.

Returns
-------
tuple[Mock, str]
Tuple of (mock_credentials, project_id).
"""
mock_credentials = Mock()
mock_credentials.service_account_email = "[email protected]"
mock_credentials.signer = Mock()

def mock_default() -> tuple[Mock, str]:
return mock_credentials, "test-project"

monkeypatch.setattr("google.auth.default", mock_default)
return mock_credentials, "test-project"


@pytest.fixture
def mock_subprocess_run(monkeypatch: pytest.MonkeyPatch) -> Mock:
"""
Mock subprocess.run for testing command execution.

Parameters
----------
monkeypatch : pytest.MonkeyPatch
Pytest monkeypatch fixture.

Returns
-------
Mock
Mock subprocess.run function.
"""
mock_result = Mock()
mock_result.returncode = 0
mock_result.stdout = "test output"
mock_result.stderr = ""

mock_run = Mock(return_value=mock_result)
monkeypatch.setattr("subprocess.run", mock_run)
return mock_run


@pytest.fixture
def mock_console(monkeypatch: pytest.MonkeyPatch) -> Mock:
"""
Mock Rich console for testing output.

Parameters
----------
monkeypatch : pytest.MonkeyPatch
Pytest monkeypatch fixture.

Returns
-------
Mock
Mock console instance.
"""
mock_console_instance = Mock()
monkeypatch.setattr("aieng_platform_onboard.utils.console", mock_console_instance)
monkeypatch.setattr("aieng_platform_onboard.cli.console", mock_console_instance)
return mock_console_instance
Loading