@olane/o-test 0.7.12

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.
Files changed (60) hide show
  1. package/LICENSE +33 -0
  2. package/README.md +400 -0
  3. package/dist/src/builders/index.d.ts +7 -0
  4. package/dist/src/builders/index.d.ts.map +1 -0
  5. package/dist/src/builders/index.js +6 -0
  6. package/dist/src/builders/leader-child-builder.d.ts +50 -0
  7. package/dist/src/builders/leader-child-builder.d.ts.map +1 -0
  8. package/dist/src/builders/leader-child-builder.js +59 -0
  9. package/dist/src/builders/manager-worker-builder.d.ts +49 -0
  10. package/dist/src/builders/manager-worker-builder.d.ts.map +1 -0
  11. package/dist/src/builders/manager-worker-builder.js +79 -0
  12. package/dist/src/builders/simple-node-builder.d.ts +49 -0
  13. package/dist/src/builders/simple-node-builder.d.ts.map +1 -0
  14. package/dist/src/builders/simple-node-builder.js +66 -0
  15. package/dist/src/example-tool.tool.d.ts +58 -0
  16. package/dist/src/example-tool.tool.d.ts.map +1 -0
  17. package/dist/src/example-tool.tool.js +89 -0
  18. package/dist/src/fixtures/index.d.ts +6 -0
  19. package/dist/src/fixtures/index.d.ts.map +1 -0
  20. package/dist/src/fixtures/index.js +5 -0
  21. package/dist/src/fixtures/mock-data.d.ts +201 -0
  22. package/dist/src/fixtures/mock-data.d.ts.map +1 -0
  23. package/dist/src/fixtures/mock-data.js +180 -0
  24. package/dist/src/fixtures/test-methods.d.ts +33 -0
  25. package/dist/src/fixtures/test-methods.d.ts.map +1 -0
  26. package/dist/src/fixtures/test-methods.js +185 -0
  27. package/dist/src/index.d.ts +18 -0
  28. package/dist/src/index.d.ts.map +1 -0
  29. package/dist/src/index.js +25 -0
  30. package/dist/src/methods/example.methods.d.ts +9 -0
  31. package/dist/src/methods/example.methods.d.ts.map +1 -0
  32. package/dist/src/methods/example.methods.js +50 -0
  33. package/dist/src/test-environment.d.ts +185 -0
  34. package/dist/src/test-environment.d.ts.map +1 -0
  35. package/dist/src/test-environment.js +260 -0
  36. package/dist/src/utils/assertions.d.ts +159 -0
  37. package/dist/src/utils/assertions.d.ts.map +1 -0
  38. package/dist/src/utils/assertions.js +201 -0
  39. package/dist/src/utils/chunk-capture.d.ts +108 -0
  40. package/dist/src/utils/chunk-capture.d.ts.map +1 -0
  41. package/dist/src/utils/chunk-capture.js +156 -0
  42. package/dist/src/utils/index.d.ts +8 -0
  43. package/dist/src/utils/index.d.ts.map +1 -0
  44. package/dist/src/utils/index.js +7 -0
  45. package/dist/src/utils/mock-factories.d.ts +211 -0
  46. package/dist/src/utils/mock-factories.d.ts.map +1 -0
  47. package/dist/src/utils/mock-factories.js +181 -0
  48. package/dist/src/utils/wait-for.d.ts +42 -0
  49. package/dist/src/utils/wait-for.d.ts.map +1 -0
  50. package/dist/src/utils/wait-for.js +59 -0
  51. package/dist/test/example.spec.d.ts +1 -0
  52. package/dist/test/example.spec.d.ts.map +1 -0
  53. package/dist/test/example.spec.js +240 -0
  54. package/dist/test/fixtures/mock-data.d.ts +1 -0
  55. package/dist/test/fixtures/mock-data.d.ts.map +1 -0
  56. package/dist/test/fixtures/mock-data.js +90 -0
  57. package/dist/test/test-environment.spec.d.ts +8 -0
  58. package/dist/test/test-environment.spec.d.ts.map +1 -0
  59. package/dist/test/test-environment.spec.js +393 -0
  60. package/package.json +87 -0
