test: record protocol 2 fixtures from a device

hello, the schema and a whole sync as the device sent them, recorded with pytest test/e2e --record. Remote addresses, rolling codes, names, the server id, hostname and SSID are replaced with fake values while recording, since with an address and its rolling code anyone could forge frames for a real installation's shades.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-24 10:56:54 +02:00
parent 10521cf78d
commit d869bb53d5
4 changed files with 1593 additions and 2 deletions

View file

@ -100,8 +100,36 @@ def test_resync_sends_it_all_again(connect, schema):
assert "T" not in f["d"]
# What a recording must not keep from a real installation: the remote
# addresses and rolling codes would let anyone forge frames for its shades.
SCRUBBED = {"remoteAddress", "lastRollingCode", "sourceAddress", "startingAddress", "repeaters",
"name", "serverId", "hostname", "ssid"}
def scrub(value, schema, counter):
"""Fake values of the same kind in place of anything identifying."""
if isinstance(value, list):
return [scrub(v, schema, counter) for v in value]
if not isinstance(value, dict):
return value
out = {}
for key, v in value.items():
name = schema.fields.get(key, (key,))[0]
if name in SCRUBBED:
counter[0] += 1
if isinstance(v, list):
v = [0x100000 + counter[0] * 16 + i for i in range(len(v))]
elif isinstance(v, str):
v = "%s %u" % (name, counter[0])
else:
v = 0x100000 + counter[0]
out[key] = scrub(v, schema, counter)
return out
def test_record_fixtures(connect, recording):
"""With --record, saves hello and a whole first sync for the other suites."""
"""With --record, saves hello and a whole first sync for the other suites,
with the installation's identifying values replaced."""
if not recording:
return
c = connect()
@ -111,8 +139,12 @@ def test_record_fixtures(connect, recording):
hello = json.loads(c.raw[0])
with open(os.path.join(FIXTURES, "hello.json"), "w", encoding="utf-8") as f:
json.dump(hello, f, indent=1)
counter = [0]
frames = [json.loads(r) for r in c.raw[1:]]
for frame in frames:
frame["d"] = scrub(frame["d"], schema, counter)
with open(os.path.join(FIXTURES, "sync.json"), "w", encoding="utf-8") as f:
json.dump([json.loads(r) for r in c.raw[1:]], f, indent=1)
json.dump(frames, f, indent=1)
expanded = {
"hash": hello["d"]["sh"],
"types": {str(k): v for k, v in sorted(schema.types.items())},

8
test/fixtures/proto2/hello.json vendored Normal file
View file

@ -0,0 +1,8 @@
{
"v": 2,
"t": 1,
"d": {
"fw": "v3.0.14",
"sh": "8198c564"
}
}

510
test/fixtures/proto2/schema.json vendored Normal file
View file

@ -0,0 +1,510 @@
{
"hash": "8198c564",
"types": {
"1": "hello",
"2": "reply",
"3": "next",
"4": "sync",
"5": "syncEnd",
"6": "subscribe",
"7": "unsubscribe",
"128": "shadeState",
"129": "shade",
"130": "shadeRemoved",
"131": "group",
"132": "groupState",
"133": "groupRemoved",
"134": "room",
"135": "roomRemoved",
"136": "roomOrder",
"137": "shadeOrder",
"138": "groupOrder",
"139": "shadeCommand",
"140": "memory",
"141": "wifi",
"142": "ethernet",
"143": "frame",
"144": "frequencyScan",
"145": "repeaters"
},
"fields": {
"D": {
"name": "downTime",
"kind": "i",
"default": 0
},
"E": {
"name": "errors",
"kind": "l",
"default": 0
},
"F": {
"name": "fields",
"kind": "l",
"default": 0
},
"G": {
"name": "groups",
"kind": "l",
"default": 0
},
"M": {
"name": "myTiltPos",
"kind": "i",
"default": -1
},
"Q": {
"name": "feeds",
"kind": "l",
"default": 0
},
"R": {
"name": "rooms",
"kind": "l",
"default": 0
},
"S": {
"name": "shades",
"kind": "l",
"default": 0
},
"T": {
"name": "types",
"kind": "l",
"default": 0
},
"U": {
"name": "upTime",
"kind": "i",
"default": 0
},
"X": {
"name": "repeaters",
"kind": "l",
"default": 0
},
"Y": {
"name": "tiltTime",
"kind": "i",
"default": 0
},
"Z": {
"name": "status",
"kind": "o",
"default": 0
},
"a": {
"name": "remoteAddress",
"kind": "i",
"default": 0
},
"bl": {
"name": "bitLength",
"kind": "i",
"default": 56
},
"bt": {
"name": "bits",
"kind": "i",
"default": 0
},
"c": {
"name": "lastRollingCode",
"kind": "i",
"default": 0
},
"ch": {
"name": "channel",
"kind": "i",
"default": 0
},
"cm": {
"name": "command",
"kind": "s",
"default": 0
},
"co": {
"name": "connected",
"kind": "b",
"default": 0
},
"cp": {
"name": "chipModel",
"kind": "s",
"default": 0
},
"ct": {
"name": "connType",
"kind": "s",
"default": 0
},
"d": {
"name": "direction",
"kind": "i",
"default": 0
},
"e": {
"name": "code",
"kind": "i",
"default": 0
},
"ek": {
"name": "encKey",
"kind": "i",
"default": 0
},
"f": {
"name": "flags",
"kind": "i",
"default": 0
},
"fc": {
"name": "flipCommands",
"kind": "b",
"default": 0
},
"fd": {
"name": "fullDuplex",
"kind": "b",
"default": 0
},
"fe": {
"name": "feed",
"kind": "i",
"default": 0
},
"fp": {
"name": "flipPosition",
"kind": "b",
"default": 0
},
"fq": {
"name": "frequency",
"kind": "n",
"default": 0
},
"fr": {
"name": "freeHeap",
"kind": "i",
"default": 0
},
"fw": {
"name": "fw",
"kind": "s",
"default": 0
},
"g": {
"name": "groupId",
"kind": "i",
"default": 0
},
"gd": {
"name": "gpioDown",
"kind": "i",
"default": 0
},
"gl": {
"name": "gpioLLTrigger",
"kind": "b",
"default": 0
},
"gm": {
"name": "gpioMy",
"kind": "i",
"default": 0
},
"gu": {
"name": "gpioUp",
"kind": "i",
"default": 0
},
"h": {
"name": "header",
"kind": "o",
"default": 0
},
"hn": {
"name": "hostname",
"kind": "s",
"default": 0
},
"hs": {
"name": "hwsync",
"kind": "i",
"default": 0
},
"i": {
"name": "paired",
"kind": "b",
"default": 0
},
"ig": {
"name": "inGroup",
"kind": "b",
"default": 0
},
"is": {
"name": "ids",
"kind": "l",
"default": 0
},
"k": {
"name": "ok",
"kind": "b",
"default": 0
},
"li": {
"name": "light",
"kind": "b",
"default": 0
},
"lk": {
"name": "linkedRemotes",
"kind": "l",
"default": 0
},
"lp": {
"name": "loopRate",
"kind": "i",
"default": 0
},
"ls": {
"name": "linkedShades",
"kind": "l",
"default": 0
},
"lx": {
"name": "loopMaxUs",
"kind": "i",
"default": 0
},
"m": {
"name": "myPos",
"kind": "i",
"default": -1
},
"md": {
"name": "model",
"kind": "s",
"default": 0
},
"mn": {
"name": "minHeap",
"kind": "i",
"default": 0
},
"mx": {
"name": "maxHeap",
"kind": "i",
"default": 0
},
"n": {
"name": "name",
"kind": "s",
"default": 0
},
"o": {
"name": "sortOrder",
"kind": "i",
"default": 0
},
"p": {
"name": "position",
"kind": "i",
"default": 0
},
"pr": {
"name": "proto",
"kind": "i",
"default": 0
},
"pu": {
"name": "pulses",
"kind": "l",
"default": 0
},
"q": {
"name": "tiltPosition",
"kind": "i",
"default": 0
},
"r": {
"name": "roomId",
"kind": "i",
"default": 0
},
"rp": {
"name": "repeats",
"kind": "i",
"default": 1
},
"rs": {
"name": "rssi",
"kind": "i",
"default": 0
},
"rw": {
"name": "rawCmd",
"kind": "i",
"default": 0
},
"s": {
"name": "shadeId",
"kind": "i",
"default": 0
},
"sa": {
"name": "startingAddress",
"kind": "i",
"default": 0
},
"sc": {
"name": "scanning",
"kind": "b",
"default": 0
},
"sd": {
"name": "ssid",
"kind": "s",
"default": 0
},
"sg": {
"name": "strength",
"kind": "i",
"default": 0
},
"sh": {
"name": "schemaHash",
"kind": "s",
"default": 0
},
"sm": {
"name": "simMy",
"kind": "b",
"default": 0
},
"so": {
"name": "source",
"kind": "s",
"default": 0
},
"sp": {
"name": "speed",
"kind": "i",
"default": 0
},
"sr": {
"name": "sourceAddress",
"kind": "i",
"default": 0
},
"ss": {
"name": "sunSensor",
"kind": "b",
"default": 0
},
"st": {
"name": "shadeType",
"kind": "i",
"default": 0
},
"sv": {
"name": "serverId",
"kind": "s",
"default": 0
},
"t": {
"name": "target",
"kind": "i",
"default": 0
},
"tf": {
"name": "testFreq",
"kind": "n",
"default": 0
},
"to": {
"name": "totalHeap",
"kind": "i",
"default": 0
},
"tr": {
"name": "testRssi",
"kind": "i",
"default": 0
},
"u": {
"name": "tiltTarget",
"kind": "i",
"default": 0
},
"up": {
"name": "uptime",
"kind": "i",
"default": 0
},
"va": {
"name": "valid",
"kind": "b",
"default": 0
},
"w": {
"name": "tiltDirection",
"kind": "i",
"default": 0
},
"x": {
"name": "desc",
"kind": "s",
"default": 0
},
"xg": {
"name": "maxGroups",
"kind": "i",
"default": 0
},
"xl": {
"name": "maxLinkedRemotes",
"kind": "i",
"default": 0
},
"xm": {
"name": "maxGroupedShades",
"kind": "i",
"default": 0
},
"xr": {
"name": "maxRooms",
"kind": "i",
"default": 0
},
"xs": {
"name": "maxShades",
"kind": "i",
"default": 0
},
"y": {
"name": "tiltType",
"kind": "i",
"default": 0
},
"z": {
"name": "stepSize",
"kind": "i",
"default": 100
}
},
"errors": {
"1": "badRequest",
"2": "unknownOp",
"3": "unauthorized",
"4": "notFound",
"5": "busy",
"6": "radioUnavailable",
"7": "internal"
},
"feeds": {
"1": "frames",
"2": "frequencyScan"
}
}

1041
test/fixtures/proto2/sync.json vendored Normal file

File diff suppressed because it is too large Load diff