Init
This commit is contained in:
119
tests/test_fit_device.py
Normal file
119
tests/test_fit_device.py
Normal file
@@ -0,0 +1,119 @@
|
||||
from pathlib import Path
|
||||
import struct
|
||||
|
||||
import pytest
|
||||
|
||||
from mywhoosh_garmin_sync.fit_crc import fit_crc
|
||||
from mywhoosh_garmin_sync.fit_device import (
|
||||
DeviceFieldValue,
|
||||
FitFormatError,
|
||||
GarminDevice,
|
||||
convert_fit_device,
|
||||
read_device_field_values,
|
||||
)
|
||||
|
||||
|
||||
def test_convert_fit_device_patches_metadata_and_crc(tmp_path: Path):
|
||||
source = tmp_path / "source.fit"
|
||||
output = tmp_path / "output.fit"
|
||||
source.write_bytes(_minimal_activity_fit())
|
||||
|
||||
result = convert_fit_device(
|
||||
source,
|
||||
output,
|
||||
GarminDevice(product_id=3578, product_name="Edge 1030 Plus", serial_number=42),
|
||||
)
|
||||
|
||||
assert result.patched_field_count == 8
|
||||
values = read_device_field_values(output)
|
||||
assert DeviceFieldValue(0, 1, 1) in values
|
||||
assert DeviceFieldValue(0, 2, 3578) in values
|
||||
assert DeviceFieldValue(0, 3, 42) in values
|
||||
assert DeviceFieldValue(0, 8, "Edge 1030 Plus") in values
|
||||
assert DeviceFieldValue(23, 2, 1) in values
|
||||
assert DeviceFieldValue(23, 3, 42) in values
|
||||
assert DeviceFieldValue(23, 4, 3578) in values
|
||||
assert DeviceFieldValue(23, 27, "Edge 1030 Plus") in values
|
||||
|
||||
data = output.read_bytes()
|
||||
assert struct.unpack_from("<H", data, 12)[0] == fit_crc(data[:12])
|
||||
assert struct.unpack_from("<H", data, len(data) - 2)[0] == fit_crc(data[:-2])
|
||||
|
||||
|
||||
def test_convert_fit_device_rejects_bad_crc(tmp_path: Path):
|
||||
source = tmp_path / "bad.fit"
|
||||
output = tmp_path / "output.fit"
|
||||
data = bytearray(_minimal_activity_fit())
|
||||
data[-1] ^= 0xFF
|
||||
source.write_bytes(data)
|
||||
|
||||
with pytest.raises(FitFormatError):
|
||||
convert_fit_device(source, output)
|
||||
|
||||
|
||||
def _minimal_activity_fit() -> bytes:
|
||||
data = bytearray()
|
||||
|
||||
data.extend(
|
||||
_definition(
|
||||
local=0,
|
||||
global_message=0,
|
||||
fields=[
|
||||
(0, 1, 0x00),
|
||||
(1, 2, 0x84),
|
||||
(2, 2, 0x84),
|
||||
(3, 4, 0x8C),
|
||||
(8, 20, 0x07),
|
||||
],
|
||||
)
|
||||
)
|
||||
data.extend(b"\x00")
|
||||
data.extend(struct.pack("<BHHI", 4, 32, 40, 999))
|
||||
data.extend(_fit_string("MyWhoosh", 20))
|
||||
|
||||
data.extend(
|
||||
_definition(
|
||||
local=1,
|
||||
global_message=23,
|
||||
fields=[
|
||||
(2, 2, 0x84),
|
||||
(3, 4, 0x8C),
|
||||
(4, 2, 0x84),
|
||||
(27, 20, 0x07),
|
||||
],
|
||||
)
|
||||
)
|
||||
data.extend(b"\x01")
|
||||
data.extend(struct.pack("<HIH", 32, 999, 40))
|
||||
data.extend(_fit_string("Trainer", 20))
|
||||
|
||||
header = bytearray(14)
|
||||
header[0] = 14
|
||||
header[1] = 0x10
|
||||
struct.pack_into("<H", header, 2, 0x08)
|
||||
struct.pack_into("<I", header, 4, len(data))
|
||||
header[8:12] = b".FIT"
|
||||
struct.pack_into("<H", header, 12, fit_crc(header[:12]))
|
||||
|
||||
fit_file = header + data + b"\x00\x00"
|
||||
struct.pack_into("<H", fit_file, len(fit_file) - 2, fit_crc(fit_file[:-2]))
|
||||
return bytes(fit_file)
|
||||
|
||||
|
||||
def _definition(
|
||||
local: int, global_message: int, fields: list[tuple[int, int, int]]
|
||||
) -> bytes:
|
||||
result = bytearray()
|
||||
result.append(0x40 | local)
|
||||
result.append(0)
|
||||
result.append(0)
|
||||
result.extend(struct.pack("<H", global_message))
|
||||
result.append(len(fields))
|
||||
for field_num, size, base_type in fields:
|
||||
result.extend(bytes([field_num, size, base_type]))
|
||||
return bytes(result)
|
||||
|
||||
|
||||
def _fit_string(value: str, size: int) -> bytes:
|
||||
encoded = value.encode("utf-8")[: size - 1] + b"\x00"
|
||||
return encoded + b"\x00" * (size - len(encoded))
|
||||
Reference in New Issue
Block a user