test_vehicles_service.py 51 KB

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