@xiboplayer/xmr 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +170 -0
- package/docs/README.md +360 -0
- package/docs/XMR_COMMANDS.md +303 -0
- package/docs/XMR_TESTING.md +509 -0
- package/package.json +36 -0
- package/src/index.js +2 -0
- package/src/test-utils.js +132 -0
- package/src/xmr-wrapper.js +359 -0
- package/src/xmr-wrapper.test.js +737 -0
- package/vitest.config.js +35 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
# XMR Testing Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to test XMR (Xibo Message Relay) integration in the Xibo player.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Unit Tests](#unit-tests)
|
|
8
|
+
2. [Integration Testing](#integration-testing)
|
|
9
|
+
3. [Manual Testing](#manual-testing)
|
|
10
|
+
4. [Real XMR Server Testing](#real-xmr-server-testing)
|
|
11
|
+
5. [Troubleshooting](#troubleshooting)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Unit Tests
|
|
16
|
+
|
|
17
|
+
The XMR package includes comprehensive unit tests covering all functionality.
|
|
18
|
+
|
|
19
|
+
### Running Tests
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# From xmr package directory
|
|
23
|
+
cd packages/xmr
|
|
24
|
+
npm test
|
|
25
|
+
|
|
26
|
+
# With coverage report
|
|
27
|
+
npm run test:coverage
|
|
28
|
+
|
|
29
|
+
# Watch mode (auto-run on file changes)
|
|
30
|
+
npm run test:watch
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Test Coverage
|
|
34
|
+
|
|
35
|
+
Current coverage: **48 test cases**
|
|
36
|
+
|
|
37
|
+
Categories:
|
|
38
|
+
- ✅ **Constructor** (2 tests) - Initialization and state
|
|
39
|
+
- ✅ **Connection lifecycle** (8 tests) - Start, stop, reconnect
|
|
40
|
+
- ✅ **Connection events** (4 tests) - connected, disconnected, error
|
|
41
|
+
- ✅ **CMS commands** (14 tests) - All 7 commands + error handling
|
|
42
|
+
- ✅ **Reconnection logic** (4 tests) - Exponential backoff, max attempts
|
|
43
|
+
- ✅ **stop() method** (4 tests) - Cleanup, error handling
|
|
44
|
+
- ✅ **isConnected()** (3 tests) - Connection state queries
|
|
45
|
+
- ✅ **send()** (4 tests) - Sending messages to CMS
|
|
46
|
+
- ✅ **Edge cases** (3 tests) - Simultaneous commands, rapid cycles
|
|
47
|
+
- ✅ **Memory management** (2 tests) - Timer cleanup, garbage collection
|
|
48
|
+
|
|
49
|
+
### Test Structure
|
|
50
|
+
|
|
51
|
+
Tests use Vitest with:
|
|
52
|
+
- **Mocking**: `vi.mock()` for @xibosignage/xibo-communication-framework
|
|
53
|
+
- **Fake timers**: `vi.useFakeTimers()` for reconnection testing
|
|
54
|
+
- **Async handling**: `vi.runAllTimersAsync()` for event handlers
|
|
55
|
+
|
|
56
|
+
Example test:
|
|
57
|
+
```javascript
|
|
58
|
+
it('should handle collectNow command', async () => {
|
|
59
|
+
await wrapper.start('wss://test.xmr.com', 'cms-key-123');
|
|
60
|
+
const xmr = wrapper.xmr;
|
|
61
|
+
|
|
62
|
+
xmr.simulateCommand('collectNow');
|
|
63
|
+
await vi.runAllTimersAsync();
|
|
64
|
+
|
|
65
|
+
expect(mockPlayer.collect).toHaveBeenCalled();
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Integration Testing
|
|
72
|
+
|
|
73
|
+
### Mock XMR Server
|
|
74
|
+
|
|
75
|
+
For integration testing without a real CMS, create a mock XMR server:
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
// test-xmr-server.js
|
|
79
|
+
import { WebSocketServer } from 'ws';
|
|
80
|
+
|
|
81
|
+
const wss = new WebSocketServer({ port: 9505 });
|
|
82
|
+
|
|
83
|
+
wss.on('connection', (ws) => {
|
|
84
|
+
console.log('Player connected');
|
|
85
|
+
|
|
86
|
+
// Send test command after 2 seconds
|
|
87
|
+
setTimeout(() => {
|
|
88
|
+
ws.send(JSON.stringify({
|
|
89
|
+
action: 'collectNow'
|
|
90
|
+
}));
|
|
91
|
+
}, 2000);
|
|
92
|
+
|
|
93
|
+
ws.on('message', (data) => {
|
|
94
|
+
console.log('Received from player:', data.toString());
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
console.log('Mock XMR server listening on ws://localhost:9505');
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Run:
|
|
102
|
+
```bash
|
|
103
|
+
node test-xmr-server.js
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Configure player to connect:
|
|
107
|
+
```javascript
|
|
108
|
+
await xmrWrapper.start('ws://localhost:9505', 'test-key');
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Testing Commands
|
|
112
|
+
|
|
113
|
+
Send commands from mock server:
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
// collectNow
|
|
117
|
+
ws.send(JSON.stringify({ action: 'collectNow' }));
|
|
118
|
+
|
|
119
|
+
// screenShot
|
|
120
|
+
ws.send(JSON.stringify({ action: 'screenShot' }));
|
|
121
|
+
|
|
122
|
+
// changeLayout
|
|
123
|
+
ws.send(JSON.stringify({
|
|
124
|
+
action: 'changeLayout',
|
|
125
|
+
layoutId: 'layout-123'
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
// criteriaUpdate
|
|
129
|
+
ws.send(JSON.stringify({
|
|
130
|
+
action: 'criteriaUpdate',
|
|
131
|
+
data: { displayId: '123', criteria: 'new-criteria' }
|
|
132
|
+
}));
|
|
133
|
+
|
|
134
|
+
// currentGeoLocation
|
|
135
|
+
ws.send(JSON.stringify({
|
|
136
|
+
action: 'currentGeoLocation',
|
|
137
|
+
data: { latitude: 40.7128, longitude: -74.0060 }
|
|
138
|
+
}));
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Manual Testing
|
|
144
|
+
|
|
145
|
+
### Prerequisites
|
|
146
|
+
|
|
147
|
+
1. **XMR-enabled CMS**: Xibo CMS 2.3+ with XMR configured
|
|
148
|
+
2. **Player**: Xibo PWA/Linux player with XMR package
|
|
149
|
+
3. **Network**: Player can reach CMS XMR endpoint (typically port 9505)
|
|
150
|
+
|
|
151
|
+
### Testing Steps
|
|
152
|
+
|
|
153
|
+
#### 1. Enable XMR in CMS
|
|
154
|
+
|
|
155
|
+
1. Go to **Administration** → **Settings** → **Displays**
|
|
156
|
+
2. Set **Enable XMR** to **Yes**
|
|
157
|
+
3. Configure **XMR Address** (e.g., `ws://your-cms.com:9505`)
|
|
158
|
+
4. Save settings
|
|
159
|
+
|
|
160
|
+
#### 2. Configure Player
|
|
161
|
+
|
|
162
|
+
Player should receive XMR settings automatically from `registerDisplay`:
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
// In registerDisplay response
|
|
166
|
+
{
|
|
167
|
+
settings: {
|
|
168
|
+
xmrWebSocketAddress: 'wss://cms.example.com:9505',
|
|
169
|
+
xmrCmsKey: 'your-cms-key-here'
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
#### 3. Verify Connection
|
|
175
|
+
|
|
176
|
+
Check player logs:
|
|
177
|
+
```
|
|
178
|
+
[XMR] Initializing connection to: wss://cms.example.com:9505
|
|
179
|
+
[XMR] WebSocket connected
|
|
180
|
+
[XMR] Connected successfully
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Check CMS logs (XMR server):
|
|
184
|
+
```
|
|
185
|
+
Player connected: player-hw-key-123
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### 4. Test Commands
|
|
189
|
+
|
|
190
|
+
From CMS display management page:
|
|
191
|
+
|
|
192
|
+
**Test collectNow:**
|
|
193
|
+
1. Click display name
|
|
194
|
+
2. Click **Send Command** → **Collect Now**
|
|
195
|
+
3. Verify player logs:
|
|
196
|
+
```
|
|
197
|
+
[XMR] Received collectNow command from CMS
|
|
198
|
+
[XMR] collectNow completed successfully
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Test screenShot:**
|
|
202
|
+
1. Click **Send Command** → **Request Screenshot**
|
|
203
|
+
2. Verify screenshot appears in display's **Screenshots** tab
|
|
204
|
+
|
|
205
|
+
**Test changeLayout:**
|
|
206
|
+
1. Click **Send Command** → **Change Layout**
|
|
207
|
+
2. Select layout
|
|
208
|
+
3. Verify player switches immediately
|
|
209
|
+
|
|
210
|
+
#### 5. Test Reconnection
|
|
211
|
+
|
|
212
|
+
**Simulate connection loss:**
|
|
213
|
+
1. Stop XMR server on CMS
|
|
214
|
+
2. Verify player logs:
|
|
215
|
+
```
|
|
216
|
+
[XMR] WebSocket disconnected
|
|
217
|
+
[XMR] Connection lost, scheduling reconnection...
|
|
218
|
+
[XMR] Scheduling reconnect attempt 1/10 in 5000ms
|
|
219
|
+
```
|
|
220
|
+
3. Restart XMR server
|
|
221
|
+
4. Verify player reconnects automatically
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Real XMR Server Testing
|
|
226
|
+
|
|
227
|
+
### Setup
|
|
228
|
+
|
|
229
|
+
1. **Install Xibo CMS** with Docker:
|
|
230
|
+
```bash
|
|
231
|
+
git clone https://github.com/xibosignage/xibo-docker
|
|
232
|
+
cd xibo-docker
|
|
233
|
+
docker-compose up -d
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
2. **Enable XMR** in CMS settings (see Manual Testing above)
|
|
237
|
+
|
|
238
|
+
3. **Configure player** with CMS credentials:
|
|
239
|
+
```javascript
|
|
240
|
+
const config = {
|
|
241
|
+
cmsAddress: 'http://localhost',
|
|
242
|
+
hardwareKey: 'test-player-123',
|
|
243
|
+
serverKey: 'your-server-key'
|
|
244
|
+
};
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Test Scenarios
|
|
248
|
+
|
|
249
|
+
#### Scenario 1: Basic Commands
|
|
250
|
+
|
|
251
|
+
1. Register player with CMS
|
|
252
|
+
2. Send collectNow via CMS display page
|
|
253
|
+
3. Verify XMDS collection triggered
|
|
254
|
+
4. Send screenShot
|
|
255
|
+
5. Verify screenshot uploaded
|
|
256
|
+
6. Send changeLayout
|
|
257
|
+
7. Verify layout changes
|
|
258
|
+
|
|
259
|
+
**Expected**: All commands execute successfully
|
|
260
|
+
|
|
261
|
+
#### Scenario 2: Network Interruption
|
|
262
|
+
|
|
263
|
+
1. Establish XMR connection
|
|
264
|
+
2. Block port 9505 with firewall:
|
|
265
|
+
```bash
|
|
266
|
+
sudo iptables -A OUTPUT -p tcp --dport 9505 -j DROP
|
|
267
|
+
```
|
|
268
|
+
3. Wait for reconnection attempts
|
|
269
|
+
4. Unblock port:
|
|
270
|
+
```bash
|
|
271
|
+
sudo iptables -D OUTPUT -p tcp --dport 9505 -j DROP
|
|
272
|
+
```
|
|
273
|
+
5. Verify automatic reconnection
|
|
274
|
+
|
|
275
|
+
**Expected**: Player reconnects within 50 seconds (max 10 attempts × 5s)
|
|
276
|
+
|
|
277
|
+
#### Scenario 3: Multiple Players
|
|
278
|
+
|
|
279
|
+
1. Start 5 players with different hardware keys
|
|
280
|
+
2. Send collectNow to all
|
|
281
|
+
3. Verify all receive command
|
|
282
|
+
4. Send changeLayout to specific player
|
|
283
|
+
5. Verify only that player changes layout
|
|
284
|
+
|
|
285
|
+
**Expected**: Commands routed to correct players
|
|
286
|
+
|
|
287
|
+
#### Scenario 4: High Frequency Commands
|
|
288
|
+
|
|
289
|
+
1. Send 10 collectNow commands in 10 seconds
|
|
290
|
+
2. Verify all execute without dropping
|
|
291
|
+
3. Monitor memory usage
|
|
292
|
+
|
|
293
|
+
**Expected**: No memory leaks, all commands processed
|
|
294
|
+
|
|
295
|
+
#### Scenario 5: CMS Upgrade
|
|
296
|
+
|
|
297
|
+
1. Establish XMR connection
|
|
298
|
+
2. Upgrade CMS (restart XMR server)
|
|
299
|
+
3. Verify player reconnects after upgrade
|
|
300
|
+
|
|
301
|
+
**Expected**: Automatic reconnection within 5-10 seconds
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Troubleshooting
|
|
306
|
+
|
|
307
|
+
### Connection Issues
|
|
308
|
+
|
|
309
|
+
**Problem**: XMR won't connect
|
|
310
|
+
|
|
311
|
+
**Checks**:
|
|
312
|
+
1. Verify XMR enabled in CMS settings
|
|
313
|
+
2. Check firewall allows port 9505
|
|
314
|
+
3. Verify `xmrWebSocketAddress` in registerDisplay response
|
|
315
|
+
4. Check player logs for errors
|
|
316
|
+
|
|
317
|
+
**Solution**:
|
|
318
|
+
```bash
|
|
319
|
+
# Test XMR connectivity
|
|
320
|
+
wscat -c wss://cms.example.com:9505
|
|
321
|
+
|
|
322
|
+
# Check XMR server status
|
|
323
|
+
docker logs xibo-xmr
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
### Commands Not Executing
|
|
329
|
+
|
|
330
|
+
**Problem**: collectNow sent but not executed
|
|
331
|
+
|
|
332
|
+
**Checks**:
|
|
333
|
+
1. Verify XMR connected (`wrapper.isConnected() === true`)
|
|
334
|
+
2. Check player logs for command reception
|
|
335
|
+
3. Verify player.collect() method exists
|
|
336
|
+
4. Check for errors in command handler
|
|
337
|
+
|
|
338
|
+
**Solution**:
|
|
339
|
+
```javascript
|
|
340
|
+
// Add debug logging
|
|
341
|
+
this.xmr.on('collectNow', async () => {
|
|
342
|
+
console.log('[XMR] collectNow handler triggered');
|
|
343
|
+
console.log('[XMR] player.collect:', typeof this.player.collect);
|
|
344
|
+
// ...
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
### Reconnection Loops
|
|
351
|
+
|
|
352
|
+
**Problem**: Player keeps reconnecting endlessly
|
|
353
|
+
|
|
354
|
+
**Checks**:
|
|
355
|
+
1. Verify XMR server is actually running
|
|
356
|
+
2. Check for auth errors (wrong cmsKey)
|
|
357
|
+
3. Monitor reconnectAttempts counter
|
|
358
|
+
|
|
359
|
+
**Solution**:
|
|
360
|
+
```javascript
|
|
361
|
+
// Check reconnect state
|
|
362
|
+
console.log('Reconnect attempts:', wrapper.reconnectAttempts);
|
|
363
|
+
console.log('Max attempts:', wrapper.maxReconnectAttempts);
|
|
364
|
+
|
|
365
|
+
// Manually stop reconnecting
|
|
366
|
+
await wrapper.stop();
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
### Memory Leaks
|
|
372
|
+
|
|
373
|
+
**Problem**: Memory usage grows over time
|
|
374
|
+
|
|
375
|
+
**Checks**:
|
|
376
|
+
1. Verify `stop()` clears timers
|
|
377
|
+
2. Check for event listener leaks
|
|
378
|
+
3. Monitor with Chrome DevTools heap snapshots
|
|
379
|
+
|
|
380
|
+
**Solution**:
|
|
381
|
+
```javascript
|
|
382
|
+
// Ensure cleanup on shutdown
|
|
383
|
+
await wrapper.stop();
|
|
384
|
+
expect(wrapper.reconnectTimer).toBeNull();
|
|
385
|
+
|
|
386
|
+
// Check event listeners
|
|
387
|
+
console.log('XMR listeners:', wrapper.xmr?.events?.size);
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Performance Benchmarks
|
|
393
|
+
|
|
394
|
+
Expected performance metrics:
|
|
395
|
+
|
|
396
|
+
| Metric | Target | Acceptable |
|
|
397
|
+
|--------|--------|------------|
|
|
398
|
+
| Connection time | < 1s | < 3s |
|
|
399
|
+
| Command execution | < 100ms | < 500ms |
|
|
400
|
+
| Reconnection time | < 5s | < 30s |
|
|
401
|
+
| Memory overhead | < 5MB | < 10MB |
|
|
402
|
+
| CPU usage | < 1% | < 5% |
|
|
403
|
+
|
|
404
|
+
Measure with:
|
|
405
|
+
```javascript
|
|
406
|
+
// Connection time
|
|
407
|
+
const start = Date.now();
|
|
408
|
+
await wrapper.start(url, key);
|
|
409
|
+
console.log('Connect time:', Date.now() - start);
|
|
410
|
+
|
|
411
|
+
// Command execution
|
|
412
|
+
const cmdStart = Date.now();
|
|
413
|
+
xmr.simulateCommand('collectNow');
|
|
414
|
+
await waitFor(() => mockPlayer.collect.called);
|
|
415
|
+
console.log('Exec time:', Date.now() - cmdStart);
|
|
416
|
+
|
|
417
|
+
// Memory
|
|
418
|
+
const baseline = process.memoryUsage().heapUsed;
|
|
419
|
+
await wrapper.start(url, key);
|
|
420
|
+
const withXmr = process.memoryUsage().heapUsed;
|
|
421
|
+
console.log('Memory overhead:', (withXmr - baseline) / 1024 / 1024, 'MB');
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## Continuous Integration
|
|
427
|
+
|
|
428
|
+
### GitHub Actions
|
|
429
|
+
|
|
430
|
+
```yaml
|
|
431
|
+
name: XMR Tests
|
|
432
|
+
|
|
433
|
+
on: [push, pull_request]
|
|
434
|
+
|
|
435
|
+
jobs:
|
|
436
|
+
test:
|
|
437
|
+
runs-on: ubuntu-latest
|
|
438
|
+
steps:
|
|
439
|
+
- uses: actions/checkout@v2
|
|
440
|
+
- uses: actions/setup-node@v2
|
|
441
|
+
with:
|
|
442
|
+
node-version: '18'
|
|
443
|
+
- run: npm install
|
|
444
|
+
- run: npm test
|
|
445
|
+
working-directory: packages/xmr
|
|
446
|
+
- run: npm run test:coverage
|
|
447
|
+
working-directory: packages/xmr
|
|
448
|
+
- uses: codecov/codecov-action@v2
|
|
449
|
+
with:
|
|
450
|
+
files: packages/xmr/coverage/lcov.info
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## Test Data
|
|
456
|
+
|
|
457
|
+
### Sample registerDisplay Response
|
|
458
|
+
|
|
459
|
+
```json
|
|
460
|
+
{
|
|
461
|
+
"displayName": "Test Display",
|
|
462
|
+
"settings": {
|
|
463
|
+
"collectInterval": "300",
|
|
464
|
+
"xmrWebSocketAddress": "wss://cms.example.com:9505",
|
|
465
|
+
"xmrCmsKey": "abcdef123456",
|
|
466
|
+
"xmrChannel": "player-hw-key-123"
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Sample XMR Messages
|
|
472
|
+
|
|
473
|
+
```javascript
|
|
474
|
+
// collectNow
|
|
475
|
+
{ "action": "collectNow" }
|
|
476
|
+
|
|
477
|
+
// screenShot
|
|
478
|
+
{ "action": "screenShot" }
|
|
479
|
+
|
|
480
|
+
// changeLayout
|
|
481
|
+
{ "action": "changeLayout", "layoutId": "42" }
|
|
482
|
+
|
|
483
|
+
// criteriaUpdate
|
|
484
|
+
{
|
|
485
|
+
"action": "criteriaUpdate",
|
|
486
|
+
"data": {
|
|
487
|
+
"displayId": "123",
|
|
488
|
+
"criteria": "tag:urgent,location:lobby"
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// currentGeoLocation
|
|
493
|
+
{
|
|
494
|
+
"action": "currentGeoLocation",
|
|
495
|
+
"data": {
|
|
496
|
+
"latitude": 40.7128,
|
|
497
|
+
"longitude": -74.0060
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## References
|
|
505
|
+
|
|
506
|
+
- XMR Commands: [XMR_COMMANDS.md](./XMR_COMMANDS.md)
|
|
507
|
+
- XMR Library: [@xibosignage/xibo-communication-framework](https://www.npmjs.com/package/@xibosignage/xibo-communication-framework)
|
|
508
|
+
- Xibo CMS: https://xibosignage.com
|
|
509
|
+
- WebSocket Testing: https://github.com/websockets/wscat
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xiboplayer/xmr",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "XMR WebSocket client for real-time Xibo CMS commands",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@xibosignage/xibo-communication-framework": "^0.0.6",
|
|
12
|
+
"@xiboplayer/utils": "0.1.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"vitest": "^2.0.0"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"xibo",
|
|
19
|
+
"digital-signage",
|
|
20
|
+
"xmr",
|
|
21
|
+
"websocket",
|
|
22
|
+
"real-time"
|
|
23
|
+
],
|
|
24
|
+
"author": "Pau Aliagas <linuxnow@gmail.com>",
|
|
25
|
+
"license": "AGPL-3.0-or-later",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/xibo-players/xiboplayer.git",
|
|
29
|
+
"directory": "packages/xmr"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest",
|
|
34
|
+
"test:coverage": "vitest run --coverage"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Utilities for XMR Package
|
|
3
|
+
*
|
|
4
|
+
* Provides mocking utilities, test helpers, and fixtures for XMR tests.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { vi } from 'vitest';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a spy that tracks calls but doesn't interfere
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* const spy = createSpy();
|
|
14
|
+
* xmr.on('event', spy);
|
|
15
|
+
* // ... trigger event
|
|
16
|
+
* expect(spy).toHaveBeenCalledWith('arg1', 'arg2');
|
|
17
|
+
*/
|
|
18
|
+
export function createSpy() {
|
|
19
|
+
return vi.fn();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Mock Xmr class from @xibosignage/xibo-communication-framework
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* const MockXmr = mockXmr();
|
|
27
|
+
* // Use in tests
|
|
28
|
+
*/
|
|
29
|
+
export function mockXmr() {
|
|
30
|
+
class MockXmr {
|
|
31
|
+
constructor(channel) {
|
|
32
|
+
this.channel = channel;
|
|
33
|
+
this.events = new Map();
|
|
34
|
+
this.connected = false;
|
|
35
|
+
this.init = vi.fn(() => Promise.resolve());
|
|
36
|
+
this.start = vi.fn(() => {
|
|
37
|
+
this.connected = true;
|
|
38
|
+
this.emit('connected');
|
|
39
|
+
return Promise.resolve();
|
|
40
|
+
});
|
|
41
|
+
this.stop = vi.fn(() => {
|
|
42
|
+
this.connected = false;
|
|
43
|
+
this.emit('disconnected');
|
|
44
|
+
return Promise.resolve();
|
|
45
|
+
});
|
|
46
|
+
this.send = vi.fn(() => Promise.resolve());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
on(event, callback) {
|
|
50
|
+
if (!this.events.has(event)) {
|
|
51
|
+
this.events.set(event, []);
|
|
52
|
+
}
|
|
53
|
+
this.events.get(event).push(callback);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
emit(event, ...args) {
|
|
57
|
+
const listeners = this.events.get(event);
|
|
58
|
+
if (listeners) {
|
|
59
|
+
listeners.forEach(callback => callback(...args));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Simulate CMS sending a command
|
|
64
|
+
simulateCommand(command, data) {
|
|
65
|
+
this.emit(command, data);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return MockXmr;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Mock player instance with all required methods
|
|
74
|
+
*
|
|
75
|
+
* Usage:
|
|
76
|
+
* const mockPlayer = createMockPlayer();
|
|
77
|
+
* const wrapper = new XmrWrapper(config, mockPlayer);
|
|
78
|
+
*/
|
|
79
|
+
export function createMockPlayer() {
|
|
80
|
+
return {
|
|
81
|
+
collect: vi.fn(() => Promise.resolve()),
|
|
82
|
+
captureScreenshot: vi.fn(() => Promise.resolve()),
|
|
83
|
+
changeLayout: vi.fn(() => Promise.resolve()),
|
|
84
|
+
overlayLayout: vi.fn(() => Promise.resolve()),
|
|
85
|
+
revertToSchedule: vi.fn(() => Promise.resolve()),
|
|
86
|
+
purgeAll: vi.fn(() => Promise.resolve()),
|
|
87
|
+
executeCommand: vi.fn(() => Promise.resolve()),
|
|
88
|
+
triggerWebhook: vi.fn(),
|
|
89
|
+
refreshDataConnectors: vi.fn(),
|
|
90
|
+
reportGeoLocation: vi.fn(() => Promise.resolve()),
|
|
91
|
+
updateStatus: vi.fn()
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Mock config for XMR tests
|
|
97
|
+
*/
|
|
98
|
+
export function createMockConfig(overrides = {}) {
|
|
99
|
+
return {
|
|
100
|
+
cmsAddress: 'https://test.cms.com',
|
|
101
|
+
hardwareKey: 'test-hw-key',
|
|
102
|
+
serverKey: 'test-server-key',
|
|
103
|
+
xmrChannel: 'test-channel',
|
|
104
|
+
...overrides
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Wait for condition to be true
|
|
110
|
+
*
|
|
111
|
+
* Usage:
|
|
112
|
+
* await waitFor(() => wrapper.isConnected(), 5000);
|
|
113
|
+
*/
|
|
114
|
+
export async function waitFor(condition, timeout = 5000) {
|
|
115
|
+
const start = Date.now();
|
|
116
|
+
while (!condition()) {
|
|
117
|
+
if (Date.now() - start > timeout) {
|
|
118
|
+
throw new Error('waitFor timeout');
|
|
119
|
+
}
|
|
120
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Wait for a specific time (for testing timing-dependent logic)
|
|
126
|
+
*
|
|
127
|
+
* Usage:
|
|
128
|
+
* await wait(100); // Wait 100ms
|
|
129
|
+
*/
|
|
130
|
+
export async function wait(ms) {
|
|
131
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
132
|
+
}
|