test_vehicles_service.py 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147
  1. """
  2. Tests for the Vehicles Service.
  3. """
  4. import pytest
  5. from unittest.mock import Mock
  6. from metadata_manager import Vehicle
  7. from metadata_manager.versions_fetcher import VersionInfo, RemoteInfo
  8. from web.services.vehicles import VehiclesService
  9. @pytest.fixture
  10. def service(mock_vehicles_manager, mock_versions_fetcher, mock_ap_src_metadata_fetcher, mock_git_repo):
  11. return VehiclesService(
  12. vehicle_manager=mock_vehicles_manager,
  13. versions_fetcher=mock_versions_fetcher,
  14. ap_src_metadata_fetcher=mock_ap_src_metadata_fetcher,
  15. repo=mock_git_repo,
  16. )
  17. class TestVehiclesService:
  18. """Test suite for VehiclesService."""
  19. # Tests for get_all_vehicles
  20. def test_get_all_vehicles_returns_all(self, service, mock_vehicles_manager):
  21. """Test fetching all vehicles returns correct count and values."""
  22. mock_vehicles_manager.get_all_vehicles.return_value = [
  23. Vehicle(
  24. id="copter",
  25. name="Copter",
  26. ap_source_subdir="ArduCopter",
  27. fw_server_vehicle_sdir="Copter",
  28. waf_build_command="copter"
  29. ),
  30. Vehicle(
  31. id="plane",
  32. name="Plane",
  33. ap_source_subdir="ArduPlane",
  34. fw_server_vehicle_sdir="Plane",
  35. waf_build_command="plane"
  36. ),
  37. ]
  38. vehicles = service.get_all_vehicles()
  39. assert len(vehicles) == 2
  40. assert vehicles[0].id == "copter"
  41. assert vehicles[0].name == "Copter"
  42. assert vehicles[1].id == "plane"
  43. assert vehicles[1].name == "Plane"
  44. def test_get_all_vehicles_empty(self, service, mock_vehicles_manager):
  45. """Test fetching all vehicles when none exist."""
  46. mock_vehicles_manager.get_all_vehicles.return_value = []
  47. vehicles = service.get_all_vehicles()
  48. assert vehicles == []
  49. def test_get_all_vehicles_single(self, service, mock_vehicles_manager):
  50. """Test fetching all vehicles when only one exists."""
  51. mock_vehicles_manager.get_all_vehicles.return_value = [
  52. Vehicle(
  53. id="copter",
  54. name="Copter",
  55. ap_source_subdir="ArduCopter",
  56. fw_server_vehicle_sdir="Copter",
  57. waf_build_command="copter"
  58. ),
  59. ]
  60. vehicles = service.get_all_vehicles()
  61. assert len(vehicles) == 1
  62. assert vehicles[0].id == "copter"
  63. def test_get_all_vehicles_sorted_by_name(self, service, mock_vehicles_manager):
  64. """Test fetching all vehicles returns them sorted by name."""
  65. mock_vehicles_manager.get_all_vehicles.return_value = [
  66. Vehicle(
  67. id="plane",
  68. name="Plane",
  69. ap_source_subdir="ArduPlane",
  70. fw_server_vehicle_sdir="Plane",
  71. waf_build_command="plane"
  72. ),
  73. Vehicle(
  74. id="copter",
  75. name="Copter",
  76. ap_source_subdir="ArduCopter",
  77. fw_server_vehicle_sdir="Copter",
  78. waf_build_command="copter"
  79. ),
  80. Vehicle(
  81. id="rover",
  82. name="Rover",
  83. ap_source_subdir="ArduRover",
  84. fw_server_vehicle_sdir="Rover",
  85. waf_build_command="rover"
  86. ),
  87. ]
  88. vehicles = service.get_all_vehicles()
  89. names = [v.name for v in vehicles]
  90. assert names == sorted(names)
  91. def test_get_all_vehicles_calls_manager_once(self, service, mock_vehicles_manager):
  92. """Test that get_all_vehicles calls the manager exactly once."""
  93. mock_vehicles_manager.get_all_vehicles.return_value = []
  94. service.get_all_vehicles()
  95. mock_vehicles_manager.get_all_vehicles.assert_called_once_with()
  96. # Tests for get_vehicle
  97. def test_get_vehicle_found(self, service, mock_vehicles_manager):
  98. """Test fetching a specific vehicle that exists."""
  99. mock_vehicles_manager.get_vehicle_by_id.return_value = Vehicle(
  100. id="copter",
  101. name="Copter",
  102. ap_source_subdir="ArduCopter",
  103. fw_server_vehicle_sdir="Copter",
  104. waf_build_command="copter"
  105. )
  106. vehicle = service.get_vehicle("copter")
  107. assert vehicle is not None
  108. assert vehicle.id == "copter"
  109. assert vehicle.name == "Copter"
  110. def test_get_vehicle_not_found(self, service, mock_vehicles_manager):
  111. """Test fetching a specific vehicle that does not exist."""
  112. mock_vehicles_manager.get_vehicle_by_id.return_value = None
  113. vehicle = service.get_vehicle("copter")
  114. assert vehicle is None
  115. def test_get_vehicle_calls_manager_with_correct_id(self, service, mock_vehicles_manager):
  116. """Test that get_vehicle calls manager with the provided ID."""
  117. mock_vehicles_manager.get_vehicle_by_id.return_value = None
  118. service.get_vehicle("copter")
  119. mock_vehicles_manager.get_vehicle_by_id.assert_called_once_with("copter")
  120. # Tests for get_versions
  121. def test_get_versions_empty(self, service, mock_versions_fetcher):
  122. """Test that an empty list is returned when no versions exist."""
  123. mock_versions_fetcher.get_versions_for_vehicle.return_value = []
  124. versions = service.get_versions("copter")
  125. assert versions == []
  126. def test_get_versions_single(self, service, mock_versions_fetcher):
  127. """Test fetching versions when only one version exists."""
  128. mock_versions_fetcher.get_versions_for_vehicle.return_value = [
  129. VersionInfo(
  130. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  131. commit_ref="refs/heads/master",
  132. release_type="latest",
  133. version_number="NA",
  134. ap_build_artifacts_url=None,
  135. ),
  136. ]
  137. versions = service.get_versions("copter")
  138. assert len(versions) == 1
  139. def test_get_versions_many(self, service, mock_versions_fetcher):
  140. """Test fetching versions when multiple versions exist."""
  141. mock_versions_fetcher.get_versions_for_vehicle.return_value = [
  142. VersionInfo(
  143. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  144. commit_ref="refs/heads/master",
  145. release_type="latest",
  146. version_number="NA",
  147. ap_build_artifacts_url=None,
  148. ),
  149. VersionInfo(
  150. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  151. commit_ref="refs/tags/Copter-4.5.0",
  152. release_type="stable",
  153. version_number="4.5.0",
  154. ap_build_artifacts_url=None,
  155. ),
  156. VersionInfo(
  157. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  158. commit_ref="refs/tags/Copter-4.6.0-beta",
  159. release_type="beta",
  160. version_number="4.6.0",
  161. ap_build_artifacts_url=None,
  162. ),
  163. ]
  164. versions = service.get_versions("copter")
  165. assert len(versions) == 3
  166. def test_get_versions_sorted_by_name(self, service, mock_versions_fetcher):
  167. """Test that versions are returned sorted by their display name."""
  168. mock_versions_fetcher.get_versions_for_vehicle.return_value = [
  169. VersionInfo(
  170. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  171. commit_ref="refs/tags/Copter-4.5.0",
  172. release_type="stable",
  173. version_number="4.5.0",
  174. ap_build_artifacts_url=None,
  175. ),
  176. VersionInfo(
  177. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  178. commit_ref="refs/heads/master",
  179. release_type="latest",
  180. version_number="NA",
  181. ap_build_artifacts_url=None,
  182. ),
  183. VersionInfo(
  184. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  185. commit_ref="refs/tags/Copter-4.6.0-beta",
  186. release_type="beta",
  187. version_number="4.6.0",
  188. ap_build_artifacts_url=None,
  189. ),
  190. ]
  191. versions = service.get_versions("copter")
  192. names = [v.name for v in versions]
  193. assert names == sorted(names)
  194. def test_get_versions_calls_fetcher_once_with_correct_vehicle_id(
  195. self, service, mock_versions_fetcher
  196. ):
  197. """Test that get_versions calls the fetcher exactly once with the correct vehicle_id."""
  198. mock_versions_fetcher.get_versions_for_vehicle.return_value = []
  199. service.get_versions("copter")
  200. mock_versions_fetcher.get_versions_for_vehicle.assert_called_once_with(
  201. vehicle_id="copter"
  202. )
  203. def test_get_versions_type_filter_keeps_matching(
  204. self, service, mock_versions_fetcher
  205. ):
  206. """Test that type_filter returns only versions of the specified type."""
  207. mock_versions_fetcher.get_versions_for_vehicle.return_value = [
  208. VersionInfo(
  209. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  210. commit_ref="refs/tags/Copter-4.5.0",
  211. release_type="stable",
  212. version_number="4.5.0",
  213. ap_build_artifacts_url=None,
  214. ),
  215. VersionInfo(
  216. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  217. commit_ref="refs/tags/Copter-4.6.0-beta",
  218. release_type="beta",
  219. version_number="4.6.0",
  220. ap_build_artifacts_url=None,
  221. ),
  222. VersionInfo(
  223. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  224. commit_ref="refs/heads/master",
  225. release_type="latest",
  226. version_number="NA",
  227. ap_build_artifacts_url=None,
  228. ),
  229. ]
  230. versions = service.get_versions("copter", type_filter="stable")
  231. assert len(versions) == 1
  232. assert versions[0].type == "stable"
  233. def test_get_versions_type_filter_excludes_non_matching(
  234. self, service, mock_versions_fetcher
  235. ):
  236. """Test that type_filter excludes versions that do not match."""
  237. mock_versions_fetcher.get_versions_for_vehicle.return_value = [
  238. VersionInfo(
  239. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  240. commit_ref="refs/tags/Copter-4.5.0",
  241. release_type="stable",
  242. version_number="4.5.0",
  243. ap_build_artifacts_url=None,
  244. ),
  245. VersionInfo(
  246. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  247. commit_ref="refs/tags/Copter-4.6.0-beta",
  248. release_type="beta",
  249. version_number="4.6.0",
  250. ap_build_artifacts_url=None,
  251. ),
  252. ]
  253. versions = service.get_versions("copter", type_filter="latest")
  254. assert versions == []
  255. def test_get_versions_type_filter_none_returns_all(
  256. self, service, mock_versions_fetcher
  257. ):
  258. """Test that passing no type_filter returns all versions."""
  259. mock_versions_fetcher.get_versions_for_vehicle.return_value = [
  260. VersionInfo(
  261. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  262. commit_ref="refs/tags/Copter-4.5.0",
  263. release_type="stable",
  264. version_number="4.5.0",
  265. ap_build_artifacts_url=None,
  266. ),
  267. VersionInfo(
  268. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  269. commit_ref="refs/tags/Copter-4.6.0-beta",
  270. release_type="beta",
  271. version_number="4.6.0",
  272. ap_build_artifacts_url=None,
  273. ),
  274. VersionInfo(
  275. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  276. commit_ref="refs/heads/master",
  277. release_type="latest",
  278. version_number="NA",
  279. ap_build_artifacts_url=None,
  280. ),
  281. ]
  282. versions = service.get_versions("copter")
  283. assert len(versions) == 3
  284. def test_get_versions_type_filter_multiple_matches(
  285. self, service, mock_versions_fetcher
  286. ):
  287. """Test that type_filter returns all versions matching the type when there are multiple."""
  288. mock_versions_fetcher.get_versions_for_vehicle.return_value = [
  289. VersionInfo(
  290. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  291. commit_ref="refs/tags/Copter-4.4.0",
  292. release_type="stable",
  293. version_number="4.4.0",
  294. ap_build_artifacts_url=None,
  295. ),
  296. VersionInfo(
  297. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  298. commit_ref="refs/tags/Copter-4.5.0",
  299. release_type="stable",
  300. version_number="4.5.0",
  301. ap_build_artifacts_url=None,
  302. ),
  303. VersionInfo(
  304. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  305. commit_ref="refs/heads/master",
  306. release_type="latest",
  307. version_number="NA",
  308. ap_build_artifacts_url=None,
  309. ),
  310. ]
  311. versions = service.get_versions("copter", type_filter="stable")
  312. assert len(versions) == 2
  313. assert all(v.type == "stable" for v in versions)
  314. def test_get_versions_latest_name_format(
  315. self, service, mock_versions_fetcher
  316. ):
  317. """Test that latest versions have the correct display name format."""
  318. mock_versions_fetcher.get_versions_for_vehicle.return_value = [
  319. VersionInfo(
  320. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  321. commit_ref="refs/heads/master",
  322. release_type="latest",
  323. version_number="NA",
  324. ap_build_artifacts_url=None,
  325. ),
  326. ]
  327. versions = service.get_versions("copter")
  328. assert versions[0].name == "Latest (ardupilot)"
  329. def test_get_versions_non_latest_name_format(
  330. self, service, mock_versions_fetcher
  331. ):
  332. """Test that non-latest versions have the correct display name format."""
  333. mock_versions_fetcher.get_versions_for_vehicle.return_value = [
  334. VersionInfo(
  335. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  336. commit_ref="refs/tags/Copter-4.5.0",
  337. release_type="stable",
  338. version_number="4.5.0",
  339. ap_build_artifacts_url=None,
  340. ),
  341. ]
  342. versions = service.get_versions("copter")
  343. assert versions[0].name == "stable 4.5.0 (ardupilot)"
  344. # Tests for get_version
  345. def test_get_version_found(self, service, mock_versions_fetcher):
  346. """Test that the correct version is returned when it exists."""
  347. version_info = VersionInfo(
  348. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  349. commit_ref="refs/tags/Copter-4.5.0",
  350. release_type="stable",
  351. version_number="4.5.0",
  352. ap_build_artifacts_url=None,
  353. )
  354. mock_versions_fetcher.get_versions_for_vehicle.return_value = [version_info]
  355. result = service.get_version("copter", version_info.version_id)
  356. assert result is not None
  357. assert result.id == version_info.version_id
  358. def test_get_version_not_found(self, service, mock_versions_fetcher):
  359. """Test that None is returned when the version does not exist."""
  360. mock_versions_fetcher.get_versions_for_vehicle.return_value = [
  361. VersionInfo(
  362. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  363. commit_ref="refs/tags/Copter-4.5.0",
  364. release_type="stable",
  365. version_number="4.5.0",
  366. ap_build_artifacts_url=None,
  367. ),
  368. ]
  369. result = service.get_version("copter", "nonexistent-version-id")
  370. assert result is None
  371. def test_get_version_no_versions_available(self, service, mock_versions_fetcher):
  372. """Test that None is returned when there are no versions at all."""
  373. mock_versions_fetcher.get_versions_for_vehicle.return_value = []
  374. result = service.get_version("copter", "any-version-id")
  375. assert result is None
  376. def test_get_version_returns_correct_match_among_many(self, service, mock_versions_fetcher):
  377. """Test that only the matching version is returned when multiple exist."""
  378. stable_info = VersionInfo(
  379. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  380. commit_ref="refs/tags/Copter-4.5.0",
  381. release_type="stable",
  382. version_number="4.5.0",
  383. ap_build_artifacts_url=None,
  384. )
  385. beta_info = VersionInfo(
  386. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  387. commit_ref="refs/tags/Copter-4.6.0-beta",
  388. release_type="beta",
  389. version_number="4.6.0",
  390. ap_build_artifacts_url=None,
  391. )
  392. latest_info = VersionInfo(
  393. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  394. commit_ref="refs/heads/master",
  395. release_type="latest",
  396. version_number="NA",
  397. ap_build_artifacts_url=None,
  398. )
  399. mock_versions_fetcher.get_versions_for_vehicle.return_value = [
  400. stable_info, beta_info, latest_info,
  401. ]
  402. result = service.get_version("copter", beta_info.version_id)
  403. assert result is not None
  404. assert result.id == beta_info.version_id
  405. assert result.type == "beta"
  406. # Tests for get_boards
  407. def test_get_boards_version_not_found_returns_empty(self, service, mock_versions_fetcher):
  408. """Test that an empty list is returned when the version does not exist."""
  409. mock_versions_fetcher.get_version_info.return_value = None
  410. result = service.get_boards("copter", "nonexistent-version-id")
  411. assert result == []
  412. def test_get_boards_version_info_queried_with_correct_params(
  413. self, service, mock_versions_fetcher
  414. ):
  415. """Test that get_version_info is called with the correct vehicle and version IDs."""
  416. mock_versions_fetcher.get_version_info.return_value = None
  417. service.get_boards("copter", "some-version-id")
  418. mock_versions_fetcher.get_version_info.assert_called_once_with(
  419. vehicle_id="copter",
  420. version_id="some-version-id",
  421. )
  422. def test_get_boards_empty(
  423. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  424. ):
  425. """Test that an empty list is returned when there are no boards for a version."""
  426. version_info = VersionInfo(
  427. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  428. commit_ref="refs/tags/Copter-4.5.0",
  429. release_type="stable",
  430. version_number="4.5.0",
  431. ap_build_artifacts_url=None,
  432. )
  433. mock_versions_fetcher.get_version_info.return_value = version_info
  434. mock_ap_src_metadata_fetcher.get_boards.return_value = []
  435. result = service.get_boards("copter", version_info.version_id)
  436. assert result == []
  437. def test_get_boards_single(
  438. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  439. ):
  440. """Test that a single board is returned correctly."""
  441. version_info = VersionInfo(
  442. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  443. commit_ref="refs/tags/Copter-4.5.0",
  444. release_type="stable",
  445. version_number="4.5.0",
  446. ap_build_artifacts_url=None,
  447. )
  448. mock_versions_fetcher.get_version_info.return_value = version_info
  449. mock_ap_src_metadata_fetcher.get_boards.return_value = ["CubeRed"]
  450. result = service.get_boards("copter", version_info.version_id)
  451. assert len(result) == 1
  452. assert result[0].id == "CubeRed"
  453. assert result[0].name == "CubeRed"
  454. def test_get_boards_many(
  455. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  456. ):
  457. """Test that multiple boards are returned correctly."""
  458. version_info = VersionInfo(
  459. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  460. commit_ref="refs/tags/Copter-4.5.0",
  461. release_type="stable",
  462. version_number="4.5.0",
  463. ap_build_artifacts_url=None,
  464. )
  465. mock_versions_fetcher.get_version_info.return_value = version_info
  466. mock_ap_src_metadata_fetcher.get_boards.return_value = [
  467. "CubeRed", "CubeOrange", "MatekF405",
  468. ]
  469. result = service.get_boards("copter", version_info.version_id)
  470. assert len(result) == 3
  471. assert [b.id for b in result] == ["CubeRed", "CubeOrange", "MatekF405"]
  472. def test_get_boards_sets_correct_vehicle_and_version_ids(
  473. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  474. ):
  475. """Test that returned boards carry the correct vehicle_id and version_id."""
  476. version_info = VersionInfo(
  477. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  478. commit_ref="refs/tags/Copter-4.5.0",
  479. release_type="stable",
  480. version_number="4.5.0",
  481. ap_build_artifacts_url=None,
  482. )
  483. mock_versions_fetcher.get_version_info.return_value = version_info
  484. mock_ap_src_metadata_fetcher.get_boards.return_value = ["CubeRed"]
  485. result = service.get_boards("copter", version_info.version_id)
  486. assert result[0].vehicle_id == "copter"
  487. assert result[0].version_id == version_info.version_id
  488. def test_get_boards_fetcher_called_with_correct_params(
  489. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  490. ):
  491. """Test that the metadata fetcher is called with remote name, commit ref, and vehicle ID from version info."""
  492. version_info = VersionInfo(
  493. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  494. commit_ref="refs/tags/Copter-4.5.0",
  495. release_type="stable",
  496. version_number="4.5.0",
  497. ap_build_artifacts_url=None,
  498. )
  499. mock_versions_fetcher.get_version_info.return_value = version_info
  500. mock_ap_src_metadata_fetcher.get_boards.return_value = []
  501. service.get_boards("copter", version_info.version_id)
  502. mock_ap_src_metadata_fetcher.get_boards.assert_called_once_with(
  503. remote="ardupilot",
  504. commit_ref="refs/tags/Copter-4.5.0",
  505. vehicle_id="copter",
  506. )
  507. # Tests for get_board
  508. def test_get_board_found(self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher):
  509. """Test that the correct board is returned when it exists."""
  510. version_info = VersionInfo(
  511. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  512. commit_ref="refs/tags/Copter-4.5.0",
  513. release_type="stable",
  514. version_number="4.5.0",
  515. ap_build_artifacts_url=None,
  516. )
  517. mock_versions_fetcher.get_version_info.return_value = version_info
  518. mock_ap_src_metadata_fetcher.get_boards.return_value = ["CubeRed", "CubeOrange"]
  519. result = service.get_board("copter", version_info.version_id, "CubeRed")
  520. assert result is not None
  521. assert result.id == "CubeRed"
  522. assert result.name == "CubeRed"
  523. assert result.vehicle_id == "copter"
  524. assert result.version_id == version_info.version_id
  525. def test_get_board_not_found(self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher):
  526. """Test that None is returned when the board does not exist."""
  527. version_info = VersionInfo(
  528. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  529. commit_ref="refs/tags/Copter-4.5.0",
  530. release_type="stable",
  531. version_number="4.5.0",
  532. ap_build_artifacts_url=None,
  533. )
  534. mock_versions_fetcher.get_version_info.return_value = version_info
  535. mock_ap_src_metadata_fetcher.get_boards.return_value = ["CubeRed", "CubeOrange"]
  536. result = service.get_board("copter", version_info.version_id, "NonExistentBoard")
  537. assert result is None
  538. def test_get_board_returns_correct_match_among_many(
  539. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  540. ):
  541. """Test that only the matching board is returned when multiple boards exist."""
  542. version_info = VersionInfo(
  543. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  544. commit_ref="refs/tags/Copter-4.5.0",
  545. release_type="stable",
  546. version_number="4.5.0",
  547. ap_build_artifacts_url=None,
  548. )
  549. mock_versions_fetcher.get_version_info.return_value = version_info
  550. mock_ap_src_metadata_fetcher.get_boards.return_value = [
  551. "CubeRed", "CubeOrange", "MatekF405",
  552. ]
  553. result = service.get_board("copter", version_info.version_id, "CubeOrange")
  554. assert result is not None
  555. assert result.id == "CubeOrange"
  556. # Tests for get_features
  557. def test_get_features_version_not_found_returns_empty(
  558. self, service, mock_versions_fetcher
  559. ):
  560. """Test that an empty list is returned when the version does not exist."""
  561. mock_versions_fetcher.get_version_info.return_value = None
  562. result = service.get_features("copter", "nonexistent-version-id", "CubeRed")
  563. assert result == []
  564. def test_get_features_zero_options_returns_empty(
  565. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  566. ):
  567. """Test that an empty list is returned when there are no build options."""
  568. version_info = VersionInfo(
  569. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  570. commit_ref="refs/tags/Copter-4.5.0",
  571. release_type="stable",
  572. version_number="4.5.0",
  573. ap_build_artifacts_url=None,
  574. )
  575. mock_versions_fetcher.get_version_info.return_value = version_info
  576. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = []
  577. result = service.get_features("copter", version_info.version_id, "CubeRed")
  578. assert result == []
  579. def test_get_features_one_option(
  580. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  581. ):
  582. """Test that a single feature is returned correctly."""
  583. version_info = VersionInfo(
  584. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  585. commit_ref="refs/tags/Copter-4.5.0",
  586. release_type="stable",
  587. version_number="4.5.0",
  588. ap_build_artifacts_url=None,
  589. )
  590. opt = Mock()
  591. opt.label = "HAL_LOGGING_ENABLED"
  592. opt.define = "HAL_LOGGING_ENABLED"
  593. opt.category = "Logging"
  594. opt.description = ""
  595. opt.default = 1
  596. opt.dependency = None
  597. mock_versions_fetcher.get_version_info.return_value = version_info
  598. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt]
  599. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = None
  600. result = service.get_features("copter", version_info.version_id, "CubeRed")
  601. assert len(result) == 1
  602. assert result[0].id == "HAL_LOGGING_ENABLED"
  603. assert result[0].name == "HAL_LOGGING_ENABLED"
  604. def test_get_features_many_options(
  605. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  606. ):
  607. """Test that all features are returned when multiple options exist."""
  608. version_info = VersionInfo(
  609. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  610. commit_ref="refs/tags/Copter-4.5.0",
  611. release_type="stable",
  612. version_number="4.5.0",
  613. ap_build_artifacts_url=None,
  614. )
  615. opt_logging = Mock()
  616. opt_logging.label, opt_logging.define, opt_logging.category = "HAL_LOGGING_ENABLED", "HAL_LOGGING_ENABLED", "Logging"
  617. opt_logging.description, opt_logging.default, opt_logging.dependency = "", 1, None
  618. opt_ekf = Mock()
  619. opt_ekf.label, opt_ekf.define, opt_ekf.category = "HAL_NAVEKF3_AVAILABLE", "HAL_NAVEKF3_AVAILABLE", "EKF"
  620. opt_ekf.description, opt_ekf.default, opt_ekf.dependency = "", 1, None
  621. opt_sensors = Mock()
  622. opt_sensors.label, opt_sensors.define, opt_sensors.category = "HAL_BEACON_ENABLED", "HAL_BEACON_ENABLED", "Sensors"
  623. opt_sensors.description, opt_sensors.default, opt_sensors.dependency = "", 1, None
  624. mock_versions_fetcher.get_version_info.return_value = version_info
  625. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt_logging, opt_ekf, opt_sensors]
  626. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = None
  627. result = service.get_features("copter", version_info.version_id, "CubeRed")
  628. assert len(result) == 3
  629. def test_get_features_sorted_by_category(
  630. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  631. ):
  632. """Test that features are sorted by category name."""
  633. version_info = VersionInfo(
  634. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  635. commit_ref="refs/tags/Copter-4.5.0",
  636. release_type="stable",
  637. version_number="4.5.0",
  638. ap_build_artifacts_url=None,
  639. )
  640. opt_z = Mock()
  641. opt_z.label, opt_z.define, opt_z.category = "FEATURE_Z", "DEFINE_Z", "Sensors"
  642. opt_z.description, opt_z.default, opt_z.dependency = "", 1, None
  643. opt_a = Mock()
  644. opt_a.label, opt_a.define, opt_a.category = "FEATURE_A", "DEFINE_A", "EKF"
  645. opt_a.description, opt_a.default, opt_a.dependency = "", 1, None
  646. opt_m = Mock()
  647. opt_m.label, opt_m.define, opt_m.category = "FEATURE_M", "DEFINE_M", "Logging"
  648. opt_m.description, opt_m.default, opt_m.dependency = "", 1, None
  649. mock_versions_fetcher.get_version_info.return_value = version_info
  650. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt_z, opt_a, opt_m]
  651. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = None
  652. result = service.get_features("copter", version_info.version_id, "CubeRed")
  653. assert [f.category.name for f in result] == ["EKF", "Logging", "Sensors"]
  654. def test_get_features_uses_fallback_defaults_when_no_artifacts_url(
  655. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  656. ):
  657. """Test that build-options-py defaults are used when ap_build_artifacts_url is None."""
  658. version_info = VersionInfo(
  659. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  660. commit_ref="refs/tags/Copter-4.5.0",
  661. release_type="stable",
  662. version_number="4.5.0",
  663. ap_build_artifacts_url=None,
  664. )
  665. opt_on = Mock()
  666. opt_on.label, opt_on.define, opt_on.category = "FEATURE_ON", "DEFINE_ON", "Cat"
  667. opt_on.description, opt_on.default, opt_on.dependency = "", 1, None
  668. opt_off = Mock()
  669. opt_off.label, opt_off.define, opt_off.category = "FEATURE_OFF", "DEFINE_OFF", "Cat"
  670. opt_off.description, opt_off.default, opt_off.dependency = "", 0, None
  671. mock_versions_fetcher.get_version_info.return_value = version_info
  672. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt_on, opt_off]
  673. result = service.get_features("copter", version_info.version_id, "CubeRed")
  674. by_id = {f.id: f.default for f in result}
  675. assert by_id["FEATURE_ON"].enabled is True
  676. assert by_id["FEATURE_ON"].source == "build-options-py"
  677. assert by_id["FEATURE_OFF"].enabled is False
  678. assert by_id["FEATURE_OFF"].source == "build-options-py"
  679. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.assert_not_called()
  680. def test_get_features_uses_firmware_server_defaults_when_available(
  681. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  682. ):
  683. """Test that firmware-server defaults override build-options-py when present."""
  684. version_info = VersionInfo(
  685. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  686. commit_ref="refs/tags/Copter-4.5.0",
  687. release_type="stable",
  688. version_number="4.5.0",
  689. ap_build_artifacts_url="https://firmware.ardupilot.org/Copter/stable-4.5.0",
  690. )
  691. opt_a = Mock()
  692. opt_a.label, opt_a.define, opt_a.category = "FEATURE_A", "DEFINE_A", "Cat"
  693. opt_a.description, opt_a.default, opt_a.dependency = "", 1, None
  694. opt_b = Mock()
  695. opt_b.label, opt_b.define, opt_b.category = "FEATURE_B", "DEFINE_B", "Cat"
  696. opt_b.description, opt_b.default, opt_b.dependency = "", 1, None
  697. mock_versions_fetcher.get_version_info.return_value = version_info
  698. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt_a, opt_b]
  699. # firmware server says DEFINE_A is disabled, DEFINE_B is enabled
  700. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = {
  701. "DEFINE_A": 0,
  702. "DEFINE_B": 1,
  703. }
  704. result = service.get_features("copter", version_info.version_id, "CubeRed")
  705. by_id = {f.id: f.default for f in result}
  706. assert by_id["FEATURE_A"].enabled is False
  707. assert by_id["FEATURE_A"].source == "firmware-server"
  708. assert by_id["FEATURE_B"].enabled is True
  709. assert by_id["FEATURE_B"].source == "firmware-server"
  710. def test_get_features_falls_back_to_defaults_when_firmware_server_returns_none(
  711. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  712. ):
  713. """Test that build-options-py fallback is used when firmware server fetch fails."""
  714. version_info = VersionInfo(
  715. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  716. commit_ref="refs/tags/Copter-4.5.0",
  717. release_type="stable",
  718. version_number="4.5.0",
  719. ap_build_artifacts_url="https://firmware.ardupilot.org/Copter/stable-4.5.0",
  720. )
  721. opt = Mock()
  722. opt.label, opt.define, opt.category = "FEATURE_A", "DEFINE_A", "Cat"
  723. opt.description, opt.default, opt.dependency = "", 1, None
  724. mock_versions_fetcher.get_version_info.return_value = version_info
  725. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt]
  726. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = None
  727. result = service.get_features("copter", version_info.version_id, "CubeRed")
  728. assert result[0].default.enabled is True
  729. assert result[0].default.source == "build-options-py"
  730. def test_get_features_firmware_server_overrides_only_known_defines(
  731. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  732. ):
  733. """Test that a define absent from firmware-server data falls back to build-options-py."""
  734. version_info = VersionInfo(
  735. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  736. commit_ref="refs/tags/Copter-4.5.0",
  737. release_type="stable",
  738. version_number="4.5.0",
  739. ap_build_artifacts_url="https://firmware.ardupilot.org/Copter/stable-4.5.0",
  740. )
  741. opt_known = Mock()
  742. opt_known.label, opt_known.define, opt_known.category = "FEATURE_KNOWN", "DEFINE_KNOWN", "Cat"
  743. opt_known.description, opt_known.default, opt_known.dependency = "", 0, None
  744. opt_unknown = Mock()
  745. opt_unknown.label, opt_unknown.define, opt_unknown.category = "FEATURE_UNKNOWN", "DEFINE_UNKNOWN", "Cat"
  746. opt_unknown.description, opt_unknown.default, opt_unknown.dependency = "", 1, None
  747. mock_versions_fetcher.get_version_info.return_value = version_info
  748. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt_known, opt_unknown]
  749. # firmware server only knows about DEFINE_KNOWN
  750. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = {
  751. "DEFINE_KNOWN": 1,
  752. }
  753. result = service.get_features("copter", version_info.version_id, "CubeRed")
  754. by_id = {f.id: f.default for f in result}
  755. assert by_id["FEATURE_KNOWN"].enabled is True
  756. assert by_id["FEATURE_KNOWN"].source == "firmware-server"
  757. assert by_id["FEATURE_UNKNOWN"].enabled is True
  758. assert by_id["FEATURE_UNKNOWN"].source == "build-options-py"
  759. def test_get_features_dependency_none(
  760. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  761. ):
  762. """Test that a feature with no dependency produces an empty dependencies list."""
  763. version_info = VersionInfo(
  764. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  765. commit_ref="refs/tags/Copter-4.5.0",
  766. release_type="stable",
  767. version_number="4.5.0",
  768. ap_build_artifacts_url=None,
  769. )
  770. opt = Mock()
  771. opt.label, opt.define, opt.category = "FEATURE_A", "DEFINE_A", "Cat"
  772. opt.description, opt.default, opt.dependency = "", 1, None
  773. mock_versions_fetcher.get_version_info.return_value = version_info
  774. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt]
  775. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = None
  776. result = service.get_features("copter", version_info.version_id, "CubeRed")
  777. assert result[0].dependencies == []
  778. def test_get_features_dependency_single(
  779. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  780. ):
  781. """Test that a single dependency string is parsed into a one-element list."""
  782. version_info = VersionInfo(
  783. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  784. commit_ref="refs/tags/Copter-4.5.0",
  785. release_type="stable",
  786. version_number="4.5.0",
  787. ap_build_artifacts_url=None,
  788. )
  789. opt = Mock()
  790. opt.label, opt.define, opt.category = "FEATURE_A", "DEFINE_A", "Cat"
  791. opt.description, opt.default, opt.dependency = "", 1, "DEP_ONE"
  792. mock_versions_fetcher.get_version_info.return_value = version_info
  793. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt]
  794. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = None
  795. result = service.get_features("copter", version_info.version_id, "CubeRed")
  796. assert result[0].dependencies == ["DEP_ONE"]
  797. def test_get_features_dependency_multiple_comma_separated(
  798. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  799. ):
  800. """Test that a comma-separated dependency string is split into multiple entries."""
  801. version_info = VersionInfo(
  802. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  803. commit_ref="refs/tags/Copter-4.5.0",
  804. release_type="stable",
  805. version_number="4.5.0",
  806. ap_build_artifacts_url=None,
  807. )
  808. opt = Mock()
  809. opt.label, opt.define, opt.category = "FEATURE_A", "DEFINE_A", "Cat"
  810. opt.description, opt.default, opt.dependency = "", 1, "DEP_ONE,DEP_TWO,DEP_THREE"
  811. mock_versions_fetcher.get_version_info.return_value = version_info
  812. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt]
  813. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = None
  814. result = service.get_features("copter", version_info.version_id, "CubeRed")
  815. assert result[0].dependencies == ["DEP_ONE", "DEP_TWO", "DEP_THREE"]
  816. def test_get_features_dependency_with_spaces(
  817. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  818. ):
  819. """Test that spaces around dependency labels are stripped."""
  820. version_info = VersionInfo(
  821. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  822. commit_ref="refs/tags/Copter-4.5.0",
  823. release_type="stable",
  824. version_number="4.5.0",
  825. ap_build_artifacts_url=None,
  826. )
  827. opt = Mock()
  828. opt.label, opt.define, opt.category = "FEATURE_A", "DEFINE_A", "Cat"
  829. opt.description, opt.default, opt.dependency = "", 1, "DEP_ONE , DEP_TWO , DEP_THREE"
  830. mock_versions_fetcher.get_version_info.return_value = version_info
  831. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt]
  832. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = None
  833. result = service.get_features("copter", version_info.version_id, "CubeRed")
  834. assert result[0].dependencies == ["DEP_ONE", "DEP_TWO", "DEP_THREE"]
  835. def test_get_features_ids_filled_correctly(
  836. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  837. ):
  838. """Test that vehicle_id, version_id, and board_id are correctly set on each feature."""
  839. version_info = VersionInfo(
  840. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  841. commit_ref="refs/tags/Copter-4.5.0",
  842. release_type="stable",
  843. version_number="4.5.0",
  844. ap_build_artifacts_url=None,
  845. )
  846. opt = Mock()
  847. opt.label, opt.define, opt.category = "FEATURE_A", "DEFINE_A", "Cat"
  848. opt.description, opt.default, opt.dependency = "", 1, None
  849. mock_versions_fetcher.get_version_info.return_value = version_info
  850. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt]
  851. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = None
  852. result = service.get_features("copter", version_info.version_id, "CubeRed")
  853. assert result[0].vehicle_id == "copter"
  854. assert result[0].version_id == version_info.version_id
  855. assert result[0].board_id == "CubeRed"
  856. def test_get_features_category_filter_keeps_matching(
  857. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  858. ):
  859. """Test that category_id filter returns only features whose category matches."""
  860. version_info = VersionInfo(
  861. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  862. commit_ref="refs/tags/Copter-4.5.0",
  863. release_type="stable",
  864. version_number="4.5.0",
  865. ap_build_artifacts_url=None,
  866. )
  867. opt_logging = Mock()
  868. opt_logging.label, opt_logging.define, opt_logging.category = "HAL_LOGGING_ENABLED", "HAL_LOGGING_ENABLED", "Logging"
  869. opt_logging.description, opt_logging.default, opt_logging.dependency = "", 1, None
  870. opt_ekf = Mock()
  871. opt_ekf.label, opt_ekf.define, opt_ekf.category = "HAL_NAVEKF3_AVAILABLE", "HAL_NAVEKF3_AVAILABLE", "EKF"
  872. opt_ekf.description, opt_ekf.default, opt_ekf.dependency = "", 1, None
  873. opt_sensors = Mock()
  874. opt_sensors.label, opt_sensors.define, opt_sensors.category = "HAL_BEACON_ENABLED", "HAL_BEACON_ENABLED", "Sensors"
  875. opt_sensors.description, opt_sensors.default, opt_sensors.dependency = "", 1, None
  876. mock_versions_fetcher.get_version_info.return_value = version_info
  877. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt_logging, opt_ekf, opt_sensors]
  878. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = None
  879. result = service.get_features("copter", version_info.version_id, "CubeRed", category_id="Logging")
  880. assert len(result) == 1
  881. assert result[0].id == "HAL_LOGGING_ENABLED"
  882. assert result[0].category.name == "Logging"
  883. def test_get_features_category_filter_excludes_non_matching(
  884. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  885. ):
  886. """Test that category_id filter excludes features whose category does not match."""
  887. version_info = VersionInfo(
  888. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  889. commit_ref="refs/tags/Copter-4.5.0",
  890. release_type="stable",
  891. version_number="4.5.0",
  892. ap_build_artifacts_url=None,
  893. )
  894. opt_logging = Mock()
  895. opt_logging.label, opt_logging.define, opt_logging.category = "HAL_LOGGING_ENABLED", "HAL_LOGGING_ENABLED", "Logging"
  896. opt_logging.description, opt_logging.default, opt_logging.dependency = "", 1, None
  897. opt_ekf = Mock()
  898. opt_ekf.label, opt_ekf.define, opt_ekf.category = "HAL_NAVEKF3_AVAILABLE", "HAL_NAVEKF3_AVAILABLE", "EKF"
  899. opt_ekf.description, opt_ekf.default, opt_ekf.dependency = "", 1, None
  900. mock_versions_fetcher.get_version_info.return_value = version_info
  901. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt_logging, opt_ekf]
  902. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = None
  903. result = service.get_features("copter", version_info.version_id, "CubeRed", category_id="Sensors")
  904. assert result == []
  905. def test_get_features_category_filter_no_matches_returns_empty(
  906. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  907. ):
  908. """Test that a category_id with no matching features returns an empty list."""
  909. version_info = VersionInfo(
  910. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  911. commit_ref="refs/tags/Copter-4.5.0",
  912. release_type="stable",
  913. version_number="4.5.0",
  914. ap_build_artifacts_url=None,
  915. )
  916. opt_a = Mock()
  917. opt_a.label, opt_a.define, opt_a.category = "FEATURE_A", "DEFINE_A", "Logging"
  918. opt_a.description, opt_a.default, opt_a.dependency = "", 1, None
  919. opt_b = Mock()
  920. opt_b.label, opt_b.define, opt_b.category = "FEATURE_B", "DEFINE_B", "Logging"
  921. opt_b.description, opt_b.default, opt_b.dependency = "", 1, None
  922. mock_versions_fetcher.get_version_info.return_value = version_info
  923. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt_a, opt_b]
  924. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = None
  925. result = service.get_features("copter", version_info.version_id, "CubeRed", category_id="NonExistent")
  926. assert result == []
  927. # Tests for get_feature
  928. def test_get_feature_found(
  929. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  930. ):
  931. """Test that the correct feature is returned when it exists."""
  932. version_info = VersionInfo(
  933. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  934. commit_ref="refs/tags/Copter-4.5.0",
  935. release_type="stable",
  936. version_number="4.5.0",
  937. ap_build_artifacts_url=None,
  938. )
  939. opt = Mock()
  940. opt.label, opt.define, opt.category = "HAL_LOGGING_ENABLED", "HAL_LOGGING_ENABLED", "Logging"
  941. opt.description, opt.default, opt.dependency = "", 1, None
  942. mock_versions_fetcher.get_version_info.return_value = version_info
  943. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt]
  944. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = None
  945. result = service.get_feature("copter", version_info.version_id, "CubeRed", "HAL_LOGGING_ENABLED")
  946. assert result is not None
  947. assert result.id == "HAL_LOGGING_ENABLED"
  948. assert result.name == "HAL_LOGGING_ENABLED"
  949. def test_get_feature_not_found(
  950. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  951. ):
  952. """Test that None is returned when the feature does not exist."""
  953. version_info = VersionInfo(
  954. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  955. commit_ref="refs/tags/Copter-4.5.0",
  956. release_type="stable",
  957. version_number="4.5.0",
  958. ap_build_artifacts_url=None,
  959. )
  960. opt = Mock()
  961. opt.label, opt.define, opt.category = "HAL_LOGGING_ENABLED", "HAL_LOGGING_ENABLED", "Logging"
  962. opt.description, opt.default, opt.dependency = "", 1, None
  963. mock_versions_fetcher.get_version_info.return_value = version_info
  964. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt]
  965. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = None
  966. result = service.get_feature("copter", version_info.version_id, "CubeRed", "NONEXISTENT_FEATURE")
  967. assert result is None
  968. def test_get_feature_returns_correct_match_among_many(
  969. self, service, mock_versions_fetcher, mock_ap_src_metadata_fetcher
  970. ):
  971. """Test that only the matching feature is returned when multiple features exist."""
  972. version_info = VersionInfo(
  973. remote_info=RemoteInfo(name="ardupilot", url="https://github.com/ArduPilot/ardupilot.git"),
  974. commit_ref="refs/tags/Copter-4.5.0",
  975. release_type="stable",
  976. version_number="4.5.0",
  977. ap_build_artifacts_url=None,
  978. )
  979. opt_a = Mock()
  980. opt_a.label, opt_a.define, opt_a.category = "FEATURE_A", "DEFINE_A", "Cat"
  981. opt_a.description, opt_a.default, opt_a.dependency = "", 1, None
  982. opt_b = Mock()
  983. opt_b.label, opt_b.define, opt_b.category = "FEATURE_B", "DEFINE_B", "Cat"
  984. opt_b.description, opt_b.default, opt_b.dependency = "", 0, None
  985. opt_c = Mock()
  986. opt_c.label, opt_c.define, opt_c.category = "FEATURE_C", "DEFINE_C", "Cat"
  987. opt_c.description, opt_c.default, opt_c.dependency = "", 1, None
  988. mock_versions_fetcher.get_version_info.return_value = version_info
  989. mock_ap_src_metadata_fetcher.get_build_options_at_commit.return_value = [opt_a, opt_b, opt_c]
  990. mock_ap_src_metadata_fetcher.get_board_defaults_from_fw_server.return_value = None
  991. result = service.get_feature("copter", version_info.version_id, "CubeRed", "FEATURE_B")
  992. assert result is not None
  993. assert result.id == "FEATURE_B"
  994. assert result.default.enabled is False