Skip to content

Commit cbd1196

Browse files
[TECH] Mise en place d'Events liés au passage de module (PIX-16726)
#11536
2 parents 927f9cc + 1a522b6 commit cbd1196

File tree

5 files changed

+362
-0
lines changed

5 files changed

+362
-0
lines changed

api/src/devcomp/domain/errors.js

+7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ class ElementInstantiationError extends DomainError {
1818
}
1919
}
2020

21+
class PassageEventInstantiationError extends TypeError {
22+
constructor(message = 'A passage event cannot be instantiated directly') {
23+
super(message);
24+
}
25+
}
26+
2127
class PassageDoesNotExistError extends DomainError {
2228
constructor(message = 'The passage does not exist') {
2329
super(message);
@@ -41,6 +47,7 @@ export {
4147
ModuleDoesNotExistError,
4248
ModuleInstantiationError,
4349
PassageDoesNotExistError,
50+
PassageEventInstantiationError,
4451
PassageTerminatedError,
4552
UserNotAuthorizedToFindTrainings,
4653
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { assertNotNullOrUndefined } from '../../../../shared/domain/models/asserts.js';
2+
import { PassageEventInstantiationError } from '../../errors.js';
3+
4+
/**
5+
* @abstract PassageEvent
6+
* After the write operation is successful, the system generates events that describe what changed (like "Passage started" or "Video started").
7+
* These events serve as notifications about the updates.
8+
* See Event sourcing pattern for more information.
9+
* https://martinfowler.com/eaaDev/EventSourcing.html
10+
*
11+
* This is the base class for all PassageEvents. Subclasses should be named in past tense.
12+
*/
13+
class PassageEvent {
14+
constructor({ id, type, occurredAt, createdAt, passageId, data } = {}) {
15+
if (this.constructor === PassageEvent) {
16+
throw new PassageEventInstantiationError();
17+
}
18+
19+
assertNotNullOrUndefined(id, 'The id is required for a PassageEvent');
20+
assertNotNullOrUndefined(type, 'The type is required for a PassageEvent');
21+
assertNotNullOrUndefined(occurredAt, 'The occurredAt is required for a PassageEvent');
22+
assertNotNullOrUndefined(createdAt, 'The createdAt is required for a PassageEvent');
23+
assertNotNullOrUndefined(passageId, 'The passageId is required for a PassageEvent');
24+
25+
this.id = id;
26+
this.type = type;
27+
this.occurredAt = occurredAt;
28+
this.createdAt = createdAt;
29+
this.passageId = passageId;
30+
this.data = data;
31+
}
32+
}
33+
34+
export { PassageEvent };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { assertNotNullOrUndefined } from '../../../../shared/domain/models/asserts.js';
2+
import { PassageEvent } from './PassageEvent.js';
3+
4+
/**
5+
* @class PassageStartedEvent
6+
*
7+
* A PassageStartedEvent is generated when a Modulix passage is started and saved in DB.
8+
*/
9+
class PassageStartedEvent extends PassageEvent {
10+
constructor({ id, occurredAt, createdAt, passageId, contentHash }) {
11+
super({ id, type: 'PASSAGE_STARTED', occurredAt, createdAt, passageId, data: { contentHash } });
12+
13+
assertNotNullOrUndefined(contentHash, 'The contentHash is required for a PassageStartedEvent');
14+
15+
this.contentHash = contentHash;
16+
}
17+
}
18+
19+
/**
20+
* @class PassageTerminatedEvent
21+
*
22+
* A PassageTerminatedEvent is generated when a Modulix passage is terminated and saved in DB.
23+
*/
24+
class PassageTerminatedEvent extends PassageEvent {
25+
constructor({ id, occurredAt, createdAt, passageId }) {
26+
super({ id, type: 'PASSAGE_TERMINATED', occurredAt, createdAt, passageId });
27+
}
28+
}
29+
30+
export { PassageStartedEvent, PassageTerminatedEvent };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import {
2+
PassageStartedEvent,
3+
PassageTerminatedEvent,
4+
} from '../../../../../../src/devcomp/domain/models/passage-events/passage-events.js';
5+
import { DomainError } from '../../../../../../src/shared/domain/errors.js';
6+
import { catchErrSync, expect } from '../../../../../test-helper.js';
7+
8+
describe('Integration | Devcomp | Domain | Models | passage-events | passage-events', function () {
9+
describe('#PassageStartedEvent', function () {
10+
it('should init and keep attributes', function () {
11+
// given
12+
const id = Symbol('id');
13+
const occurredAt = Symbol('date');
14+
const createdAt = Symbol('date');
15+
const passageId = Symbol('passage');
16+
const contentHash = Symbol('contentHash');
17+
18+
// when
19+
const passageStartedEvent = new PassageStartedEvent({ id, occurredAt, createdAt, passageId, contentHash });
20+
21+
// then
22+
expect(passageStartedEvent.id).to.equal(id);
23+
expect(passageStartedEvent.type).to.equal('PASSAGE_STARTED');
24+
expect(passageStartedEvent.occurredAt).to.equal(occurredAt);
25+
expect(passageStartedEvent.createdAt).to.equal(createdAt);
26+
expect(passageStartedEvent.passageId).to.equal(passageId);
27+
expect(passageStartedEvent.contentHash).to.equal(contentHash);
28+
expect(passageStartedEvent.data).to.deep.equal({ contentHash });
29+
});
30+
31+
describe('when contentHash is not given', function () {
32+
it('should throw an error', function () {
33+
// given
34+
const id = Symbol('id');
35+
const occurredAt = Symbol('date');
36+
const createdAt = Symbol('date');
37+
const passageId = Symbol('passage');
38+
39+
// when
40+
const error = catchErrSync(() => new PassageStartedEvent({ id, occurredAt, createdAt, passageId }))();
41+
42+
// then
43+
expect(error).to.be.instanceOf(DomainError);
44+
expect(error.message).to.equal('The contentHash is required for a PassageStartedEvent');
45+
});
46+
});
47+
});
48+
49+
describe('#PassageTerminatedEvent', function () {
50+
it('should init and keep attributes', function () {
51+
// given
52+
const id = Symbol('id');
53+
const occurredAt = Symbol('date');
54+
const createdAt = Symbol('date');
55+
const passageId = Symbol('passage');
56+
57+
// when
58+
const passageTerminatedEvent = new PassageTerminatedEvent({ id, occurredAt, createdAt, passageId });
59+
60+
// then
61+
expect(passageTerminatedEvent.id).to.equal(id);
62+
expect(passageTerminatedEvent.type).to.equal('PASSAGE_TERMINATED');
63+
expect(passageTerminatedEvent.occurredAt).to.equal(occurredAt);
64+
expect(passageTerminatedEvent.createdAt).to.equal(createdAt);
65+
expect(passageTerminatedEvent.passageId).to.equal(passageId);
66+
expect(passageTerminatedEvent.data).to.be.undefined;
67+
});
68+
});
69+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import { PassageEventInstantiationError } from '../../../../../../src/devcomp/domain/errors.js';
2+
import { PassageEvent } from '../../../../../../src/devcomp/domain/models/passage-events/PassageEvent.js';
3+
import { DomainError } from '../../../../../../src/shared/domain/errors.js';
4+
import { catchErrSync, expect } from '../../../../../test-helper.js';
5+
6+
describe('Unit | Devcomp | Domain | Models | PassageEvent', function () {
7+
describe('#constructor', function () {
8+
it('should not be able to create a PassageEvent directly', function () {
9+
// given & when
10+
const error = catchErrSync(() => new PassageEvent({}))();
11+
12+
// then
13+
expect(error).to.be.instanceOf(PassageEventInstantiationError);
14+
});
15+
16+
describe('if a passage event does not have an id', function () {
17+
it('should throw an error', function () {
18+
// given
19+
class FakeEvent extends PassageEvent {
20+
constructor() {
21+
super({});
22+
}
23+
}
24+
25+
// when
26+
const error = catchErrSync(() => new FakeEvent())();
27+
28+
// then
29+
expect(error).to.be.instanceOf(DomainError);
30+
expect(error.message).to.equal('The id is required for a PassageEvent');
31+
});
32+
});
33+
34+
describe('if a passage event does not have a type', function () {
35+
it('should throw an error', function () {
36+
// given
37+
class FakeEvent extends PassageEvent {
38+
constructor() {
39+
super({ id: 1 });
40+
}
41+
}
42+
43+
// when
44+
const error = catchErrSync(() => new FakeEvent())();
45+
46+
// then
47+
expect(error).to.be.instanceOf(DomainError);
48+
expect(error.message).to.equal('The type is required for a PassageEvent');
49+
});
50+
});
51+
52+
describe('if a passage event does not have a occurredAt', function () {
53+
it('should throw an error', function () {
54+
// given
55+
class FakeEvent extends PassageEvent {
56+
constructor() {
57+
super({ id: 1, type: 'FAKE' });
58+
}
59+
}
60+
61+
// when
62+
const error = catchErrSync(() => new FakeEvent())();
63+
64+
// then
65+
expect(error).to.be.instanceOf(DomainError);
66+
expect(error.message).to.equal('The occurredAt is required for a PassageEvent');
67+
});
68+
});
69+
70+
describe('if a passage event does not have a createdAt', function () {
71+
it('should throw an error', function () {
72+
// given
73+
class FakeEvent extends PassageEvent {
74+
constructor() {
75+
super({ id: 1, type: 'FAKE', occurredAt: Symbol('date') });
76+
}
77+
}
78+
79+
// when
80+
const error = catchErrSync(() => new FakeEvent())();
81+
82+
// then
83+
expect(error).to.be.instanceOf(DomainError);
84+
expect(error.message).to.equal('The createdAt is required for a PassageEvent');
85+
});
86+
});
87+
88+
describe('if a passage event does not have a passageId', function () {
89+
it('should throw an error', function () {
90+
// given
91+
class FakeEvent extends PassageEvent {
92+
constructor() {
93+
super({ id: 1, type: 'FAKE', occurredAt: Symbol('date'), createdAt: Symbol('date') });
94+
}
95+
}
96+
97+
// when
98+
const error = catchErrSync(() => new FakeEvent())();
99+
100+
// then
101+
expect(error).to.be.instanceOf(DomainError);
102+
expect(error.message).to.equal('The passageId is required for a PassageEvent');
103+
});
104+
});
105+
106+
describe('if a passage event has minimal required attributes', function () {
107+
it('should create a PassageEvent and set id attribute', function () {
108+
// given
109+
const id = Symbol('id');
110+
const occurredAt = Symbol('date');
111+
const createdAt = Symbol('date');
112+
const passageId = Symbol('passage');
113+
class FakeEvent extends PassageEvent {
114+
constructor() {
115+
super({ id, type: 'FAKE', occurredAt, createdAt, passageId });
116+
}
117+
}
118+
119+
// when
120+
const event = new FakeEvent();
121+
122+
// then
123+
expect(event.id).to.equal(id);
124+
});
125+
126+
it('should create a PassageEvent and set type attribute', function () {
127+
// given
128+
const id = Symbol('id');
129+
const occurredAt = Symbol('date');
130+
const createdAt = Symbol('date');
131+
const passageId = Symbol('passage');
132+
class FakeEvent extends PassageEvent {
133+
constructor() {
134+
super({ id, type: 'FAKE', occurredAt, createdAt, passageId });
135+
}
136+
}
137+
138+
// when
139+
const event = new FakeEvent();
140+
141+
// then
142+
expect(event.type).to.equal('FAKE');
143+
});
144+
145+
it('should create a PassageEvent and set occurredAt attribute', function () {
146+
// given
147+
const id = Symbol('id');
148+
const occurredAt = Symbol('date');
149+
const createdAt = Symbol('date');
150+
const passageId = Symbol('passage');
151+
class FakeEvent extends PassageEvent {
152+
constructor() {
153+
super({ id, type: 'FAKE', occurredAt, createdAt, passageId });
154+
}
155+
}
156+
157+
// when
158+
const event = new FakeEvent();
159+
160+
// then
161+
expect(event.occurredAt).to.equal(occurredAt);
162+
});
163+
164+
it('should create a PassageEvent and set createdAt attribute', function () {
165+
// given
166+
const id = Symbol('id');
167+
const occurredAt = Symbol('date');
168+
const createdAt = Symbol('date');
169+
const passageId = Symbol('passage');
170+
class FakeEvent extends PassageEvent {
171+
constructor() {
172+
super({ id, type: 'FAKE', occurredAt, createdAt, passageId });
173+
}
174+
}
175+
176+
// when
177+
const event = new FakeEvent();
178+
179+
// then
180+
expect(event.createdAt).to.equal(createdAt);
181+
});
182+
183+
it('should create a PassageEvent and set passageId attribute', function () {
184+
// given
185+
const id = Symbol('id');
186+
const occurredAt = Symbol('date');
187+
const createdAt = Symbol('date');
188+
const passageId = Symbol('passage');
189+
class FakeEvent extends PassageEvent {
190+
constructor() {
191+
super({ id, type: 'FAKE', occurredAt, createdAt, passageId });
192+
}
193+
}
194+
195+
// when
196+
const event = new FakeEvent();
197+
198+
// then
199+
expect(event.passageId).to.equal(passageId);
200+
});
201+
202+
it('should create a PassageEvent and set data to undefined', function () {
203+
// given
204+
const id = Symbol('id');
205+
const occurredAt = Symbol('date');
206+
const createdAt = Symbol('date');
207+
const passageId = Symbol('passage');
208+
class FakeEvent extends PassageEvent {
209+
constructor() {
210+
super({ id, type: 'FAKE', occurredAt, createdAt, passageId });
211+
}
212+
}
213+
214+
// when
215+
const event = new FakeEvent();
216+
217+
// then
218+
expect(event.data).to.be.undefined;
219+
});
220+
});
221+
});
222+
});

0 commit comments

Comments
 (0)