diff --git a/tests/ReadMe.md b/tests/ReadMe.md index 60ebd01..e969145 100644 --- a/tests/ReadMe.md +++ b/tests/ReadMe.md @@ -43,7 +43,7 @@ python -m pytest tests/unit ### Integration Tests Integration tests validate the **end-to-end behavior** of vCache by checking how components interact (e.g., LLM inference + vector database + thresholding policy). -They may involve real API calls and require a valid OpenAI key. +Tests that make real API calls require a valid OpenAI key. Pytest skips those tests when `OPENAI_API_KEY` is not set and still runs integration tests that do not require the API. #### Running Integration Tests diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py new file mode 100644 index 0000000..fb32a55 --- /dev/null +++ b/tests/integration/conftest.py @@ -0,0 +1,41 @@ +import os + +import pytest + +_REQUIRES_OPENAI_MARKER: str = "requires_openai" + + +def pytest_configure(config: pytest.Config) -> None: + """Register the ``requires_openai`` marker. + + Args: + config: The active pytest configuration object. + """ + config.addinivalue_line( + "markers", + f"{_REQUIRES_OPENAI_MARKER}: test needs a live OPENAI_API_KEY to run.", + ) + + +def pytest_collection_modifyitems( + config: pytest.Config, items: list[pytest.Item] +) -> None: + """Skip live OpenAI tests when no API key is available. + + Pull requests opened from forks do not receive repository secrets, so + ``OPENAI_API_KEY`` is empty in those CI runs. Tests that call the real + OpenAI API are skipped instead of failing with an empty ``Bearer`` header. + + Args: + config: The active pytest configuration object. + items: The collected test items to inspect and mark for skipping. + """ + if os.environ.get("OPENAI_API_KEY"): + return + + skip_marker: pytest.MarkDecorator = pytest.mark.skip( + reason="OPENAI_API_KEY not set; skipping live OpenAI integration test." + ) + for item in items: + if item.get_closest_marker(_REQUIRES_OPENAI_MARKER) is not None: + item.add_marker(skip_marker) diff --git a/tests/integration/test_dynamic_threshold.py b/tests/integration/test_dynamic_threshold.py index f891d1a..85ecf49 100644 --- a/tests/integration/test_dynamic_threshold.py +++ b/tests/integration/test_dynamic_threshold.py @@ -1,5 +1,6 @@ import unittest +import pytest from dotenv import load_dotenv from vcache import ( @@ -14,6 +15,8 @@ load_dotenv() +pytestmark = pytest.mark.requires_openai + def create_default_config_and_policy(): config = VCacheConfig( diff --git a/tests/integration/test_no_cache.py b/tests/integration/test_no_cache.py index 00e7269..a10d1b0 100644 --- a/tests/integration/test_no_cache.py +++ b/tests/integration/test_no_cache.py @@ -1,6 +1,7 @@ import unittest from unittest.mock import MagicMock +import pytest from dotenv import load_dotenv from vcache import ( @@ -12,6 +13,8 @@ load_dotenv() +pytestmark = pytest.mark.requires_openai + def create_default_config_and_policy(): config = VCacheConfig( diff --git a/tests/integration/test_static_threshold.py b/tests/integration/test_static_threshold.py index 7c84d65..d454b8a 100644 --- a/tests/integration/test_static_threshold.py +++ b/tests/integration/test_static_threshold.py @@ -1,5 +1,6 @@ import unittest +import pytest from dotenv import load_dotenv from vcache import ( @@ -14,6 +15,8 @@ load_dotenv() +pytestmark = pytest.mark.requires_openai + def create_default_config_and_policy(): config = VCacheConfig(