Passed
Push — main ( f86915...d13a4d )
by Lorenzo
01:17 queued 15s
created

src/core/ExpressBeans.spec.ts   A

Complexity

Total Complexity 4
Complexity/F 0

Size

Lines of Code 313
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 4
eloc 252
mnd 4
bc 4
fnc 0
dl 0
loc 313
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
import express from 'express';
2
import { flushPromises } from '@test/utils/testUtils';
3
import { pinoHttp } from 'pino-http';
4
import ExpressBeans from '@/core/ExpressBeans';
5
import { logger, registeredBeans } from '@/core';
6
import { ExpressBean } from '@/ExpressBeansTypes';
7
8
jest.mock('express');
9
jest.mock('pino-http');
10
jest.mock('@/core', () => ({
11
  registeredBeans: new Map(),
12
  logger: {
13
    info: jest.fn(),
14
    debug: jest.fn(),
15
    error: jest.fn(),
16
  },
17
}));
18
19
describe('ExpressBeans.ts', () => {
20
  const realSetImmediate = setImmediate;
21
  const expressMock = {
22
    disable: jest.fn(),
23
    listen: jest.fn(),
24
    use: jest.fn(),
25
  };
26
27
  beforeEach(async () => {
28
    jest.clearAllMocks();
29
    jest.resetAllMocks();
30
    jest.resetModules();
31
    jest.mocked(express).mockReturnValue(expressMock as unknown as express.Express);
32
    registeredBeans.clear();
33
    jest.spyOn(global, 'setImmediate').mockImplementation((cb) => realSetImmediate(cb));
34
    await flushPromises();
35
  });
36
37
  test('creation of a new application', async () => {
38
    // GIVEN
39
    expressMock.listen.mockImplementation((_port, cb) => cb());
40
    const application = new ExpressBeans();
41
    const mockedLogger = jest.mocked(logger);
42
43
    // WHEN
44
    await flushPromises();
45
46
    // THEN
47
    expect(application instanceof ExpressBeans).toBe(true);
48
    expect(expressMock.disable).toHaveBeenCalledWith('x-powered-by');
49
    expect(expressMock.listen).toHaveBeenCalledWith(8080, expect.any(Function));
50
    expect(mockedLogger.info).toHaveBeenCalledWith('Server listening on port 8080');
51
  });
52
53
  test('creation of a new application with static method', async () => {
54
    // GIVEN
55
    expressMock.listen.mockImplementation((_port, cb) => cb());
56
    const application = await ExpressBeans.createApp();
57
    const mockedLogger = jest.mocked(logger);
58
59
    // WHEN
60
    await flushPromises();
61
62
    // THEN
63
    expect(application instanceof ExpressBeans).toBe(true);
64
    expect(expressMock.disable).toHaveBeenCalledWith('x-powered-by');
65
    expect(expressMock.listen).toHaveBeenCalledWith(8080, expect.any(Function));
66
    expect(mockedLogger.info).toHaveBeenCalledWith('Server listening on port 8080');
67
  });
68
69
  it('exposes use method of express application', async () => {
70
    // GIVEN
71
    const application = new ExpressBeans();
72
    const middleware = (
73
      _req: express.Request,
74
      _res: express.Response,
75
      next: express.NextFunction,
76
    ) => next();
77
78
    // WHEN
79
    application.use(middleware);
80
81
    // THEN
82
    expect(expressMock.use).toHaveBeenCalledWith(middleware);
83
  });
84
85
  it('exposes express application', async () => {
86
    // GIVEN
87
    const application = new ExpressBeans();
88
89
    // WHEN
90
    const expressApp = application.getApp();
91
92
    // THEN
93
    expect(expressApp).toBe(expressMock);
94
  });
95
96
  it('calls onInitialized callback', async () => {
97
    // GIVEN
98
    expressMock.listen.mockImplementation((_port, cb) => cb());
99
    const onInitialized = jest.fn();
100
101
    // WHEN
102
    const application = new ExpressBeans({ onInitialized });
103
    await flushPromises();
104
105
    // THEN
106
    expect(application instanceof ExpressBeans).toBe(true);
107
    expect(onInitialized).toHaveBeenCalled();
108
  });
109
110
  it('calls onError callback', async () => {
111
    // GIVEN
112
    const error = new Error('Port already in use');
113
    expressMock.listen.mockImplementation(() => {
114
      throw error;
115
    });
116
    const onError = jest.fn();
117
118
    // WHEN
119
    const application = new ExpressBeans({ onError });
120
    await flushPromises();
121
122
    // THEN
123
    expect(application instanceof ExpressBeans).toBe(true);
124
    expect(onError).toHaveBeenCalledWith(error);
125
  });
126
127
  it('throws an error if listen function fails', async () => {
128
    // GIVEN
129
    jest.spyOn(global, 'setImmediate').mockImplementationOnce((cb) => cb() as unknown as ReturnType<typeof setImmediate>);
130
    const error = new Error('Port already in use');
131
    expressMock.listen.mockImplementationOnce(() => {
132
      throw error;
133
    });
134
135
    // WHEN
136
    try {
137
      await ExpressBeans.createApp();
138
      await flushPromises();
139
    } catch (err) {
140
      expect(err).toBe(error);
141
    }
142
  });
143
144
  it('stops the process if listen function fails', async () => {
145
    // GIVEN
146
    const mockExit = jest.spyOn(process, 'exit')
147
      .mockImplementationOnce(
148
        // prevent process.exit to actually ending the process
149
        () => undefined as never,
150
      );
151
    jest.spyOn(global, 'setImmediate').mockImplementationOnce((cb) => cb() as unknown as ReturnType<typeof setImmediate>);
152
    const error = new Error('Port already in use');
153
    expressMock.listen.mockImplementationOnce(() => {
154
      throw error;
155
    });
156
157
    // WHEN
158
    const app = new ExpressBeans({ onInitialized: jest.fn() });
159
    await flushPromises();
160
161
    // THEN
162
    expect(mockExit).toHaveBeenCalledWith(1);
163
    expect((app as any).onInitialized).not.toHaveBeenCalled();
164
    mockExit.mockRestore();
165
  });
166
167
  it('accepts a list of beans', async () => {
168
    // GIVEN
169
    const bean1 = class Bean1 {};
170
    const bean2 = class Bean2 {};
171
    const bean3 = class Bean3 {};
172
    const beans = [bean1, bean2, bean3] as unknown as ExpressBean[];
173
    beans.forEach((bean: any) => {
174
      bean._beanUUID = crypto.randomUUID();
175
    });
176
177
    // WHEN
178
    const application = new ExpressBeans({
179
      routerBeans: beans,
180
    });
181
182
    // THEN
183
    expect(application instanceof ExpressBeans).toBe(true);
184
  });
185
186
  it('throws an error if passing a non bean class', async () => {
187
    // GIVEN
188
    const bean1 = class Bean1 {} as any;
189
    bean1._beanUUID = crypto.randomUUID();
190
    const bean2 = class Bean2 {} as any;
191
    bean2._beanUUID = crypto.randomUUID();
192
    const notABean = class NotABean {};
193
    const beans = [bean1, bean2, notABean];
194
    const error = new Error('Trying to use something that is not an ExpressBean: NotABean');
195
    let application;
196
197
    // WHEN
198
    try {
199
      application = new ExpressBeans({
200
        routerBeans: beans,
201
      });
202
    } catch (e) {
203
      expect(e).toStrictEqual(error);
204
      expect(application).toBeUndefined();
205
    }
206
    expect.assertions(2);
207
  });
208
209
  it('registers router beans in express application', async () => {
210
    // GIVEN
211
    const loggerMock = jest.fn();
212
    jest.mocked(pinoHttp).mockReturnValueOnce(loggerMock as unknown as ReturnType<typeof pinoHttp>);
213
    const bean1 = class Bean1 {};
214
    const bean2 = class Bean2 {};
215
    const bean3 = class Bean3 {};
216
    const beans = [bean1, bean2, bean3];
217
    beans.forEach((Bean: any, index) => {
218
      Bean._beanUUID = crypto.randomUUID();
219
      const bean = new Bean();
220
      bean._routerConfig = {
221
        path: `router-path/${index}`,
222
        router: { id: index },
223
      };
224
      registeredBeans.set(`Bean${index + 1}`, bean);
225
    });
226
227
    // WHEN
228
    expressMock.use.mockReset();
229
    const application = new ExpressBeans({
230
      routerBeans: beans,
231
    });
232
    await flushPromises();
233
234
    // THEN
235
    expect(application instanceof ExpressBeans).toBe(true);
236
    expect(expressMock.use).toHaveBeenCalledTimes(4);
237
    expect(expressMock.use).toHaveBeenCalledWith(expect.any(Function));
238
    expect(expressMock.use).toHaveBeenCalledWith('router-path/0', { id: 0 });
239
    expect(expressMock.use).toHaveBeenCalledWith('router-path/1', { id: 1 });
240
    expect(expressMock.use).toHaveBeenCalledWith('router-path/2', { id: 2 });
241
  });
242
243
  it('throws an error if a router has not created correctly', async () => {
244
    // GIVEN
245
    const bean1 = class Bean1 {};
246
    const bean2 = class Bean2 {};
247
    const bean3 = class Bean3 {};
248
    const beans = [bean1, bean2, bean3];
249
    beans.forEach((Bean: any, index) => {
250
      Bean._beanUUID = crypto.randomUUID();
251
      Bean._className = `Bean${index + 1}`;
252
      const bean = new Bean();
253
      bean._className = `Bean${index + 1}`;
254
      bean._routerConfig = {
255
        path: `router-path/${index}`,
256
        router: { id: index },
257
      };
258
      registeredBeans.set(Bean._className, bean);
259
    });
260
    const error = new Error('router initialization failed');
261
    expressMock.use
262
      .mockReturnValueOnce(undefined)
263
      .mockReturnValueOnce(undefined)
264
      .mockReturnValueOnce(undefined)
265
      .mockImplementationOnce(() => {
266
        throw error;
267
      });
268
269
    try {
270
      // WHEN
271
      await ExpressBeans.createApp({ routerBeans: beans });
272
    } catch (e) {
273
      // THEN
274
      expect(e).toStrictEqual(new Error('Router Bean3 not initialized correctly'));
275
    }
276
  });
277
278
  it('throws an error (no callback) if a router has not created correctly', async () => {
279
    // GIVEN
280
    const bean1 = class Bean1 {};
281
    const bean2 = class Bean2 {};
282
    const bean3 = class Bean3 {};
283
    const beans = [bean1, bean2, bean3];
284
    beans.forEach((Bean: any, index) => {
285
      Bean._beanUUID = crypto.randomUUID();
286
      Bean._className = `Bean${index + 1}`;
287
      const bean = new Bean();
288
      bean._className = `Bean${index + 1}`;
289
      bean._routerConfig = {
290
        path: `router-path/${index}`,
291
        router: { id: index },
292
      };
293
      registeredBeans.set(Bean._className, bean);
294
    });
295
    const error = new Error('router initialization failed');
296
    expressMock.use
297
      .mockReturnValueOnce(undefined)
298
      .mockReturnValueOnce(undefined)
299
      .mockImplementationOnce(() => {
300
        throw error;
301
      });
302
303
    try {
304
      // WHEN
305
      await ExpressBeans.createApp({ routerBeans: beans });
306
      throw new Error('Should not reach here');
307
    } catch (e) {
308
      // THEN
309
      expect(e).toStrictEqual(new Error('Router Bean2 not initialized correctly'));
310
    }
311
  });
312
});
313