@alt-javascript/camel-lite-core 1.0.2
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/README.md +161 -0
- package/package.json +47 -0
- package/src/AggregationStrategies.js +40 -0
- package/src/CamelContext.js +131 -0
- package/src/ConsumerTemplate.js +77 -0
- package/src/Exchange.js +70 -0
- package/src/ExpressionBuilder.js +122 -0
- package/src/Message.js +38 -0
- package/src/Pipeline.js +96 -0
- package/src/ProcessorNormalizer.js +14 -0
- package/src/ProducerTemplate.js +98 -0
- package/src/RouteBuilder.js +21 -0
- package/src/RouteDefinition.js +526 -0
- package/src/RouteLoader.js +390 -0
- package/src/component.js +33 -0
- package/src/errors/CamelError.js +9 -0
- package/src/errors/CamelFilterStopException.js +15 -0
- package/src/errors/CycleDetectedError.js +12 -0
- package/src/errors/SedaQueueFullError.js +12 -0
- package/src/index.js +17 -0
- package/test/ConsumerTemplate.test.js +146 -0
- package/test/ProducerTemplate.test.js +132 -0
- package/test/context.test.js +97 -0
- package/test/dsl.test.js +375 -0
- package/test/eip.test.js +497 -0
- package/test/exchange.test.js +42 -0
- package/test/fixtures/routes.yaml +58 -0
- package/test/message.test.js +36 -0
- package/test/pipeline.test.js +308 -0
- package/test/routeBuilder.test.js +208 -0
- package/test/routeLoader.test.js +557 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { normalize } from '../src/ProcessorNormalizer.js';
|
|
4
|
+
import { Pipeline } from '../src/Pipeline.js';
|
|
5
|
+
import CamelError from '../src/errors/CamelError.js';
|
|
6
|
+
import { Exchange } from '../src/Exchange.js';
|
|
7
|
+
|
|
8
|
+
describe('ProcessorNormalizer', () => {
|
|
9
|
+
it('accepts an async function and returns it unchanged', async () => {
|
|
10
|
+
const fn = async (exchange) => { exchange.in.body = 'hello'; };
|
|
11
|
+
const normalised = normalize(fn);
|
|
12
|
+
assert.strictEqual(normalised, fn);
|
|
13
|
+
const ex = new Exchange();
|
|
14
|
+
await normalised(ex);
|
|
15
|
+
assert.equal(ex.in.body, 'hello');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('accepts a plain (non-async) function and returns it', () => {
|
|
19
|
+
const fn = (exchange) => { exchange.in.body = 'sync'; };
|
|
20
|
+
const normalised = normalize(fn);
|
|
21
|
+
assert.strictEqual(normalised, fn);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('accepts a processor object with process() method and wraps it', async () => {
|
|
25
|
+
const obj = {
|
|
26
|
+
process(exchange) {
|
|
27
|
+
exchange.in.body = 'from-object';
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
const normalised = normalize(obj);
|
|
31
|
+
assert.equal(typeof normalised, 'function');
|
|
32
|
+
const ex = new Exchange();
|
|
33
|
+
await normalised(ex);
|
|
34
|
+
assert.equal(ex.in.body, 'from-object');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('throws CamelError on null input', () => {
|
|
38
|
+
assert.throws(() => normalize(null), (err) => {
|
|
39
|
+
assert.ok(err instanceof CamelError);
|
|
40
|
+
assert.equal(err.name, 'CamelError');
|
|
41
|
+
return true;
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('throws CamelError on a plain object without process()', () => {
|
|
46
|
+
assert.throws(() => normalize({ foo: 'bar' }), (err) => {
|
|
47
|
+
assert.ok(err instanceof CamelError);
|
|
48
|
+
return true;
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('throws CamelError on a number', () => {
|
|
53
|
+
assert.throws(() => normalize(42), (err) => {
|
|
54
|
+
assert.ok(err instanceof CamelError);
|
|
55
|
+
return true;
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('Pipeline', () => {
|
|
61
|
+
it('single step: processor mutates exchange.in.body directly', async () => {
|
|
62
|
+
const step = normalize(async (ex) => { ex.in.body = 'step1'; });
|
|
63
|
+
const pipeline = new Pipeline([step]);
|
|
64
|
+
const ex = new Exchange();
|
|
65
|
+
await pipeline.run(ex);
|
|
66
|
+
assert.equal(ex.in.body, 'step1');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('multi-step out→in promotion: step 2 receives promoted body', async () => {
|
|
70
|
+
let step2InBody;
|
|
71
|
+
let step2OutBody;
|
|
72
|
+
|
|
73
|
+
const step1 = normalize(async (ex) => { ex.out.body = 'A'; });
|
|
74
|
+
const step2 = normalize(async (ex) => {
|
|
75
|
+
step2InBody = ex.in.body;
|
|
76
|
+
step2OutBody = ex.out.body;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const pipeline = new Pipeline([step1, step2]);
|
|
80
|
+
const ex = new Exchange();
|
|
81
|
+
await pipeline.run(ex);
|
|
82
|
+
|
|
83
|
+
assert.equal(step2InBody, 'A', 'step2 should see promoted in.body');
|
|
84
|
+
assert.equal(step2OutBody, null, 'step2 should see reset out.body');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('out NOT set: step 2 receives unchanged exchange.in', async () => {
|
|
88
|
+
const ex = new Exchange();
|
|
89
|
+
ex.in.body = 'original';
|
|
90
|
+
|
|
91
|
+
let step2InBody;
|
|
92
|
+
const step1 = normalize(async () => { /* does not touch out */ });
|
|
93
|
+
const step2 = normalize(async (e) => { step2InBody = e.in.body; });
|
|
94
|
+
|
|
95
|
+
const pipeline = new Pipeline([step1, step2]);
|
|
96
|
+
await pipeline.run(ex);
|
|
97
|
+
assert.equal(step2InBody, 'original');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('exception capture: throws → isFailed() true, later steps not called', async () => {
|
|
101
|
+
let step2Called = false;
|
|
102
|
+
const step1 = normalize(async () => { throw new Error('boom'); });
|
|
103
|
+
const step2 = normalize(async () => { step2Called = true; });
|
|
104
|
+
|
|
105
|
+
const pipeline = new Pipeline([step1, step2]);
|
|
106
|
+
const ex = new Exchange();
|
|
107
|
+
await pipeline.run(ex);
|
|
108
|
+
|
|
109
|
+
assert.equal(ex.isFailed(), true);
|
|
110
|
+
assert.ok(ex.exception instanceof Error);
|
|
111
|
+
assert.equal(ex.exception.message, 'boom');
|
|
112
|
+
assert.equal(step2Called, false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('exception capture: exchange.exception is the exact thrown error', async () => {
|
|
116
|
+
const err = new TypeError('type mismatch');
|
|
117
|
+
const step = normalize(async () => { throw err; });
|
|
118
|
+
const pipeline = new Pipeline([step]);
|
|
119
|
+
const ex = new Exchange();
|
|
120
|
+
await pipeline.run(ex);
|
|
121
|
+
assert.strictEqual(ex.exception, err);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('both processor flavours work together in a pipeline', async () => {
|
|
125
|
+
const fnStep = normalize(async (ex) => { ex.out.body = 'from-fn'; });
|
|
126
|
+
const objStep = normalize({
|
|
127
|
+
process(ex) {
|
|
128
|
+
ex.in.body = ex.in.body + '-processed';
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const pipeline = new Pipeline([fnStep, objStep]);
|
|
133
|
+
const ex = new Exchange();
|
|
134
|
+
await pipeline.run(ex);
|
|
135
|
+
|
|
136
|
+
// fnStep sets out.body → promoted to in.body; objStep appends '-processed'
|
|
137
|
+
assert.equal(ex.in.body, 'from-fn-processed');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('out→in promotion copies headers from out to in', async () => {
|
|
141
|
+
const step1 = normalize(async (ex) => {
|
|
142
|
+
ex.out.body = 'data';
|
|
143
|
+
ex.out.setHeader('Content-Type', 'text/plain');
|
|
144
|
+
});
|
|
145
|
+
const pipeline = new Pipeline([step1]);
|
|
146
|
+
const ex = new Exchange();
|
|
147
|
+
await pipeline.run(ex);
|
|
148
|
+
assert.equal(ex.in.body, 'data');
|
|
149
|
+
assert.equal(ex.in.getHeader('Content-Type'), 'text/plain');
|
|
150
|
+
assert.equal(ex.out.body, null);
|
|
151
|
+
assert.equal(ex.out.getHeader('Content-Type'), undefined);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('empty pipeline runs without error', async () => {
|
|
155
|
+
const pipeline = new Pipeline([]);
|
|
156
|
+
const ex = new Exchange();
|
|
157
|
+
await pipeline.run(ex);
|
|
158
|
+
assert.equal(ex.isFailed(), false);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('Pipeline onException / redelivery', () => {
|
|
163
|
+
it('onException clause fires when error class matches — handler called, exchange.exception null after (handled:true default)', async () => {
|
|
164
|
+
let handlerCalled = false;
|
|
165
|
+
const clause = {
|
|
166
|
+
errorClass: TypeError,
|
|
167
|
+
processor: async (ex) => { handlerCalled = true; ex.in.body = 'caught'; },
|
|
168
|
+
handled: true,
|
|
169
|
+
};
|
|
170
|
+
const step = normalize(async () => { throw new TypeError('type error'); });
|
|
171
|
+
const pipeline = new Pipeline([step], { clauses: [clause] });
|
|
172
|
+
const ex = new Exchange();
|
|
173
|
+
await pipeline.run(ex);
|
|
174
|
+
assert.equal(handlerCalled, true);
|
|
175
|
+
assert.equal(ex.exception, null, 'exception should be cleared when handled:true');
|
|
176
|
+
assert.equal(ex.in.body, 'caught');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('onException clause does NOT fire when error class does not match — exchange.exception set, handler not called', async () => {
|
|
180
|
+
let handlerCalled = false;
|
|
181
|
+
const clause = {
|
|
182
|
+
errorClass: RangeError,
|
|
183
|
+
processor: async () => { handlerCalled = true; },
|
|
184
|
+
handled: true,
|
|
185
|
+
};
|
|
186
|
+
const step = normalize(async () => { throw new TypeError('type error'); });
|
|
187
|
+
const pipeline = new Pipeline([step], { clauses: [clause] });
|
|
188
|
+
const ex = new Exchange();
|
|
189
|
+
await pipeline.run(ex);
|
|
190
|
+
assert.equal(handlerCalled, false);
|
|
191
|
+
assert.ok(ex.exception instanceof TypeError, 'exception should remain set');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('handled:false — clause fires but exchange.exception remains set after handler', async () => {
|
|
195
|
+
let handlerCalled = false;
|
|
196
|
+
const err = new Error('boom');
|
|
197
|
+
const clause = {
|
|
198
|
+
errorClass: Error,
|
|
199
|
+
processor: async () => { handlerCalled = true; },
|
|
200
|
+
handled: false,
|
|
201
|
+
};
|
|
202
|
+
const step = normalize(async () => { throw err; });
|
|
203
|
+
const pipeline = new Pipeline([step], { clauses: [clause] });
|
|
204
|
+
const ex = new Exchange();
|
|
205
|
+
await pipeline.run(ex);
|
|
206
|
+
assert.equal(handlerCalled, true);
|
|
207
|
+
assert.strictEqual(ex.exception, err, 'exception should remain when handled:false');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('redelivery retries step N times before dispatching to clause — track call count', async () => {
|
|
211
|
+
let callCount = 0;
|
|
212
|
+
let handlerCalled = false;
|
|
213
|
+
const step = normalize(async () => {
|
|
214
|
+
callCount++;
|
|
215
|
+
throw new Error('always fails');
|
|
216
|
+
});
|
|
217
|
+
const clause = {
|
|
218
|
+
errorClass: Error,
|
|
219
|
+
processor: async () => { handlerCalled = true; },
|
|
220
|
+
handled: true,
|
|
221
|
+
};
|
|
222
|
+
const pipeline = new Pipeline([step], { clauses: [clause], maxAttempts: 2 });
|
|
223
|
+
const ex = new Exchange();
|
|
224
|
+
await pipeline.run(ex);
|
|
225
|
+
assert.equal(callCount, 3, 'should attempt 1 + 2 retries = 3 total');
|
|
226
|
+
assert.equal(handlerCalled, true);
|
|
227
|
+
assert.equal(ex.exception, null);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('redelivery: step succeeds on 2nd attempt — no clause fired, exchange.exception null', async () => {
|
|
231
|
+
let callCount = 0;
|
|
232
|
+
let handlerCalled = false;
|
|
233
|
+
const step = normalize(async (ex) => {
|
|
234
|
+
callCount++;
|
|
235
|
+
if (callCount < 2) throw new Error('transient');
|
|
236
|
+
ex.in.body = 'recovered';
|
|
237
|
+
});
|
|
238
|
+
const clause = {
|
|
239
|
+
errorClass: Error,
|
|
240
|
+
processor: async () => { handlerCalled = true; },
|
|
241
|
+
handled: true,
|
|
242
|
+
};
|
|
243
|
+
const pipeline = new Pipeline([step], { clauses: [clause], maxAttempts: 2 });
|
|
244
|
+
const ex = new Exchange();
|
|
245
|
+
await pipeline.run(ex);
|
|
246
|
+
assert.equal(callCount, 2, 'should succeed on 2nd attempt');
|
|
247
|
+
assert.equal(handlerCalled, false, 'clause should not fire on success');
|
|
248
|
+
assert.equal(ex.exception, null);
|
|
249
|
+
assert.equal(ex.in.body, 'recovered');
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
describe('Pipeline AbortSignal redelivery cancellation', () => {
|
|
255
|
+
it('aborting signal cancels redelivery sleep — pipeline exits well before full delay', async () => {
|
|
256
|
+
const controller = new AbortController();
|
|
257
|
+
let callCount = 0;
|
|
258
|
+
|
|
259
|
+
const step = normalize(async () => {
|
|
260
|
+
callCount++;
|
|
261
|
+
throw new Error('always fails');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// redeliveryDelay=5000ms would normally take 10s for 2 retries
|
|
265
|
+
const pipeline = new Pipeline([step], {
|
|
266
|
+
maxAttempts: 2,
|
|
267
|
+
redeliveryDelay: 5000,
|
|
268
|
+
signal: controller.signal,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const ex = new (await import('../src/Exchange.js')).Exchange();
|
|
272
|
+
|
|
273
|
+
// Abort after a short delay (well before 5s sleep would complete)
|
|
274
|
+
setTimeout(() => controller.abort(), 50);
|
|
275
|
+
|
|
276
|
+
const start = Date.now();
|
|
277
|
+
await pipeline.run(ex);
|
|
278
|
+
const elapsed = Date.now() - start;
|
|
279
|
+
|
|
280
|
+
// Should complete in well under 1s despite 5s redeliveryDelay
|
|
281
|
+
assert.ok(elapsed < 1000, `expected abort within 1000ms, took ${elapsed}ms`);
|
|
282
|
+
// callCount should be at least 1 (first attempt before sleep)
|
|
283
|
+
assert.ok(callCount >= 1, 'step should have been called at least once');
|
|
284
|
+
// exchange still has exception set
|
|
285
|
+
assert.ok(ex.isFailed(), 'exchange should be failed after abort');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('signal=null: no abort support, redelivery behaves normally', async () => {
|
|
289
|
+
let callCount = 0;
|
|
290
|
+
const step = normalize(async (ex) => {
|
|
291
|
+
callCount++;
|
|
292
|
+
if (callCount < 3) throw new Error('transient');
|
|
293
|
+
ex.in.body = 'recovered';
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const pipeline = new Pipeline([step], {
|
|
297
|
+
maxAttempts: 2,
|
|
298
|
+
redeliveryDelay: 0,
|
|
299
|
+
signal: null,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const ex = new (await import('../src/Exchange.js')).Exchange();
|
|
303
|
+
await pipeline.run(ex);
|
|
304
|
+
assert.equal(callCount, 3);
|
|
305
|
+
assert.equal(ex.in.body, 'recovered');
|
|
306
|
+
assert.equal(ex.exception, null);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { RouteBuilder } from '../src/RouteBuilder.js';
|
|
4
|
+
import { RouteDefinition } from '../src/RouteDefinition.js';
|
|
5
|
+
import { Pipeline } from '../src/Pipeline.js';
|
|
6
|
+
import { CamelContext } from '../src/CamelContext.js';
|
|
7
|
+
import { Exchange } from '../src/Exchange.js';
|
|
8
|
+
|
|
9
|
+
describe('RouteBuilder', () => {
|
|
10
|
+
it('from() returns a RouteDefinition with correct fromUri', () => {
|
|
11
|
+
const builder = new RouteBuilder();
|
|
12
|
+
const routeDef = builder.from('direct:start');
|
|
13
|
+
assert.ok(routeDef instanceof RouteDefinition);
|
|
14
|
+
assert.equal(routeDef.fromUri, 'direct:start');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('process() and to() are fluent; getRoutes() has 1 route after one from()', () => {
|
|
18
|
+
const builder = new RouteBuilder();
|
|
19
|
+
const fn = async (ex) => { ex.in.body = 'x'; };
|
|
20
|
+
const routeDef = builder.from('direct:start');
|
|
21
|
+
const returned = routeDef.process(fn).to('direct:next');
|
|
22
|
+
assert.strictEqual(returned, routeDef, 'process().to() should return routeDef');
|
|
23
|
+
assert.equal(builder.getRoutes().length, 1);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('getRoutes() returns a copy — mutations do not affect internal state', () => {
|
|
27
|
+
const builder = new RouteBuilder();
|
|
28
|
+
builder.from('direct:a');
|
|
29
|
+
const routes = builder.getRoutes();
|
|
30
|
+
routes.push('garbage');
|
|
31
|
+
assert.equal(builder.getRoutes().length, 1);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('RouteBuilder subclass: configure() populates routes', () => {
|
|
35
|
+
const fn = async (ex) => { ex.in.body = 'sub'; };
|
|
36
|
+
class MyRoutes extends RouteBuilder {
|
|
37
|
+
configure() {
|
|
38
|
+
this.from('direct:a').process(fn);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const myRoutes = new MyRoutes();
|
|
42
|
+
myRoutes.configure();
|
|
43
|
+
assert.equal(myRoutes.getRoutes().length, 1);
|
|
44
|
+
assert.equal(myRoutes.getRoutes()[0].fromUri, 'direct:a');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('configure() default no-op does not throw', () => {
|
|
48
|
+
const builder = new RouteBuilder();
|
|
49
|
+
assert.doesNotThrow(() => builder.configure({}));
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('RouteDefinition.compile()', () => {
|
|
54
|
+
it('compile() returns a Pipeline instance', () => {
|
|
55
|
+
const routeDef = new RouteDefinition('direct:test');
|
|
56
|
+
routeDef.process(async (ex) => { ex.in.body = 'y'; });
|
|
57
|
+
assert.ok(routeDef.compile() instanceof Pipeline);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('compile() skips to() nodes — only process() nodes go into pipeline', async () => {
|
|
61
|
+
let step1Called = false;
|
|
62
|
+
const routeDef = new RouteDefinition('direct:test');
|
|
63
|
+
routeDef
|
|
64
|
+
.process(async (ex) => { step1Called = true; ex.out.body = 'a'; })
|
|
65
|
+
.to('direct:other');
|
|
66
|
+
|
|
67
|
+
const pipeline = routeDef.compile();
|
|
68
|
+
const ex = new Exchange();
|
|
69
|
+
await pipeline.run(ex);
|
|
70
|
+
assert.equal(step1Called, true);
|
|
71
|
+
assert.equal(ex.exception, null);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('CamelContext route registry', () => {
|
|
76
|
+
it('addRoutes() stores route; getRoute() returns a Pipeline', () => {
|
|
77
|
+
const builder = new RouteBuilder();
|
|
78
|
+
builder.from('direct:start').process(async (ex) => { ex.in.body = 'ok'; });
|
|
79
|
+
const context = new CamelContext();
|
|
80
|
+
context.addRoutes(builder);
|
|
81
|
+
const pipeline = context.getRoute('direct:start');
|
|
82
|
+
assert.ok(pipeline instanceof Pipeline);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('addRoutes() returns context (fluent)', () => {
|
|
86
|
+
const builder = new RouteBuilder();
|
|
87
|
+
builder.from('direct:x');
|
|
88
|
+
const context = new CamelContext();
|
|
89
|
+
assert.strictEqual(context.addRoutes(builder), context);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('getRoute() returns undefined for unknown uri', () => {
|
|
93
|
+
const context = new CamelContext();
|
|
94
|
+
assert.equal(context.getRoute('direct:unknown'), undefined);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('addRoutes() calls configure() on builder if defined', () => {
|
|
98
|
+
let configureCalled = false;
|
|
99
|
+
class MyRoutes extends RouteBuilder {
|
|
100
|
+
configure(ctx) {
|
|
101
|
+
configureCalled = true;
|
|
102
|
+
assert.ok(ctx instanceof CamelContext, 'context should be passed to configure');
|
|
103
|
+
this.from('direct:b').process(async (ex) => { ex.in.body = 'b'; });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const context = new CamelContext();
|
|
107
|
+
context.addRoutes(new MyRoutes());
|
|
108
|
+
assert.equal(configureCalled, true);
|
|
109
|
+
assert.ok(context.getRoute('direct:b') instanceof Pipeline);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('addRoutes() with multiple routes registers each', () => {
|
|
113
|
+
const builder = new RouteBuilder();
|
|
114
|
+
builder.from('direct:one').process(async (ex) => { ex.in.body = '1'; });
|
|
115
|
+
builder.from('direct:two').process(async (ex) => { ex.in.body = '2'; });
|
|
116
|
+
const context = new CamelContext();
|
|
117
|
+
context.addRoutes(builder);
|
|
118
|
+
assert.ok(context.getRoute('direct:one') instanceof Pipeline);
|
|
119
|
+
assert.ok(context.getRoute('direct:two') instanceof Pipeline);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('Integration: 3-step pipeline via addRoutes', () => {
|
|
124
|
+
it('step 1 sets out.body → step 2 sees it in in.body → step 3 same pattern; no exception', async () => {
|
|
125
|
+
let step2InBody;
|
|
126
|
+
let step3InBody;
|
|
127
|
+
|
|
128
|
+
const builder = new RouteBuilder();
|
|
129
|
+
builder
|
|
130
|
+
.from('direct:start')
|
|
131
|
+
.process(async (ex) => {
|
|
132
|
+
ex.out.body = 'step1';
|
|
133
|
+
})
|
|
134
|
+
.process(async (ex) => {
|
|
135
|
+
step2InBody = ex.in.body;
|
|
136
|
+
ex.out.body = 'step2';
|
|
137
|
+
})
|
|
138
|
+
.process(async (ex) => {
|
|
139
|
+
step3InBody = ex.in.body;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const context = new CamelContext();
|
|
143
|
+
context.addRoutes(builder);
|
|
144
|
+
|
|
145
|
+
const pipeline = context.getRoute('direct:start');
|
|
146
|
+
assert.ok(pipeline instanceof Pipeline);
|
|
147
|
+
|
|
148
|
+
const exchange = new Exchange();
|
|
149
|
+
await pipeline.run(exchange);
|
|
150
|
+
|
|
151
|
+
assert.equal(exchange.exception, null, 'no exception should be set');
|
|
152
|
+
assert.equal(step2InBody, 'step1', 'step2 should see promoted in.body from step1');
|
|
153
|
+
assert.equal(step3InBody, 'step2', 'step3 should see promoted in.body from step2');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('both fn and object processor forms work in a compiled route', async () => {
|
|
157
|
+
const objProcessor = {
|
|
158
|
+
process(ex) {
|
|
159
|
+
ex.out.body = ex.in.body + '-obj';
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const builder = new RouteBuilder();
|
|
164
|
+
builder
|
|
165
|
+
.from('direct:mixed')
|
|
166
|
+
.process(async (ex) => { ex.out.body = 'fn'; })
|
|
167
|
+
.process(objProcessor);
|
|
168
|
+
|
|
169
|
+
const context = new CamelContext();
|
|
170
|
+
context.addRoutes(builder);
|
|
171
|
+
|
|
172
|
+
const pipeline = context.getRoute('direct:mixed');
|
|
173
|
+
const exchange = new Exchange();
|
|
174
|
+
await pipeline.run(exchange);
|
|
175
|
+
|
|
176
|
+
assert.equal(exchange.exception, null);
|
|
177
|
+
// step1 fn sets out.body='fn' → promoted to in.body='fn'
|
|
178
|
+
// step2 obj reads in.body='fn', sets out.body='fn-obj' → promoted to in.body='fn-obj'
|
|
179
|
+
assert.equal(exchange.in.body, 'fn-obj');
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('RouteDefinition.onException()', () => {
|
|
184
|
+
it('onException() is fluent (returns routeDef)', () => {
|
|
185
|
+
const routeDef = new RouteDefinition('direct:test');
|
|
186
|
+
const returned = routeDef.onException(Error, async () => {});
|
|
187
|
+
assert.strictEqual(returned, routeDef, 'onException() should return routeDef for chaining');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('compiled pipeline from routeDef.onException() handles matching error', async () => {
|
|
191
|
+
let handlerCalled = false;
|
|
192
|
+
const routeDef = new RouteDefinition('direct:test');
|
|
193
|
+
routeDef
|
|
194
|
+
.process(async () => { throw new TypeError('test error'); })
|
|
195
|
+
.onException(TypeError, async (ex) => {
|
|
196
|
+
handlerCalled = true;
|
|
197
|
+
ex.in.body = 'handled';
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const pipeline = routeDef.compile();
|
|
201
|
+
const exchange = new Exchange();
|
|
202
|
+
await pipeline.run(exchange);
|
|
203
|
+
|
|
204
|
+
assert.equal(handlerCalled, true, 'onException handler should be called');
|
|
205
|
+
assert.equal(exchange.exception, null, 'exception cleared — handled:true is default');
|
|
206
|
+
assert.equal(exchange.in.body, 'handled');
|
|
207
|
+
});
|
|
208
|
+
});
|