Skip to content

Commit caba9e5

Browse files
alex-laycalvertoskardudycz
authored andcommitted
add support for async deciders in the decider specification, fix typo
1 parent acc73ab commit caba9e5

File tree

2 files changed

+380
-85
lines changed

2 files changed

+380
-85
lines changed
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import { describe, it } from 'node:test';
2+
import { IllegalStateError, ValidationError } from '../errors';
3+
import { AssertionError, assertTrue } from '../testing/assertions';
4+
import { type Command, type Event } from '../typing';
5+
import { DeciderSpecification } from './deciderSpecification';
6+
7+
type DoSomething = Command<'Do', { something: string }>;
8+
type SomethingHappened = Event<'Did', { something: string }>;
9+
type Entity = { something: string };
10+
11+
const decide = async (
12+
{ data: { something } }: DoSomething,
13+
_entity: Entity,
14+
): Promise<SomethingHappened | [] | [SomethingHappened]> => {
15+
const event: SomethingHappened = { type: 'Did', data: { something } };
16+
17+
if (something === 'Ignore!') return [];
18+
if (something === 'Array!') return [event];
19+
if (something !== 'Yes!') throw new IllegalStateError('Nope!');
20+
21+
return Promise.resolve(event);
22+
};
23+
24+
const initialState = (): Entity => ({ something: 'Meh' });
25+
26+
const evolve = (_entity: Entity, _event: SomethingHappened): Entity => ({
27+
something: 'Nothing',
28+
});
29+
30+
const given = DeciderSpecification.for({
31+
decide: decide,
32+
evolve,
33+
initialState: initialState,
34+
});
35+
36+
void describe('AsyncDeciderSpecification', () => {
37+
void describe('then', () => {
38+
void it('then fails if returns event, but assertion has an empty array', async () => {
39+
try {
40+
await given([])
41+
.when({
42+
type: 'Do',
43+
data: {
44+
something: 'Yes!',
45+
},
46+
})
47+
.then([]);
48+
} catch (error) {
49+
assertTrue(
50+
error instanceof AssertionError &&
51+
error.message ===
52+
`Arrays lengths don't match:\nExpected: 1\nActual: 0`,
53+
);
54+
}
55+
});
56+
});
57+
58+
void describe('thenNothingHappened', () => {
59+
void it('thenNothingHappened succeeds if returns empty array', async () => {
60+
await given([])
61+
.when({
62+
type: 'Do',
63+
data: {
64+
something: 'Ignore!',
65+
},
66+
})
67+
.thenNothingHappened();
68+
});
69+
70+
void it('thenNothingHappened fails if returns event', async () => {
71+
try {
72+
await given([])
73+
.when({
74+
type: 'Do',
75+
data: {
76+
something: 'Yes!',
77+
},
78+
})
79+
.thenNothingHappened();
80+
} catch (error) {
81+
assertTrue(
82+
error instanceof AssertionError &&
83+
error.message ===
84+
`Array is not empty [{"type":"Did","data":{"something":"Yes!"}}]:\nExpected: 1\nActual: 0`,
85+
);
86+
}
87+
});
88+
89+
void it('thenNothingHappened fails if returns array of events', async () => {
90+
try {
91+
await given([])
92+
.when({
93+
type: 'Do',
94+
data: {
95+
something: 'Array!',
96+
},
97+
})
98+
.thenNothingHappened();
99+
} catch (error) {
100+
assertTrue(
101+
error instanceof AssertionError &&
102+
error.message ===
103+
`Array is not empty [{"type":"Did","data":{"something":"Array!"}}]:\nExpected: 1\nActual: 0`,
104+
);
105+
}
106+
});
107+
});
108+
109+
void describe('thenThrows', () => {
110+
void it('check error was thrown', async () => {
111+
await given([])
112+
.when({
113+
type: 'Do',
114+
data: {
115+
something: 'Nope!',
116+
},
117+
})
118+
.thenThrows();
119+
});
120+
121+
void it('checks error condition', async () => {
122+
await given([])
123+
.when({
124+
type: 'Do',
125+
data: {
126+
something: 'Nope!',
127+
},
128+
})
129+
.thenThrows((error) => error.message === 'Nope!');
130+
});
131+
132+
void it('checks error type', async () => {
133+
await given([])
134+
.when({
135+
type: 'Do',
136+
data: {
137+
something: 'Nope!',
138+
},
139+
})
140+
.thenThrows(IllegalStateError);
141+
});
142+
143+
void it('checks error type and condition', async () => {
144+
await given([])
145+
.when({
146+
type: 'Do',
147+
data: {
148+
something: 'Nope!',
149+
},
150+
})
151+
.thenThrows(IllegalStateError, (error) => error.message === 'Nope!');
152+
});
153+
154+
void it('fails if no error was thrown', async () => {
155+
try {
156+
await given([])
157+
.when({
158+
type: 'Do',
159+
data: {
160+
something: 'Yes!',
161+
},
162+
})
163+
.thenThrows();
164+
} catch (error) {
165+
assertTrue(
166+
error instanceof AssertionError &&
167+
error.message === 'Handler did not fail as expected',
168+
);
169+
}
170+
});
171+
172+
void it('fails if wrong error type', async () => {
173+
try {
174+
await given([])
175+
.when({
176+
type: 'Do',
177+
data: {
178+
something: 'Nope!',
179+
},
180+
})
181+
.thenThrows(ValidationError);
182+
} catch (error) {
183+
assertTrue(
184+
error instanceof AssertionError &&
185+
error.message.startsWith(
186+
'Caught error is not an instance of the expected type:',
187+
),
188+
);
189+
}
190+
});
191+
192+
void it('fails if wrong error type and correct condition', async () => {
193+
try {
194+
await given([])
195+
.when({
196+
type: 'Do',
197+
data: {
198+
something: 'Nope!',
199+
},
200+
})
201+
.thenThrows(ValidationError, (error) => error.message === 'Nope!');
202+
} catch (error) {
203+
assertTrue(
204+
error instanceof AssertionError &&
205+
error.message.startsWith(
206+
'Caught error is not an instance of the expected type:',
207+
),
208+
);
209+
}
210+
});
211+
212+
void it('fails if correct error type but wrong correct condition', async () => {
213+
try {
214+
await given([])
215+
.when({
216+
type: 'Do',
217+
data: {
218+
something: 'Nope!',
219+
},
220+
})
221+
.thenThrows(IllegalStateError, (error) => error.message !== 'Nope!');
222+
} catch (error) {
223+
assertTrue(
224+
error instanceof AssertionError &&
225+
error.message ===
226+
`Error didn't match the error condition: Error: Nope!`,
227+
);
228+
}
229+
});
230+
});
231+
});

0 commit comments

Comments
 (0)