js4j 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.
@@ -0,0 +1,280 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ encodeCommandPart,
5
+ decodeReturnValue,
6
+ decodeTypedValue,
7
+ escapeNewLines,
8
+ unescapeNewLines,
9
+ REFERENCE_TYPE,
10
+ INTEGER_TYPE,
11
+ LONG_TYPE,
12
+ DOUBLE_TYPE,
13
+ BOOLEAN_TYPE,
14
+ STRING_TYPE,
15
+ NULL_TYPE,
16
+ VOID_TYPE,
17
+ BYTES_TYPE,
18
+ LIST_TYPE,
19
+ SET_TYPE,
20
+ MAP_TYPE,
21
+ ARRAY_TYPE,
22
+ ITERATOR_TYPE,
23
+ SUCCESS,
24
+ ERROR,
25
+ } = require('../../src/protocol');
26
+
27
+ const { Js4JJavaError, Js4JNetworkError, Js4JError } = require('../../src/exceptions');
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Helpers
31
+ // ---------------------------------------------------------------------------
32
+
33
+ /** A minimal stub gatewayClient for decodeReturnValue tests */
34
+ const stubClient = {
35
+ _wrapObject: (id, hint) => ({ _targetId: id, _typeHint: hint }),
36
+ _lookupProxy: () => null,
37
+ };
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // encodeCommandPart
41
+ // ---------------------------------------------------------------------------
42
+
43
+ describe('encodeCommandPart', () => {
44
+ test('null encodes to NULL_TYPE', () => {
45
+ expect(encodeCommandPart(null)).toBe('n\n');
46
+ });
47
+
48
+ test('undefined encodes to NULL_TYPE', () => {
49
+ expect(encodeCommandPart(undefined)).toBe('n\n');
50
+ });
51
+
52
+ test('boolean true', () => {
53
+ expect(encodeCommandPart(true)).toBe('btrue\n');
54
+ });
55
+
56
+ test('boolean false', () => {
57
+ expect(encodeCommandPart(false)).toBe('bfalse\n');
58
+ });
59
+
60
+ test('small integer uses INTEGER_TYPE', () => {
61
+ expect(encodeCommandPart(42)).toBe('i42\n');
62
+ expect(encodeCommandPart(0)).toBe('i0\n');
63
+ expect(encodeCommandPart(-99)).toBe('i-99\n');
64
+ expect(encodeCommandPart(2147483647)).toBe('i2147483647\n');
65
+ expect(encodeCommandPart(-2147483648)).toBe('i-2147483648\n');
66
+ });
67
+
68
+ test('large integer uses LONG_TYPE', () => {
69
+ expect(encodeCommandPart(2147483648)).toBe('L2147483648\n');
70
+ expect(encodeCommandPart(-2147483649)).toBe('L-2147483649\n');
71
+ expect(encodeCommandPart(10 ** 12)).toBe('L1000000000000\n');
72
+ });
73
+
74
+ test('BigInt uses LONG_TYPE', () => {
75
+ expect(encodeCommandPart(BigInt(100))).toBe('L100\n');
76
+ expect(encodeCommandPart(BigInt(-1))).toBe('L-1\n');
77
+ });
78
+
79
+ test('float uses DOUBLE_TYPE', () => {
80
+ expect(encodeCommandPart(3.14)).toBe('d3.14\n');
81
+ // In JS, 0.0 === 0 (both are the same Number), so it encodes as integer
82
+ expect(encodeCommandPart(0.0)).toBe('i0\n');
83
+ expect(encodeCommandPart(-1.5)).toBe('d-1.5\n');
84
+ });
85
+
86
+ test('string uses STRING_TYPE', () => {
87
+ expect(encodeCommandPart('hello')).toBe('shello\n');
88
+ expect(encodeCommandPart('')).toBe('s\n');
89
+ });
90
+
91
+ test('string with newlines gets escaped', () => {
92
+ const encoded = encodeCommandPart('line1\nline2');
93
+ expect(encoded).toBe('sline1\\nline2\n');
94
+ });
95
+
96
+ test('Buffer/Uint8Array uses BYTES_TYPE (base64)', () => {
97
+ const buf = Buffer.from('hello');
98
+ const encoded = encodeCommandPart(buf);
99
+ expect(encoded.startsWith(BYTES_TYPE)).toBe(true);
100
+ const b64 = encoded.slice(1, -1); // strip type prefix and \n
101
+ expect(Buffer.from(b64, 'base64').toString('utf8')).toBe('hello');
102
+ });
103
+
104
+ test('JavaObject uses REFERENCE_TYPE', () => {
105
+ const jobj = { _targetId: 'o42' };
106
+ expect(encodeCommandPart(jobj)).toBe('ro42\n');
107
+ });
108
+
109
+ test('plain JS array throws without auto_convert', () => {
110
+ expect(() => encodeCommandPart([1, 2, 3])).toThrow(Js4JError);
111
+ });
112
+ });
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // escapeNewLines / unescapeNewLines
116
+ // ---------------------------------------------------------------------------
117
+
118
+ describe('escapeNewLines / unescapeNewLines', () => {
119
+ test('round-trip plain string', () => {
120
+ const s = 'hello world';
121
+ expect(unescapeNewLines(escapeNewLines(s))).toBe(s);
122
+ });
123
+
124
+ test('round-trip string with newlines', () => {
125
+ const s = 'line1\nline2\nline3';
126
+ expect(unescapeNewLines(escapeNewLines(s))).toBe(s);
127
+ });
128
+
129
+ test('round-trip string with backslashes', () => {
130
+ const s = 'path\\to\\file';
131
+ expect(unescapeNewLines(escapeNewLines(s))).toBe(s);
132
+ });
133
+
134
+ test('escaped newline becomes literal', () => {
135
+ expect(unescapeNewLines('line1\\nline2')).toBe('line1\nline2');
136
+ });
137
+ });
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // decodeReturnValue
141
+ // ---------------------------------------------------------------------------
142
+
143
+ describe('decodeReturnValue', () => {
144
+ test('empty answer throws Js4JNetworkError', () => {
145
+ expect(() => decodeReturnValue('', stubClient)).toThrow(Js4JNetworkError);
146
+ });
147
+
148
+ test('yv (success + void) returns null', () => {
149
+ expect(decodeReturnValue('yv', stubClient)).toBeNull();
150
+ });
151
+
152
+ test('yn (success + null) returns null', () => {
153
+ expect(decodeReturnValue('yn', stubClient)).toBeNull();
154
+ });
155
+
156
+ test('yi42 returns integer 42', () => {
157
+ expect(decodeReturnValue('yi42', stubClient)).toBe(42);
158
+ });
159
+
160
+ test('yi-99 returns integer -99', () => {
161
+ expect(decodeReturnValue('yi-99', stubClient)).toBe(-99);
162
+ });
163
+
164
+ test('yd3.14 returns float', () => {
165
+ expect(decodeReturnValue('yd3.14', stubClient)).toBeCloseTo(3.14);
166
+ });
167
+
168
+ test('ybtrue returns boolean true', () => {
169
+ expect(decodeReturnValue('ybtrue', stubClient)).toBe(true);
170
+ });
171
+
172
+ test('ybfalse returns boolean false', () => {
173
+ expect(decodeReturnValue('ybfalse', stubClient)).toBe(false);
174
+ });
175
+
176
+ test('yshello returns string', () => {
177
+ expect(decodeReturnValue('yshello', stubClient)).toBe('hello');
178
+ });
179
+
180
+ test('ys with escaped newline round-trips', () => {
181
+ const answer = 'ys' + escapeNewLines('line1\nline2');
182
+ expect(decodeReturnValue(answer, stubClient)).toBe('line1\nline2');
183
+ });
184
+
185
+ test('yr returns wrapped object (REFERENCE_TYPE = r)', () => {
186
+ const result = decodeReturnValue('yro99', stubClient);
187
+ expect(result).toEqual({ _targetId: 'o99', _typeHint: REFERENCE_TYPE });
188
+ });
189
+
190
+ test('yl returns wrapped list (LIST_TYPE = l)', () => {
191
+ const result = decodeReturnValue('ylo5', stubClient);
192
+ expect(result._typeHint).toBe(LIST_TYPE);
193
+ });
194
+
195
+ test('yh returns wrapped set (SET_TYPE = h)', () => {
196
+ const result = decodeReturnValue('yho6', stubClient);
197
+ expect(result._typeHint).toBe(SET_TYPE);
198
+ });
199
+
200
+ test('ya returns wrapped map (MAP_TYPE = a)', () => {
201
+ const result = decodeReturnValue('yao7', stubClient);
202
+ expect(result._typeHint).toBe(MAP_TYPE);
203
+ });
204
+
205
+ test('yt returns wrapped array (ARRAY_TYPE = t)', () => {
206
+ const result = decodeReturnValue('yto8', stubClient);
207
+ expect(result._typeHint).toBe(ARRAY_TYPE);
208
+ });
209
+
210
+ test('yg returns wrapped iterator (ITERATOR_TYPE = g)', () => {
211
+ const result = decodeReturnValue('ygo9', stubClient);
212
+ expect(result._typeHint).toBe(ITERATOR_TYPE);
213
+ });
214
+
215
+ test('! prefix is stripped before decoding', () => {
216
+ expect(decodeReturnValue('!yi42', stubClient)).toBe(42);
217
+ expect(decodeReturnValue('!yv', stubClient)).toBeNull();
218
+ });
219
+
220
+ test('x prefix raises Js4JJavaError (error payload is reference ro0)', () => {
221
+ expect(() => decodeReturnValue('xro0', stubClient)).toThrow(Js4JJavaError);
222
+ });
223
+
224
+ test('Js4JJavaError.javaException is the decoded exception object', () => {
225
+ try {
226
+ decodeReturnValue('xro0', stubClient);
227
+ } catch (err) {
228
+ expect(err.javaException).toBeTruthy();
229
+ expect(err.javaException._targetId).toBe('o0');
230
+ }
231
+ });
232
+
233
+ test('unknown response code raises Js4JNetworkError', () => {
234
+ expect(() => decodeReturnValue('?something', stubClient)).toThrow(Js4JNetworkError);
235
+ });
236
+
237
+ test('bytes type decodes from base64 (BYTES_TYPE = j)', () => {
238
+ const original = Buffer.from('hello');
239
+ const b64 = original.toString('base64');
240
+ const result = decodeReturnValue('yj' + b64, stubClient);
241
+ expect(Buffer.isBuffer(result)).toBe(true);
242
+ expect(result.toString('utf8')).toBe('hello');
243
+ });
244
+
245
+ test('long type returns number for safe integers (LONG_TYPE = L)', () => {
246
+ expect(decodeReturnValue('yL12345', stubClient)).toBe(12345);
247
+ });
248
+ });
249
+
250
+ // ---------------------------------------------------------------------------
251
+ // Command building (integration smoke tests for the protocol strings)
252
+ // ---------------------------------------------------------------------------
253
+
254
+ describe('Command format smoke tests', () => {
255
+ const {
256
+ CALL_COMMAND_NAME,
257
+ CONSTRUCTOR_COMMAND_NAME,
258
+ FIELD_COMMAND_NAME,
259
+ FIELD_GET_SUB_COMMAND_NAME,
260
+ END,
261
+ END_COMMAND_PART,
262
+ } = require('../../src/protocol');
263
+
264
+ test('CALL command starts with c', () => {
265
+ expect(CALL_COMMAND_NAME).toBe('c\n');
266
+ });
267
+
268
+ test('CONSTRUCTOR command starts with i', () => {
269
+ expect(CONSTRUCTOR_COMMAND_NAME).toBe('i\n');
270
+ });
271
+
272
+ test('FIELD_GET command is f + g', () => {
273
+ const cmd = FIELD_COMMAND_NAME + FIELD_GET_SUB_COMMAND_NAME + 'o1\n' + 'myField\n' + END + END_COMMAND_PART;
274
+ expect(cmd).toBe('f\ng\no1\nmyField\ne\n');
275
+ });
276
+
277
+ test('END is e', () => {
278
+ expect(END).toBe('e');
279
+ });
280
+ });