@hardwarebridge/client 1.0.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.
@@ -0,0 +1,248 @@
1
+ import { HardwareBridgeClient } from '../core/hardware-bridge-client';
2
+ import { ConnectionConfig, ClientOptions } from '../types';
3
+
4
+ // Mock WebSocket
5
+ class MockWebSocket {
6
+ static CONNECTING = 0;
7
+ static OPEN = 1;
8
+ static CLOSING = 2;
9
+ static CLOSED = 3;
10
+
11
+ readyState = MockWebSocket.CLOSED;
12
+ onopen: (() => void) | null = null;
13
+ onclose: (() => void) | null = null;
14
+ onmessage: ((event: { data: string }) => void) | null = null;
15
+ onerror: ((error: Error) => void) | null = null;
16
+
17
+ constructor(_url: string, _protocols?: string | string[]) {
18
+ setTimeout(() => {
19
+ this.readyState = MockWebSocket.OPEN;
20
+ if (this.onopen) this.onopen();
21
+ }, 10);
22
+ }
23
+
24
+ send(_data: string): void {
25
+ // Mock send
26
+ }
27
+
28
+ close(): void {
29
+ this.readyState = MockWebSocket.CLOSED;
30
+ if (this.onclose) this.onclose();
31
+ }
32
+ }
33
+
34
+ // Store original WebSocket
35
+ const OriginalWebSocket = global.WebSocket;
36
+
37
+ describe('HardwareBridgeClient', () => {
38
+ beforeAll(() => {
39
+ // @ts-expect-error - Mock WebSocket globally
40
+ global.WebSocket = MockWebSocket;
41
+ });
42
+
43
+ afterAll(() => {
44
+ global.WebSocket = OriginalWebSocket;
45
+ });
46
+
47
+ describe('constructor', () => {
48
+ it('should create client with default options', () => {
49
+ const config: ConnectionConfig = {
50
+ url: 'ws://localhost:9876',
51
+ };
52
+ const client = new HardwareBridgeClient(config);
53
+ expect(client).toBeInstanceOf(HardwareBridgeClient);
54
+ });
55
+
56
+ it('should create client with custom options', () => {
57
+ const config: ConnectionConfig = {
58
+ url: 'ws://localhost:9876',
59
+ };
60
+ const options: ClientOptions = {
61
+ autoReconnect: false,
62
+ timeout: 5000,
63
+ debug: true,
64
+ };
65
+ const client = new HardwareBridgeClient(config, options);
66
+ expect(client).toBeInstanceOf(HardwareBridgeClient);
67
+ });
68
+
69
+ it('should accept secure WebSocket URL', () => {
70
+ const config: ConnectionConfig = {
71
+ url: 'wss://localhost:9876',
72
+ };
73
+ const client = new HardwareBridgeClient(config);
74
+ expect(client).toBeInstanceOf(HardwareBridgeClient);
75
+ });
76
+ });
77
+
78
+ describe('connection state', () => {
79
+ it('should report not connected initially', () => {
80
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
81
+ expect(client.isConnected).toBe(false);
82
+ });
83
+
84
+ it('should report not connecting initially', () => {
85
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
86
+ expect(client.isConnecting).toBe(false);
87
+ });
88
+
89
+ it('should return connection status string', () => {
90
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
91
+ const status = client.getConnectionStatus();
92
+ expect(typeof status).toBe('string');
93
+ });
94
+ });
95
+
96
+ describe('dispose', () => {
97
+ it('should dispose client without error', () => {
98
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
99
+ expect(() => client.dispose()).not.toThrow();
100
+ });
101
+ });
102
+
103
+ describe('connection management', () => {
104
+ it('should have connect method', () => {
105
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
106
+ expect(typeof client.connect).toBe('function');
107
+ });
108
+
109
+ it('should have disconnect method', () => {
110
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
111
+ expect(typeof client.disconnect).toBe('function');
112
+ });
113
+
114
+ it('should have onConnectionStateChange method', () => {
115
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
116
+ expect(typeof client.onConnectionStateChange).toBe('function');
117
+ });
118
+ });
119
+
120
+ describe('device operations', () => {
121
+ it('should have enumerateDevices method', () => {
122
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
123
+ expect(typeof client.enumerateDevices).toBe('function');
124
+ });
125
+
126
+ it('should have getDevice method', () => {
127
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
128
+ expect(typeof client.getDevice).toBe('function');
129
+ });
130
+
131
+ it('should have watchDevices method', () => {
132
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
133
+ expect(typeof client.watchDevices).toBe('function');
134
+ });
135
+
136
+ it('should have unwatchDevices method', () => {
137
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
138
+ expect(typeof client.unwatchDevices).toBe('function');
139
+ });
140
+ });
141
+
142
+ describe('printer operations', () => {
143
+ it('should have print method', () => {
144
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
145
+ expect(typeof client.print).toBe('function');
146
+ });
147
+
148
+ it('should have getPrinterStatus method', () => {
149
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
150
+ expect(typeof client.getPrinterStatus).toBe('function');
151
+ });
152
+
153
+ it('should have getPrinterCapabilities method', () => {
154
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
155
+ expect(typeof client.getPrinterCapabilities).toBe('function');
156
+ });
157
+ });
158
+
159
+ describe('serial port operations', () => {
160
+ it('should have openSerialPort method', () => {
161
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
162
+ expect(typeof client.openSerialPort).toBe('function');
163
+ });
164
+
165
+ it('should have closeSerialPort method', () => {
166
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
167
+ expect(typeof client.closeSerialPort).toBe('function');
168
+ });
169
+
170
+ it('should have sendSerialData method', () => {
171
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
172
+ expect(typeof client.sendSerialData).toBe('function');
173
+ });
174
+
175
+ it('should have receiveSerialData method', () => {
176
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
177
+ expect(typeof client.receiveSerialData).toBe('function');
178
+ });
179
+
180
+ it('should have getSerialPortStatus method', () => {
181
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
182
+ expect(typeof client.getSerialPortStatus).toBe('function');
183
+ });
184
+ });
185
+
186
+ describe('USB HID operations', () => {
187
+ it('should have openUsbDevice method', () => {
188
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
189
+ expect(typeof client.openUsbDevice).toBe('function');
190
+ });
191
+
192
+ it('should have closeUsbDevice method', () => {
193
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
194
+ expect(typeof client.closeUsbDevice).toBe('function');
195
+ });
196
+
197
+ it('should have sendUsbReport method', () => {
198
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
199
+ expect(typeof client.sendUsbReport).toBe('function');
200
+ });
201
+
202
+ it('should have receiveUsbReport method', () => {
203
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
204
+ expect(typeof client.receiveUsbReport).toBe('function');
205
+ });
206
+
207
+ it('should have getUsbDeviceStatus method', () => {
208
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
209
+ expect(typeof client.getUsbDeviceStatus).toBe('function');
210
+ });
211
+ });
212
+
213
+ describe('queue operations', () => {
214
+ it('should have getQueueStatus method', () => {
215
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
216
+ expect(typeof client.getQueueStatus).toBe('function');
217
+ });
218
+
219
+ it('should have getQueueJobs method', () => {
220
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
221
+ expect(typeof client.getQueueJobs).toBe('function');
222
+ });
223
+
224
+ it('should have cancelQueueJob method', () => {
225
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
226
+ expect(typeof client.cancelQueueJob).toBe('function');
227
+ });
228
+ });
229
+
230
+ describe('system operations', () => {
231
+ it('should have getSystemInfo method', () => {
232
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
233
+ expect(typeof client.getSystemInfo).toBe('function');
234
+ });
235
+
236
+ it('should have getSystemHealth method', () => {
237
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
238
+ expect(typeof client.getSystemHealth).toBe('function');
239
+ });
240
+ });
241
+
242
+ describe('utility methods', () => {
243
+ it('should have waitForConnection method', () => {
244
+ const client = new HardwareBridgeClient({ url: 'ws://localhost:9876' });
245
+ expect(typeof client.waitForConnection).toBe('function');
246
+ });
247
+ });
248
+ });
@@ -0,0 +1,153 @@
1
+ import {
2
+ ConnectionConfig,
3
+ ClientOptions,
4
+ DeviceInfo,
5
+ PrintFormat,
6
+ SerialPortConfig,
7
+ QueueStatus,
8
+ SystemHealth,
9
+ } from '../types';
10
+
11
+ describe('Types', () => {
12
+ describe('ConnectionConfig', () => {
13
+ it('should accept valid connection config', () => {
14
+ const config: ConnectionConfig = {
15
+ url: 'ws://localhost:9876',
16
+ };
17
+ expect(config.url).toBe('ws://localhost:9876');
18
+ });
19
+
20
+ it('should accept connection config with all options', () => {
21
+ const config: ConnectionConfig = {
22
+ url: 'wss://localhost:9876',
23
+ protocols: ['jsonrpc-2.0'],
24
+ timeout: 30000,
25
+ reconnectInterval: 5000,
26
+ maxReconnectAttempts: 10,
27
+ };
28
+ expect(config.url).toBe('wss://localhost:9876');
29
+ expect(config.protocols).toEqual(['jsonrpc-2.0']);
30
+ expect(config.timeout).toBe(30000);
31
+ });
32
+ });
33
+
34
+ describe('ClientOptions', () => {
35
+ it('should accept valid client options', () => {
36
+ const options: ClientOptions = {
37
+ autoReconnect: true,
38
+ reconnectInterval: 5000,
39
+ maxReconnectAttempts: 10,
40
+ timeout: 30000,
41
+ debug: false,
42
+ };
43
+ expect(options.autoReconnect).toBe(true);
44
+ expect(options.timeout).toBe(30000);
45
+ });
46
+
47
+ it('should accept partial client options', () => {
48
+ const options: ClientOptions = {
49
+ debug: true,
50
+ };
51
+ expect(options.debug).toBe(true);
52
+ expect(options.autoReconnect).toBeUndefined();
53
+ });
54
+ });
55
+
56
+ describe('DeviceInfo', () => {
57
+ it('should accept valid device info', () => {
58
+ const device: DeviceInfo = {
59
+ id: 'printer_1',
60
+ name: 'Test Printer',
61
+ type: 'printer',
62
+ status: 'available',
63
+ manufacturer: 'Test Corp',
64
+ model: 'Model X',
65
+ serialNumber: 'SN12345',
66
+ properties: {},
67
+ lastSeen: new Date(),
68
+ isConnected: true,
69
+ };
70
+ expect(device.id).toBe('printer_1');
71
+ expect(device.type).toBe('printer');
72
+ expect(device.isConnected).toBe(true);
73
+ });
74
+ });
75
+
76
+ describe('PrintFormat', () => {
77
+ it('should accept valid print formats', () => {
78
+ const formats: PrintFormat[] = ['raw', 'escpos', 'zpl', 'epl'];
79
+ expect(formats).toHaveLength(4);
80
+ expect(formats).toContain('escpos');
81
+ expect(formats).toContain('zpl');
82
+ });
83
+ });
84
+
85
+ describe('SerialPortConfig', () => {
86
+ it('should accept valid serial port config', () => {
87
+ const config: SerialPortConfig = {
88
+ baudRate: 9600,
89
+ dataBits: 8,
90
+ stopBits: '1',
91
+ parity: 'None',
92
+ flowControl: 'None',
93
+ };
94
+ expect(config.baudRate).toBe(9600);
95
+ expect(config.dataBits).toBe(8);
96
+ });
97
+
98
+ it('should accept different configurations', () => {
99
+ const config1: SerialPortConfig = {
100
+ baudRate: 115200,
101
+ dataBits: 8,
102
+ stopBits: '1',
103
+ parity: 'None',
104
+ flowControl: 'None',
105
+ };
106
+
107
+ const config2: SerialPortConfig = {
108
+ baudRate: 9600,
109
+ dataBits: 7,
110
+ stopBits: '2',
111
+ parity: 'Even',
112
+ flowControl: 'XOnXOff',
113
+ };
114
+
115
+ expect(config1.baudRate).toBe(115200);
116
+ expect(config2.parity).toBe('Even');
117
+ });
118
+ });
119
+
120
+ describe('QueueStatus', () => {
121
+ it('should accept valid queue status', () => {
122
+ const status: QueueStatus = {
123
+ totalJobs: 100,
124
+ pendingJobs: 5,
125
+ processingJobs: 2,
126
+ completedJobs: 90,
127
+ failedJobs: 3,
128
+ lastProcessed: new Date(),
129
+ averageProcessingTime: 150,
130
+ };
131
+ expect(status.totalJobs).toBe(100);
132
+ expect(status.pendingJobs).toBe(5);
133
+ });
134
+ });
135
+
136
+ describe('SystemHealth', () => {
137
+ it('should accept valid system health', () => {
138
+ const health: SystemHealth = {
139
+ status: 'healthy',
140
+ timestamp: new Date(),
141
+ totalDevices: 5,
142
+ connectedDevices: 3,
143
+ activeConnections: 2,
144
+ jobsInQueue: 10,
145
+ cpuUsage: 25.5,
146
+ memoryUsage: 512,
147
+ deviceHealth: {},
148
+ };
149
+ expect(health.status).toBe('healthy');
150
+ expect(health.connectedDevices).toBe(3);
151
+ });
152
+ });
153
+ });
@@ -0,0 +1,198 @@
1
+ import { WebSocketClient } from '../core/websocket-client';
2
+ import { ConnectionConfig, ClientOptions } from '../types';
3
+
4
+ // Mock WebSocket
5
+ class MockWebSocket {
6
+ static CONNECTING = 0;
7
+ static OPEN = 1;
8
+ static CLOSING = 2;
9
+ static CLOSED = 3;
10
+
11
+ url: string;
12
+ readyState = MockWebSocket.CLOSED;
13
+ onopen: (() => void) | null = null;
14
+ onclose: ((event: { code: number; reason: string }) => void) | null = null;
15
+ onmessage: ((event: { data: string }) => void) | null = null;
16
+ onerror: ((error: Error) => void) | null = null;
17
+
18
+ private messageQueue: string[] = [];
19
+
20
+ constructor(url: string, _protocols?: string | string[]) {
21
+ this.url = url;
22
+ // Simulate async connection
23
+ setTimeout(() => {
24
+ this.readyState = MockWebSocket.OPEN;
25
+ if (this.onopen) this.onopen();
26
+ }, 10);
27
+ }
28
+
29
+ send(data: string): void {
30
+ this.messageQueue.push(data);
31
+ }
32
+
33
+ close(code?: number, reason?: string): void {
34
+ this.readyState = MockWebSocket.CLOSED;
35
+ if (this.onclose) this.onclose({ code: code || 1000, reason: reason || '' });
36
+ }
37
+
38
+ // Helper to simulate receiving a message
39
+ simulateMessage(data: string): void {
40
+ if (this.onmessage) {
41
+ this.onmessage({ data });
42
+ }
43
+ }
44
+
45
+ // Helper to get sent messages
46
+ getSentMessages(): string[] {
47
+ return [...this.messageQueue];
48
+ }
49
+ }
50
+
51
+ // Store original WebSocket
52
+ const OriginalWebSocket = global.WebSocket;
53
+
54
+ describe('WebSocketClient', () => {
55
+ beforeAll(() => {
56
+ // @ts-expect-error - Mock WebSocket globally
57
+ global.WebSocket = MockWebSocket;
58
+ });
59
+
60
+ afterAll(() => {
61
+ global.WebSocket = OriginalWebSocket;
62
+ });
63
+
64
+ describe('constructor', () => {
65
+ it('should create WebSocketClient with config', () => {
66
+ const config: ConnectionConfig = {
67
+ url: 'ws://localhost:9876',
68
+ };
69
+ const client = new WebSocketClient(config);
70
+ expect(client).toBeInstanceOf(WebSocketClient);
71
+ });
72
+
73
+ it('should create WebSocketClient with options', () => {
74
+ const config: ConnectionConfig = {
75
+ url: 'ws://localhost:9876',
76
+ };
77
+ const options: ClientOptions = {
78
+ autoReconnect: true,
79
+ reconnectInterval: 1000,
80
+ maxReconnectAttempts: 5,
81
+ timeout: 10000,
82
+ debug: true,
83
+ };
84
+ const client = new WebSocketClient(config, options);
85
+ expect(client).toBeInstanceOf(WebSocketClient);
86
+ });
87
+ });
88
+
89
+ describe('connection state', () => {
90
+ it('should report not connected initially', () => {
91
+ const client = new WebSocketClient({ url: 'ws://localhost:9876' });
92
+ expect(client.isConnected).toBe(false);
93
+ });
94
+
95
+ it('should report not connecting initially', () => {
96
+ const client = new WebSocketClient({ url: 'ws://localhost:9876' });
97
+ expect(client.isConnecting).toBe(false);
98
+ });
99
+
100
+ it('should return disconnected status initially', () => {
101
+ const client = new WebSocketClient({ url: 'ws://localhost:9876' });
102
+ expect(client.getConnectionStatus()).toBe('disconnected');
103
+ });
104
+ });
105
+
106
+ describe('connect', () => {
107
+ it('should connect successfully', async () => {
108
+ const client = new WebSocketClient({ url: 'ws://localhost:9876' });
109
+ await client.connect();
110
+ expect(client.isConnected).toBe(true);
111
+ });
112
+
113
+ it('should report connecting status during connection', () => {
114
+ const client = new WebSocketClient({ url: 'ws://localhost:9876' });
115
+ client.connect();
116
+ expect(client.isConnecting).toBe(true);
117
+ });
118
+
119
+ it('should report connected status after connection', async () => {
120
+ const client = new WebSocketClient({ url: 'ws://localhost:9876' });
121
+ await client.connect();
122
+ expect(client.getConnectionStatus()).toBe('connected');
123
+ });
124
+ });
125
+
126
+ describe('disconnect', () => {
127
+ it('should disconnect successfully', async () => {
128
+ const client = new WebSocketClient({ url: 'ws://localhost:9876' });
129
+ await client.connect();
130
+ client.disconnect();
131
+ expect(client.isConnected).toBe(false);
132
+ });
133
+
134
+ it('should handle disconnect when not connected', () => {
135
+ const client = new WebSocketClient({ url: 'ws://localhost:9876' });
136
+ expect(() => client.disconnect()).not.toThrow();
137
+ });
138
+ });
139
+
140
+ describe('onConnectionStateChange', () => {
141
+ it('should register connection state listener', () => {
142
+ const client = new WebSocketClient({ url: 'ws://localhost:9876' });
143
+ const listener = jest.fn();
144
+ client.onConnectionStateChange(listener);
145
+ expect(typeof client.onConnectionStateChange).toBe('function');
146
+ });
147
+
148
+ it('should call listener on connect', async () => {
149
+ const client = new WebSocketClient({ url: 'ws://localhost:9876' });
150
+ const listener = jest.fn();
151
+ client.onConnectionStateChange(listener);
152
+ await client.connect();
153
+ expect(listener).toHaveBeenCalled();
154
+ });
155
+ });
156
+
157
+ describe('onDeviceEvent', () => {
158
+ it('should register device event listener', () => {
159
+ const client = new WebSocketClient({ url: 'ws://localhost:9876' });
160
+ const listener = jest.fn();
161
+ client.onDeviceEvent(listener);
162
+ expect(typeof client.onDeviceEvent).toBe('function');
163
+ });
164
+ });
165
+
166
+ describe('dispose', () => {
167
+ it('should dispose client', () => {
168
+ const client = new WebSocketClient({ url: 'ws://localhost:9876' });
169
+ expect(() => client.dispose()).not.toThrow();
170
+ });
171
+
172
+ it('should disconnect on dispose', async () => {
173
+ const client = new WebSocketClient({ url: 'ws://localhost:9876' });
174
+ await client.connect();
175
+ client.dispose();
176
+ expect(client.isConnected).toBe(false);
177
+ });
178
+ });
179
+
180
+ describe('sendRequest', () => {
181
+ it('should have sendRequest method', () => {
182
+ const client = new WebSocketClient({ url: 'ws://localhost:9876' });
183
+ expect(typeof client.sendRequest).toBe('function');
184
+ });
185
+
186
+ it('should reject when not connected', async () => {
187
+ const client = new WebSocketClient({ url: 'ws://localhost:9876' });
188
+ await expect(client.sendRequest('test.method')).rejects.toThrow();
189
+ });
190
+ });
191
+
192
+ describe('sendNotification', () => {
193
+ it('should have sendNotification method', () => {
194
+ const client = new WebSocketClient({ url: 'ws://localhost:9876' });
195
+ expect(typeof client.sendNotification).toBe('function');
196
+ });
197
+ });
198
+ });