Skip to content

Commit 408e250

Browse files
authored
test(e2e): add MQTT bridge E2E tests (#336)
Implements comprehensive E2E tests for mqtt-bridge functionality with emulated devices. Test Coverage (7 passing, 1 skipped): - Single device: startup, data publishing, value validation - Multiple devices: skipped for separate PR (transport layer fix needed) - Error handling: graceful degradation when device disconnects - Reconnection: automatic recovery with data freshness validation Performance Optimizations: - Device polling: 500ms → 100ms (5x faster) - Test assertions: 100ms → 10ms polling (10x faster) - wait_for API now accepts seconds instead of deciseconds Key Improvements: - JSON validation added to prevent race conditions - Reconnection test fixed with subscriber restart logic - Startup functions refactored to reduce duplication - Uses official CLI directly (no wrapper scripts) Closes #237
1 parent d000b65 commit 408e250

5 files changed

Lines changed: 702 additions & 49 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"mqtt": {
3+
"url": "mqtt://localhost:1883",
4+
"clientId": "ya-modbus-test-bridge-multi"
5+
},
6+
"devices": [
7+
{
8+
"deviceId": "xymd1-1",
9+
"driver": "@ya-modbus/driver-xymd1",
10+
"connection": {
11+
"type": "rtu",
12+
"port": "/tmp/ttyV3",
13+
"baudRate": 9600,
14+
"parity": "even",
15+
"dataBits": 8,
16+
"stopBits": 1,
17+
"slaveId": 1
18+
},
19+
"polling": {
20+
"interval": 100
21+
}
22+
},
23+
{
24+
"deviceId": "ex9em-2",
25+
"driver": "@ya-modbus/driver-ex9em",
26+
"connection": {
27+
"type": "rtu",
28+
"port": "/tmp/ttyV3",
29+
"baudRate": 9600,
30+
"parity": "even",
31+
"dataBits": 8,
32+
"stopBits": 1,
33+
"slaveId": 2
34+
},
35+
"polling": {
36+
"interval": 100
37+
}
38+
}
39+
]
40+
}

