@cratis/chronicle 6.21.0 → 6.22.1
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/eventSequences/EventSequence.d.ts.map +1 -1
- package/dist/eventSequences/EventSequence.js +1 -1
- package/dist/eventSequences/EventSequence.js.map +1 -1
- package/dist/reactors/ReactorDispatcher.d.ts +20 -0
- package/dist/reactors/ReactorDispatcher.d.ts.map +1 -0
- package/dist/reactors/ReactorDispatcher.js +76 -0
- package/dist/reactors/ReactorDispatcher.js.map +1 -0
- package/dist/reactors/ReactorSideEffects.d.ts +3 -0
- package/dist/reactors/ReactorSideEffects.d.ts.map +1 -1
- package/dist/reactors/ReactorSideEffects.js +16 -11
- package/dist/reactors/ReactorSideEffects.js.map +1 -1
- package/dist/reactors/Reactors.d.ts +0 -1
- package/dist/reactors/Reactors.d.ts.map +1 -1
- package/dist/reactors/Reactors.js +5 -71
- package/dist/reactors/Reactors.js.map +1 -1
- package/dist/testing/InProcessEventSequence.d.ts +8 -0
- package/dist/testing/InProcessEventSequence.d.ts.map +1 -1
- package/dist/testing/InProcessEventSequence.js +14 -1
- package/dist/testing/InProcessEventSequence.js.map +1 -1
- package/dist/testing/ReactorDeliveryResult.d.ts +10 -0
- package/dist/testing/ReactorDeliveryResult.d.ts.map +1 -0
- package/dist/testing/ReactorDeliveryResult.js +4 -0
- package/dist/testing/ReactorDeliveryResult.js.map +1 -0
- package/dist/testing/ReactorScenario.d.ts +55 -0
- package/dist/testing/ReactorScenario.d.ts.map +1 -0
- package/dist/testing/ReactorScenario.js +275 -0
- package/dist/testing/ReactorScenario.js.map +1 -0
- package/dist/testing/ReactorScenarioOptions.d.ts +17 -0
- package/dist/testing/ReactorScenarioOptions.d.ts.map +1 -0
- package/dist/testing/ReactorScenarioOptions.js +4 -0
- package/dist/testing/ReactorScenarioOptions.js.map +1 -0
- package/dist/testing/RecordedReactorSideEffect.d.ts +13 -0
- package/dist/testing/RecordedReactorSideEffect.d.ts.map +1 -0
- package/dist/testing/RecordedReactorSideEffect.js +4 -0
- package/dist/testing/RecordedReactorSideEffect.js.map +1 -0
- package/dist/testing/UnsupportedReactorOperation.d.ts +7 -0
- package/dist/testing/UnsupportedReactorOperation.d.ts.map +1 -0
- package/dist/testing/UnsupportedReactorOperation.js +14 -0
- package/dist/testing/UnsupportedReactorOperation.js.map +1 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +2 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/eventSequences/EventSequence.spec.ts +37 -2
- package/eventSequences/EventSequence.ts +17 -12
- package/events/constraints/for_Constraints/when_resolving_violation_messages.spec.ts +9 -4
- package/package.json +1 -1
- package/reactors/ReactorDispatcher.ts +95 -0
- package/reactors/ReactorSideEffects.ts +21 -13
- package/reactors/Reactors.ts +11 -83
- package/testing/InProcessEventSequence.ts +16 -1
- package/testing/ReactorDeliveryResult.ts +13 -0
- package/testing/ReactorScenario.rejections.spec.ts +100 -0
- package/testing/ReactorScenario.spec.ts +611 -0
- package/testing/ReactorScenario.ts +279 -0
- package/testing/ReactorScenarioOptions.ts +20 -0
- package/testing/RecordedReactorSideEffect.ts +16 -0
- package/testing/UnsupportedReactorOperation.ts +10 -0
- package/testing/index.ts +5 -0
package/reactors/Reactors.ts
CHANGED
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
import 'reflect-metadata';
|
|
5
5
|
import { diag } from '@opentelemetry/api';
|
|
6
6
|
import { Constructor } from '@cratis/fundamentals';
|
|
7
|
-
import {
|
|
7
|
+
import { ObservationState, ReactorMessage, ReplayState } from '@cratis/chronicle.contracts';
|
|
8
8
|
import { IClientArtifactsProvider } from '../artifacts/index.js';
|
|
9
9
|
import { ChronicleConnection } from '../connection/index.js';
|
|
10
10
|
import { ConnectionLifecycle } from '../connection/ConnectionLifecycle.js';
|
|
11
|
-
import { getEventTypeMetadata } from '../events/eventTypeDecorator.js';
|
|
12
11
|
import { toClientEventContext } from '../events/toClientEventContext.js';
|
|
13
12
|
import { getTagsFor } from '../events/tagDecorator.js';
|
|
14
13
|
import { getFilterTagsFor } from '../events/filterEventsByTagDecorator.js';
|
|
@@ -19,7 +18,8 @@ import { IReactors } from './IReactors.js';
|
|
|
19
18
|
import { dispatchReactorSideEffects } from './ReactorSideEffects.js';
|
|
20
19
|
import { getReactorMetadata } from './reactor.js';
|
|
21
20
|
import { isOnceOnly } from './onceOnly.js';
|
|
22
|
-
import {
|
|
21
|
+
import { getReactorEventTypes, selectReactorHandler, invokeReactorHandler } from './ReactorDispatcher.js';
|
|
22
|
+
import type { ReactorEventTypeEntry } from './ReactorDispatcher.js';
|
|
23
23
|
import type { ReactorResultHandler } from './ReactorResultHandler.js';
|
|
24
24
|
import type { ReactorServices } from './ReactorServices.js';
|
|
25
25
|
import type { IEventStore } from '../IEventStore.js';
|
|
@@ -36,13 +36,6 @@ const EVENT_SOURCE_ID_KEY = '$eventSourceId';
|
|
|
36
36
|
/** Sentinel sequence number sent back when no event was successfully processed. */
|
|
37
37
|
const SEQUENCE_NUMBER_UNAVAILABLE = 4294967295n;
|
|
38
38
|
|
|
39
|
-
interface EventTypeEntry {
|
|
40
|
-
readonly id: string;
|
|
41
|
-
readonly generation: number;
|
|
42
|
-
readonly methodName?: string;
|
|
43
|
-
readonly replayMethodName?: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
39
|
/**
|
|
47
40
|
* A push-based async queue that implements {@link AsyncIterable} for use with nice-grpc
|
|
48
41
|
* bidirectional streaming. Values pushed via {@link send} are yielded in order to
|
|
@@ -187,7 +180,7 @@ export class Reactors implements IReactors {
|
|
|
187
180
|
if (this._disposed) return;
|
|
188
181
|
const metadata = getReactorMetadata(reactorType)!;
|
|
189
182
|
const eventSequenceId = metadata.eventSequenceId ?? EventSequenceId.eventLog.value;
|
|
190
|
-
const eventTypes = this.
|
|
183
|
+
const eventTypes = getReactorEventTypes(reactorType, this._clientArtifacts.eventTypes);
|
|
191
184
|
|
|
192
185
|
this._logger.info('Starting reactor observation', {
|
|
193
186
|
reactorId: id,
|
|
@@ -203,7 +196,7 @@ export class Reactors implements IReactors {
|
|
|
203
196
|
id: string,
|
|
204
197
|
reactorType: Constructor,
|
|
205
198
|
eventSequenceId: string,
|
|
206
|
-
eventTypes:
|
|
199
|
+
eventTypes: ReactorEventTypeEntry[]
|
|
207
200
|
): Promise<void> {
|
|
208
201
|
try {
|
|
209
202
|
await this.observeReactor(id, reactorType, eventSequenceId, eventTypes);
|
|
@@ -246,7 +239,7 @@ export class Reactors implements IReactors {
|
|
|
246
239
|
id: string,
|
|
247
240
|
reactorType: Constructor,
|
|
248
241
|
eventSequenceId: string,
|
|
249
|
-
eventTypes:
|
|
242
|
+
eventTypes: ReactorEventTypeEntry[]
|
|
250
243
|
): Promise<void> {
|
|
251
244
|
const queue = new AsyncQueue<ReactorMessage>();
|
|
252
245
|
const controller = new AbortController();
|
|
@@ -323,12 +316,8 @@ export class Reactors implements IReactors {
|
|
|
323
316
|
}
|
|
324
317
|
|
|
325
318
|
const selectHandler = (event: typeof eventsToObserve.Events[number]) => {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const isReplay = (event.Context!.ObservationState & EventObservationState.Replay) !== 0;
|
|
329
|
-
const methodName = isReplay ? (entry.replayMethodName ?? entry.methodName) : entry.methodName;
|
|
330
|
-
const method = methodName ? (reactorInstance?.[methodName] ?? (reactorType.prototype as Record<string, Function>)[methodName]) : undefined;
|
|
331
|
-
return { methodName, isReplay, skipReplay: isReplay && method !== undefined && isOnceOnly(method) };
|
|
319
|
+
return selectReactorHandler(eventTypes, reactorType, reactorInstance,
|
|
320
|
+
event.Context?.EventType?.Id, event.Context?.ObservationState ?? 0);
|
|
332
321
|
};
|
|
333
322
|
|
|
334
323
|
const processEvents = async (artifact: ActivatedArtifact<Record<string, Function>>) => {
|
|
@@ -369,11 +358,9 @@ export class Reactors implements IReactors {
|
|
|
369
358
|
eventTypeId
|
|
370
359
|
});
|
|
371
360
|
|
|
372
|
-
await
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
this._eventStoreName, this._namespace, this._resultHandler);
|
|
376
|
-
}, { delivery: ArtifactDelivery.Events, eventContext: context, methodName });
|
|
361
|
+
await invokeReactorHandler(artifact, methodName, content, context, services, result =>
|
|
362
|
+
dispatchReactorSideEffects(this._eventLog, result, context, reactorType as Function,
|
|
363
|
+
this._eventStoreName, this._namespace, this._resultHandler));
|
|
377
364
|
|
|
378
365
|
lastSuccessfullyObservedEvent = event.Context!.SequenceNumber;
|
|
379
366
|
} catch (err) {
|
|
@@ -446,65 +433,6 @@ export class Reactors implements IReactors {
|
|
|
446
433
|
}
|
|
447
434
|
}
|
|
448
435
|
|
|
449
|
-
private getEventTypesFor(reactorType: Constructor): EventTypeEntry[] {
|
|
450
|
-
const proto = reactorType.prototype as Record<string, unknown>;
|
|
451
|
-
const entries: EventTypeEntry[] = [];
|
|
452
|
-
const replayHandlers = new Map<string, string>();
|
|
453
|
-
const eventTypes = new Map<string, { eventTypeClass: Function; id: string; generation: number }>();
|
|
454
|
-
|
|
455
|
-
for (const eventTypeClass of this._clientArtifacts.eventTypes) {
|
|
456
|
-
const metadata = getEventTypeMetadata(eventTypeClass);
|
|
457
|
-
if (metadata) {
|
|
458
|
-
eventTypes.set((eventTypeClass as Function).name, {
|
|
459
|
-
eventTypeClass: eventTypeClass as Function,
|
|
460
|
-
id: metadata.eventType.id.value,
|
|
461
|
-
generation: metadata.eventType.generation.value
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// A derived method shadows a base method of the same name, even if it is not marked for replay.
|
|
467
|
-
const seenMethods = new Set<string>();
|
|
468
|
-
for (let current = proto; current && current !== Object.prototype; current = Object.getPrototypeOf(current) as Record<string, unknown>) {
|
|
469
|
-
for (const name of Object.getOwnPropertyNames(current)) {
|
|
470
|
-
if (seenMethods.has(name)) continue;
|
|
471
|
-
seenMethods.add(name);
|
|
472
|
-
const method = current[name];
|
|
473
|
-
if (typeof method !== 'function') continue;
|
|
474
|
-
const replayEventType = getReplayEventType(method);
|
|
475
|
-
if (replayEventType === undefined) continue;
|
|
476
|
-
|
|
477
|
-
const eventType = replayEventType === true
|
|
478
|
-
? eventTypes.get(name.startsWith('replay') ? name.slice('replay'.length) : '')
|
|
479
|
-
: [...eventTypes.values()].find(candidate => candidate.eventTypeClass === replayEventType);
|
|
480
|
-
if (!eventType) {
|
|
481
|
-
throw new Error(`Replay handler '${name}' on reactor '${(reactorType as Function).name}' has no registered event type.`);
|
|
482
|
-
}
|
|
483
|
-
if (replayHandlers.has(eventType.id)) {
|
|
484
|
-
throw new Error(`Reactor '${(reactorType as Function).name}' has multiple replay handlers for event type '${eventType.id}': '${replayHandlers.get(eventType.id)}' and '${name}'.`);
|
|
485
|
-
}
|
|
486
|
-
replayHandlers.set(eventType.id, name);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
for (const [className, eventType] of eventTypes) {
|
|
491
|
-
const methodName = className.charAt(0).toLowerCase() + className.slice(1);
|
|
492
|
-
const liveMethod = proto[methodName];
|
|
493
|
-
const liveMethodName = typeof liveMethod === 'function' && getReplayEventType(liveMethod) === undefined ? methodName : undefined;
|
|
494
|
-
const replayMethodName = replayHandlers.get(eventType.id);
|
|
495
|
-
if (liveMethodName || replayMethodName) {
|
|
496
|
-
entries.push({
|
|
497
|
-
id: eventType.id,
|
|
498
|
-
generation: eventType.generation,
|
|
499
|
-
methodName: liveMethodName,
|
|
500
|
-
replayMethodName
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
return entries;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
436
|
private disconnectAll(): void {
|
|
509
437
|
for (const controller of this._observations.values()) controller.abort();
|
|
510
438
|
for (const queue of this._queues.values()) queue.complete();
|
|
@@ -34,6 +34,19 @@ import { UnsupportedEventSequenceOperation } from './UnsupportedEventSequenceOpe
|
|
|
34
34
|
// JS trim() omits U+0085, which the kernel trims. Reject unproven non-ASCII whitespace and controls.
|
|
35
35
|
const unprovenFilterCharacters = /[\u007f-\u009f]|(?=[^\x00-\x7f])\p{White_Space}/u;
|
|
36
36
|
|
|
37
|
+
// Observers ReactorScenario registers to latch unsupported operations, keyed by sequence.
|
|
38
|
+
const unsupportedObservers = new WeakMap<object, (error: UnsupportedEventSequenceOperation) => void>();
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Observe every unsupported operation a scenario sequence reports, however the caller receives it.
|
|
42
|
+
* Internal to the testing harness; ReactorScenario uses it to fail deliveries that swallow such errors.
|
|
43
|
+
* @param sequence - The in-process sequence to observe.
|
|
44
|
+
* @param observer - Called with each unsupported-operation error the sequence creates.
|
|
45
|
+
*/
|
|
46
|
+
export function observeUnsupportedOperations(sequence: object, observer: (error: UnsupportedEventSequenceOperation) => void): void {
|
|
47
|
+
unsupportedObservers.set(sequence, observer);
|
|
48
|
+
}
|
|
49
|
+
|
|
37
50
|
/** Fixture-backed, scenario-local append sequence; no kernel or observer scheduler is started. */
|
|
38
51
|
export class InProcessEventSequence implements IEventSequence {
|
|
39
52
|
readonly id: EventSequenceId;
|
|
@@ -455,6 +468,8 @@ export class InProcessEventSequence implements IEventSequence {
|
|
|
455
468
|
}
|
|
456
469
|
|
|
457
470
|
private unsupported(operation: string, artifact: string, reason: string): UnsupportedEventSequenceOperation {
|
|
458
|
-
|
|
471
|
+
const error = new UnsupportedEventSequenceOperation(operation, artifact, reason);
|
|
472
|
+
unsupportedObservers.get(this)?.(error);
|
|
473
|
+
return error;
|
|
459
474
|
}
|
|
460
475
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import type { EventContext } from '../events/EventContext.js';
|
|
5
|
+
|
|
6
|
+
/** Observable outcome of one ordered source-partition delivery (not a kernel checkpoint). */
|
|
7
|
+
export interface ReactorDeliveryResult {
|
|
8
|
+
readonly sourceId: string;
|
|
9
|
+
readonly handled: readonly EventContext[];
|
|
10
|
+
readonly skipped: readonly EventContext[];
|
|
11
|
+
readonly completed: boolean;
|
|
12
|
+
readonly error?: unknown;
|
|
13
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { field } from '@cratis/fundamentals';
|
|
5
|
+
import { chai, describe, it } from 'vitest';
|
|
6
|
+
import { ArtifactCompletionFailed } from '../artifacts/ArtifactCompletionFailed.js';
|
|
7
|
+
import { eventType } from '../events/eventTypeDecorator.js';
|
|
8
|
+
import { reactor } from '../reactors/reactor.js';
|
|
9
|
+
import { ReactorScenario } from './ReactorScenario.js';
|
|
10
|
+
import { UnsupportedReactorOperation } from './UnsupportedReactorOperation.js';
|
|
11
|
+
|
|
12
|
+
chai.should();
|
|
13
|
+
@eventType('rejection-event')
|
|
14
|
+
class Registered {
|
|
15
|
+
@field(String) name: string;
|
|
16
|
+
constructor(name: string) { this.name = name; }
|
|
17
|
+
}
|
|
18
|
+
class Unregistered { constructor(readonly name: string) {} }
|
|
19
|
+
const options = { artifacts: { eventTypes: [Registered] }, constraints: 'disabled' as const };
|
|
20
|
+
|
|
21
|
+
async function rejected(operation: string, call: () => Promise<unknown>): Promise<void> {
|
|
22
|
+
await call().then(() => { throw new Error('expected rejection'); }, error => {
|
|
23
|
+
(error instanceof UnsupportedReactorOperation).should.be.true;
|
|
24
|
+
error.operation.should.equal(operation);
|
|
25
|
+
error.message.should.contain('Use a kernel-backed test.');
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('unsupported reactor operations', () => {
|
|
30
|
+
it('rejects missing registration, custom sequence and commands during configuration', () => {
|
|
31
|
+
class Plain {}
|
|
32
|
+
(() => new ReactorScenario(Plain, options)).should.throw(UnsupportedReactorOperation, 'Use a kernel-backed test.');
|
|
33
|
+
@reactor('custom-sequence', 'custom') class Custom { registered() {} }
|
|
34
|
+
(() => new ReactorScenario(Custom, options)).should.throw(UnsupportedReactorOperation, 'Use a kernel-backed test.');
|
|
35
|
+
@reactor('commands') class Commands { registered() {} }
|
|
36
|
+
(() => new ReactorScenario(Commands, { ...options, commandTypes: [Unregistered] }))
|
|
37
|
+
.should.throw(UnsupportedReactorOperation, 'Use a kernel-backed test.');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('rejects replay and redelivery asynchronously without mutation', async () => {
|
|
41
|
+
@reactor('replay-rejection') class R { registered() {} }
|
|
42
|
+
const scenario = new ReactorScenario(R, options);
|
|
43
|
+
await rejected('replay', () => scenario.replay());
|
|
44
|
+
await rejected('redeliver', () => scenario.redeliver());
|
|
45
|
+
await rejected('when.events', () => scenario.when.forEventSource('A').events());
|
|
46
|
+
scenario.results.length.should.equal(0);
|
|
47
|
+
scenario.sideEffects.length.should.equal(0);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('rejects unsupported returned values instead of silently dropping them', async () => {
|
|
51
|
+
for (const value of [{ command: true }, [new Registered('ok'), { command: true }], [undefined]]) {
|
|
52
|
+
@reactor('unknown-result') class R { registered() { return value; } }
|
|
53
|
+
const scenario = new ReactorScenario(R, options);
|
|
54
|
+
await rejected('handler.return', () => scenario.when.forEventSource('A').events(new Registered('one')));
|
|
55
|
+
scenario.sideEffects.length.should.equal(0);
|
|
56
|
+
scenario.results[0].completed.should.be.false;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('rejects unsupported setup before invocation and without delivery results', async () => {
|
|
61
|
+
let called = 0;
|
|
62
|
+
@reactor('unsupported-input') class R { registered() { called++; } }
|
|
63
|
+
const scenario = new ReactorScenario(R, options);
|
|
64
|
+
await scenario.given.forEventSource('A').events(new Registered('ok'), new Unregistered('bad')).then(() => {
|
|
65
|
+
throw new Error('expected rejection');
|
|
66
|
+
}, error => { error.message.should.contain('Use a kernel-backed test.'); });
|
|
67
|
+
called.should.equal(0);
|
|
68
|
+
scenario.results.length.should.equal(0);
|
|
69
|
+
await scenario.when.forEventSource('A').events(new Registered('after'));
|
|
70
|
+
scenario.results[0].handled[0].sequenceNumber.should.equal(0n);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('rejects missing read-model services instead of supplying fake data', async () => {
|
|
74
|
+
@reactor('read-model-rejection') class R {
|
|
75
|
+
registered(_event: Registered, _context: unknown, services: { readModels: { findInstanceById(): unknown } }) {
|
|
76
|
+
return services.readModels.findInstanceById();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const scenario = new ReactorScenario(R, options);
|
|
80
|
+
await rejected('services.readModels.findInstanceById', () => scenario.when.forEventSource('A').events(new Registered('one')));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('preserves processing-only, completion-only and activation failures', async () => {
|
|
84
|
+
@reactor('failure-rejection') class R { registered() { throw new Error('handler'); } }
|
|
85
|
+
const processing = new ReactorScenario(R, { ...options, artifactActivator: type => ({ instance: new type() }) });
|
|
86
|
+
await processing.when.forEventSource('A').events(new Registered('one')).then(() => { throw new Error('expected rejection'); },
|
|
87
|
+
error => { String(error).should.contain('handler'); });
|
|
88
|
+
processing.results[0].completed.should.be.false;
|
|
89
|
+
@reactor('completion-rejection') class Complete { registered() {} }
|
|
90
|
+
const completion = new ReactorScenario(Complete, { ...options, artifactActivator: type => ({ instance: new type(),
|
|
91
|
+
complete: () => { throw new Error('completion'); } }) });
|
|
92
|
+
await completion.when.forEventSource('A').events(new Registered('one')).then(() => { throw new Error('expected rejection'); },
|
|
93
|
+
error => { (error instanceof ArtifactCompletionFailed).should.be.true; chai.expect(error.processingError).to.be.undefined; });
|
|
94
|
+
completion.results[0].completed.should.be.false;
|
|
95
|
+
const activation = new ReactorScenario(Complete, { ...options, artifactActivator: () => { throw new Error('activation'); } });
|
|
96
|
+
await activation.when.forEventSource('A').events(new Registered('one')).then(() => { throw new Error('expected rejection'); },
|
|
97
|
+
error => { String(error).should.contain('activation'); });
|
|
98
|
+
activation.results[0].handled.length.should.equal(0);
|
|
99
|
+
});
|
|
100
|
+
});
|