@rsdk/db.typeorm 2.5.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.
Files changed (62) hide show
  1. package/.env +2 -0
  2. package/CHANGELOG.md +155 -0
  3. package/dist/index.d.ts +8 -0
  4. package/dist/index.js +12 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/internal/context.d.ts +46 -0
  7. package/dist/internal/context.js +123 -0
  8. package/dist/internal/context.js.map +1 -0
  9. package/dist/internal/exceptions.d.ts +67 -0
  10. package/dist/internal/exceptions.js +99 -0
  11. package/dist/internal/exceptions.js.map +1 -0
  12. package/dist/internal/initializer.d.ts +15 -0
  13. package/dist/internal/initializer.js +158 -0
  14. package/dist/internal/initializer.js.map +1 -0
  15. package/dist/internal/strategy.d.ts +55 -0
  16. package/dist/internal/strategy.js +109 -0
  17. package/dist/internal/strategy.js.map +1 -0
  18. package/dist/internal/test/strategy.test.e2e.d.ts +5 -0
  19. package/dist/internal/test/strategy.test.e2e.js +779 -0
  20. package/dist/internal/test/strategy.test.e2e.js.map +1 -0
  21. package/dist/internal/test/util.d.ts +22 -0
  22. package/dist/internal/test/util.js +72 -0
  23. package/dist/internal/test/util.js.map +1 -0
  24. package/dist/providers/index.d.ts +3 -0
  25. package/dist/providers/index.js +20 -0
  26. package/dist/providers/index.js.map +1 -0
  27. package/dist/providers/typeorm-logger.adapter.d.ts +13 -0
  28. package/dist/providers/typeorm-logger.adapter.js +61 -0
  29. package/dist/providers/typeorm-logger.adapter.js.map +1 -0
  30. package/dist/providers/typeorm.config.d.ts +10 -0
  31. package/dist/providers/typeorm.config.js +79 -0
  32. package/dist/providers/typeorm.config.js.map +1 -0
  33. package/dist/providers/typeorm.healthcheck.d.ts +8 -0
  34. package/dist/providers/typeorm.healthcheck.js +36 -0
  35. package/dist/providers/typeorm.healthcheck.js.map +1 -0
  36. package/dist/typeorm.errors-transformer.d.ts +5 -0
  37. package/dist/typeorm.errors-transformer.js +32 -0
  38. package/dist/typeorm.errors-transformer.js.map +1 -0
  39. package/dist/typeorm.plugin.d.ts +15 -0
  40. package/dist/typeorm.plugin.js +67 -0
  41. package/dist/typeorm.plugin.js.map +1 -0
  42. package/dist/types.d.ts +14 -0
  43. package/dist/types.js +18 -0
  44. package/dist/types.js.map +1 -0
  45. package/jest.config.js +7 -0
  46. package/package.json +26 -0
  47. package/src/index.ts +9 -0
  48. package/src/internal/context.ts +164 -0
  49. package/src/internal/exceptions.ts +96 -0
  50. package/src/internal/initializer.ts +213 -0
  51. package/src/internal/strategy.ts +149 -0
  52. package/src/internal/test/strategy.test.e2e.ts +1050 -0
  53. package/src/internal/test/util.ts +51 -0
  54. package/src/providers/index.ts +3 -0
  55. package/src/providers/typeorm-logger.adapter.ts +59 -0
  56. package/src/providers/typeorm.config.ts +61 -0
  57. package/src/providers/typeorm.healthcheck.ts +20 -0
  58. package/src/typeorm.errors-transformer.ts +35 -0
  59. package/src/typeorm.plugin.ts +83 -0
  60. package/src/types.ts +23 -0
  61. package/tsconfig.build.json +9 -0
  62. package/tsconfig.json +9 -0
