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
35 changes: 30 additions & 5 deletions src/aieng_platform_onboard/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,32 @@ def get_version() -> str:
return "unknown"


def run_integration_test(test_script: Path) -> tuple[bool, str]:
def run_integration_test(
test_script: Path, marker: str | None = None
) -> tuple[bool, str]:
"""
Execute integration test script to verify API keys.

Parameters
----------
test_script : Path
Path to the test script.
marker : str | None
Optional pytest marker expression to pass via ``-m``. For example,
``"integration_test"`` will run only tests marked with
``@pytest.mark.integration_test``.

Returns
-------
tuple[bool, str]
Tuple of (success, output/error).
"""
try:
cmd = [sys.executable, "-m", "pytest", str(test_script)]
if marker:
cmd += ["-m", marker]
result = subprocess.run(
[sys.executable, "-m", "pytest", str(test_script)],
cmd,
check=False,
capture_output=True,
text=True,
Expand Down Expand Up @@ -393,7 +402,11 @@ def display_onboarding_status_report(gcp_project: str) -> int:


def _run_tests_and_finalize(
db: Any, github_user: str, skip_test: bool, test_script: str
db: Any,
github_user: str,
skip_test: bool,
test_script: str,
test_marker: str | None = None,
) -> bool:
"""
Run integration tests and update onboarded status.
Expand All @@ -408,6 +421,8 @@ def _run_tests_and_finalize(
Whether to skip integration tests.
test_script : str
Path to the integration test script.
test_marker : str | None
Optional pytest marker expression to filter tests (e.g. ``"integration_test"``).

Returns
-------
Expand All @@ -426,7 +441,9 @@ def _run_tests_and_finalize(
f"[red]✗ Test script not found at: {test_script_path}[/red]\n"
)
return False
success_test, output = run_integration_test(test_script_path)
success_test, output = run_integration_test(
test_script_path, marker=test_marker
)

if success_test:
console.print("[green]✓ All API keys tested successfully[/green]\n")
Expand Down Expand Up @@ -515,6 +532,12 @@ def main() -> int: # noqa: PLR0911
type=str,
help="Path to integration test script",
)
parser.add_argument(
"--test-marker",
type=str,
default=None,
help="Pytest marker expression to filter tests (e.g. 'integration_test')",
)
parser.add_argument(
"--firebase-api-key",
type=str,
Expand Down Expand Up @@ -620,7 +643,9 @@ def main() -> int: # noqa: PLR0911
output_path = env_output_path

# Run tests and finalize
if not _run_tests_and_finalize(db, github_user, args.skip_test, args.test_script):
if not _run_tests_and_finalize(
db, github_user, args.skip_test, args.test_script, args.test_marker
):
return 1

# Final summary
Expand Down
259 changes: 259 additions & 0 deletions tests/aieng_platform_onboard/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
"""Unit tests for aieng_platform_onboard.cli module."""

import subprocess
import sys
from pathlib import Path
from typing import Any
from unittest.mock import Mock

import pytest

from aieng_platform_onboard.cli import (
_run_tests_and_finalize,
display_onboarding_status_report,
get_version,
main,
Expand Down Expand Up @@ -97,6 +99,173 @@ def mock_run(*args: Any, **kwargs: Any) -> None:
assert success is False
assert "timed out" in output

def test_run_integration_test_with_marker_passes_m_flag(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test that a marker is forwarded as -m <marker> to pytest."""
test_script = tmp_path / "test.py"
test_script.write_text("")

mock_result = Mock()
mock_result.returncode = 0
mock_result.stdout = "1 passed"
mock_result.stderr = ""

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

success, _ = run_integration_test(test_script, marker="integration_test")

assert success is True
cmd = mock_run.call_args[0][0]
# The command is [python, "-m", "pytest", script, "-m", "integration_test"].
# Two "-m" flags: one for module invocation, one for the marker.
assert cmd.count("-m") == 2
assert "integration_test" in cmd
# Marker value must immediately follow the second -m flag
assert cmd[-2:] == ["-m", "integration_test"]

def test_run_integration_test_without_marker_omits_m_flag(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test that omitting marker does not add a second -m flag to pytest."""
test_script = tmp_path / "test.py"
test_script.write_text("")

mock_result = Mock()
mock_result.returncode = 0
mock_result.stdout = "1 passed"
mock_result.stderr = ""

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

run_integration_test(test_script) # marker defaults to None

cmd = mock_run.call_args[0][0]
# Only one "-m" in the command: the "python -m pytest" module invocation.
assert cmd.count("-m") == 1

def test_run_integration_test_base_command_structure(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test that the base pytest command is always [python, -m, pytest, script]."""
test_script = tmp_path / "test.py"
test_script.write_text("")

mock_result = Mock()
mock_result.returncode = 0
mock_result.stdout = ""
mock_result.stderr = ""

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

run_integration_test(test_script, marker="smoke")

cmd = mock_run.call_args[0][0]
assert cmd[0] == sys.executable
assert cmd[1:3] == ["-m", "pytest"]
assert str(test_script) in cmd


class TestRunTestsAndFinalize:
"""Tests for _run_tests_and_finalize function."""

def test_marker_forwarded_to_run_integration_test(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, mock_console: Mock
) -> None:
"""Test that test_marker is forwarded to run_integration_test."""
test_script = tmp_path / "test.py"
test_script.write_text("")

captured_marker: list[str | None] = []

def mock_run_integration_test(
script: Path, marker: str | None = None
) -> tuple[bool, str]:
captured_marker.append(marker)
return True, "passed"

monkeypatch.setattr(
"aieng_platform_onboard.cli.run_integration_test", mock_run_integration_test
)
monkeypatch.setattr(
"aieng_platform_onboard.cli.update_onboarded_status",
lambda db, user: (True, None),
)

mock_db = Mock()
result = _run_tests_and_finalize(
mock_db,
"test-user",
skip_test=False,
test_script=str(test_script),
test_marker="smoke",
)

assert result is True
assert captured_marker == ["smoke"]

def test_no_marker_passes_none_to_run_integration_test(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, mock_console: Mock
) -> None:
"""Test that omitting test_marker passes None to run_integration_test."""
test_script = tmp_path / "test.py"
test_script.write_text("")

captured_marker: list[str | None] = []

def mock_run_integration_test(
script: Path, marker: str | None = None
) -> tuple[bool, str]:
captured_marker.append(marker)
return True, "passed"

monkeypatch.setattr(
"aieng_platform_onboard.cli.run_integration_test", mock_run_integration_test
)
monkeypatch.setattr(
"aieng_platform_onboard.cli.update_onboarded_status",
lambda db, user: (True, None),
)

mock_db = Mock()
result = _run_tests_and_finalize(
mock_db, "test-user", skip_test=False, test_script=str(test_script)
)

assert result is True
assert captured_marker == [None]

def test_skip_test_does_not_call_run_integration_test(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, mock_console: Mock
) -> None:
"""Test that skip_test=True bypasses run_integration_test entirely."""
test_script = tmp_path / "test.py"
test_script.write_text("")

mock_run_integration_test = Mock(return_value=(True, "passed"))
monkeypatch.setattr(
"aieng_platform_onboard.cli.run_integration_test", mock_run_integration_test
)
monkeypatch.setattr(
"aieng_platform_onboard.cli.update_onboarded_status",
lambda db, user: (True, None),
)

mock_db = Mock()
result = _run_tests_and_finalize(
mock_db,
"test-user",
skip_test=True,
test_script=str(test_script),
test_marker="smoke",
)

assert result is True
mock_run_integration_test.assert_not_called()


class TestDisplayOnboardingStatusReport:
"""Tests for display_onboarding_status_report function."""
Expand Down Expand Up @@ -493,6 +662,96 @@ def test_main_participant_not_found(

assert exit_code == 1

def test_main_test_marker_passed_to_subprocess(
self,
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
mock_console: Mock,
) -> None:
"""Test that --test-marker is forwarded as -m <marker> to pytest."""
test_script = tmp_path / "test.py"
test_script.write_text("")

monkeypatch.setattr(
"sys.argv",
[
"onboard",
"--bootcamp-name",
"test",
"--test-script",
str(test_script),
"--output-dir",
str(tmp_path),
"--firebase-api-key",
"test-key",
"--test-marker",
"integration_test",
],
)
monkeypatch.setenv("GITHUB_USER", "test-user")

monkeypatch.setattr(
"aieng_platform_onboard.cli.fetch_token_from_service",
lambda user: (True, "test-token", None),
)
mock_db = Mock()
monkeypatch.setattr(
"aieng_platform_onboard.cli.initialize_firestore_with_token",
lambda *args, **kwargs: mock_db,
)
monkeypatch.setattr(
"aieng_platform_onboard.cli.check_onboarded_status",
lambda db, user: (True, False),
)
monkeypatch.setattr(
"aieng_platform_onboard.cli.get_participant_data",
lambda db, user: {"github_handle": "test-user", "team_name": "test-team"},
)
monkeypatch.setattr(
"aieng_platform_onboard.cli.get_team_data",
lambda db, team: {
"team_name": "test-team",
"openai_api_key": "test-key",
"langfuse_secret_key": "test-secret",
"langfuse_public_key": "test-public",
"langfuse_url": "https://test.example.com",
"web_search_api_key": "test-search",
},
)
monkeypatch.setattr(
"aieng_platform_onboard.cli.get_global_keys",
lambda db: {
"EMBEDDING_BASE_URL": "https://embedding.example.com",
"EMBEDDING_API_KEY": "test-embedding",
"WEAVIATE_HTTP_HOST": "weaviate.example.com",
"WEAVIATE_GRPC_HOST": "weaviate-grpc.example.com",
"WEAVIATE_API_KEY": "test-weaviate",
"WEAVIATE_HTTP_PORT": "443",
"WEAVIATE_GRPC_PORT": "50051",
"WEAVIATE_HTTP_SECURE": "true",
"WEAVIATE_GRPC_SECURE": "true",
},
)

mock_result = Mock()
mock_result.returncode = 0
mock_result.stdout = "1 passed"
mock_result.stderr = ""
mock_run = Mock(return_value=mock_result)
monkeypatch.setattr("subprocess.run", mock_run)

monkeypatch.setattr(
"aieng_platform_onboard.cli.update_onboarded_status",
lambda db, user: (True, None),
)

exit_code = main()

assert exit_code == 0
cmd = mock_run.call_args[0][0]
assert "-m" in cmd
assert "integration_test" in cmd

def test_main_skip_test_flag(
self,
tmp_path: Path,
Expand Down