conftest.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. """
  2. Pytest configuration and shared fixtures for end-to-end tests.
  3. This module provides fixtures for setting up test environment including:
  4. - Test client with FastAPI app
  5. - Mock git repository
  6. - Predefined remotes.json to avoid version fetching
  7. - Remote reload authentication tokens
  8. """
  9. import os
  10. import json
  11. import tempfile
  12. import shutil
  13. from typing import Generator
  14. from unittest.mock import Mock, MagicMock
  15. import pytest
  16. from fastapi.testclient import TestClient
  17. # Sample remotes.json for testing - no version fetching needed
  18. TEST_REMOTES_JSON = [
  19. {
  20. "name": "test-remote-1",
  21. "url": "https://github.com/test/ardupilot.git",
  22. "vehicles": [
  23. {
  24. "id": "copter",
  25. "releases": [
  26. {
  27. "release_type": "latest",
  28. "version_number": "4.6.0",
  29. "commit_reference": "refs/heads/master"
  30. },
  31. {
  32. "release_type": "stable",
  33. "version_number": "4.3.0",
  34. "commit_reference": "refs/tags/Copter-4.3.0"
  35. }
  36. ]
  37. },
  38. {
  39. "id": "plane",
  40. "releases": [
  41. {
  42. "release_type": "latest",
  43. "version_number": "4.5.0",
  44. "commit_reference": "refs/heads/master"
  45. }
  46. ]
  47. }
  48. ]
  49. },
  50. {
  51. "name": "test-remote-2",
  52. "url": "https://github.com/another/ardupilot.git",
  53. "vehicles": [
  54. {
  55. "id": "rover",
  56. "releases": [
  57. {
  58. "release_type": "Custom",
  59. "version_number": "Custom",
  60. "commit_reference": "refs/tags/Rover-4.2.0"
  61. }
  62. ]
  63. }
  64. ]
  65. }
  66. ]
  67. @pytest.fixture(scope="session")
  68. def test_base_dir() -> Generator[str, None, None]:
  69. """
  70. Create a temporary base directory structure for testing.
  71. Yields:
  72. str: Path to the temporary base directory
  73. """
  74. temp_dir = tempfile.mkdtemp(prefix="custombuild_test_")
  75. # Create required subdirectories
  76. subdirs = ["artifacts", "configs", "workdir", "secrets", "ardupilot"]
  77. for subdir in subdirs:
  78. os.makedirs(os.path.join(temp_dir, subdir), exist_ok=True)
  79. # Create remotes.json with test data
  80. remotes_json_path = os.path.join(temp_dir, "configs", "remotes.json")
  81. with open(remotes_json_path, "w") as f:
  82. json.dump(TEST_REMOTES_JSON, f, indent=2)
  83. # Create admin token file
  84. token_file_path = os.path.join(temp_dir, "secrets", "admin_token")
  85. with open(token_file_path, "w") as f:
  86. f.write("test-admin-token-12345")
  87. yield temp_dir
  88. # Cleanup
  89. shutil.rmtree(temp_dir, ignore_errors=True)
  90. @pytest.fixture
  91. def mock_git_repo():
  92. """
  93. Create a mock GitRepo object.
  94. Returns:
  95. Mock: Mock GitRepo instance
  96. """
  97. mock_repo = Mock()
  98. mock_repo.path = "/tmp/test/ardupilot"
  99. mock_repo.get_current_commit_hash.return_value = "abc123def456"
  100. mock_repo.checkout_commit.return_value = True
  101. mock_repo.get_tags.return_value = ["Copter-4.3.0", "Copter-4.4.0"]
  102. mock_repo.get_checkout_lock.return_value = MagicMock()
  103. return mock_repo
  104. @pytest.fixture
  105. def mock_ap_src_metadata_fetcher():
  106. """
  107. Create a mock APSourceMetadataFetcher for testing.
  108. Returns:
  109. Mock: Mock APSourceMetadataFetcher instance
  110. """
  111. return Mock()
  112. @pytest.fixture
  113. def mock_versions_manager(test_base_dir):
  114. """
  115. Create a mock VersionsManager that doesn't actually fetch versions.
  116. This allows tests to run without starting background threads or
  117. making actual git operations.
  118. Args:
  119. test_base_dir: Test base directory fixture
  120. Returns:
  121. Mock: Mock VersionsManager instance
  122. """
  123. from metadata_manager import RemoteInfo
  124. mock_manager = Mock()
  125. # Mock the refresh methods
  126. mock_manager.refresh_all = Mock(return_value=None)
  127. # Mock get_all_remotes_info to return test remotes
  128. test_remotes = [
  129. RemoteInfo(name="test-remote-1", url="https://github.com/test/ardupilot.git"),
  130. RemoteInfo(name="test-remote-2", url="https://github.com/another/ardupilot.git")
  131. ]
  132. mock_manager.get_all_remotes_info = Mock(return_value=test_remotes)
  133. # Mock start/stop methods (no-op for tests)
  134. mock_manager.start = Mock()
  135. mock_manager.stop = Mock()
  136. return mock_manager
  137. @pytest.fixture
  138. def mock_build_manager():
  139. """
  140. Create a mock BuildManager for testing.
  141. Returns:
  142. Mock: Mock BuildManager instance
  143. """
  144. mock_manager = Mock()
  145. mock_manager.submit_build = Mock(return_value="test-build-id-123")
  146. mock_manager.get_build_progress = Mock(return_value={
  147. "build_id": "test-build-id-123",
  148. "status": "queued",
  149. "progress": 0
  150. })
  151. return mock_manager
  152. @pytest.fixture
  153. def mock_vehicles_manager():
  154. """
  155. Create a mock VehiclesManager for testing.
  156. Returns:
  157. Mock: Mock VehiclesManager instance
  158. """
  159. mock_manager = Mock()
  160. return mock_manager
  161. @pytest.fixture
  162. def app_with_mocked_dependencies(
  163. test_base_dir,
  164. mock_git_repo,
  165. mock_versions_manager,
  166. mock_build_manager,
  167. mock_vehicles_manager,
  168. ):
  169. """
  170. Create a FastAPI app instance with mocked dependencies.
  171. This fixture sets up the application without requiring actual:
  172. - Git repository cloning
  173. - Version fetching background tasks
  174. - Redis connection
  175. - Build artifacts
  176. Args:
  177. test_base_dir: Test base directory
  178. mock_git_repo: Mock git repository
  179. mock_versions_manager: Mock versions manager
  180. mock_build_manager: Mock build manager
  181. mock_vehicles_manager: Mock vehicles manager
  182. Yields:
  183. FastAPI: Configured FastAPI application instance
  184. """
  185. from contextlib import asynccontextmanager
  186. from fastapi import FastAPI
  187. from slowapi.middleware import SlowAPIMiddleware
  188. from slowapi.errors import RateLimitExceeded
  189. from web.api.v1 import router as v1_router
  190. from web.core.limiter import limiter, rate_limit_exceeded_handler
  191. # Set environment variables for test configuration
  192. os.environ["CBS_BASEDIR"] = test_base_dir
  193. os.environ["CBS_REDIS_HOST"] = "localhost"
  194. os.environ["CBS_REDIS_PORT"] = "6379"
  195. os.environ["CBS_ENABLE_INBUILT_BUILDER"] = "0" # Disable builder for tests
  196. @asynccontextmanager
  197. async def test_lifespan(app: FastAPI):
  198. """Test lifespan that doesn't start background tasks."""
  199. # Setup: Attach mocked dependencies to app state
  200. app.state.repo = mock_git_repo
  201. app.state.versions_manager = mock_versions_manager
  202. app.state.vehicles_manager = mock_vehicles_manager
  203. app.state.build_manager = mock_build_manager
  204. app.state.limiter = limiter
  205. # Create mock AP source metadata fetcher
  206. mock_ap_src_fetcher = Mock()
  207. app.state.ap_src_metadata_fetcher = mock_ap_src_fetcher
  208. app.state.manifest_json = Mock()
  209. app.state.features_txt_client = Mock()
  210. # Don't start background tasks in test mode
  211. # versions_manager.start()
  212. # cleaner.start()
  213. # progress_updater.start()
  214. yield
  215. # Shutdown logic also skipped
  216. app = FastAPI(title="CustomBuild Test API", lifespan=test_lifespan)
  217. app.add_middleware(SlowAPIMiddleware)
  218. app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler)
  219. app.include_router(v1_router, prefix="/api")
  220. return app
  221. @pytest.fixture
  222. def client(app_with_mocked_dependencies) -> Generator[TestClient, None, None]:
  223. """
  224. Create a TestClient for making requests to the app.
  225. Args:
  226. app_with_mocked_dependencies: FastAPI app with mocked dependencies
  227. Yields:
  228. TestClient: Test client for making API requests
  229. """
  230. with TestClient(app_with_mocked_dependencies) as test_client:
  231. yield test_client