@@ -0,0 +1,1050 @@
1
+ /* eslint-disable unicorn/consistent-function-scoping,unicorn/catch-error-name */
2
+ import {
3
+ CallOutOfContextWithMandatory,
4
+ IncompatibleIsolationLevels,
5
+ NeverRunningInTransaction,
6
+ Propagation,
7
+ TransactionRunner,
8
+ } from '@rsdk/db';
9
+ import * as process from 'node:process';
10
+ import { Client } from 'pg';
11
+ import type { QueryRunner } from 'typeorm';
12
+ import { DataSource } from 'typeorm';
13
+
14
+ import { DEFAULT_ISOLATION_LEVEL, IsolationLevel } from '../../types';
15
+ import { Initializer } from '../initializer';
16
+ import { TypeormTransactionalStrategy } from '../strategy';
17
+
18
+ import {
19
+ Account,
20
+ Cat,
21
+ cat,
22
+ contextTransactionMustBeInitialized,
23
+ contextTransactionMustNotInitialized,
24
+ secondCat,
25
+ SyntheticException,
26
+ } from './util';
27
+
28
+ export const fetchCats = async (): Promise<Cat[]> =>
29
+ dataSource.manager.find(Cat);
30
+ export const createCat = async (): Promise<Cat> =>
31
+ dataSource.manager.getRepository(Cat).save(cat);
32
+ export const createSecondCat = async (): Promise<Cat> => {
33
+ const catRepo = dataSource.manager.getRepository(Cat);
34
+
35
+ return await catRepo.save(catRepo.create(secondCat));
36
+ };
37
+
38
+ const runner = new TransactionRunner(new TypeormTransactionalStrategy());
39
+ let dataSource: DataSource;
40
+
41
+ export const pingDatabase = async (): Promise<void> =>
42
+ await dataSource.query('select 1 + 1');
43
+
44
+ const schemaName = `typeorm`;
45
+ const setupSchema = async (): Promise<void> => {
46
+ const client = new Client(process.env.DB_URL);
47
+
48
+ await client.connect();
49
+ await client.query(`CREATE SCHEMA IF NOT EXISTS ${schemaName}`);
50
+ await client.end();
51
+ };
52
+
53
+ beforeAll(setupSchema);
54
+
55
+ beforeAll(async () => {
56
+ Initializer.initialize();
57
+ dataSource = new DataSource({
58
+ url: process.env.DB_URL as string,
59
+ type: process.env.DB_TYPE as 'postgres',
60
+ entities: [Cat, Account],
61
+ schema: schemaName,
62
+ logging: process.env.DB_LOGGING as 'all',
63
+ });
64
+ await dataSource.initialize();
65
+ });
66
+
67
+ beforeEach(async () => {
68
+ await dataSource.synchronize(true);
69
+ });
70
+
71
+ afterAll(async () => {
72
+ await dataSource.destroy();
73
+ });
74
+
75
+ it('correct run transaction', async () => {
76
+ const testedFunc = async (): Promise<void> => {
77
+ contextTransactionMustNotInitialized();
78
+ await pingDatabase();
79
+ contextTransactionMustBeInitialized();
80
+ };
81
+
82
+ await runner.transactional(testedFunc);
83
+ });
84
+
85
+ it('correct release queryRunner', async () => {
86
+ let qr: QueryRunner | undefined;
87
+ const testedFunc = async (): Promise<void> => {
88
+ const context = Initializer.storage.getContext();
89
+ const queryRunner = context?.queryRunner;
90
+
91
+ expect(queryRunner).toBeUndefined();
92
+ await pingDatabase();
93
+
94
+ {
95
+ const queryRunner = Initializer.storage.getContext()?.queryRunner;
96
+
97
+ expect(queryRunner).toBeDefined();
98
+ qr = queryRunner;
99
+
100
+ const transactionActive = queryRunner?.isTransactionActive;
101
+
102
+ expect(transactionActive).toBe(true);
103
+ }
104
+ };
105
+
106
+ await runner.transactional(testedFunc);
107
+ expect(qr?.isTransactionActive).toBeFalsy();
108
+ expect(qr?.isReleased).toBeTruthy();
109
+ });
110
+
111
+ it('example correct release queryRunner', async () => {
112
+ /**
113
+ * В случае если queryRunner некорректно релизится по завершению транзакции
114
+ * Здесь будет ошибка так как queryRunner будет использоваться после своего релиза
115
+ * Также это проверка на то что queryRunner не прибивается намертво к менеджеру
116
+ */
117
+ const testedFunc = async (): Promise<void> => {
118
+ await dataSource.manager.query('select 1 + 1');
119
+ };
120
+
121
+ await runner.transactional(testedFunc);
122
+ await testedFunc();
123
+
124
+ await runner.transactional(testedFunc);
125
+ await testedFunc();
126
+ });
127
+
128
+ it('correct commit transaction', async () => {
129
+ const testedFunc = async (): Promise<void> => {
130
+ await createCat();
131
+ };
132
+
133
+ await runner.transactional(testedFunc);
134
+
135
+ const cats = await fetchCats();
136
+
137
+ expect(cats).toMatchObject([cat]);
138
+ });
139
+
140
+ it('correct rollback transaction', async () => {
141
+ const testedFunc = async (): Promise<void> => {
142
+ await createCat();
143
+
144
+ throw new SyntheticException();
145
+ };
146
+
147
+ const err = await runner.transactional(testedFunc).catch((e) => e);
148
+
149
+ expect(err).toBeInstanceOf(SyntheticException);
150
+
151
+ const cats = await fetchCats();
152
+
153
+ expect(cats).toHaveLength(0);
154
+ });
155
+
156
+ describe('nested', () => {
157
+ const doSuccessful = async (): Promise<any> => {
158
+ contextTransactionMustBeInitialized();
159
+
160
+ return await createSecondCat();
161
+ };
162
+ const doFailed = async (): Promise<void> => {
163
+ await doSuccessful();
164
+
165
+ throw new SyntheticException();
166
+ };
167
+
168
+ it('correct run transaction', async () => {
169
+ const testedFunc = async (): Promise<void> => {
170
+ contextTransactionMustNotInitialized();
171
+ await pingDatabase();
172
+ await doSuccessful();
173
+ contextTransactionMustBeInitialized();
174
+ };
175
+
176
+ await runner.transactional(testedFunc);
177
+ });
178
+ it('correct release queryRunner', async () => {
179
+ let qr: QueryRunner | undefined;
180
+ const testedFunc = async (): Promise<void> => {
181
+ contextTransactionMustNotInitialized();
182
+ await pingDatabase();
183
+ await doSuccessful();
184
+ {
185
+ const queryRunner = Initializer.storage.getContext()?.queryRunner;
186
+
187
+ expect(queryRunner).toBeDefined();
188
+ qr = queryRunner;
189
+
190
+ const transactionActive = queryRunner?.isTransactionActive;
191
+
192
+ expect(transactionActive).toBe(true);
193
+ }
194
+ };
195
+
196
+ await runner.transactional(testedFunc);
197
+ expect(qr?.isReleased).toBeTruthy();
198
+ });
199
+
200
+ it('correct commit transaction', async () => {
201
+ const testedFunc = async (): Promise<void> => {
202
+ await createCat();
203
+ await doSuccessful();
204
+ };
205
+
206
+ await runner.transactional(testedFunc);
207
+
208
+ const cats = await fetchCats();
209
+
210
+ expect(cats).toMatchObject([cat, secondCat]);
211
+ });
212
+
213
+ it('correct rollback transaction', async () => {
214
+ const testedFunc = async (): Promise<void> => {
215
+ await createCat();
216
+ await doFailed();
217
+ };
218
+ const err = await runner.transactional(testedFunc).catch((e) => e);
219
+
220
+ expect(err).toBeInstanceOf(SyntheticException);
221
+
222
+ const cats = await fetchCats();
223
+
224
+ expect(cats).toHaveLength(0);
225
+ });
226
+ it('correct rollback actions in nested function calls', async () => {
227
+ const testedFunc = async (): Promise<void> => {
228
+ await createCat();
229
+ await doSuccessful();
230
+
231
+ throw new SyntheticException();
232
+ };
233
+ const err = await runner.transactional(testedFunc).catch((e) => e);
234
+
235
+ expect(err).toBeInstanceOf(SyntheticException);
236
+
237
+ const cats = await fetchCats();
238
+
239
+ expect(cats).toHaveLength(0);
240
+ });
241
+ });
242
+
243
+ describe('binded nested', () => {
244
+ const doSuccessfulCreateSecondCat = async (): Promise<any> => {
245
+ contextTransactionMustBeInitialized();
246
+
247
+ return await createSecondCat();
248
+ };
249
+
250
+ const contextDoSuccessful = (
251
+ options: IsolationLevel = DEFAULT_ISOLATION_LEVEL,
252
+ ): Promise<any> => runner.transactional(options, doSuccessfulCreateSecondCat);
253
+
254
+ const doFailed = async (): Promise<void> => {
255
+ await doSuccessfulCreateSecondCat();
256
+
257
+ throw new SyntheticException();
258
+ };
259
+
260
+ const contextDoFailed = (
261
+ options: IsolationLevel = DEFAULT_ISOLATION_LEVEL,
262
+ ): Promise<any> => runner.transactional(options, doFailed);
263
+
264
+ it('correct run transaction', async () => {
265
+ const testedFunc = async (): Promise<void> => {
266
+ contextTransactionMustNotInitialized();
267
+ await pingDatabase();
268
+ await contextDoSuccessful();
269
+ contextTransactionMustBeInitialized();
270
+ };
271
+
272
+ await runner.transactional(testedFunc);
273
+ });
274
+
275
+ it('correct run nested transaction (savepoint)', async () => {
276
+ const testedFunc = async (): Promise<void> => {
277
+ contextTransactionMustNotInitialized();
278
+ await runner.transactional(async (): Promise<any> => {
279
+ contextTransactionMustNotInitialized();
280
+
281
+ return await createSecondCat();
282
+ });
283
+ contextTransactionMustBeInitialized();
284
+ };
285
+
286
+ await runner.transactional(testedFunc);
287
+ });
288
+
289
+ it('correct release queryRunner', async () => {
290
+ let qr: QueryRunner | undefined;
291
+ const testedFunc = async (): Promise<void> => {
292
+ contextTransactionMustNotInitialized();
293
+ await pingDatabase();
294
+ await contextDoSuccessful();
295
+ {
296
+ const queryRunner = Initializer.storage.getContext()?.queryRunner;
297
+
298
+ qr = queryRunner;
299
+
300
+ const transactionActive = queryRunner?.isTransactionActive;
301
+
302
+ expect(transactionActive).toBe(true);
303
+ }
304
+ };
305
+
306
+ await runner.transactional(testedFunc);
307
+ expect(qr?.isReleased).toBeTruthy();
308
+ });
309
+
310
+ it('correct commit transaction', async () => {
311
+ const testedFunc = async (): Promise<void> => {
312
+ await createCat();
313
+ await contextDoSuccessful();
314
+ };
315
+
316
+ await runner.transactional(testedFunc);
317
+
318
+ const cats = await fetchCats();
319
+
320
+ expect(cats).toMatchObject([cat, secondCat]);
321
+ });
322
+
323
+ it('correct rollback transaction', async () => {
324
+ const testedFunc = async (): Promise<void> => {
325
+ await createCat();
326
+ await contextDoFailed();
327
+ };
328
+ const err = await runner.transactional(testedFunc).catch((e) => e);
329
+
330
+ expect(err).toBeInstanceOf(SyntheticException);
331
+
332
+ const cats = await fetchCats();
333
+
334
+ expect(cats).toHaveLength(0);
335
+ });
336
+ });
337
+
338
+ /**
339
+ * - Поведение при вызове из обернутого в контекст с целевым `propagation` вложенного метода обернутого в контекст с `propagation`
340
+ * - NON_SUPPORTED
341
+ * - Проверить что внутренние обращения никак не аффектят внешний контекст, то есть в этих обращениях не используется внешнее соединение и как следствие действия внутри вложенного метода не будут откачены вместе с внешними
342
+ * - При исключении во вложенном методе сделанные внутри этого метода изменения в бд не будут откачены
343
+ * - REQUIRES_NEW
344
+ * - Проверить что внутренние обращения никак не аффектят внешний контекст, то есть в этих обращениях не используется внешнее соединение и как следствие действия внутри вложенного метода не будут откачены вместе с внешними
345
+ * - Изменения в БД сделанные после успешного завершения вложенного метода не должны быть откачены при завершении с ошибкой внешнего контекста
346
+ * - NESTED
347
+ * - Проверить что внутренние обращения создают транзакцию если её не было
348
+ * - Проверить что внутренние обращения создают savepoint если транзакция проинициализирована во внешнем контексте
349
+ * - Изменения в БД сделанные после успешного завершения вложенного метода должны быть откачены в случае неуспешного завершения внешнего контекста
350
+ * - Изменения в БД сделанные внутри метода, до выброса исключения должны быть откачены
351
+ * - REQUIRED
352
+ * - Проверить что внутренние обращения создают транзакцию если её не было
353
+ * - Проверить что внутренние обращения не создают savepoint если транзакция проинициализирована во внешнем контексте
354
+ * - Изменения в БД сделанные после успешного завершения вложенного метода должны быть откачены в случае неуспешного завершения внешнего контекста
355
+ * - Изменения в БД сделанные внутри метода, до выброса исключения не должны быть откачены
356
+ */
357
+ describe('MANDATORY', () => {
358
+ const defaultOptions = {
359
+ isolation: DEFAULT_ISOLATION_LEVEL,
360
+ propagation: Propagation.MANDATORY,
361
+ };
362
+ const contextDoSuccessful = (
363
+ isolationLevel: IsolationLevel = DEFAULT_ISOLATION_LEVEL,
364
+ ): Promise<any> =>
365
+ runner.transactional(
366
+ {
367
+ isolation: isolationLevel,
368
+ propagation: Propagation.MANDATORY,
369
+ },
370
+ async (): Promise<any> => {
371
+ contextTransactionMustBeInitialized();
372
+
373
+ return await createSecondCat();
374
+ },
375
+ );
376
+
377
+ it('throw out of transaction context', async () => {
378
+ await expect(contextDoSuccessful()).rejects.toBeInstanceOf(
379
+ CallOutOfContextWithMandatory,
380
+ );
381
+ });
382
+
383
+ it('correct run transaction', async () => {
384
+ const testedFunc = async (): Promise<void> => {
385
+ contextTransactionMustNotInitialized();
386
+ await pingDatabase();
387
+ contextTransactionMustBeInitialized();
388
+ };
389
+
390
+ await runner.transactional(testedFunc);
391
+ });
392
+
393
+ it('correct run new transaction', async () => {
394
+ const testedFunc = async (): Promise<void> => {
395
+ contextTransactionMustNotInitialized();
396
+ await runner.transactional(defaultOptions, async (): Promise<any> => {
397
+ contextTransactionMustNotInitialized();
398
+
399
+ return await createSecondCat();
400
+ });
401
+ contextTransactionMustBeInitialized();
402
+ };
403
+
404
+ await runner.transactional(testedFunc);
405
+ });
406
+
407
+ it('correct run without nested transaction', async () => {
408
+ const testedFunc = async (): Promise<void> => {
409
+ contextTransactionMustNotInitialized();
410
+ await runner.transactional(
411
+ {
412
+ isolation: DEFAULT_ISOLATION_LEVEL,
413
+ propagation: Propagation.REQUIRES_NEW,
414
+ },
415
+ async (): Promise<any> => {
416
+ contextTransactionMustNotInitialized();
417
+ await createSecondCat();
418
+ contextTransactionMustBeInitialized();
419
+ },
420
+ );
421
+ contextTransactionMustNotInitialized();
422
+ };
423
+
424
+ await runner.transactional(testedFunc);
425
+ });
426
+
427
+ it('non rollback actions in nested function calls', async () => {
428
+ const testedFunc = async (): Promise<void> => {
429
+ try {
430
+ await runner.transactional(async () => {
431
+ await createCat();
432
+
433
+ const cats = await fetchCats();
434
+
435
+ expect(cats).toEqual([cat]);
436
+
437
+ throw new SyntheticException();
438
+ });
439
+ } catch (e) {
440
+ const cats = await fetchCats();
441
+
442
+ expect(cats).toHaveLength(1);
443
+
444
+ throw e;
445
+ }
446
+ };
447
+
448
+ await expect(runner.transactional(testedFunc)).rejects.toBeInstanceOf(
449
+ SyntheticException,
450
+ );
451
+
452
+ const cats = await fetchCats();
453
+
454
+ expect(cats).toHaveLength(0);
455
+ });
456
+
457
+ it('correct throw on call incompatible isolation levels', async () => {
458
+ const testedFunc = async (): Promise<void> => {
459
+ await createCat();
460
+ await contextDoSuccessful(IsolationLevel.READ_COMMITTED);
461
+ };
462
+
463
+ await expect(runner.transactional(testedFunc)).rejects.toBeInstanceOf(
464
+ IncompatibleIsolationLevels,
465
+ );
466
+
467
+ const cats = await fetchCats();
468
+
469
+ expect(cats).toHaveLength(0);
470
+ });
471
+ });
472
+ describe('NESTED', () => {
473
+ const usedPropagationLevel = Propagation.NESTED;
474
+ const defaultOptions = {
475
+ isolation: DEFAULT_ISOLATION_LEVEL,
476
+ propagation: usedPropagationLevel,
477
+ };
478
+ const contextDoSuccessful = (
479
+ isolationLevel: IsolationLevel = DEFAULT_ISOLATION_LEVEL,
480
+ ): Promise<any> =>
481
+ runner.transactional(
482
+ {
483
+ isolation: isolationLevel,
484
+ propagation: usedPropagationLevel,
485
+ },
486
+ async (): Promise<any> => {
487
+ contextTransactionMustBeInitialized();
488
+
489
+ return await createSecondCat();
490
+ },
491
+ );
492
+
493
+ it('correct run transaction', async () => {
494
+ const testedFunc = async (): Promise<void> => {
495
+ contextTransactionMustNotInitialized();
496
+ await pingDatabase();
497
+ contextTransactionMustBeInitialized();
498
+ };
499
+
500
+ await runner.transactional(testedFunc);
501
+ });
502
+
503
+ it('correct run new transaction', async () => {
504
+ const testedFunc = async (): Promise<void> => {
505
+ contextTransactionMustNotInitialized();
506
+ await runner.transactional(defaultOptions, async (): Promise<any> => {
507
+ contextTransactionMustNotInitialized();
508
+ await createSecondCat();
509
+ contextTransactionMustBeInitialized();
510
+ });
511
+ contextTransactionMustBeInitialized();
512
+ };
513
+
514
+ await runner.transactional(defaultOptions, testedFunc);
515
+ });
516
+
517
+ it('throw for incompatible isolation levels', async () => {
518
+ const testedFunc = async (): Promise<void> => {
519
+ await createCat();
520
+ await contextDoSuccessful(IsolationLevel.READ_COMMITTED);
521
+ };
522
+
523
+ await expect(runner.transactional(testedFunc)).rejects.toBeInstanceOf(
524
+ IncompatibleIsolationLevels,
525
+ );
526
+
527
+ const cats = await fetchCats();
528
+
529
+ expect(cats).toHaveLength(0);
530
+ });
531
+
532
+ it('non rollback actions in nested function calls', async () => {
533
+ const testedFunc = async (): Promise<void> => {
534
+ try {
535
+ await runner.transactional(defaultOptions, async () => {
536
+ await createCat();
537
+
538
+ const cats = await fetchCats();
539
+
540
+ expect(cats).toEqual([cat]);
541
+
542
+ throw new SyntheticException();
543
+ });
544
+ } catch (e) {
545
+ const cats = await fetchCats();
546
+
547
+ expect(cats).toHaveLength(0);
548
+
549
+ throw e;
550
+ }
551
+ };
552
+
553
+ await expect(runner.transactional(testedFunc)).rejects.toBeInstanceOf(
554
+ SyntheticException,
555
+ );
556
+
557
+ const cats = await fetchCats();
558
+
559
+ expect(cats).toHaveLength(0);
560
+ });
561
+ });
562
+ describe('NEVER', () => {
563
+ const usedPropagationLevel = Propagation.NEVER;
564
+ const defaultOptions = {
565
+ isolation: DEFAULT_ISOLATION_LEVEL,
566
+ propagation: usedPropagationLevel,
567
+ };
568
+ const contextDoSuccessful = (
569
+ isolationLevel: IsolationLevel = DEFAULT_ISOLATION_LEVEL,
570
+ ): Promise<any> =>
571
+ runner.transactional(
572
+ {
573
+ isolation: isolationLevel,
574
+ propagation: usedPropagationLevel,
575
+ },
576
+ async (): Promise<any> => {
577
+ contextTransactionMustBeInitialized();
578
+
579
+ return await createSecondCat();
580
+ },
581
+ );
582
+
583
+ it('correct run non transaction', async () => {
584
+ const testedFunc = async (): Promise<void> => {
585
+ contextTransactionMustNotInitialized();
586
+ await pingDatabase();
587
+ contextTransactionMustNotInitialized();
588
+ };
589
+
590
+ await runner.transactional(defaultOptions, testedFunc);
591
+ });
592
+
593
+ it('correct run nested transaction', async () => {
594
+ const testedFunc = async (): Promise<void> => {
595
+ contextTransactionMustNotInitialized();
596
+ await runner.transactional(async (): Promise<any> => {
597
+ contextTransactionMustNotInitialized();
598
+ await createSecondCat();
599
+ contextTransactionMustBeInitialized();
600
+ });
601
+ contextTransactionMustNotInitialized();
602
+ };
603
+
604
+ await runner.transactional(defaultOptions, testedFunc);
605
+ });
606
+
607
+ it('correct throw on call in transaction', async () => {
608
+ const testedFunc = async (): Promise<void> => {
609
+ await createCat();
610
+ await contextDoSuccessful();
611
+ };
612
+
613
+ await expect(runner.transactional(testedFunc)).rejects.toBeInstanceOf(
614
+ NeverRunningInTransaction,
615
+ );
616
+
617
+ const cats = await fetchCats();
618
+
619
+ expect(cats).toHaveLength(0);
620
+ });
621
+ });
622
+ describe('NOT_SUPPORTED', () => {
623
+ const usedPropagationLevel = Propagation.NOT_SUPPORTED;
624
+ const defaultOptions = {
625
+ isolation: DEFAULT_ISOLATION_LEVEL,
626
+ propagation: usedPropagationLevel,
627
+ };
628
+ const contextDoSuccessful = async (
629
+ isolationLevel: IsolationLevel = DEFAULT_ISOLATION_LEVEL,
630
+ ): Promise<any> =>
631
+ await runner.transactional(
632
+ {
633
+ isolation: isolationLevel,
634
+ propagation: usedPropagationLevel,
635
+ },
636
+ async (): Promise<any> => {
637
+ contextTransactionMustNotInitialized();
638
+ await createSecondCat();
639
+ contextTransactionMustNotInitialized();
640
+ },
641
+ );
642
+
643
+ it('correct run transaction', async () => {
644
+ const testedFunc = async (): Promise<void> => {
645
+ contextTransactionMustNotInitialized();
646
+ await pingDatabase();
647
+ contextTransactionMustBeInitialized();
648
+ await contextDoSuccessful();
649
+
650
+ contextTransactionMustBeInitialized();
651
+ };
652
+
653
+ await runner.transactional(testedFunc);
654
+ });
655
+
656
+ it('correct run new transaction', async () => {
657
+ const testedFunc = async (): Promise<void> => {
658
+ contextTransactionMustNotInitialized();
659
+ await runner.transactional(defaultOptions, async (): Promise<any> => {
660
+ contextTransactionMustNotInitialized();
661
+ await createSecondCat();
662
+ contextTransactionMustNotInitialized();
663
+ });
664
+ contextTransactionMustNotInitialized();
665
+ };
666
+
667
+ await runner.transactional(defaultOptions, testedFunc);
668
+ });
669
+
670
+ it('correct run without nested transaction', async () => {
671
+ const testedFunc = async (): Promise<void> => {
672
+ contextTransactionMustNotInitialized();
673
+ await runner.transactional(defaultOptions, async (): Promise<any> => {
674
+ contextTransactionMustNotInitialized();
675
+ await createSecondCat();
676
+ contextTransactionMustNotInitialized();
677
+ });
678
+ contextTransactionMustNotInitialized();
679
+ };
680
+
681
+ await runner.transactional(testedFunc);
682
+ });
683
+
684
+ it('correct run for incompatible isolation levels', async () => {
685
+ const testedFunc = async (): Promise<void> => {
686
+ await createCat();
687
+ await contextDoSuccessful();
688
+ };
689
+
690
+ await runner.transactional(testedFunc);
691
+
692
+ const cats = await fetchCats();
693
+
694
+ expect(cats).toEqual([cat, secondCat]);
695
+ });
696
+
697
+ it('non rollback actions in nested function calls', async () => {
698
+ const testedFunc = async (): Promise<void> => {
699
+ try {
700
+ await runner.transactional(defaultOptions, async () => {
701
+ await createCat();
702
+
703
+ const cats = await fetchCats();
704
+
705
+ expect(cats).toEqual([cat]);
706
+
707
+ throw new SyntheticException();
708
+ });
709
+ } catch (e) {
710
+ const cats = await fetchCats();
711
+
712
+ expect(cats).toEqual([cat]);
713
+
714
+ throw e;
715
+ }
716
+ };
717
+
718
+ await expect(runner.transactional(testedFunc)).rejects.toBeInstanceOf(
719
+ SyntheticException,
720
+ );
721
+
722
+ const cats = await fetchCats();
723
+
724
+ expect(cats).toEqual([cat]);
725
+ });
726
+ });
727
+ describe('REQUIRED', () => {
728
+ const usedPropagationLevel = Propagation.REQUIRED;
729
+ const defaultOptions = {
730
+ isolation: DEFAULT_ISOLATION_LEVEL,
731
+ propagation: usedPropagationLevel,
732
+ };
733
+ const contextDoSuccessful = (
734
+ isolationLevel: IsolationLevel = DEFAULT_ISOLATION_LEVEL,
735
+ ): Promise<any> =>
736
+ runner.transactional(
737
+ {
738
+ isolation: isolationLevel,
739
+ propagation: usedPropagationLevel,
740
+ },
741
+ async (): Promise<any> => {
742
+ contextTransactionMustBeInitialized();
743
+
744
+ return await createSecondCat();
745
+ },
746
+ );
747
+
748
+ it('correct run transaction', async () => {
749
+ const testedFunc = async (): Promise<void> => {
750
+ contextTransactionMustNotInitialized();
751
+ await pingDatabase();
752
+ contextTransactionMustBeInitialized();
753
+ };
754
+
755
+ await runner.transactional(testedFunc);
756
+ });
757
+
758
+ it('correct run new transaction', async () => {
759
+ const testedFunc = async (): Promise<void> => {
760
+ contextTransactionMustNotInitialized();
761
+ await runner.transactional(async (): Promise<any> => {
762
+ contextTransactionMustNotInitialized();
763
+ await createSecondCat();
764
+ contextTransactionMustBeInitialized();
765
+ });
766
+ contextTransactionMustBeInitialized();
767
+ };
768
+
769
+ await runner.transactional(testedFunc);
770
+ });
771
+
772
+ it('correct run without nested transaction', async () => {
773
+ const testedFunc = async (): Promise<void> => {
774
+ contextTransactionMustNotInitialized();
775
+ await runner.transactional(defaultOptions, async (): Promise<any> => {
776
+ contextTransactionMustNotInitialized();
777
+ await createSecondCat();
778
+ contextTransactionMustBeInitialized();
779
+ });
780
+ contextTransactionMustBeInitialized();
781
+ };
782
+
783
+ await runner.transactional(testedFunc);
784
+ });
785
+
786
+ it('correct run for incompatible isolation levels', async () => {
787
+ const testedFunc = async (): Promise<void> => {
788
+ await createCat();
789
+ await contextDoSuccessful(IsolationLevel.READ_COMMITTED);
790
+ };
791
+
792
+ await expect(runner.transactional(testedFunc)).rejects.toBeInstanceOf(
793
+ IncompatibleIsolationLevels,
794
+ );
795
+
796
+ const cats = await fetchCats();
797
+
798
+ expect(cats).toHaveLength(0);
799
+ });
800
+
801
+ it('non rollback actions in nested function calls', async () => {
802
+ const testedFunc = async (): Promise<void> => {
803
+ try {
804
+ await runner.transactional(defaultOptions, async () => {
805
+ await createCat();
806
+
807
+ const cats = await fetchCats();
808
+
809
+ expect(cats).toEqual([cat]);
810
+
811
+ throw new SyntheticException();
812
+ });
813
+ } catch (e) {
814
+ const cats = await fetchCats();
815
+
816
+ expect(cats).toHaveLength(1);
817
+
818
+ throw e;
819
+ }
820
+ };
821
+
822
+ await expect(runner.transactional(testedFunc)).rejects.toBeInstanceOf(
823
+ SyntheticException,
824
+ );
825
+
826
+ const cats = await fetchCats();
827
+
828
+ expect(cats).toHaveLength(0);
829
+ });
830
+
831
+ it('correct throw on call incompatible isolation levels', async () => {
832
+ const testedFunc = async (): Promise<void> => {
833
+ await createCat();
834
+ await contextDoSuccessful(IsolationLevel.READ_COMMITTED);
835
+ };
836
+
837
+ await expect(runner.transactional(testedFunc)).rejects.toBeInstanceOf(
838
+ IncompatibleIsolationLevels,
839
+ );
840
+
841
+ const cats = await fetchCats();
842
+
843
+ expect(cats).toHaveLength(0);
844
+ });
845
+ });
846
+ describe('REQUIRES_NEW', () => {
847
+ const usedPropagationLevel = Propagation.REQUIRES_NEW;
848
+ const defaultOptions = {
849
+ isolation: DEFAULT_ISOLATION_LEVEL,
850
+ propagation: usedPropagationLevel,
851
+ };
852
+ const contextDoSuccessful = (
853
+ isolationLevel: IsolationLevel = DEFAULT_ISOLATION_LEVEL,
854
+ ): Promise<any> =>
855
+ runner.transactional(
856
+ {
857
+ isolation: isolationLevel,
858
+ propagation: usedPropagationLevel,
859
+ },
860
+ async (): Promise<void> => {
861
+ contextTransactionMustNotInitialized();
862
+ await createSecondCat();
863
+ contextTransactionMustBeInitialized();
864
+ },
865
+ );
866
+
867
+ it('correct run transaction', async () => {
868
+ const testedFunc = async (): Promise<void> => {
869
+ contextTransactionMustNotInitialized();
870
+ await pingDatabase();
871
+ contextTransactionMustBeInitialized();
872
+ await contextDoSuccessful();
873
+
874
+ contextTransactionMustBeInitialized();
875
+ };
876
+
877
+ await runner.transactional(testedFunc);
878
+ });
879
+
880
+ it('correct run new transaction', async () => {
881
+ const testedFunc = async (): Promise<void> => {
882
+ contextTransactionMustNotInitialized();
883
+ /**
884
+ * Не должно задействовать текущее соединение
885
+ */
886
+ await contextDoSuccessful();
887
+ /**
888
+ * Убеждаемся в том что текущий контекст так и не был проинициализирован
889
+ */
890
+ contextTransactionMustNotInitialized();
891
+ };
892
+
893
+ await runner.transactional(defaultOptions, testedFunc);
894
+ });
895
+
896
+ it('correct run for incompatible isolation levels', async () => {
897
+ const testedFunc = async (): Promise<void> => {
898
+ await createCat();
899
+
900
+ const cats = await fetchCats();
901
+
902
+ expect(cats).toEqual([cat]);
903
+ /**
904
+ * Исключения не будет так как будет использовано другое соединение
905
+ */
906
+ await contextDoSuccessful(IsolationLevel.READ_COMMITTED);
907
+
908
+ const cats2 = await fetchCats();
909
+
910
+ expect(cats2).toEqual([cat, secondCat]);
911
+ };
912
+
913
+ await runner.transactional(testedFunc);
914
+
915
+ const cats = await fetchCats();
916
+
917
+ expect(cats).toEqual([cat, secondCat]);
918
+ });
919
+
920
+ it('rollback actions in nested function calls', async () => {
921
+ const testedFunc = async (): Promise<void> => {
922
+ try {
923
+ await runner.transactional(defaultOptions, async () => {
924
+ await createCat();
925
+
926
+ const cats = await fetchCats();
927
+
928
+ expect(cats).toEqual([cat]);
929
+
930
+ throw new SyntheticException();
931
+ });
932
+ } catch (e) {
933
+ const cats = await fetchCats();
934
+
935
+ expect(cats).toHaveLength(0);
936
+
937
+ throw e;
938
+ }
939
+ };
940
+
941
+ await expect(runner.transactional(testedFunc)).rejects.toBeInstanceOf(
942
+ SyntheticException,
943
+ );
944
+
945
+ const cats = await fetchCats();
946
+
947
+ expect(cats).toHaveLength(0);
948
+ });
949
+ it('non rollback actions in nested function calls', async () => {
950
+ const testedFunc = async (): Promise<void> => {
951
+ await runner.transactional(defaultOptions, async () => {
952
+ await createCat();
953
+
954
+ const cats = await fetchCats();
955
+
956
+ expect(cats).toEqual([cat]);
957
+ });
958
+
959
+ /**
960
+ * Изменения вложенного вызова не должны быть откачены
961
+ */
962
+ throw new SyntheticException();
963
+ };
964
+
965
+ await expect(runner.transactional(testedFunc)).rejects.toBeInstanceOf(
966
+ SyntheticException,
967
+ );
968
+
969
+ const cats = await fetchCats();
970
+
971
+ expect(cats).toEqual([cat]);
972
+ });
973
+ });
974
+ describe('SUPPORTS', () => {
975
+ const usedPropagationLevel = Propagation.SUPPORTS;
976
+ const contextDoSuccessful = (
977
+ isolationLevel: IsolationLevel = DEFAULT_ISOLATION_LEVEL,
978
+ ): Promise<any> =>
979
+ runner.transactional(
980
+ {
981
+ isolation: isolationLevel,
982
+ propagation: usedPropagationLevel,
983
+ },
984
+ async (): Promise<any> => {
985
+ return await createSecondCat();
986
+ },
987
+ );
988
+
989
+ it('correct run transaction', async () => {
990
+ const testedFunc = async (): Promise<void> => {
991
+ contextTransactionMustNotInitialized();
992
+ await pingDatabase();
993
+ await contextDoSuccessful();
994
+
995
+ contextTransactionMustBeInitialized();
996
+ };
997
+
998
+ await runner.transactional(testedFunc);
999
+ });
1000
+
1001
+ it('correct run new transaction', async () => {
1002
+ const testedFunc = async (): Promise<void> => {
1003
+ contextTransactionMustNotInitialized();
1004
+ await runner.transactional(async (): Promise<any> => {
1005
+ contextTransactionMustNotInitialized();
1006
+
1007
+ return await createSecondCat();
1008
+ });
1009
+ contextTransactionMustBeInitialized();
1010
+ };
1011
+
1012
+ await runner.transactional(testedFunc);
1013
+ });
1014
+
1015
+ it('correct run without nested transaction', async () => {
1016
+ const testedFunc = async (): Promise<void> => {
1017
+ contextTransactionMustNotInitialized();
1018
+ await runner.transactional(
1019
+ {
1020
+ isolation: DEFAULT_ISOLATION_LEVEL,
1021
+ propagation: usedPropagationLevel,
1022
+ },
1023
+ async (): Promise<any> => {
1024
+ contextTransactionMustNotInitialized();
1025
+ await createSecondCat();
1026
+ contextTransactionMustBeInitialized();
1027
+ },
1028
+ );
1029
+ contextTransactionMustBeInitialized();
1030
+ };
1031
+
1032
+ await runner.transactional(testedFunc);
1033
+ });
1034
+
1035
+ it('correct run for incompatible isolation levels', async () => {
1036
+ const testedFunc = async (): Promise<void> => {
1037
+ await createCat();
1038
+ /**
1039
+ * Всё должно сработать так как мы создаём новую транзакцию
1040
+ */
1041
+ await contextDoSuccessful(IsolationLevel.READ_COMMITTED);
1042
+ };
1043
+
1044
+ await runner.transactional(testedFunc);
1045
+
1046
+ const cats = await fetchCats();
1047
+
1048
+ expect(cats).toEqual([cat, secondCat]);
1049
+ });
1050
+ });