tests/e2e/fixtures/bridge-config.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@
55
},
66
"devices": [
77
{
8-
"id": "ex9em-1",
8+
"deviceId": "ex9em-1",
99
"driver": "@ya-modbus/driver-ex9em",
1010
"connection": {
1111
"type": "rtu",
1212
"port": "/tmp/ttyV1",
1313
"baudRate": 9600,
1414
"parity": "even",
1515
"dataBits": 8,
16-
"stopBits": 1
16+
"stopBits": 1,
17+
"slaveId": 1
1718
},
18-
"slaveId": 1,
1919
"polling": {
20-
"interval": 2000
20+
"interval": 100
2121
}
2222
}
2323
]
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"description": "Single EX9EM energy meter device on port 1 with alternative register values",
3+
"transport": {
4+
"type": "rtu",
5+
"port": "/tmp/ttyV0",
6+
"baudRate": 9600,
7+
"parity": "even",
8+
"lock": false
9+
},
10+
"devices": [
11+
{
12+
"slaveId": 1,
13+
"deviceType": "EX9EM",
14+
"registers": {
15+
"holding": {
16+
"0": 2400,
17+
"1": 520,
18+
"2": 500,
19+
"3": 1196,
20+
"4": 100,
21+
"5": 1200,
22+
"6": 990,
23+
"7": 1234,
24+
"8": 5678,
25+
"9": 9012,
26+
"10": 3456,
27+
"42": 4,
28+
"43": 1
29+
}
30+
},
31+
"timing": {
32+
"pollingInterval": 10,
33+
"commandDetectionDelay": [2, 5],
34+
"processingDelay": [1, 3],
35+
"perRegisterDelay": 0.1
36+
}
37+
}
38+
]
39+
}
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
#!/usr/bin/env bats
2+
#
3+
# MQTT Bridge E2E Tests
4+
#
5+
# These tests verify mqtt-bridge functionality with real emulated devices.
6+
# Tests cover single device, multiple devices, and error handling scenarios.
7+
#
8+
9+
load helpers
10+
11+
setup() {
12+
EMULATOR_PID=""
13+
BRIDGE_PID=""
14+
MQTT_SUB_PID=""
15+
MQTT_MESSAGES_FILE=$(mktemp /tmp/mqtt-messages-XXXXXX.txt)
16+
clean_test_artifacts
17+
}
18+
19+
teardown() {
20+
# Stop MQTT subscriber first to avoid broken pipe errors
21+
if [ -n "$MQTT_SUB_PID" ]; then
22+
stop_mqtt_subscriber "$MQTT_SUB_PID"
23+
fi
24+
25+
# Stop bridge before emulator to ensure clean shutdown
26+
if [ -n "$BRIDGE_PID" ]; then
27+
stop_mqtt_bridge "$BRIDGE_PID"
28+
fi
29+
30+
if [ -n "$EMULATOR_PID" ]; then
31+
stop_test_emulator "$EMULATOR_PID"
32+
fi
33+
34+
# Clean up temp files
35+
rm -f "$MQTT_MESSAGES_FILE"
36+
clean_test_artifacts
37+
}
38+
39+
@test "mqtt-bridge binary exists" {
40+
[ -f "../../packages/mqtt-bridge/dist/esm/bin/ya-modbus-bridge.js" ]
41+
}
42+
43+
@test "mqtt-bridge config files exist" {
44+
[ -f "fixtures/bridge-config.json" ]
45+
[ -f "fixtures/bridge-config-multi-device.json" ]
46+
}
47+
48+
@test "mqtt-bridge starts with single device" {
49+
# Start emulator with ex9em device
50+
run start_test_emulator "fixtures/emulators/port1-single-device.json"
51+
assert_success
52+
EMULATOR_PID="$output"
53+
54+
# Start MQTT subscriber
55+
run start_mqtt_subscriber "modbus/#" "$MQTT_MESSAGES_FILE"
56+
assert_success
57+
MQTT_SUB_PID="$output"
58+
59+
# Start bridge
60+
run start_mqtt_bridge "fixtures/bridge-config.json"
61+
assert_success
62+
BRIDGE_PID="$output"
63+
64+
# Verify bridge is running and healthy
65+
run assert_bridge_running "$BRIDGE_PID"
66+
assert_success
67+
}
68+
69+
@test "mqtt-bridge publishes data from single device" {
70+
# Start emulator with ex9em device
71+
run start_test_emulator "fixtures/emulators/port1-single-device.json"
72+
assert_success
73+
EMULATOR_PID="$output"
74+
75+
# Start MQTT subscriber
76+
run start_mqtt_subscriber "modbus/#" "$MQTT_MESSAGES_FILE"
77+
assert_success
78+
MQTT_SUB_PID="$output"
79+
80+
# Start bridge
81+
run start_mqtt_bridge "fixtures/bridge-config.json"
82+
assert_success
83+
BRIDGE_PID="$output"
84+
85+
# Wait for bridge to poll and publish data
86+
run wait_for 5 'assert_file_contains "$MQTT_MESSAGES_FILE" "modbus/ex9em-1/data"'
87+
assert_success
88+
89+
# Verify message structure using JSON validation
90+
run assert_json_field "$MQTT_MESSAGES_FILE" "modbus/ex9em-1/data" ".deviceId" '"ex9em-1"'
91+
assert_success
92+
run assert_json_field "$MQTT_MESSAGES_FILE" "modbus/ex9em-1/data" ".timestamp" "number"
93+
assert_success
94+
run assert_json_field "$MQTT_MESSAGES_FILE" "modbus/ex9em-1/data" ".data" "object"
95+
assert_success
96+
}
97+
98+
@test "mqtt-bridge publishes data matching emulator configuration" {
99+
# Start emulator with ex9em device (voltage_l1=2300 from config)
100+
run start_test_emulator "fixtures/emulators/port1-single-device.json"
101+
assert_success
102+
EMULATOR_PID="$output"
103+
104+
# Start MQTT subscriber
105+
run start_mqtt_subscriber "modbus/#" "$MQTT_MESSAGES_FILE"
106+
assert_success
107+
MQTT_SUB_PID="$output"
108+
109+
# Start bridge
110+
run start_mqtt_bridge "fixtures/bridge-config.json"
111+
assert_success
112+
BRIDGE_PID="$output"
113+
114+
# Wait for bridge to poll and publish data with expected voltage value
115+
# The ex9em driver should decode register 0 (value 2300) as voltage=230.0V
116+
run wait_for 5 'assert_json_field "$MQTT_MESSAGES_FILE" "modbus/ex9em-1/data" ".data.voltage" 230'
117+
assert_success
118+
}
119+
120+
@test "mqtt-bridge handles multiple devices on same bus" {
121+
skip "Multi-device support needs transport layer fix - tracked in separate PR"
122+
123+
# Start emulator with xymd1 (slave 1) and ex9em (slave 2)
124+
run start_test_emulator "fixtures/emulators/port2-multi-device.json"
125+
assert_success
126+
EMULATOR_PID="$output"
127+
128+
# Start MQTT subscriber
129+
run start_mqtt_subscriber "modbus/#" "$MQTT_MESSAGES_FILE"
130+
assert_success
131+
MQTT_SUB_PID="$output"
132+
133+
# Start bridge
134+
run start_mqtt_bridge "fixtures/bridge-config-multi-device.json"
135+
assert_success
136+
BRIDGE_PID="$output"
137+
138+
# Wait for bridge to poll both devices (longer timeout for multi-device)
139+
run wait_for 10 'assert_file_contains "$MQTT_MESSAGES_FILE" "modbus/xymd1-1/data"'
140+
assert_success
141+
run wait_for 10 'assert_file_contains "$MQTT_MESSAGES_FILE" "modbus/ex9em-2/data"'
142+
assert_success
143+
144+
# Verify each device publishes to its own correct topic with valid JSON structure
145+
run assert_json_field "$MQTT_MESSAGES_FILE" "modbus/xymd1-1/data" ".deviceId" '"xymd1-1"'
146+
assert_success
147+
run assert_json_field "$MQTT_MESSAGES_FILE" "modbus/xymd1-1/data" ".data" "object"
148+
assert_success
149+
run assert_json_field "$MQTT_MESSAGES_FILE" "modbus/ex9em-2/data" ".deviceId" '"ex9em-2"'
150+
assert_success
151+
run assert_json_field "$MQTT_MESSAGES_FILE" "modbus/ex9em-2/data" ".data" "object"
152+
assert_success
153+
}
154+
155+
@test "mqtt-bridge continues running when device disconnects" {
156+
# Start emulator with ex9em device
157+
run start_test_emulator "fixtures/emulators/port1-single-device.json"
158+
assert_success
159+
EMULATOR_PID="$output"
160+
161+
# Start MQTT subscriber
162+
run start_mqtt_subscriber "modbus/#" "$MQTT_MESSAGES_FILE"
163+
assert_success
164+
MQTT_SUB_PID="$output"
165+
166+
# Start bridge
167+
run start_mqtt_bridge "fixtures/bridge-config.json"
168+
assert_success
169+
BRIDGE_PID="$output"
170+
171+
# Wait for initial data to be published
172+
run wait_for 5 'assert_file_contains "$MQTT_MESSAGES_FILE" "modbus/ex9em-1/data"'
173+
assert_success
174+
175+
# Stop emulator to simulate disconnection
176+
stop_test_emulator "$EMULATOR_PID"
177+
EMULATOR_PID=""
178+
179+
# Wait for bridge to attempt polling and detect disconnection
180+
# Bridge should log polling errors without crashing
181+
run wait_for 5 'assert_bridge_log_contains "$BRIDGE_PID" "Polling error for device"'
182+
assert_success
183+
184+
# Verify bridge is still running (does not crash on disconnection)
185+
run assert_bridge_running "$BRIDGE_PID"
186+
assert_success
187+
}
188+
189+
@test "mqtt-bridge reconnects after device comes back online" {
190+
# Start emulator with ex9em device (voltage register 0 = 2300)
191+
run start_test_emulator "fixtures/emulators/port1-single-device.json"
192+
assert_success
193+
EMULATOR_PID="$output"
194+
195+
# Start MQTT subscriber
196+
run start_mqtt_subscriber "modbus/#" "$MQTT_MESSAGES_FILE"
197+
assert_success
198+
MQTT_SUB_PID="$output"
199+
200+
# Start bridge
201+
run start_mqtt_bridge "fixtures/bridge-config.json"
202+
assert_success
203+
BRIDGE_PID="$output"
204+
205+
# Wait for initial data with voltage=230V using JSON validation
206+
run wait_for 5 'assert_json_field "$MQTT_MESSAGES_FILE" "modbus/ex9em-1/data" ".data.voltage" 230'
207+
assert_success
208+
209+
# Stop MQTT subscriber before clearing file to avoid concurrent write issues
210+
stop_mqtt_subscriber "$MQTT_SUB_PID"
211+
MQTT_SUB_PID=""
212+
213+
# Clear MQTT messages to track reconnection with new data
214+
> "$MQTT_MESSAGES_FILE"
215+
216+
# Restart MQTT subscriber with clean file
217+
run start_mqtt_subscriber "modbus/#" "$MQTT_MESSAGES_FILE"
218+
assert_success
219+
MQTT_SUB_PID="$output"
220+
221+
# Stop emulator
222+
stop_test_emulator "$EMULATOR_PID"
223+
EMULATOR_PID=""
224+
225+
# Wait for bridge to detect disconnection
226+
run wait_for 5 'assert_bridge_log_contains "$BRIDGE_PID" "Polling error for device"'
227+
assert_success
228+
229+
# Restart emulator with different register values (voltage register 0 = 2400)
230+
run start_test_emulator "fixtures/emulators/port1-single-device-alt.json"
231+
assert_success
232+
EMULATOR_PID="$output"
233+
234+
# Wait for bridge to reconnect and publish new data with voltage=240V (longer timeout for reconnection)
235+
run wait_for 15 'assert_json_field "$MQTT_MESSAGES_FILE" "modbus/ex9em-1/data" ".data.voltage" 240'
236+
assert_success
237+
238+
# Verify bridge resumed publishing with valid JSON structure
239+
run assert_json_field "$MQTT_MESSAGES_FILE" "modbus/ex9em-1/data" ".deviceId" '"ex9em-1"'
240+
assert_success
241+
run assert_json_field "$MQTT_MESSAGES_FILE" "modbus/ex9em-1/data" ".timestamp" "number"
242+
assert_success
243+
}

0 commit comments

Comments
 (0)