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,104 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ Js4JError,
5
+ Js4JJavaError,
6
+ Js4JNetworkError,
7
+ Js4JAuthenticationError,
8
+ } = require('../../src/exceptions');
9
+
10
+ describe('Js4JError', () => {
11
+ test('is an instance of Error', () => {
12
+ const err = new Js4JError('test');
13
+ expect(err).toBeInstanceOf(Error);
14
+ });
15
+
16
+ test('has correct name', () => {
17
+ expect(new Js4JError('x').name).toBe('Js4JError');
18
+ });
19
+
20
+ test('has correct message', () => {
21
+ expect(new Js4JError('something went wrong').message).toBe('something went wrong');
22
+ });
23
+
24
+ test('instanceof Js4JError', () => {
25
+ expect(new Js4JError('x')).toBeInstanceOf(Js4JError);
26
+ });
27
+ });
28
+
29
+ describe('Js4JJavaError', () => {
30
+ test('extends Js4JError', () => {
31
+ expect(new Js4JJavaError('msg', 'java trace')).toBeInstanceOf(Js4JError);
32
+ });
33
+
34
+ test('has correct name', () => {
35
+ expect(new Js4JJavaError('m', 't').name).toBe('Js4JJavaError');
36
+ });
37
+
38
+ test('stores javaExceptionMessage', () => {
39
+ const err = new Js4JJavaError('A occurred', 'java.lang.RuntimeException: A');
40
+ expect(err.javaExceptionMessage).toBe('java.lang.RuntimeException: A');
41
+ });
42
+
43
+ test('toString includes Java trace', () => {
44
+ const err = new Js4JJavaError('msg', 'trace here');
45
+ expect(err.toString()).toContain('trace here');
46
+ });
47
+
48
+ test('javaExceptionMessage defaults to empty string', () => {
49
+ const err = new Js4JJavaError('msg');
50
+ expect(err.javaExceptionMessage).toBe('');
51
+ });
52
+ });
53
+
54
+ describe('Js4JNetworkError', () => {
55
+ test('extends Js4JError', () => {
56
+ expect(new Js4JNetworkError('net')).toBeInstanceOf(Js4JError);
57
+ });
58
+
59
+ test('has correct name', () => {
60
+ expect(new Js4JNetworkError('net').name).toBe('Js4JNetworkError');
61
+ });
62
+ });
63
+
64
+ describe('Js4JAuthenticationError', () => {
65
+ test('extends Js4JError', () => {
66
+ expect(new Js4JAuthenticationError()).toBeInstanceOf(Js4JError);
67
+ });
68
+
69
+ test('has correct name', () => {
70
+ expect(new Js4JAuthenticationError().name).toBe('Js4JAuthenticationError');
71
+ });
72
+
73
+ test('default message', () => {
74
+ expect(new Js4JAuthenticationError().message).toBe('Authentication failed');
75
+ });
76
+
77
+ test('custom message', () => {
78
+ expect(new Js4JAuthenticationError('bad token').message).toBe('bad token');
79
+ });
80
+ });
81
+
82
+ describe('Error hierarchy', () => {
83
+ test('all errors are instanceof Error', () => {
84
+ expect(new Js4JError('x')).toBeInstanceOf(Error);
85
+ expect(new Js4JJavaError('x', 'y')).toBeInstanceOf(Error);
86
+ expect(new Js4JNetworkError('x')).toBeInstanceOf(Error);
87
+ expect(new Js4JAuthenticationError()).toBeInstanceOf(Error);
88
+ });
89
+
90
+ test('Js4JJavaError is NOT instanceof Js4JNetworkError', () => {
91
+ expect(new Js4JJavaError('x', 'y')).not.toBeInstanceOf(Js4JNetworkError);
92
+ });
93
+
94
+ test('can be caught as Js4JError', () => {
95
+ function throwIt() { throw new Js4JJavaError('boom', 'trace'); }
96
+ expect(throwIt).toThrow(Js4JError);
97
+ });
98
+
99
+ test('stack trace is available', () => {
100
+ const err = new Js4JError('oops');
101
+ expect(typeof err.stack).toBe('string');
102
+ expect(err.stack).toContain('Js4JError');
103
+ });
104
+ });
@@ -0,0 +1,178 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Unit tests for gateway-level functionality that can be tested without a
5
+ * live Java server (using mocked connections).
6
+ */
7
+
8
+ const { GatewayParameters, CallbackServerParameters } = require('../../src/gateway');
9
+ const { ProxyPool, createJavaProxy } = require('../../src/callbackServer');
10
+ const { createJavaObject } = require('../../src/javaObject');
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // GatewayParameters
14
+ // ---------------------------------------------------------------------------
15
+
16
+ describe('GatewayParameters', () => {
17
+ test('defaults', () => {
18
+ const p = new GatewayParameters();
19
+ expect(p.host).toBe('127.0.0.1');
20
+ expect(p.port).toBe(25333);
21
+ expect(p.authToken).toBeNull();
22
+ expect(p.autoField).toBe(false);
23
+ expect(p.autoConvert).toBe(false);
24
+ expect(p.enableMemoryManagement).toBe(false);
25
+ expect(p.poolSize).toBe(4);
26
+ });
27
+
28
+ test('custom values', () => {
29
+ const p = new GatewayParameters({
30
+ host: '0.0.0.0',
31
+ port: 9999,
32
+ authToken: 'secret',
33
+ autoField: true,
34
+ autoConvert: true,
35
+ enableMemoryManagement: true,
36
+ poolSize: 8,
37
+ });
38
+ expect(p.host).toBe('0.0.0.0');
39
+ expect(p.port).toBe(9999);
40
+ expect(p.authToken).toBe('secret');
41
+ expect(p.autoField).toBe(true);
42
+ expect(p.autoConvert).toBe(true);
43
+ expect(p.enableMemoryManagement).toBe(true);
44
+ expect(p.poolSize).toBe(8);
45
+ });
46
+ });
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // CallbackServerParameters
50
+ // ---------------------------------------------------------------------------
51
+
52
+ describe('CallbackServerParameters', () => {
53
+ test('defaults', () => {
54
+ const p = new CallbackServerParameters();
55
+ expect(p.host).toBe('127.0.0.1');
56
+ expect(p.port).toBe(25334);
57
+ expect(p.daemonize).toBe(true);
58
+ expect(p.propagateException).toBe(false);
59
+ });
60
+
61
+ test('custom values', () => {
62
+ const p = new CallbackServerParameters({
63
+ host: '0.0.0.0',
64
+ port: 25335,
65
+ daemonize: false,
66
+ propagateException: true,
67
+ });
68
+ expect(p.host).toBe('0.0.0.0');
69
+ expect(p.port).toBe(25335);
70
+ expect(p.daemonize).toBe(false);
71
+ expect(p.propagateException).toBe(true);
72
+ });
73
+ });
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // ProxyPool
77
+ // ---------------------------------------------------------------------------
78
+
79
+ describe('ProxyPool', () => {
80
+ test('register returns unique IDs', () => {
81
+ const pool = new ProxyPool();
82
+ const a = {};
83
+ const b = {};
84
+ const idA = pool.register(a);
85
+ const idB = pool.register(b);
86
+ expect(idA).not.toBe(idB);
87
+ expect(idA).toMatch(/^p\d+$/);
88
+ });
89
+
90
+ test('get returns registered object', () => {
91
+ const pool = new ProxyPool();
92
+ const obj = { method: () => 42 };
93
+ const id = pool.register(obj);
94
+ expect(pool.get(id)).toBe(obj);
95
+ });
96
+
97
+ test('remove deletes the entry', () => {
98
+ const pool = new ProxyPool();
99
+ const id = pool.register({});
100
+ pool.remove(id);
101
+ expect(pool.get(id)).toBeNull();
102
+ });
103
+
104
+ test('has() returns correct boolean', () => {
105
+ const pool = new ProxyPool();
106
+ const id = pool.register({});
107
+ expect(pool.has(id)).toBe(true);
108
+ pool.remove(id);
109
+ expect(pool.has(id)).toBe(false);
110
+ });
111
+ });
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // createJavaProxy
115
+ // ---------------------------------------------------------------------------
116
+
117
+ describe('createJavaProxy', () => {
118
+ test('creates an object with _js4jProxy flag', () => {
119
+ const proxy = createJavaProxy(['java.lang.Runnable'], { run: () => {} });
120
+ expect(proxy._js4jProxy).toBe(true);
121
+ });
122
+
123
+ test('stores interfaces', () => {
124
+ const proxy = createJavaProxy(['java.lang.Runnable', 'java.lang.Comparable'], {});
125
+ expect(proxy._interfaces).toEqual(['java.lang.Runnable', 'java.lang.Comparable']);
126
+ });
127
+
128
+ test('includes implementation methods', () => {
129
+ const proxy = createJavaProxy(['java.lang.Runnable'], {
130
+ run: () => 'ran',
131
+ });
132
+ expect(typeof proxy.run).toBe('function');
133
+ expect(proxy.run()).toBe('ran');
134
+ });
135
+ });
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // createJavaObject
139
+ // ---------------------------------------------------------------------------
140
+
141
+ describe('createJavaObject', () => {
142
+ const mockClient = {
143
+ async callMethod(targetId, method, args) {
144
+ return { targetId, method, args };
145
+ },
146
+ };
147
+
148
+ test('_targetId is accessible', () => {
149
+ const obj = createJavaObject('o123', mockClient);
150
+ expect(obj._targetId).toBe('o123');
151
+ });
152
+
153
+ test('method access returns async function', () => {
154
+ const obj = createJavaObject('o1', mockClient);
155
+ expect(typeof obj.someMethod).toBe('function');
156
+ });
157
+
158
+ test('method call invokes callMethod with correct args', async () => {
159
+ const obj = createJavaObject('o1', mockClient);
160
+ const result = await obj.greet('World');
161
+ expect(result).toEqual({ targetId: 'o1', method: 'greet', args: ['World'] });
162
+ });
163
+
164
+ test('then is undefined (not a Promise)', () => {
165
+ const obj = createJavaObject('o1', mockClient);
166
+ expect(obj.then).toBeUndefined();
167
+ });
168
+
169
+ test('Symbol access is handled gracefully', () => {
170
+ const obj = createJavaObject('o1', mockClient);
171
+ expect(obj[Symbol.iterator]).toBeUndefined();
172
+ });
173
+
174
+ test('direct field assignment throws helpful error', () => {
175
+ const obj = createJavaObject('o1', mockClient);
176
+ expect(() => { obj.someField = 42; }).toThrow(/setField/);
177
+ });
178
+ });
@@ -0,0 +1,126 @@
1
+ 'use strict';
2
+
3
+ const { JVMView, createJavaClass, createJavaPackage } = require('../../src/jvmView');
4
+
5
+ // Minimal stub client
6
+ const stubClient = {
7
+ async callMethod(targetId, methodName, args) {
8
+ return { _call: { targetId, methodName, args } };
9
+ },
10
+ async callConstructor(fqn, args) {
11
+ return { _ctor: { fqn, args } };
12
+ },
13
+ async _sendCommand() { return 'yv'; },
14
+ _wrapObject: (id) => ({ _targetId: id }),
15
+ _lookupProxy: () => null,
16
+ _proxyPool: null,
17
+ };
18
+
19
+ describe('createJavaClass', () => {
20
+ test('has correct _fqn', () => {
21
+ const cls = createJavaClass('java.lang.String', stubClient);
22
+ expect(cls._fqn).toBe('java.lang.String');
23
+ });
24
+
25
+ test('has _targetId equal to z:<fqn> (for static calls)', () => {
26
+ const cls = createJavaClass('java.lang.Math', stubClient);
27
+ expect(cls._targetId).toBe('z:java.lang.Math');
28
+ });
29
+
30
+ test('static method access returns async function', () => {
31
+ const cls = createJavaClass('java.lang.Math', stubClient);
32
+ expect(typeof cls.abs).toBe('function');
33
+ });
34
+
35
+ test('static method call invokes callMethod with z:<fqn> as target', async () => {
36
+ const cls = createJavaClass('java.lang.Math', stubClient);
37
+ const result = await cls.abs(-5);
38
+ expect(result._call.targetId).toBe('z:java.lang.Math');
39
+ expect(result._call.methodName).toBe('abs');
40
+ expect(result._call.args).toEqual([-5]);
41
+ });
42
+
43
+ test('calling class as function invokes constructor', async () => {
44
+ const cls = createJavaClass('java.lang.StringBuilder', stubClient);
45
+ const result = await cls('hello');
46
+ expect(result._ctor.fqn).toBe('java.lang.StringBuilder');
47
+ expect(result._ctor.args).toEqual(['hello']);
48
+ });
49
+
50
+ test('Symbol properties are not proxied', () => {
51
+ const cls = createJavaClass('java.lang.String', stubClient);
52
+ expect(cls[Symbol.iterator]).toBeUndefined();
53
+ });
54
+
55
+ test('.then is undefined (not a Promise)', () => {
56
+ const cls = createJavaClass('java.lang.String', stubClient);
57
+ expect(cls.then).toBeUndefined();
58
+ });
59
+ });
60
+
61
+ describe('createJavaPackage', () => {
62
+ test('uppercase child becomes JavaClass', () => {
63
+ const pkg = createJavaPackage('java.lang', stubClient);
64
+ const cls = pkg.String;
65
+ expect(cls._fqn).toBe('java.lang.String');
66
+ expect(cls._isJavaClass).toBe(true);
67
+ });
68
+
69
+ test('lowercase child stays JavaPackage', () => {
70
+ const pkg = createJavaPackage('java', stubClient);
71
+ const subpkg = pkg.lang;
72
+ expect(subpkg._fqn).toBe('java.lang');
73
+ expect(subpkg._isJavaPackage).toBe(true);
74
+ });
75
+
76
+ test('chain: java.lang.Math._fqn', () => {
77
+ const pkg = createJavaPackage('java', stubClient);
78
+ expect(pkg.lang.Math._fqn).toBe('java.lang.Math');
79
+ });
80
+
81
+ test('calling package as function throws', () => {
82
+ const pkg = createJavaPackage('java', stubClient);
83
+ expect(() => pkg()).toThrow();
84
+ });
85
+ });
86
+
87
+ describe('JVMView', () => {
88
+ test('jvm.java is a JavaPackage', () => {
89
+ const jvm = new JVMView(stubClient);
90
+ const pkg = jvm.java;
91
+ expect(pkg._isJavaPackage).toBe(true);
92
+ expect(pkg._fqn).toBe('java');
93
+ });
94
+
95
+ test('jvm.java.lang.String is a JavaClass', () => {
96
+ const jvm = new JVMView(stubClient);
97
+ const cls = jvm.java.lang.String;
98
+ expect(cls._isJavaClass).toBe(true);
99
+ expect(cls._fqn).toBe('java.lang.String');
100
+ });
101
+
102
+ test('jvm.java.util.ArrayList is a JavaClass', () => {
103
+ const jvm = new JVMView(stubClient);
104
+ const cls = jvm.java.util.ArrayList;
105
+ expect(cls._fqn).toBe('java.util.ArrayList');
106
+ });
107
+
108
+ test('.then on jvm is undefined (not a Promise)', () => {
109
+ const jvm = new JVMView(stubClient);
110
+ expect(jvm.then).toBeUndefined();
111
+ });
112
+
113
+ test('javaImport registers shortcut', async () => {
114
+ const jvm = new JVMView(stubClient);
115
+ await jvm.javaImport('java.util.ArrayList');
116
+ const cls = jvm.ArrayList;
117
+ expect(cls._fqn).toBe('java.util.ArrayList');
118
+ });
119
+
120
+ test('getClass returns JavaClass', () => {
121
+ const jvm = new JVMView(stubClient);
122
+ const cls = jvm.getClass('java.lang.Runtime');
123
+ expect(cls._fqn).toBe('java.lang.Runtime');
124
+ expect(cls._isJavaClass).toBe(true);
125
+ });
126
+ });
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ const { launchGateway } = require('../../src/launcher');
4
+
5
+ describe('launchGateway', () => {
6
+ test('throws when classpath is missing', async () => {
7
+ await expect(launchGateway({ mainClass: 'Foo' }))
8
+ .rejects.toThrow('classpath is required');
9
+ });
10
+
11
+ test('throws when mainClass is missing', async () => {
12
+ await expect(launchGateway({ classpath: '/some/path.jar' }))
13
+ .rejects.toThrow('mainClass is required');
14
+ });
15
+
16
+ test('times out when java process does not print the ready pattern', async () => {
17
+ // Spawn a process that never prints anything meaningful
18
+ await expect(
19
+ launchGateway({
20
+ classpath: '.',
21
+ mainClass: 'DoesNotExist',
22
+ readyPattern: /NEVER_MATCHES/,
23
+ timeout: 500,
24
+ })
25
+ ).rejects.toThrow(/timed out|exited with code/);
26
+ });
27
+ });