@@ -1519,3 +1519,205 @@ def test_get_fc_fw_version_missing_components(self, mock_fs_class) -> None:
15191519
15201520 # Verify empty string is returned
15211521 assert version == ""
1522+
1523+
1524+ class TestSetFcFwVersionAndTypeInComponentsJson :
1525+ """BDD tests for set_fc_fw_version_and_type_in_components_json."""
1526+
1527+ @pytest .fixture
1528+ def vehicle_components_with_data (self ) -> VehicleComponents :
1529+ """Fixture providing a VehicleComponents instance with typical component data."""
1530+ vc = VehicleComponents ()
1531+ vc .vehicle_components_fs .data = {
1532+ "Components" : {
1533+ "Flight Controller" : {
1534+ "Firmware" : {"Type" : "ArduCopter" , "Version" : "4.6.0" },
1535+ "Product" : {"Manufacturer" : "Holybro" , "Model" : "Pixhawk 6C" },
1536+ }
1537+ }
1538+ }
1539+ return vc
1540+
1541+ def test_firmware_version_is_written_into_component_data (self , vehicle_components_with_data : VehicleComponents ) -> None :
1542+ """
1543+ The firmware version is correctly updated in the in-memory component data.
1544+
1545+ GIVEN: Components data that has a placeholder firmware version "4.6.0"
1546+ WHEN: set_fc_fw_version_and_type_in_components_json is called with "4.6.3"
1547+ THEN: The Firmware Version field in the component data is updated to "4.6.3"
1548+ """
1549+ vc = vehicle_components_with_data
1550+ with patch .object (vc , "save_vehicle_components_json_data" , return_value = (False , "" )):
1551+ vc .set_fc_fw_version_and_type_in_components_json ("4.6.3" , "ArduCopter" , "/vehicles/test" )
1552+
1553+ assert vc .vehicle_components_fs .data is not None
1554+ version = vc .vehicle_components_fs .data ["Components" ]["Flight Controller" ]["Firmware" ]["Version" ]
1555+ assert version == "4.6.3"
1556+
1557+ def test_firmware_type_is_written_into_component_data (self , vehicle_components_with_data : VehicleComponents ) -> None :
1558+ """
1559+ The firmware type is correctly updated in the in-memory component data.
1560+
1561+ GIVEN: Components data that has firmware Type "ArduCopter"
1562+ WHEN: set_fc_fw_version_and_type_in_components_json is called with "Rover"
1563+ THEN: The Firmware Type field is updated to "Rover"
1564+ """
1565+ vc = vehicle_components_with_data
1566+ with patch .object (vc , "save_vehicle_components_json_data" , return_value = (False , "" )):
1567+ vc .set_fc_fw_version_and_type_in_components_json ("4.5.1" , "Rover" , "/vehicles/rover_project" )
1568+
1569+ assert vc .vehicle_components_fs .data is not None
1570+ fw_type = vc .vehicle_components_fs .data ["Components" ]["Flight Controller" ]["Firmware" ]["Type" ]
1571+ assert fw_type == "Rover"
1572+
1573+ def test_save_is_called_with_updated_data_and_correct_directory (
1574+ self , vehicle_components_with_data : VehicleComponents
1575+ ) -> None :
1576+ """
1577+ save_vehicle_components_json_data is called with the updated data and the given vehicle_dir.
1578+
1579+ GIVEN: Component data in memory and a known vehicle directory path
1580+ WHEN: set_fc_fw_version_and_type_in_components_json is called
1581+ THEN: save_vehicle_components_json_data receives the full data dict and the correct path
1582+ """
1583+ vc = vehicle_components_with_data
1584+ with patch .object (vc , "save_vehicle_components_json_data" , return_value = (False , "" )) as mock_save :
1585+ vc .set_fc_fw_version_and_type_in_components_json ("4.6.3" , "ArduCopter" , "/vehicles/my_project" )
1586+
1587+ mock_save .assert_called_once ()
1588+ saved_data , saved_dir = mock_save .call_args .args
1589+ assert saved_dir == "/vehicles/my_project"
1590+ assert saved_data ["Components" ]["Flight Controller" ]["Firmware" ]["Version" ] == "4.6.3"
1591+ assert saved_data ["Components" ]["Flight Controller" ]["Firmware" ]["Type" ] == "ArduCopter"
1592+
1593+ def test_other_component_fields_are_not_disturbed (self , vehicle_components_with_data : VehicleComponents ) -> None :
1594+ """
1595+ Non-firmware component fields remain intact after the firmware update.
1596+
1597+ GIVEN: Components data that includes manufacturer and model information
1598+ WHEN: set_fc_fw_version_and_type_in_components_json is called
1599+ THEN: Product Manufacturer and Model are unchanged
1600+ """
1601+ vc = vehicle_components_with_data
1602+ with patch .object (vc , "save_vehicle_components_json_data" , return_value = (False , "" )):
1603+ vc .set_fc_fw_version_and_type_in_components_json ("4.6.3" , "ArduCopter" , "/vehicles/test" )
1604+
1605+ assert vc .vehicle_components_fs .data is not None
1606+ product = vc .vehicle_components_fs .data ["Components" ]["Flight Controller" ]["Product" ]
1607+ assert product ["Manufacturer" ] == "Holybro"
1608+ assert product ["Model" ] == "Pixhawk 6C"
1609+
1610+ def test_creates_firmware_section_when_absent (self ) -> None :
1611+ """
1612+ The Firmware sub-dict is created if the Flight Controller component lacks it.
1613+
1614+ GIVEN: Components data where "Flight Controller" has no "Firmware" key at all
1615+ WHEN: set_fc_fw_version_and_type_in_components_json is called
1616+ THEN: A "Firmware" section is created and populated with Version and Type
1617+ """
1618+ vc = VehicleComponents ()
1619+ vc .vehicle_components_fs .data = {"Components" : {"Flight Controller" : {"Product" : {"Model" : "CubeOrange" }}}}
1620+
1621+ with patch .object (vc , "save_vehicle_components_json_data" , return_value = (False , "" )):
1622+ vc .set_fc_fw_version_and_type_in_components_json ("4.6.3" , "ArduCopter" , "/vehicles/test" )
1623+
1624+ firmware = vc .vehicle_components_fs .data ["Components" ]["Flight Controller" ]["Firmware" ]
1625+ assert firmware ["Version" ] == "4.6.3"
1626+ assert firmware ["Type" ] == "ArduCopter"
1627+
1628+ def test_creates_flight_controller_section_when_absent (self ) -> None :
1629+ """
1630+ The Flight Controller section is created if the Components dict lacks it entirely.
1631+
1632+ GIVEN: Components data that has no "Flight Controller" key
1633+ WHEN: set_fc_fw_version_and_type_in_components_json is called
1634+ THEN: "Flight Controller" → "Firmware" is created with the correct values
1635+ """
1636+ vc = VehicleComponents ()
1637+ vc .vehicle_components_fs .data = {"Components" : {}}
1638+
1639+ with patch .object (vc , "save_vehicle_components_json_data" , return_value = (False , "" )):
1640+ vc .set_fc_fw_version_and_type_in_components_json ("4.6.3" , "ArduCopter" , "/vehicles/test" )
1641+
1642+ firmware = vc .vehicle_components_fs .data ["Components" ]["Flight Controller" ]["Firmware" ]
1643+ assert firmware ["Version" ] == "4.6.3"
1644+ assert firmware ["Type" ] == "ArduCopter"
1645+
1646+ def test_does_nothing_when_data_is_none (self ) -> None :
1647+ """
1648+ No save is attempted when the component data is None.
1649+
1650+ GIVEN: vehicle_components_fs.data is None (components.json was never loaded)
1651+ WHEN: set_fc_fw_version_and_type_in_components_json is called
1652+ THEN: save_vehicle_components_json_data is NOT called and no exception is raised
1653+ """
1654+ vc = VehicleComponents ()
1655+ vc .vehicle_components_fs .data = None
1656+
1657+ with patch .object (vc , "save_vehicle_components_json_data" ) as mock_save :
1658+ vc .set_fc_fw_version_and_type_in_components_json ("4.6.3" , "ArduCopter" , "/vehicles/test" )
1659+
1660+ mock_save .assert_not_called ()
1661+
1662+ def test_does_nothing_when_data_has_no_components_key (self ) -> None :
1663+ """
1664+ No save is attempted when the loaded data lacks the "Components" top-level key.
1665+
1666+ GIVEN: vehicle_components_fs.data is a dict without a "Components" key
1667+ WHEN: set_fc_fw_version_and_type_in_components_json is called
1668+ THEN: save_vehicle_components_json_data is NOT called
1669+ """
1670+ vc = VehicleComponents ()
1671+ vc .vehicle_components_fs .data = {"SomethingElse" : {}}
1672+
1673+ with patch .object (vc , "save_vehicle_components_json_data" ) as mock_save :
1674+ vc .set_fc_fw_version_and_type_in_components_json ("4.6.3" , "ArduCopter" , "/vehicles/test" )
1675+
1676+ mock_save .assert_not_called ()
1677+
1678+ def test_version_is_readable_back_via_get_fc_fw_version (self , vehicle_components_with_data : VehicleComponents ) -> None :
1679+ """
1680+ After setting, get_fc_fw_version_from_vehicle_components_json returns the new version.
1681+
1682+ GIVEN: Components data with an old firmware version
1683+ WHEN: set_fc_fw_version_and_type_in_components_json writes "4.6.3"
1684+ THEN: get_fc_fw_version_from_vehicle_components_json returns "4.6.3"
1685+ """
1686+ vc = vehicle_components_with_data
1687+ with patch .object (vc , "save_vehicle_components_json_data" , return_value = (False , "" )):
1688+ vc .set_fc_fw_version_and_type_in_components_json ("4.6.3" , "ArduCopter" , "/vehicles/test" )
1689+
1690+ assert vc .get_fc_fw_version_from_vehicle_components_json () == "4.6.3"
1691+
1692+ def test_warning_is_logged_when_save_fails (self , vehicle_components_with_data : VehicleComponents ) -> None :
1693+ """
1694+ A warning is logged and execution continues when the save operation reports an error.
1695+
1696+ GIVEN: Component data is valid but save_vehicle_components_json_data returns an error
1697+ WHEN: set_fc_fw_version_and_type_in_components_json is called
1698+ THEN: A warning is logged containing the error message and no exception is raised
1699+ """
1700+ vc = vehicle_components_with_data
1701+ with (
1702+ patch .object (vc , "save_vehicle_components_json_data" , return_value = (True , "disk write error" )),
1703+ patch ("ardupilot_methodic_configurator.backend_filesystem_vehicle_components.logging_warning" ) as mock_warn ,
1704+ ):
1705+ vc .set_fc_fw_version_and_type_in_components_json ("4.6.3" , "ArduCopter" , "/vehicles/test" )
1706+
1707+ mock_warn .assert_called_once ()
1708+ args = mock_warn .call_args .args
1709+ assert "disk write error" in args [1 ]
1710+
1711+ def test_type_is_readable_back_via_get_fc_fw_type (self , vehicle_components_with_data : VehicleComponents ) -> None :
1712+ """
1713+ After setting, get_fc_fw_type_from_vehicle_components_json returns the new type.
1714+
1715+ GIVEN: Components data with ArduCopter as the firmware type
1716+ WHEN: set_fc_fw_version_and_type_in_components_json writes "ArduPlane"
1717+ THEN: get_fc_fw_type_from_vehicle_components_json returns "ArduPlane"
1718+ """
1719+ vc = vehicle_components_with_data
1720+ with patch .object (vc , "save_vehicle_components_json_data" , return_value = (False , "" )):
1721+ vc .set_fc_fw_version_and_type_in_components_json ("4.5.1" , "ArduPlane" , "/vehicles/test" )
1722+
1723+ assert vc .get_fc_fw_type_from_vehicle_components_json () == "ArduPlane"
0 commit comments