@@ -0,0 +1,393 @@
1
+ /**
2
+ * Tests for TestEnvironment class
3
+ *
4
+ * Validates the TestEnvironment utilities and serves as
5
+ * documentation/examples for usage patterns.
6
+ */
7
+ import 'dotenv/config';
8
+ import { expect } from 'chai';
9
+ import { NodeState } from '@olane/o-core';
10
+ import { oNodeAddress } from '@olane/o-node';
11
+ import { TestEnvironment, SimpleNodeBuilder, waitFor, ChunkCapture, assertSuccess, assertError, assertRunning, assertStopped, assertHasData, createMockUser, MOCK_USERS, } from '../src/index.js';
12
+ // Mock node class for testing
13
+ class MockNode {
14
+ constructor(config = {}) {
15
+ this.state = NodeState.STOPPED;
16
+ this.config = config;
17
+ this.address = config.address || new oNodeAddress('o://mock-node');
18
+ }
19
+ async start() {
20
+ this.state = NodeState.RUNNING;
21
+ }
22
+ async stop() {
23
+ this.state = NodeState.STOPPED;
24
+ }
25
+ }
26
+ describe('TestEnvironment', () => {
27
+ let env;
28
+ beforeEach(() => {
29
+ env = new TestEnvironment();
30
+ });
31
+ afterEach(async () => {
32
+ await env.cleanup();
33
+ });
34
+ describe('Lifecycle Management', () => {
35
+ it('should track nodes for cleanup', async () => {
36
+ const node = new MockNode();
37
+ env.track(node);
38
+ expect(env.getNodeCount()).to.equal(1);
39
+ expect(env.getNodes()).to.have.lengthOf(1);
40
+ expect(env.getNodes()[0]).to.equal(node);
41
+ });
42
+ it('should create and track simple node', async () => {
43
+ const node = await env.createNode(MockNode, {}, false);
44
+ expect(env.getNodeCount()).to.equal(1);
45
+ expect(node.state).to.equal(NodeState.STOPPED);
46
+ });
47
+ it('should auto-start nodes by default', async () => {
48
+ const node = await env.createNode(MockNode);
49
+ expect(node.state).to.equal(NodeState.RUNNING);
50
+ });
51
+ it('should stop all nodes in cleanup', async () => {
52
+ const node1 = await env.createNode(MockNode);
53
+ const node2 = await env.createNode(MockNode);
54
+ expect(node1.state).to.equal(NodeState.RUNNING);
55
+ expect(node2.state).to.equal(NodeState.RUNNING);
56
+ await env.cleanup();
57
+ expect(node1.state).to.equal(NodeState.STOPPED);
58
+ expect(node2.state).to.equal(NodeState.STOPPED);
59
+ expect(env.allNodesStopped()).to.be.true;
60
+ });
61
+ it('should execute cleanup callbacks', async () => {
62
+ let callbackExecuted = false;
63
+ env.onCleanup(async () => {
64
+ callbackExecuted = true;
65
+ });
66
+ await env.cleanup();
67
+ expect(callbackExecuted).to.be.true;
68
+ });
69
+ it('should clear tracking after cleanup', async () => {
70
+ const node = await env.createNode(MockNode);
71
+ expect(env.getNodeCount()).to.equal(1);
72
+ await env.cleanup();
73
+ expect(env.getNodeCount()).to.equal(0);
74
+ });
75
+ });
76
+ describe('Node Creation', () => {
77
+ it('should pass config to node constructor', async () => {
78
+ const testConfig = {
79
+ apiKey: 'test-key',
80
+ timeout: 5000,
81
+ };
82
+ const node = await env.createNode(MockNode, testConfig);
83
+ expect(node.config.apiKey).to.equal('test-key');
84
+ expect(node.config.timeout).to.equal(5000);
85
+ });
86
+ it('should set parent and leader to null for simple nodes', async () => {
87
+ const node = await env.createNode(MockNode);
88
+ expect(node.config.parent).to.be.null;
89
+ expect(node.config.leader).to.be.null;
90
+ });
91
+ });
92
+ describe('waitFor Utility', () => {
93
+ it('should wait for condition to be true', async () => {
94
+ let counter = 0;
95
+ setTimeout(() => { counter = 5; }, 100);
96
+ await env.waitFor(() => counter === 5, 1000);
97
+ expect(counter).to.equal(5);
98
+ });
99
+ it('should timeout if condition not met', async () => {
100
+ let error = null;
101
+ try {
102
+ await env.waitFor(() => false, 100);
103
+ }
104
+ catch (e) {
105
+ error = e;
106
+ }
107
+ expect(error).to.exist;
108
+ expect(error?.message).to.include('Timeout');
109
+ });
110
+ it('should check condition immediately', async () => {
111
+ const startTime = Date.now();
112
+ await env.waitFor(() => true, 5000);
113
+ const duration = Date.now() - startTime;
114
+ // Should return almost immediately
115
+ expect(duration).to.be.lessThan(100);
116
+ });
117
+ });
118
+ });
119
+ describe('Test Builders', () => {
120
+ let env;
121
+ beforeEach(() => {
122
+ env = new TestEnvironment();
123
+ });
124
+ afterEach(async () => {
125
+ await env.cleanup();
126
+ });
127
+ describe('SimpleNodeBuilder', () => {
128
+ it('should build node with fluent API', async () => {
129
+ const node = await new SimpleNodeBuilder(MockNode)
130
+ .withAddress('o://test-builder')
131
+ .withDescription('Test node')
132
+ .build(env);
133
+ expect(node).to.exist;
134
+ expect(node.state).to.equal(NodeState.RUNNING);
135
+ expect(env.getNodeCount()).to.equal(1);
136
+ });
137
+ it('should respect autoStart flag', async () => {
138
+ const node = await new SimpleNodeBuilder(MockNode)
139
+ .withAutoStart(false)
140
+ .build(env);
141
+ expect(node.state).to.equal(NodeState.STOPPED);
142
+ });
143
+ it('should pass config through', async () => {
144
+ const node = await new SimpleNodeBuilder(MockNode)
145
+ .withConfig({ custom: 'value' })
146
+ .build(env);
147
+ expect(node.config.custom).to.equal('value');
148
+ });
149
+ });
150
+ });
151
+ describe('Utilities', () => {
152
+ describe('waitFor', () => {
153
+ it('should wait for condition', async () => {
154
+ let value = false;
155
+ setTimeout(() => { value = true; }, 50);
156
+ await waitFor(() => value, 1000);
157
+ expect(value).to.be.true;
158
+ });
159
+ it('should throw on timeout', async () => {
160
+ let error = null;
161
+ try {
162
+ await waitFor(() => false, 50);
163
+ }
164
+ catch (e) {
165
+ error = e;
166
+ }
167
+ expect(error).to.exist;
168
+ expect(error?.message).to.include('Timeout');
169
+ });
170
+ });
171
+ describe('ChunkCapture', () => {
172
+ it('should capture chunks', () => {
173
+ const capture = new ChunkCapture();
174
+ capture.onChunk({ index: 0, data: 'chunk1' });
175
+ capture.onChunk({ index: 1, data: 'chunk2' });
176
+ capture.onChunk({ index: 2, data: 'chunk3' });
177
+ expect(capture.chunkCount).to.equal(3);
178
+ expect(capture.allChunks).to.have.lengthOf(3);
179
+ });
180
+ it('should track first and last chunks', () => {
181
+ const capture = new ChunkCapture();
182
+ capture.onChunk('first');
183
+ capture.onChunk('middle');
184
+ capture.onChunk('last');
185
+ expect(capture.firstChunk).to.equal('first');
186
+ expect(capture.lastChunk).to.equal('last');
187
+ });
188
+ it('should clear chunks', () => {
189
+ const capture = new ChunkCapture();
190
+ capture.onChunk('test1');
191
+ capture.onChunk('test2');
192
+ expect(capture.chunkCount).to.equal(2);
193
+ capture.clear();
194
+ expect(capture.chunkCount).to.equal(0);
195
+ expect(capture.allChunks).to.have.lengthOf(0);
196
+ });
197
+ it('should wait for chunks', async () => {
198
+ const capture = new ChunkCapture();
199
+ setTimeout(() => {
200
+ capture.onChunk('chunk1');
201
+ capture.onChunk('chunk2');
202
+ capture.onChunk('chunk3');
203
+ }, 50);
204
+ await capture.waitForChunks(3, 1000);
205
+ expect(capture.chunkCount).to.equal(3);
206
+ });
207
+ it('should find chunks by predicate', () => {
208
+ const capture = new ChunkCapture();
209
+ capture.onChunk({ type: 'data', value: 1 });
210
+ capture.onChunk({ type: 'error', value: 2 });
211
+ capture.onChunk({ type: 'data', value: 3 });
212
+ const dataChunks = capture.findChunks(chunk => chunk.type === 'data');
213
+ expect(dataChunks).to.have.lengthOf(2);
214
+ expect(dataChunks[0].value).to.equal(1);
215
+ expect(dataChunks[1].value).to.equal(3);
216
+ });
217
+ it('should convert chunks to string', () => {
218
+ const capture = new ChunkCapture();
219
+ capture.onChunk('Hello');
220
+ capture.onChunk(' ');
221
+ capture.onChunk('World');
222
+ expect(capture.asString()).to.equal('Hello World');
223
+ });
224
+ });
225
+ });
226
+ describe('Assertions', () => {
227
+ describe('assertSuccess', () => {
228
+ it('should pass for successful response', () => {
229
+ const response = {
230
+ jsonrpc: '2.0',
231
+ id: 'test-1',
232
+ result: {
233
+ success: true,
234
+ data: { test: 'value' },
235
+ _connectionId: '',
236
+ _requestMethod: '',
237
+ _last: true,
238
+ _isStreaming: false,
239
+ },
240
+ };
241
+ expect(() => assertSuccess(response)).to.not.throw();
242
+ });
243
+ it('should throw for error response', () => {
244
+ const response = {
245
+ jsonrpc: '2.0',
246
+ id: 'test-2',
247
+ result: {
248
+ success: false,
249
+ error: 'Test error',
250
+ _connectionId: '',
251
+ _requestMethod: '',
252
+ _last: true,
253
+ _isStreaming: false,
254
+ },
255
+ };
256
+ expect(() => assertSuccess(response)).to.throw('Test error');
257
+ });
258
+ });
259
+ describe('assertError', () => {
260
+ it('should pass for error response', () => {
261
+ const response = {
262
+ jsonrpc: '2.0',
263
+ id: 'test-3',
264
+ result: {
265
+ success: false,
266
+ error: 'Expected error',
267
+ _connectionId: '',
268
+ _requestMethod: '',
269
+ _last: true,
270
+ _isStreaming: false,
271
+ },
272
+ };
273
+ expect(() => assertError(response)).to.not.throw();
274
+ });
275
+ it('should match error substring', () => {
276
+ const response = {
277
+ jsonrpc: '2.0',
278
+ id: 'test-4',
279
+ result: {
280
+ success: false,
281
+ error: 'Parameter userId is required',
282
+ _connectionId: '',
283
+ _requestMethod: '',
284
+ _last: true,
285
+ _isStreaming: false,
286
+ },
287
+ };
288
+ expect(() => assertError(response, 'required')).to.not.throw();
289
+ });
290
+ it('should throw if error does not match', () => {
291
+ const response = {
292
+ jsonrpc: '2.0',
293
+ id: 'test-5',
294
+ result: {
295
+ success: false,
296
+ error: 'Different error',
297
+ _connectionId: '',
298
+ _requestMethod: '',
299
+ _last: true,
300
+ _isStreaming: false,
301
+ },
302
+ };
303
+ expect(() => assertError(response, 'required')).to.throw();
304
+ });
305
+ });
306
+ describe('assertRunning', () => {
307
+ it('should pass for running node', () => {
308
+ const node = new MockNode();
309
+ node.state = NodeState.RUNNING;
310
+ expect(() => assertRunning(node)).to.not.throw();
311
+ });
312
+ it('should throw for stopped node', () => {
313
+ const node = new MockNode();
314
+ node.state = NodeState.STOPPED;
315
+ expect(() => assertRunning(node)).to.throw('RUNNING');
316
+ });
317
+ });
318
+ describe('assertStopped', () => {
319
+ it('should pass for stopped node', () => {
320
+ const node = new MockNode();
321
+ node.state = NodeState.STOPPED;
322
+ expect(() => assertStopped(node)).to.not.throw();
323
+ });
324
+ it('should throw for running node', () => {
325
+ const node = new MockNode();
326
+ node.state = NodeState.RUNNING;
327
+ expect(() => assertStopped(node)).to.throw('STOPPED');
328
+ });
329
+ });
330
+ describe('assertHasData', () => {
331
+ it('should pass when data exists', () => {
332
+ const response = {
333
+ jsonrpc: '2.0',
334
+ id: 'test-6',
335
+ result: {
336
+ success: true,
337
+ data: { test: 'value' },
338
+ _connectionId: '',
339
+ _requestMethod: '',
340
+ _last: true,
341
+ _isStreaming: false,
342
+ },
343
+ };
344
+ expect(() => assertHasData(response)).to.not.throw();
345
+ });
346
+ it('should throw when data missing', () => {
347
+ const response = {
348
+ jsonrpc: '2.0',
349
+ id: 'test-7',
350
+ result: {
351
+ success: true,
352
+ _connectionId: '',
353
+ _requestMethod: '',
354
+ _last: true,
355
+ _isStreaming: false,
356
+ },
357
+ };
358
+ expect(() => assertHasData(response)).to.throw('result.data');
359
+ });
360
+ });
361
+ });
362
+ describe('Mock Factories', () => {
363
+ describe('createMockUser', () => {
364
+ it('should create user with default values', () => {
365
+ const user = createMockUser();
366
+ expect(user.userId).to.exist;
367
+ expect(user.username).to.exist;
368
+ expect(user.email).to.exist;
369
+ expect(user.role).to.equal('user');
370
+ expect(user.active).to.be.true;
371
+ });
372
+ it('should apply overrides', () => {
373
+ const user = createMockUser({
374
+ email: 'custom@test.com',
375
+ role: 'admin',
376
+ });
377
+ expect(user.email).to.equal('custom@test.com');
378
+ expect(user.role).to.equal('admin');
379
+ });
380
+ });
381
+ });
382
+ describe('Fixtures', () => {
383
+ describe('MOCK_USERS', () => {
384
+ it('should provide test users', () => {
385
+ expect(MOCK_USERS.basic).to.exist;
386
+ expect(MOCK_USERS.admin).to.exist;
387
+ expect(MOCK_USERS.inactive).to.exist;
388
+ expect(MOCK_USERS.basic.userId).to.exist;
389
+ expect(MOCK_USERS.admin.role).to.equal('admin');
390
+ expect(MOCK_USERS.inactive.active).to.be.false;
391
+ });
392
+ });
393
+ });
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "@olane/o-test",
3
+ "version": "0.7.12",
4
+ "type": "module",
5
+ "main": "dist/src/index.js",
6
+ "types": "dist/src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/src/index.d.ts",
10
+ "default": "./dist/src/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist/**/*",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "test": "aegir test",
20
+ "test:node": "aegir test -t node",
21
+ "test:browser": "aegir test -t browser",
22
+ "build": "tsc",
23
+ "clean": "rm -rf dist",
24
+ "deep:clean": "rm -rf node_modules && rm -f package-lock.json",
25
+ "start:prod": "node dist/src/index.js",
26
+ "dev": "tsx watch src/index.ts",
27
+ "prepublishOnly": "npm run build",
28
+ "lint": "aegir lint"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/olane-labs/olane.git"
33
+ },
34
+ "author": "oLane Inc.",
35
+ "license": "(MIT OR Apache-2.0)",
36
+ "description": "Comprehensive testing utilities and best practices for O-Network node development using Mocha/Chai (libp2p ecosystem)",
37
+ "keywords": [
38
+ "olane",
39
+ "o-network",
40
+ "testing",
41
+ "test-utilities",
42
+ "mocha",
43
+ "chai",
44
+ "aegir",
45
+ "libp2p",
46
+ "test-environment"
47
+ ],
48
+ "devDependencies": {
49
+ "@eslint/eslintrc": "^3.3.1",
50
+ "@eslint/js": "^9.29.0",
51
+ "@tsconfig/node20": "^20.1.6",
52
+ "@types/fs-extra": "^11.0.4",
53
+ "@types/jest": "^30.0.0",
54
+ "@types/touch": "^3.1.5",
55
+ "@typescript-eslint/eslint-plugin": "^8.34.1",
56
+ "@typescript-eslint/parser": "^8.34.1",
57
+ "aegir": "^47.0.21",
58
+ "chai": "^5.1.2",
59
+ "eslint": "^9.29.0",
60
+ "eslint-config-prettier": "^10.1.6",
61
+ "eslint-plugin-prettier": "^5.5.0",
62
+ "globals": "^16.2.0",
63
+ "jest": "^30.0.0",
64
+ "prettier": "^3.5.3",
65
+ "ts-jest": "^29.4.0",
66
+ "ts-node": "^10.9.2",
67
+ "tsconfig-paths": "^4.2.0",
68
+ "tsx": "^4.20.3",
69
+ "typescript": "5.4.5"
70
+ },
71
+ "peerDependencies": {
72
+ "@olane/o-config": "^0.7.12",
73
+ "@olane/o-core": "^0.7.12",
74
+ "@olane/o-lane": "^0.7.12",
75
+ "@olane/o-leader": "^0.7.12",
76
+ "@olane/o-node": "^0.7.12",
77
+ "@olane/o-protocol": "^0.7.12",
78
+ "@olane/o-tool": "^0.7.12",
79
+ "chai": "^5.1.2"
80
+ },
81
+ "dependencies": {
82
+ "chalk": "^5.4.1",
83
+ "debug": "^4.4.1",
84
+ "dotenv": "^16.5.0"
85
+ },
86
+ "gitHead": "580d8c51fe0a12695ba1d77f077029200f0c8af4"
87
+ }