test: cover product_name string field write path in FIT patcher

Addresses review finding: no test previously exercised file_id field 8
or device_info field 27 (product_name), the only string-typed fields
this task patches, leaving _write_field_value's string branch
(buffer sizing, null-terminator handling) unregression-tested.
This commit is contained in:
Bastian Wagner
2026-08-15 10:41:20 +02:00
parent 5944ef364f
commit e1e57d9b6b

View File

@@ -43,6 +43,26 @@ def _build_no_device_index_fixture() -> bytes:
return make_fit(records)
def _build_product_name_fixture() -> bytes:
"""file_id field 8 and device_info field 27 are both product_name (string) fields —
the only string field type this task patches. No sensor record here, so the single
device_info record present is unambiguously the creator."""
# file_id: manufacturer(1/u16), product(2/u16), product_name(8/string, size 20)
file_def = definition(0, FILE_ID_MESG_NUM, [(1, 2, 0x84), (2, 2, 0x84), (8, 20, 0x07)])
original_file_name = b"MyWhoosh\x00".ljust(20, b"\x00")
file_data = data(0, struct.pack("<HH", 255, 999) + original_file_name)
# device_info: device_index(0/u8), manufacturer(2/u16), product(4/u16), product_name(27/string, size 20)
device_def = definition(
1, DEVICE_INFO_MESG_NUM, [(0, 1, 0x02), (2, 2, 0x84), (4, 2, 0x84), (27, 20, 0x07)]
)
original_device_name = b"MyWhoosh App\x00".ljust(20, b"\x00")
creator = data(1, struct.pack("<BHH", 0, 255, 999) + original_device_name)
records = file_def + file_data + device_def + creator
return make_fit(records)
def _read_device_info_records(path: Path) -> list[dict[int, int]]:
"""Parse raw bytes and return one dict of {field_num: value} per device_info record,
in file order, so creator and sensor records can be distinguished positionally."""
@@ -124,6 +144,23 @@ def test_convert_fit_device_leaves_device_info_without_device_index_untouched(
assert values[4] == 1234
def test_convert_fit_device_patches_product_name_string_fields(tmp_path: Path) -> None:
"""file_id field 8 and device_info field 27 (creator) are both string fields, written via
_write_field_value's string branch (buffer sizing + null-terminator handling). Neither is
exercised by the numeric-only base fixture used elsewhere in this module."""
source = tmp_path / "source.fit"
source.write_bytes(_build_product_name_fixture())
output = tmp_path / "output.fit"
convert_fit_device(source, output)
values = read_device_field_values(output)
values_by_key = {(v.global_message_num, v.field_num): v.value for v in values}
assert values_by_key[(FILE_ID_MESG_NUM, 8)] == "Edge 1030 Plus"
assert values_by_key[(DEVICE_INFO_MESG_NUM, 27)] == "Edge 1030 Plus"
def test_convert_fit_device_defaults_to_edge_1030_plus() -> None:
device = GarminDevice()
assert device.manufacturer_id == 1