conftest.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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. "name": "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. "name": "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. "name": "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 remote reload token file
  84. token_file_path = os.path.join(temp_dir, "secrets", "reload_token")
  85. with open(token_file_path, "w") as f:
  86. f.write("test-remote-reload-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_fetcher(test_base_dir):
  114. """
  115. Create a mock VersionsFetcher 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 VersionsFetcher instance
  122. """
  123. from metadata_manager.versions_fetcher import RemoteInfo
  124. mock_fetcher = Mock()
  125. # Mock the reload_remotes_json method
  126. mock_fetcher.reload_remotes_json = 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_fetcher.get_all_remotes_info = Mock(return_value=test_remotes)
  133. # Mock start/stop methods (no-op for tests)
  134. mock_fetcher.start = Mock()
  135. mock_fetcher.stop = Mock()
  136. return mock_fetcher
  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. mock_manager.get_vehicle_names = Mock(return_value=["Copter", "Plane", "Rover"])
  161. return mock_manager
  162. @pytest.fixture
  163. def app_with_mocked_dependencies(
  164. test_base_dir,
  165. mock_git_repo,
  166. mock_versions_fetcher,
  167. mock_build_manager,
  168. mock_vehicles_manager,
  169. ):
  170. """
  171. Create a FastAPI app instance with mocked dependencies.
  172. This fixture sets up the application without requiring actual:
  173. - Git repository cloning
  174. - Version fetching background tasks
  175. - Redis connection
  176. - Build artifacts
  177. Args:
  178. test_base_dir: Test base directory
  179. mock_git_repo: Mock git repository
  180. mock_versions_fetcher: Mock versions fetcher
  181. mock_build_manager: Mock build manager
  182. mock_vehicles_manager: Mock vehicles manager
  183. Yields:
  184. FastAPI: Configured FastAPI application instance
  185. """
  186. from contextlib import asynccontextmanager
  187. from fastapi import FastAPI
  188. from slowapi.middleware import SlowAPIMiddleware
  189. from slowapi.errors import RateLimitExceeded
  190. from web.api.v1 import router as v1_router
  191. from web.core.limiter import limiter, rate_limit_exceeded_handler
  192. # Set environment variables for test configuration
  193. os.environ["CBS_BASEDIR"] = test_base_dir
  194. os.environ["CBS_REDIS_HOST"] = "localhost"
  195. os.environ["CBS_REDIS_PORT"] = "6379"
  196. os.environ["CBS_ENABLE_INBUILT_BUILDER"] = "0" # Disable builder for tests
  197. @asynccontextmanager
  198. async def test_lifespan(app: FastAPI):
  199. """Test lifespan that doesn't start background tasks."""
  200. # Setup: Attach mocked dependencies to app state
  201. app.state.repo = mock_git_repo
  202. app.state.versions_fetcher = mock_versions_fetcher
  203. app.state.vehicles_manager = mock_vehicles_manager
  204. app.state.build_manager = mock_build_manager
  205. app.state.limiter = limiter
  206. # Create mock AP source metadata fetcher
  207. mock_ap_src_fetcher = Mock()
  208. app.state.ap_src_metadata_fetcher = mock_ap_src_fetcher
  209. # Don't start background tasks in test mode
  210. # versions_fetcher.start()
  211. # cleaner.start()
  212. # progress_updater.start()
  213. yield
  214. # Shutdown logic also skipped
  215. app = FastAPI(title="CustomBuild Test API", lifespan=test_lifespan)
  216. app.add_middleware(SlowAPIMiddleware)
  217. app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler)
  218. app.include_router(v1_router, prefix="/api")
  219. return app
  220. @pytest.fixture
  221. def client(app_with_mocked_dependencies) -> Generator[TestClient, None, None]:
  222. """
  223. Create a TestClient for making requests to the app.
  224. Args:
  225. app_with_mocked_dependencies: FastAPI app with mocked dependencies
  226. Yields:
  227. TestClient: Test client for making API requests
  228. """
  229. with TestClient(app_with_mocked_dependencies) as test_client:
  230. yield test_client