@olane/o-client-limited 0.7.51 → 0.7.53
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/dist/src/connection/o-limited-connection.d.ts +12 -21
- package/dist/src/connection/o-limited-connection.d.ts.map +1 -1
- package/dist/src/connection/o-limited-connection.js +31 -65
- package/dist/src/connection/o-limited.stream-manager.d.ts +79 -0
- package/dist/src/connection/o-limited.stream-manager.d.ts.map +1 -0
- package/dist/src/connection/o-limited.stream-manager.js +234 -0
- package/dist/test/bidirectional-communication.spec.d.ts +2 -0
- package/dist/test/bidirectional-communication.spec.d.ts.map +1 -0
- package/dist/test/bidirectional-communication.spec.js +265 -0
- package/dist/test/helpers/index.d.ts +2 -3
- package/dist/test/helpers/index.d.ts.map +1 -1
- package/dist/test/helpers/index.js +2 -3
- package/dist/test/helpers/limited-test-tool.d.ts +41 -0
- package/dist/test/helpers/limited-test-tool.d.ts.map +1 -0
- package/dist/test/helpers/limited-test-tool.js +98 -0
- package/dist/test/helpers/receiver-test-tool.d.ts +34 -0
- package/dist/test/helpers/receiver-test-tool.d.ts.map +1 -0
- package/dist/test/helpers/receiver-test-tool.js +67 -0
- package/dist/test/limited-connection-lifecycle.spec.d.ts +2 -0
- package/dist/test/limited-connection-lifecycle.spec.d.ts.map +1 -0
- package/dist/test/limited-connection-lifecycle.spec.js +209 -0
- package/dist/test/limited-stream-manager.spec.d.ts +2 -0
- package/dist/test/limited-stream-manager.spec.d.ts.map +1 -0
- package/dist/test/limited-stream-manager.spec.js +222 -0
- package/dist/test/reader-stream-recovery.spec.d.ts +2 -0
- package/dist/test/reader-stream-recovery.spec.d.ts.map +1 -0
- package/dist/test/reader-stream-recovery.spec.js +188 -0
- package/package.json +8 -8
- package/dist/test/configuration.spec.d.ts +0 -1
- package/dist/test/configuration.spec.d.ts.map +0 -1
- package/dist/test/configuration.spec.js +0 -335
- package/dist/test/error-handling.spec.d.ts +0 -2
- package/dist/test/error-handling.spec.d.ts.map +0 -1
- package/dist/test/error-handling.spec.js +0 -378
- package/dist/test/helpers/mock-p2p-connection.d.ts +0 -38
- package/dist/test/helpers/mock-p2p-connection.d.ts.map +0 -1
- package/dist/test/helpers/mock-p2p-connection.js +0 -66
- package/dist/test/helpers/mock-stream-handler.d.ts +0 -43
- package/dist/test/helpers/mock-stream-handler.d.ts.map +0 -1
- package/dist/test/helpers/mock-stream-handler.js +0 -71
- package/dist/test/helpers/mock-stream.d.ts +0 -46
- package/dist/test/helpers/mock-stream.d.ts.map +0 -1
- package/dist/test/helpers/mock-stream.js +0 -59
- package/dist/test/method.spec.d.ts +0 -1
- package/dist/test/method.spec.d.ts.map +0 -1
- package/dist/test/method.spec.js +0 -29
- package/dist/test/stream-reuse.spec.d.ts +0 -1
- package/dist/test/stream-reuse.spec.d.ts.map +0 -1
- package/dist/test/stream-reuse.spec.js +0 -267
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from '@jest/globals';
|
|
2
|
-
import { oLimitedConnection } from '../src/connection/o-limited-connection.js';
|
|
3
|
-
import { oNodeAddress } from '@olane/o-node';
|
|
4
|
-
import { createMockP2PConnection, createMockStream, createMockStreamHandler, } from './helpers/index.js';
|
|
5
|
-
describe('Error Handling & Resource Management', () => {
|
|
6
|
-
const testProtocol = '/test/1.0.0';
|
|
7
|
-
const testAddress = new oNodeAddress('o://test');
|
|
8
|
-
const nextHopAddress = new oNodeAddress('o://next-hop');
|
|
9
|
-
describe('Connection State Validation', () => {
|
|
10
|
-
it('should throw error when p2pConnection is not open', async () => {
|
|
11
|
-
const closedConnection = createMockP2PConnection('test-conn', 'closed');
|
|
12
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
13
|
-
const connection = new oLimitedConnection({
|
|
14
|
-
nextHopAddress,
|
|
15
|
-
address: testAddress,
|
|
16
|
-
p2pConnection: closedConnection,
|
|
17
|
-
callerAddress: testAddress,
|
|
18
|
-
runOnLimitedConnection: true,
|
|
19
|
-
});
|
|
20
|
-
connection.streamHandler = mockStreamHandler;
|
|
21
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
22
|
-
// Should throw when trying to create stream on closed connection
|
|
23
|
-
await expect(connection.getOrCreateStream()).rejects.toThrow();
|
|
24
|
-
});
|
|
25
|
-
it('should throw error when p2pConnection is closing', async () => {
|
|
26
|
-
const closingConnection = createMockP2PConnection('test-conn', 'closing');
|
|
27
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
28
|
-
const connection = new oLimitedConnection({
|
|
29
|
-
nextHopAddress,
|
|
30
|
-
address: testAddress,
|
|
31
|
-
p2pConnection: closingConnection,
|
|
32
|
-
callerAddress: testAddress,
|
|
33
|
-
runOnLimitedConnection: true,
|
|
34
|
-
});
|
|
35
|
-
connection.streamHandler = mockStreamHandler;
|
|
36
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
37
|
-
await expect(connection.getOrCreateStream()).rejects.toThrow();
|
|
38
|
-
});
|
|
39
|
-
it('should succeed when p2pConnection is open', async () => {
|
|
40
|
-
const openConnection = createMockP2PConnection('test-conn', 'open');
|
|
41
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
42
|
-
const connection = new oLimitedConnection({
|
|
43
|
-
nextHopAddress,
|
|
44
|
-
address: testAddress,
|
|
45
|
-
p2pConnection: openConnection,
|
|
46
|
-
callerAddress: testAddress,
|
|
47
|
-
runOnLimitedConnection: true,
|
|
48
|
-
});
|
|
49
|
-
connection.streamHandler = mockStreamHandler;
|
|
50
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
51
|
-
// Should not throw
|
|
52
|
-
const stream = await connection.getOrCreateStream();
|
|
53
|
-
expect(stream).toBeDefined();
|
|
54
|
-
expect(stream.p2pStream.status).toBe('open');
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
describe('Stream Creation Failures', () => {
|
|
58
|
-
it('should propagate error when newStream() fails', async () => {
|
|
59
|
-
const mockConnection = createMockP2PConnection('test-conn', 'open');
|
|
60
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
61
|
-
// Override newStream to throw an error
|
|
62
|
-
mockConnection.newStream = async () => {
|
|
63
|
-
throw new Error('Stream creation failed');
|
|
64
|
-
};
|
|
65
|
-
const connection = new oLimitedConnection({
|
|
66
|
-
nextHopAddress,
|
|
67
|
-
address: testAddress,
|
|
68
|
-
p2pConnection: mockConnection,
|
|
69
|
-
callerAddress: testAddress,
|
|
70
|
-
runOnLimitedConnection: true,
|
|
71
|
-
});
|
|
72
|
-
connection.streamHandler = mockStreamHandler;
|
|
73
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
74
|
-
await expect(connection.getOrCreateStream()).rejects.toThrow('Stream creation failed');
|
|
75
|
-
});
|
|
76
|
-
it('should handle protocol negotiation failure', async () => {
|
|
77
|
-
const mockConnection = createMockP2PConnection('test-conn', 'open');
|
|
78
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
79
|
-
mockConnection.newStream = async () => {
|
|
80
|
-
throw new Error('Protocol not supported');
|
|
81
|
-
};
|
|
82
|
-
const connection = new oLimitedConnection({
|
|
83
|
-
nextHopAddress,
|
|
84
|
-
address: testAddress,
|
|
85
|
-
p2pConnection: mockConnection,
|
|
86
|
-
callerAddress: testAddress,
|
|
87
|
-
runOnLimitedConnection: true,
|
|
88
|
-
});
|
|
89
|
-
connection.streamHandler = mockStreamHandler;
|
|
90
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
91
|
-
await expect(connection.getOrCreateStream()).rejects.toThrow('Protocol not supported');
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
describe('Concurrent Operations', () => {
|
|
95
|
-
it('should handle multiple concurrent getOrCreateStream calls', async () => {
|
|
96
|
-
const mockConnection = createMockP2PConnection('test-conn', 'open');
|
|
97
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
98
|
-
const connection = new oLimitedConnection({
|
|
99
|
-
nextHopAddress,
|
|
100
|
-
address: testAddress,
|
|
101
|
-
p2pConnection: mockConnection,
|
|
102
|
-
callerAddress: testAddress,
|
|
103
|
-
runOnLimitedConnection: true,
|
|
104
|
-
});
|
|
105
|
-
connection.streamHandler = mockStreamHandler;
|
|
106
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
107
|
-
// Make 10 concurrent requests
|
|
108
|
-
const promises = Array.from({ length: 10 }, () => connection.getOrCreateStream());
|
|
109
|
-
const streams = await Promise.all(promises);
|
|
110
|
-
// All should succeed
|
|
111
|
-
expect(streams.length).toBe(10);
|
|
112
|
-
streams.forEach((stream) => {
|
|
113
|
-
expect(stream).toBeDefined();
|
|
114
|
-
expect(stream.p2pStream.protocol).toBe(testProtocol);
|
|
115
|
-
});
|
|
116
|
-
// With stream reuse, they should all get the same stream
|
|
117
|
-
// (since the first one creates it, rest reuse it)
|
|
118
|
-
const firstStream = streams[0];
|
|
119
|
-
streams.forEach((stream) => {
|
|
120
|
-
expect(stream).toBe(firstStream);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
it('should handle concurrent postTransmit calls without errors', async () => {
|
|
124
|
-
const mockConnection = createMockP2PConnection('test-conn', 'open');
|
|
125
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
126
|
-
const connection = new oLimitedConnection({
|
|
127
|
-
nextHopAddress,
|
|
128
|
-
address: testAddress,
|
|
129
|
-
p2pConnection: mockConnection,
|
|
130
|
-
callerAddress: testAddress,
|
|
131
|
-
runOnLimitedConnection: true,
|
|
132
|
-
});
|
|
133
|
-
connection.streamHandler = mockStreamHandler;
|
|
134
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
135
|
-
const stream = await connection.getOrCreateStream();
|
|
136
|
-
// Call postTransmit multiple times concurrently
|
|
137
|
-
const promises = Array.from({ length: 5 }, () => connection.postTransmit(stream));
|
|
138
|
-
// All should complete without error
|
|
139
|
-
await expect(Promise.all(promises)).resolves.not.toThrow();
|
|
140
|
-
// Stream should still be open (reuse policy)
|
|
141
|
-
expect(stream.status).toBe('open');
|
|
142
|
-
});
|
|
143
|
-
it('should maintain stream state consistency under concurrent access', async () => {
|
|
144
|
-
const mockConnection = createMockP2PConnection('test-conn', 'open');
|
|
145
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
146
|
-
const connection = new oLimitedConnection({
|
|
147
|
-
nextHopAddress,
|
|
148
|
-
address: testAddress,
|
|
149
|
-
p2pConnection: mockConnection,
|
|
150
|
-
callerAddress: testAddress,
|
|
151
|
-
runOnLimitedConnection: true,
|
|
152
|
-
});
|
|
153
|
-
connection.streamHandler = mockStreamHandler;
|
|
154
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
155
|
-
// Interleave getOrCreateStream and postTransmit calls
|
|
156
|
-
const stream1 = await connection.getOrCreateStream();
|
|
157
|
-
const postTransmit1 = connection.postTransmit(stream1);
|
|
158
|
-
const stream2Promise = connection.getOrCreateStream();
|
|
159
|
-
await postTransmit1;
|
|
160
|
-
const stream2 = await stream2Promise;
|
|
161
|
-
// Should reuse the same stream
|
|
162
|
-
expect(stream2).toBe(stream1);
|
|
163
|
-
expect(stream2.p2pStream.status).toBe('open');
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
describe('Stream State Edge Cases', () => {
|
|
167
|
-
it('should not reuse streams that become invalid after selection', async () => {
|
|
168
|
-
const mockConnection = createMockP2PConnection('test-conn', 'open');
|
|
169
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
170
|
-
// Add a stream that starts open
|
|
171
|
-
const stream1 = createMockStream('stream-1', testProtocol, {
|
|
172
|
-
status: 'open',
|
|
173
|
-
writeStatus: 'writable',
|
|
174
|
-
remoteReadStatus: 'readable',
|
|
175
|
-
});
|
|
176
|
-
mockConnection.addStream(stream1);
|
|
177
|
-
const connection = new oLimitedConnection({
|
|
178
|
-
nextHopAddress,
|
|
179
|
-
address: testAddress,
|
|
180
|
-
p2pConnection: mockConnection,
|
|
181
|
-
callerAddress: testAddress,
|
|
182
|
-
runOnLimitedConnection: true,
|
|
183
|
-
});
|
|
184
|
-
connection.streamHandler = mockStreamHandler;
|
|
185
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
186
|
-
// First call gets the stream
|
|
187
|
-
const firstStream = await connection.getOrCreateStream();
|
|
188
|
-
expect(firstStream).toBe(stream1);
|
|
189
|
-
// Simulate stream being reset externally
|
|
190
|
-
stream1.reset();
|
|
191
|
-
// Next call should create a new stream (not reuse the reset one)
|
|
192
|
-
const secondStream = await connection.getOrCreateStream();
|
|
193
|
-
expect(secondStream).not.toBe(stream1);
|
|
194
|
-
expect(secondStream.p2pStream.status).toBe('open');
|
|
195
|
-
});
|
|
196
|
-
it('should handle stream that becomes non-writable', async () => {
|
|
197
|
-
const mockConnection = createMockP2PConnection('test-conn', 'open');
|
|
198
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
199
|
-
const stream = createMockStream('stream-1', testProtocol, {
|
|
200
|
-
status: 'open',
|
|
201
|
-
writeStatus: 'writable',
|
|
202
|
-
remoteReadStatus: 'readable',
|
|
203
|
-
});
|
|
204
|
-
mockConnection.addStream(stream);
|
|
205
|
-
const connection = new oLimitedConnection({
|
|
206
|
-
nextHopAddress,
|
|
207
|
-
address: testAddress,
|
|
208
|
-
p2pConnection: mockConnection,
|
|
209
|
-
callerAddress: testAddress,
|
|
210
|
-
runOnLimitedConnection: true,
|
|
211
|
-
});
|
|
212
|
-
connection.streamHandler = mockStreamHandler;
|
|
213
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
214
|
-
// Simulate stream becoming non-writable
|
|
215
|
-
stream.writeStatus = 'closed';
|
|
216
|
-
// Should create new stream instead of using non-writable one
|
|
217
|
-
const newStream = await connection.getOrCreateStream();
|
|
218
|
-
expect(newStream).not.toBe(stream);
|
|
219
|
-
expect(newStream.p2pStream.writeStatus).toBe('writable');
|
|
220
|
-
});
|
|
221
|
-
it('should handle stream where remote side closes read', async () => {
|
|
222
|
-
const mockConnection = createMockP2PConnection('test-conn', 'open');
|
|
223
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
224
|
-
const stream = createMockStream('stream-1', testProtocol, {
|
|
225
|
-
status: 'open',
|
|
226
|
-
writeStatus: 'writable',
|
|
227
|
-
remoteReadStatus: 'readable',
|
|
228
|
-
});
|
|
229
|
-
mockConnection.addStream(stream);
|
|
230
|
-
const connection = new oLimitedConnection({
|
|
231
|
-
nextHopAddress,
|
|
232
|
-
address: testAddress,
|
|
233
|
-
p2pConnection: mockConnection,
|
|
234
|
-
callerAddress: testAddress,
|
|
235
|
-
runOnLimitedConnection: true,
|
|
236
|
-
});
|
|
237
|
-
connection.streamHandler = mockStreamHandler;
|
|
238
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
239
|
-
// Simulate remote closing read side
|
|
240
|
-
stream.remoteReadStatus = 'closed';
|
|
241
|
-
// Should create new stream
|
|
242
|
-
const newStream = await connection.getOrCreateStream();
|
|
243
|
-
expect(newStream).not.toBe(stream);
|
|
244
|
-
expect(newStream.p2pStream.remoteReadStatus).toBe('readable');
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
describe('Resource Cleanup', () => {
|
|
248
|
-
it('should not leak streams when reusing', async () => {
|
|
249
|
-
const mockConnection = createMockP2PConnection('test-conn', 'open');
|
|
250
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
251
|
-
const connection = new oLimitedConnection({
|
|
252
|
-
nextHopAddress,
|
|
253
|
-
address: testAddress,
|
|
254
|
-
p2pConnection: mockConnection,
|
|
255
|
-
callerAddress: testAddress,
|
|
256
|
-
runOnLimitedConnection: true,
|
|
257
|
-
});
|
|
258
|
-
connection.streamHandler = mockStreamHandler;
|
|
259
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
260
|
-
// Create and reuse stream multiple times
|
|
261
|
-
const stream1 = await connection.getOrCreateStream();
|
|
262
|
-
await connection.postTransmit(stream1);
|
|
263
|
-
const stream2 = await connection.getOrCreateStream();
|
|
264
|
-
await connection.postTransmit(stream2);
|
|
265
|
-
const stream3 = await connection.getOrCreateStream();
|
|
266
|
-
await connection.postTransmit(stream3);
|
|
267
|
-
// Should have only created ONE stream (reused for all)
|
|
268
|
-
expect(mockConnection.streams.length).toBe(1);
|
|
269
|
-
expect(stream1).toBe(stream2);
|
|
270
|
-
expect(stream2).toBe(stream3);
|
|
271
|
-
// Stream should still be open (not leaked, not closed)
|
|
272
|
-
expect(stream1.status).toBe('open');
|
|
273
|
-
expect(stream1.abortCallCount).toBe(0);
|
|
274
|
-
});
|
|
275
|
-
it('should track streams created but not closed prematurely', async () => {
|
|
276
|
-
const mockConnection = createMockP2PConnection('test-conn', 'open');
|
|
277
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
278
|
-
const connection = new oLimitedConnection({
|
|
279
|
-
nextHopAddress,
|
|
280
|
-
address: testAddress,
|
|
281
|
-
p2pConnection: mockConnection,
|
|
282
|
-
callerAddress: testAddress,
|
|
283
|
-
runOnLimitedConnection: true,
|
|
284
|
-
});
|
|
285
|
-
connection.streamHandler = mockStreamHandler;
|
|
286
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
287
|
-
// Make 100 requests (should all reuse the same stream)
|
|
288
|
-
for (let i = 0; i < 100; i++) {
|
|
289
|
-
const stream = await connection.getOrCreateStream();
|
|
290
|
-
await connection.postTransmit(stream);
|
|
291
|
-
}
|
|
292
|
-
// Should still only have 1 stream
|
|
293
|
-
expect(mockConnection.streams.length).toBe(1);
|
|
294
|
-
const stream = mockConnection.streams[0];
|
|
295
|
-
expect(stream.status).toBe('open');
|
|
296
|
-
// Stream should NEVER have been aborted/closed
|
|
297
|
-
expect(stream.abortCallCount).toBe(0);
|
|
298
|
-
expect(stream.closeCallCount).toBe(0);
|
|
299
|
-
});
|
|
300
|
-
it('should handle postTransmit being called without prior getOrCreateStream', async () => {
|
|
301
|
-
const mockConnection = createMockP2PConnection('test-conn', 'open');
|
|
302
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
303
|
-
const connection = new oLimitedConnection({
|
|
304
|
-
nextHopAddress,
|
|
305
|
-
address: testAddress,
|
|
306
|
-
p2pConnection: mockConnection,
|
|
307
|
-
callerAddress: testAddress,
|
|
308
|
-
runOnLimitedConnection: true,
|
|
309
|
-
});
|
|
310
|
-
connection.streamHandler = mockStreamHandler;
|
|
311
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
312
|
-
// Create a standalone stream
|
|
313
|
-
const externalStream = createMockStream('external', testProtocol);
|
|
314
|
-
// Call postTransmit with it (shouldn't error)
|
|
315
|
-
await expect(connection.postTransmit(externalStream)).resolves.not.toThrow();
|
|
316
|
-
// Verify close was called with reuse policy
|
|
317
|
-
expect(mockStreamHandler.closeCalls.length).toBe(1);
|
|
318
|
-
expect(mockStreamHandler.getLastCloseConfig()?.reusePolicy).toBe('reuse');
|
|
319
|
-
});
|
|
320
|
-
});
|
|
321
|
-
describe('Environment Variable Edge Cases', () => {
|
|
322
|
-
it('should handle very large MAX_OUTBOUND_STREAMS values', async () => {
|
|
323
|
-
process.env.MAX_OUTBOUND_STREAMS = '999999';
|
|
324
|
-
const mockConnection = createMockP2PConnection('test-conn', 'open');
|
|
325
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
326
|
-
const connection = new oLimitedConnection({
|
|
327
|
-
nextHopAddress,
|
|
328
|
-
address: testAddress,
|
|
329
|
-
p2pConnection: mockConnection,
|
|
330
|
-
callerAddress: testAddress,
|
|
331
|
-
runOnLimitedConnection: true,
|
|
332
|
-
});
|
|
333
|
-
connection.streamHandler = mockStreamHandler;
|
|
334
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
335
|
-
await connection.getOrCreateStream();
|
|
336
|
-
const config = mockStreamHandler.getLastGetOrCreateConfig();
|
|
337
|
-
expect(config?.maxOutboundStreams).toBe(999999);
|
|
338
|
-
delete process.env.MAX_OUTBOUND_STREAMS;
|
|
339
|
-
});
|
|
340
|
-
it('should handle MAX_OUTBOUND_STREAMS = 0', async () => {
|
|
341
|
-
process.env.MAX_OUTBOUND_STREAMS = '0';
|
|
342
|
-
const mockConnection = createMockP2PConnection('test-conn', 'open');
|
|
343
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
344
|
-
const connection = new oLimitedConnection({
|
|
345
|
-
nextHopAddress,
|
|
346
|
-
address: testAddress,
|
|
347
|
-
p2pConnection: mockConnection,
|
|
348
|
-
callerAddress: testAddress,
|
|
349
|
-
runOnLimitedConnection: true,
|
|
350
|
-
});
|
|
351
|
-
connection.streamHandler = mockStreamHandler;
|
|
352
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
353
|
-
await connection.getOrCreateStream();
|
|
354
|
-
const config = mockStreamHandler.getLastGetOrCreateConfig();
|
|
355
|
-
expect(config?.maxOutboundStreams).toBe(0);
|
|
356
|
-
delete process.env.MAX_OUTBOUND_STREAMS;
|
|
357
|
-
});
|
|
358
|
-
it('should handle negative MAX_OUTBOUND_STREAMS values', async () => {
|
|
359
|
-
process.env.MAX_OUTBOUND_STREAMS = '-100';
|
|
360
|
-
const mockConnection = createMockP2PConnection('test-conn', 'open');
|
|
361
|
-
const mockStreamHandler = createMockStreamHandler();
|
|
362
|
-
const connection = new oLimitedConnection({
|
|
363
|
-
nextHopAddress,
|
|
364
|
-
address: testAddress,
|
|
365
|
-
p2pConnection: mockConnection,
|
|
366
|
-
callerAddress: testAddress,
|
|
367
|
-
runOnLimitedConnection: true,
|
|
368
|
-
});
|
|
369
|
-
connection.streamHandler = mockStreamHandler;
|
|
370
|
-
connection.nextHopAddress = { protocol: testProtocol };
|
|
371
|
-
await connection.getOrCreateStream();
|
|
372
|
-
const config = mockStreamHandler.getLastGetOrCreateConfig();
|
|
373
|
-
// parseInt('-100') returns -100
|
|
374
|
-
expect(config?.maxOutboundStreams).toBe(-100);
|
|
375
|
-
delete process.env.MAX_OUTBOUND_STREAMS;
|
|
376
|
-
});
|
|
377
|
-
});
|
|
378
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { Connection } from '@olane/o-config';
|
|
2
|
-
import { MockStream } from './mock-stream.js';
|
|
3
|
-
/**
|
|
4
|
-
* Mock P2P Connection for testing
|
|
5
|
-
*/
|
|
6
|
-
export declare class MockP2PConnection implements Partial<Connection> {
|
|
7
|
-
id: string;
|
|
8
|
-
status: 'open' | 'closing' | 'closed';
|
|
9
|
-
streams: any[];
|
|
10
|
-
private streamIdCounter;
|
|
11
|
-
constructor(id?: string, status?: 'open' | 'closing' | 'closed');
|
|
12
|
-
/**
|
|
13
|
-
* Mock newStream method that creates a new MockStream
|
|
14
|
-
*/
|
|
15
|
-
newStream(protocol: string | string[], options?: any): Promise<any>;
|
|
16
|
-
/**
|
|
17
|
-
* Add a pre-existing stream to this connection
|
|
18
|
-
*/
|
|
19
|
-
addStream(stream: MockStream): void;
|
|
20
|
-
/**
|
|
21
|
-
* Get all streams matching criteria
|
|
22
|
-
*/
|
|
23
|
-
getStreams(criteria?: {
|
|
24
|
-
status?: string;
|
|
25
|
-
protocol?: string;
|
|
26
|
-
writeStatus?: string;
|
|
27
|
-
remoteReadStatus?: string;
|
|
28
|
-
}): MockStream[];
|
|
29
|
-
/**
|
|
30
|
-
* Close the connection and all streams
|
|
31
|
-
*/
|
|
32
|
-
close(options?: any): Promise<void>;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Create a mock P2P connection with optional pre-configured streams
|
|
36
|
-
*/
|
|
37
|
-
export declare function createMockP2PConnection(id?: string, status?: 'open' | 'closing' | 'closed', streams?: MockStream[]): MockP2PConnection;
|
|
38
|
-
//# sourceMappingURL=mock-p2p-connection.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mock-p2p-connection.d.ts","sourceRoot":"","sources":["../../../test/helpers/mock-p2p-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAoB,MAAM,kBAAkB,CAAC;AAEhE;;GAEG;AACH,qBAAa,iBAAkB,YAAW,OAAO,CAAC,UAAU,CAAC;IACpD,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,GAAG,EAAE,CAAM;IAC3B,OAAO,CAAC,eAAe,CAAK;gBAG1B,EAAE,GAAE,MAA0B,EAC9B,MAAM,GAAE,MAAM,GAAG,SAAS,GAAG,QAAiB;IAMhD;;OAEG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAazE;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAInC;;OAEG;IACH,UAAU,CAAC,QAAQ,CAAC,EAAE;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,GAAG,UAAU,EAAE;IAoBhB;;OAEG;IACG,KAAK,CAAC,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;CAI1C;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,GAAE,MAA0B,EAC9B,MAAM,GAAE,MAAM,GAAG,SAAS,GAAG,QAAiB,EAC9C,OAAO,GAAE,UAAU,EAAO,GACzB,iBAAiB,CAInB"}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { createMockStream } from './mock-stream.js';
|
|
2
|
-
/**
|
|
3
|
-
* Mock P2P Connection for testing
|
|
4
|
-
*/
|
|
5
|
-
export class MockP2PConnection {
|
|
6
|
-
constructor(id = 'test-connection', status = 'open') {
|
|
7
|
-
this.streams = [];
|
|
8
|
-
this.streamIdCounter = 0;
|
|
9
|
-
this.id = id;
|
|
10
|
-
this.status = status;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Mock newStream method that creates a new MockStream
|
|
14
|
-
*/
|
|
15
|
-
async newStream(protocol, options) {
|
|
16
|
-
if (this.status !== 'open') {
|
|
17
|
-
throw new Error(`Connection is ${this.status}, cannot create new stream`);
|
|
18
|
-
}
|
|
19
|
-
const protocolStr = Array.isArray(protocol) ? protocol[0] : protocol;
|
|
20
|
-
const streamId = `stream-${this.id}-${this.streamIdCounter++}`;
|
|
21
|
-
const stream = createMockStream(streamId, protocolStr);
|
|
22
|
-
this.streams.push(stream);
|
|
23
|
-
return stream;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Add a pre-existing stream to this connection
|
|
27
|
-
*/
|
|
28
|
-
addStream(stream) {
|
|
29
|
-
this.streams.push(stream);
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Get all streams matching criteria
|
|
33
|
-
*/
|
|
34
|
-
getStreams(criteria) {
|
|
35
|
-
if (!criteria) {
|
|
36
|
-
return this.streams;
|
|
37
|
-
}
|
|
38
|
-
return this.streams.filter((stream) => {
|
|
39
|
-
if (criteria.status && stream.status !== criteria.status)
|
|
40
|
-
return false;
|
|
41
|
-
if (criteria.protocol && stream.protocol !== criteria.protocol)
|
|
42
|
-
return false;
|
|
43
|
-
if (criteria.writeStatus && stream.writeStatus !== criteria.writeStatus)
|
|
44
|
-
return false;
|
|
45
|
-
if (criteria.remoteReadStatus &&
|
|
46
|
-
stream.remoteReadStatus !== criteria.remoteReadStatus)
|
|
47
|
-
return false;
|
|
48
|
-
return true;
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Close the connection and all streams
|
|
53
|
-
*/
|
|
54
|
-
async close(options) {
|
|
55
|
-
this.status = 'closed';
|
|
56
|
-
await Promise.all(this.streams.map((stream) => stream.close()));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Create a mock P2P connection with optional pre-configured streams
|
|
61
|
-
*/
|
|
62
|
-
export function createMockP2PConnection(id = 'test-connection', status = 'open', streams = []) {
|
|
63
|
-
const connection = new MockP2PConnection(id, status);
|
|
64
|
-
streams.forEach((stream) => connection.addStream(stream));
|
|
65
|
-
return connection;
|
|
66
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { Stream, Connection } from '@olane/o-config';
|
|
2
|
-
import type { StreamHandlerConfig } from '@olane/o-node/src/connection/stream-handler.config.js';
|
|
3
|
-
/**
|
|
4
|
-
* Mock StreamHandler for testing stream reuse behavior
|
|
5
|
-
*/
|
|
6
|
-
export declare class MockStreamHandler {
|
|
7
|
-
getOrCreateStreamCalls: Array<{
|
|
8
|
-
connection: Connection;
|
|
9
|
-
protocol: string;
|
|
10
|
-
config: StreamHandlerConfig;
|
|
11
|
-
}>;
|
|
12
|
-
closeCalls: Array<{
|
|
13
|
-
stream: Stream;
|
|
14
|
-
config: StreamHandlerConfig | undefined;
|
|
15
|
-
}>;
|
|
16
|
-
/**
|
|
17
|
-
* Mock getOrCreateStream implementation
|
|
18
|
-
* Mimics the real behavior of checking for reusable streams
|
|
19
|
-
*/
|
|
20
|
-
getOrCreateStream(connection: Connection, protocol: string, config: StreamHandlerConfig): Promise<Stream>;
|
|
21
|
-
/**
|
|
22
|
-
* Mock close implementation
|
|
23
|
-
* If reusePolicy is 'reuse', doesn't actually close the stream
|
|
24
|
-
*/
|
|
25
|
-
close(stream: Stream, config?: StreamHandlerConfig): Promise<void>;
|
|
26
|
-
/**
|
|
27
|
-
* Reset call tracking
|
|
28
|
-
*/
|
|
29
|
-
reset(): void;
|
|
30
|
-
/**
|
|
31
|
-
* Get the last getOrCreateStream call config
|
|
32
|
-
*/
|
|
33
|
-
getLastGetOrCreateConfig(): StreamHandlerConfig | undefined;
|
|
34
|
-
/**
|
|
35
|
-
* Get the last close call config
|
|
36
|
-
*/
|
|
37
|
-
getLastCloseConfig(): StreamHandlerConfig | undefined;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Create a mock stream handler
|
|
41
|
-
*/
|
|
42
|
-
export declare function createMockStreamHandler(): MockStreamHandler;
|
|
43
|
-
//# sourceMappingURL=mock-stream-handler.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mock-stream-handler.d.ts","sourceRoot":"","sources":["../../../test/helpers/mock-stream-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uDAAuD,CAAC;AAIjG;;GAEG;AACH,qBAAa,iBAAiB;IACrB,sBAAsB,EAAE,KAAK,CAAC;QACnC,UAAU,EAAE,UAAU,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,mBAAmB,CAAC;KAC7B,CAAC,CAAM;IAED,UAAU,EAAE,KAAK,CAAC;QACvB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,mBAAmB,GAAG,SAAS,CAAC;KACzC,CAAC,CAAM;IAER;;;OAGG;IACG,iBAAiB,CACrB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,MAAM,CAAC;IAyBlB;;;OAGG;IACG,KAAK,CACT,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC;IAchB;;OAEG;IACH,KAAK,IAAI,IAAI;IAKb;;OAEG;IACH,wBAAwB,IAAI,mBAAmB,GAAG,SAAS;IAK3D;;OAEG;IACH,kBAAkB,IAAI,mBAAmB,GAAG,SAAS;CAGtD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,iBAAiB,CAE3D"}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mock StreamHandler for testing stream reuse behavior
|
|
3
|
-
*/
|
|
4
|
-
export class MockStreamHandler {
|
|
5
|
-
constructor() {
|
|
6
|
-
this.getOrCreateStreamCalls = [];
|
|
7
|
-
this.closeCalls = [];
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Mock getOrCreateStream implementation
|
|
11
|
-
* Mimics the real behavior of checking for reusable streams
|
|
12
|
-
*/
|
|
13
|
-
async getOrCreateStream(connection, protocol, config) {
|
|
14
|
-
// Track the call
|
|
15
|
-
this.getOrCreateStreamCalls.push({ connection, protocol, config });
|
|
16
|
-
const mockConnection = connection;
|
|
17
|
-
// If reuse policy is 'reuse', try to find an existing stream
|
|
18
|
-
if (config.reusePolicy === 'reuse') {
|
|
19
|
-
const reusableStream = mockConnection.streams.find((stream) => stream.protocol === protocol &&
|
|
20
|
-
stream.status === 'open' &&
|
|
21
|
-
stream.writeStatus === 'writable' &&
|
|
22
|
-
stream.remoteReadStatus === 'readable');
|
|
23
|
-
if (reusableStream) {
|
|
24
|
-
return reusableStream;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
// Otherwise create a new stream
|
|
28
|
-
return mockConnection.newStream(protocol);
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Mock close implementation
|
|
32
|
-
* If reusePolicy is 'reuse', doesn't actually close the stream
|
|
33
|
-
*/
|
|
34
|
-
async close(stream, config) {
|
|
35
|
-
// Track the call
|
|
36
|
-
this.closeCalls.push({ stream, config });
|
|
37
|
-
// If reuse policy is 'reuse', don't close the stream
|
|
38
|
-
if (config?.reusePolicy === 'reuse') {
|
|
39
|
-
return; // Stream stays open for reuse
|
|
40
|
-
}
|
|
41
|
-
// Otherwise, close the stream
|
|
42
|
-
const mockStream = stream;
|
|
43
|
-
await mockStream.close();
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Reset call tracking
|
|
47
|
-
*/
|
|
48
|
-
reset() {
|
|
49
|
-
this.getOrCreateStreamCalls = [];
|
|
50
|
-
this.closeCalls = [];
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Get the last getOrCreateStream call config
|
|
54
|
-
*/
|
|
55
|
-
getLastGetOrCreateConfig() {
|
|
56
|
-
return this.getOrCreateStreamCalls[this.getOrCreateStreamCalls.length - 1]
|
|
57
|
-
?.config;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Get the last close call config
|
|
61
|
-
*/
|
|
62
|
-
getLastCloseConfig() {
|
|
63
|
-
return this.closeCalls[this.closeCalls.length - 1]?.config;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Create a mock stream handler
|
|
68
|
-
*/
|
|
69
|
-
export function createMockStreamHandler() {
|
|
70
|
-
return new MockStreamHandler();
|
|
71
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { Stream } from '@olane/o-config';
|
|
3
|
-
import { EventEmitter } from 'events';
|
|
4
|
-
/**
|
|
5
|
-
* Mock Stream implementation for testing
|
|
6
|
-
*/
|
|
7
|
-
export declare class MockStream extends EventEmitter implements Partial<Stream> {
|
|
8
|
-
id: string;
|
|
9
|
-
protocol: string;
|
|
10
|
-
status: 'open' | 'closing' | 'closed' | 'aborted' | 'reset';
|
|
11
|
-
writeStatus: 'writable' | 'closing' | 'closed';
|
|
12
|
-
readStatus: 'readable' | 'closing' | 'closed';
|
|
13
|
-
remoteReadStatus: 'readable' | 'closing' | 'closed';
|
|
14
|
-
timeline: {
|
|
15
|
-
open: number;
|
|
16
|
-
close?: number;
|
|
17
|
-
};
|
|
18
|
-
direction: 'inbound' | 'outbound';
|
|
19
|
-
abortCallCount: number;
|
|
20
|
-
closeCallCount: number;
|
|
21
|
-
constructor(id: string, protocol: string, options?: {
|
|
22
|
-
status?: 'open' | 'closing' | 'closed' | 'aborted' | 'reset';
|
|
23
|
-
writeStatus?: 'writable' | 'closing' | 'closed';
|
|
24
|
-
readStatus?: 'readable' | 'closing' | 'closed';
|
|
25
|
-
remoteReadStatus?: 'readable' | 'closing' | 'closed';
|
|
26
|
-
direction?: 'inbound' | 'outbound';
|
|
27
|
-
});
|
|
28
|
-
abort(err?: Error): void;
|
|
29
|
-
close(options?: any): Promise<void>;
|
|
30
|
-
reset(): void;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Create a mock stream with default open/writable/readable state
|
|
34
|
-
*/
|
|
35
|
-
export declare function createMockStream(id?: string, protocol?: string, options?: {
|
|
36
|
-
status?: 'open' | 'closing' | 'closed' | 'aborted' | 'reset';
|
|
37
|
-
writeStatus?: 'writable' | 'closing' | 'closed';
|
|
38
|
-
readStatus?: 'readable' | 'closing' | 'closed';
|
|
39
|
-
remoteReadStatus?: 'readable' | 'closing' | 'closed';
|
|
40
|
-
direction?: 'inbound' | 'outbound';
|
|
41
|
-
}): MockStream;
|
|
42
|
-
/**
|
|
43
|
-
* Create multiple mock streams
|
|
44
|
-
*/
|
|
45
|
-
export declare function createMockStreams(count: number, protocol?: string): MockStream[];
|
|
46
|
-
//# sourceMappingURL=mock-stream.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mock-stream.d.ts","sourceRoot":"","sources":["../../../test/helpers/mock-stream.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC;;GAEG;AACH,qBAAa,UAAW,SAAQ,YAAa,YAAW,OAAO,CAAC,MAAM,CAAC;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;IAC5D,WAAW,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC/C,UAAU,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC9C,gBAAgB,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IACpD,QAAQ,EAAE;QACf,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACK,SAAS,EAAE,SAAS,GAAG,UAAU,CAAC;IAClC,cAAc,EAAE,MAAM,CAAK;IAC3B,cAAc,EAAE,MAAM,CAAK;gBAGhC,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;QACP,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;QAC7D,WAAW,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;QAChD,UAAU,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;QAC/C,gBAAgB,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;QACrD,SAAS,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;KAC/B;IAeR,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI;IAUlB,KAAK,CAAC,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAUzC,KAAK,IAAI,IAAI;CAQd;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,GAAE,MAAsB,EAC1B,QAAQ,GAAE,MAAsB,EAChC,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;IAC7D,WAAW,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAChD,UAAU,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC/C,gBAAgB,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IACrD,SAAS,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;CACpC,GACA,UAAU,CAEZ;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,QAAQ,GAAE,MAAsB,GAC/B,UAAU,EAAE,CAId"}
|