Skip to content

Commit

Permalink
Merge pull request #1074 from notaphplover/fix/update-transaction-bas…
Browse files Browse the repository at this point in the history
…ed-flows-to-release-them-on-uncaught-errors

Update transaction based flows to release them on uncaught errors
  • Loading branch information
notaphplover authored Mar 16, 2024
2 parents e4372a9 + 4ecaeb1 commit fc10b5e
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
PlayCardsGameActionCreateQuery,
} from '@cornie-js/backend-game-domain/gameActions';
import { GameActionCreateQueryFixtures } from '@cornie-js/backend-game-domain/gameActions/fixtures';
import { FindOptionsWhere, Repository, SelectQueryBuilder } from 'typeorm';
import { Repository, SelectQueryBuilder } from 'typeorm';
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';

import { CardDb } from '../../../../cards/adapter/typeorm/models/CardDb';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
/*
* Ugly workaround until https://github.com/jestjs/jest/issues/14874 is provided in jest@30
*/

const disposeSymbol: unique symbol = Symbol('Symbol.dispose');
const asyncDisposeSymbol: unique symbol = Symbol('Symbol.asyncDispose');

(Symbol as Writable<SymbolConstructor>).asyncDispose ??=
asyncDisposeSymbol as unknown as SymbolConstructor['asyncDispose'];
(Symbol as Writable<SymbolConstructor>).dispose ??=
disposeSymbol as unknown as SymbolConstructor['dispose'];

import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals';

import { models as apiModels } from '@cornie-js/api-models';
Expand All @@ -7,6 +19,7 @@ import {
AppErrorKind,
Builder,
Left,
Writable,
} from '@cornie-js/backend-common';
import { TransactionWrapper } from '@cornie-js/backend-db/application';
import {
Expand Down Expand Up @@ -105,6 +118,7 @@ describe(CreateGameUseCaseHandler.name, () => {
gameSpecFixture = GameSpecFixtures.any;
gameV1Fixture = ActiveGameV1Fixtures.any;
transactionWrapperMock = {
[Symbol.asyncDispose]: jest.fn(),
rollback: jest.fn(),
tryCommit: jest
.fn()
Expand Down Expand Up @@ -200,6 +214,15 @@ describe(CreateGameUseCaseHandler.name, () => {
);
});

it('should call transactionWrapper[Symbol.asyncDispose]()', () => {
expect(
transactionWrapperMock[Symbol.asyncDispose],
).toHaveBeenCalledTimes(1);
expect(
transactionWrapperMock[Symbol.asyncDispose],
).toHaveBeenCalledWith();
});

it('should return a GameV1', () => {
expect(result).toBe(gameV1Fixture);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class CreateGameUseCaseHandler

this.#validate(gameCreateQuery);

const transactionWrapper: TransactionWrapper =
await using transactionWrapper: TransactionWrapper =
await this.#transactionProvisionOutputPort.provide();

const game: Game = await this.#gamePersistenceOutputPort.create(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
/*
* Ugly workaround until https://github.com/jestjs/jest/issues/14874 is provided in jest@30
*/

const disposeSymbol: unique symbol = Symbol('Symbol.dispose');
const asyncDisposeSymbol: unique symbol = Symbol('Symbol.asyncDispose');

(Symbol as Writable<SymbolConstructor>).asyncDispose ??=
asyncDisposeSymbol as unknown as SymbolConstructor['asyncDispose'];
(Symbol as Writable<SymbolConstructor>).dispose ??=
disposeSymbol as unknown as SymbolConstructor['dispose'];

import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals';

import { models as apiModels } from '@cornie-js/api-models';
Expand All @@ -6,6 +18,7 @@ import {
AppErrorKind,
Builder,
Handler,
Writable,
} from '@cornie-js/backend-common';
import { TransactionWrapper } from '@cornie-js/backend-db/application';
import {
Expand Down Expand Up @@ -491,6 +504,7 @@ describe(GameIdDrawCardsQueryV1Handler.name, () => {
gameDrawMutationFixture = GameDrawMutationFixtures.any;
gameUpdateQueryFixture = GameUpdateQueryFixtures.any;
transactionWrapperMock = {
[Symbol.asyncDispose]: jest.fn(),
tryCommit: jest.fn(),
} as Partial<
jest.Mocked<TransactionWrapper>
Expand Down Expand Up @@ -623,6 +637,15 @@ describe(GameIdDrawCardsQueryV1Handler.name, () => {
expect(transactionWrapperMock.tryCommit).toHaveBeenCalledWith();
});

it('should call transactionWrapper[Symbol.asyncDispose]()', () => {
expect(
transactionWrapperMock[Symbol.asyncDispose],
).toHaveBeenCalledTimes(1);
expect(
transactionWrapperMock[Symbol.asyncDispose],
).toHaveBeenCalledWith();
});

it('should return undefined', () => {
expect(result).toBeUndefined();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
/*
* Ugly workaround until https://github.com/jestjs/jest/issues/14874 is provided in jest@30
*/

const disposeSymbol: unique symbol = Symbol('Symbol.dispose');
const asyncDisposeSymbol: unique symbol = Symbol('Symbol.asyncDispose');

(Symbol as Writable<SymbolConstructor>).asyncDispose ??=
asyncDisposeSymbol as unknown as SymbolConstructor['asyncDispose'];
(Symbol as Writable<SymbolConstructor>).dispose ??=
disposeSymbol as unknown as SymbolConstructor['dispose'];

import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals';

import { models as apiModels } from '@cornie-js/api-models';
Expand All @@ -6,6 +18,7 @@ import {
AppErrorKind,
Builder,
Handler,
Writable,
} from '@cornie-js/backend-common';
import { TransactionWrapper } from '@cornie-js/backend-db/application';
import {
Expand Down Expand Up @@ -476,6 +489,7 @@ describe(GameIdPassTurnQueryV1Handler.name, () => {
gameSpecFixture = GameSpecFixtures.any;
gameUpdateQueryFixture = GameUpdateQueryFixtures.any;
transactionWrapperMock = {
[Symbol.asyncDispose]: jest.fn(),
tryCommit: jest.fn(),
} as Partial<
jest.Mocked<TransactionWrapper>
Expand Down Expand Up @@ -603,6 +617,15 @@ describe(GameIdPassTurnQueryV1Handler.name, () => {
expect(transactionWrapperMock.tryCommit).toHaveBeenCalledWith();
});

it('should call transactionWrapper[Symbol.asyncDispose]()', () => {
expect(
transactionWrapperMock[Symbol.asyncDispose],
).toHaveBeenCalledTimes(1);
expect(
transactionWrapperMock[Symbol.asyncDispose],
).toHaveBeenCalledWith();
});

it('should return undefined', () => {
expect(result).toBeUndefined();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
/*
* Ugly workaround until https://github.com/jestjs/jest/issues/14874 is provided in jest@30
*/

const disposeSymbol: unique symbol = Symbol('Symbol.dispose');
const asyncDisposeSymbol: unique symbol = Symbol('Symbol.asyncDispose');

(Symbol as Writable<SymbolConstructor>).asyncDispose ??=
asyncDisposeSymbol as unknown as SymbolConstructor['asyncDispose'];
(Symbol as Writable<SymbolConstructor>).dispose ??=
disposeSymbol as unknown as SymbolConstructor['dispose'];

import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals';

import { models as apiModels } from '@cornie-js/api-models';
Expand All @@ -6,6 +18,7 @@ import {
AppErrorKind,
Builder,
Handler,
Writable,
} from '@cornie-js/backend-common';
import { TransactionWrapper } from '@cornie-js/backend-db/application';
import { CardColor, Card } from '@cornie-js/backend-game-domain/cards';
Expand Down Expand Up @@ -508,6 +521,7 @@ describe(GameIdPlayCardsQueryV1Handler.name, () => {
gamePlayCardsUpdateQueryFixture =
GameUpdateQueryFixtures.withCurrentCard;
transactionWrapperMock = {
[Symbol.asyncDispose]: jest.fn(),
tryCommit: jest.fn(),
} as Partial<
jest.Mocked<TransactionWrapper>
Expand Down Expand Up @@ -684,6 +698,15 @@ describe(GameIdPlayCardsQueryV1Handler.name, () => {
expect(transactionWrapperMock.tryCommit).toHaveBeenCalledWith();
});

it('should call transactionWrapper[Symbol.asyncDispose]()', () => {
expect(
transactionWrapperMock[Symbol.asyncDispose],
).toHaveBeenCalledTimes(1);
expect(
transactionWrapperMock[Symbol.asyncDispose],
).toHaveBeenCalledWith();
});

it('should return undefined', () => {
expect(result).toBeUndefined();
});
Expand Down Expand Up @@ -736,6 +759,7 @@ describe(GameIdPlayCardsQueryV1Handler.name, () => {
gamePlayCardsUpdateQueryFixture =
GameUpdateQueryFixtures.withCurrentCard;
transactionWrapperMock = {
[Symbol.asyncDispose]: jest.fn(),
tryCommit: jest.fn(),
} as Partial<
jest.Mocked<TransactionWrapper>
Expand Down Expand Up @@ -924,6 +948,15 @@ describe(GameIdPlayCardsQueryV1Handler.name, () => {
expect(transactionWrapperMock.tryCommit).toHaveBeenCalledWith();
});

it('should call transactionWrapper[Symbol.asyncDispose]()', () => {
expect(
transactionWrapperMock[Symbol.asyncDispose],
).toHaveBeenCalledTimes(1);
expect(
transactionWrapperMock[Symbol.asyncDispose],
).toHaveBeenCalledWith();
});

it('should return undefined', () => {
expect(result).toBeUndefined();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export abstract class GameIdUpdateQueryV1Handler<

this._checkUnprocessableOperation(game, gameSpec, gameIdUpdateQueryV1);

const transactionWrapper: TransactionWrapper =
await using transactionWrapper: TransactionWrapper =
await this.#transactionProvisionOutputPort.provide();

const gameUpdatedEvent: ActiveGameUpdatedEvent =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
/*
* Ugly workaround until https://github.com/jestjs/jest/issues/14874 is provided in jest@30
*/

const disposeSymbol: unique symbol = Symbol('Symbol.dispose');
const asyncDisposeSymbol: unique symbol = Symbol('Symbol.asyncDispose');

(Symbol as Writable<SymbolConstructor>).asyncDispose ??=
asyncDisposeSymbol as unknown as SymbolConstructor['asyncDispose'];
(Symbol as Writable<SymbolConstructor>).dispose ??=
disposeSymbol as unknown as SymbolConstructor['dispose'];

import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals';

import { models as apiModels } from '@cornie-js/api-models';
Expand All @@ -8,6 +20,7 @@ import {
Builder,
Handler,
Spec,
Writable,
} from '@cornie-js/backend-common';
import { TransactionWrapper } from '@cornie-js/backend-db/application';
import { Card } from '@cornie-js/backend-game-domain/cards';
Expand Down Expand Up @@ -138,6 +151,7 @@ describe(GameSlotManagementInputPort.name, () => {
gameSlotFixture = NonStartedGameSlotFixtures.any;
gameSlotV1Fixture = NonStartedGameSlotV1Fixtures.any;
transactionWrapperMock = {
[Symbol.asyncDispose]: jest.fn(),
tryCommit: jest.fn(),
} as Partial<
jest.Mocked<TransactionWrapper>
Expand Down Expand Up @@ -257,6 +271,15 @@ describe(GameSlotManagementInputPort.name, () => {
);
});

it('should call transactionWrapper[Symbol.asyncDispose]()', () => {
expect(
transactionWrapperMock[Symbol.asyncDispose],
).toHaveBeenCalledTimes(1);
expect(
transactionWrapperMock[Symbol.asyncDispose],
).toHaveBeenCalledWith();
});

it('should return a GameSlotV1', () => {
expect(result).toBe(gameSlotV1Fixture);
});
Expand All @@ -276,6 +299,7 @@ describe(GameSlotManagementInputPort.name, () => {
gameSlotFixture = NonStartedGameSlotFixtures.any;
gameSlotV1Fixture = NonStartedGameSlotV1Fixtures.any;
transactionWrapperMock = {
[Symbol.asyncDispose]: jest.fn(),
tryCommit: jest.fn(),
} as Partial<
jest.Mocked<TransactionWrapper>
Expand Down Expand Up @@ -405,6 +429,15 @@ describe(GameSlotManagementInputPort.name, () => {
);
});

it('should call transactionWrapper[Symbol.asyncDispose]()', () => {
expect(
transactionWrapperMock[Symbol.asyncDispose],
).toHaveBeenCalledTimes(1);
expect(
transactionWrapperMock[Symbol.asyncDispose],
).toHaveBeenCalledWith();
});

it('should return a GameSlotV1', () => {
expect(result).toBe(gameSlotV1Fixture);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class GameSlotManagementInputPort {
this.#createGameSlotCreationQueryContext(game),
);

const transactionWrapper: TransactionWrapper =
await using transactionWrapper: TransactionWrapper =
await this.#transactionProvisionOutputPort.provide();

const gameSlot: ActiveGameSlot | NonStartedGameSlot =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
/*
* Ugly workaround until https://github.com/jestjs/jest/issues/14874 is provided in jest@30
*/

const disposeSymbol: unique symbol = Symbol('Symbol.dispose');
const asyncDisposeSymbol: unique symbol = Symbol('Symbol.asyncDispose');

(Symbol as Writable<SymbolConstructor>).asyncDispose ??=
asyncDisposeSymbol as unknown as SymbolConstructor['asyncDispose'];
(Symbol as Writable<SymbolConstructor>).dispose ??=
disposeSymbol as unknown as SymbolConstructor['dispose'];

import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals';

import {
AppError,
AppErrorKind,
Handler,
ReportBasedSpec,
Writable,
} from '@cornie-js/backend-common';
import { TransactionWrapper } from '@cornie-js/backend-db/application';
import { User, UserCreateQuery } from '@cornie-js/backend-user-domain/users';
Expand Down Expand Up @@ -121,6 +134,7 @@ describe(CreateUserUseCaseHandler.name, () => {

beforeAll(async () => {
transactionWrapperMock = {
[Symbol.asyncDispose]: jest.fn(),
rollback: jest.fn(),
tryCommit: jest
.fn()
Expand Down Expand Up @@ -192,6 +206,15 @@ describe(CreateUserUseCaseHandler.name, () => {
expect(transactionWrapperMock.tryCommit).toHaveBeenCalledWith();
});

it('should call transactionWrapper[Symbol.asyncDispose]()', () => {
expect(
transactionWrapperMock[Symbol.asyncDispose],
).toHaveBeenCalledTimes(1);
expect(
transactionWrapperMock[Symbol.asyncDispose],
).toHaveBeenCalledWith();
});

it('should return an UserV1', () => {
expect(result).toBe(userFixture);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class CreateUserUseCaseHandler
public async handle(userCreateQuery: UserCreateQuery): Promise<User> {
this.#validate(userCreateQuery);

const transactionWrapper: TransactionWrapper =
await using transactionWrapper: TransactionWrapper =
await this.#transactionProvisionOutputPort.provide();

const user: User = await this.#userPersistenceOutputPort.create(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ export class TypeOrmTransactionWrapper implements TransactionWrapper {
);
}

public async [Symbol.asyncDispose](): Promise<void> {
if (!this.#queryRunner.isReleased) {
await this.rollback();
}
}

public async rollback(): Promise<void> {
try {
await this.#queryRunner.rollbackTransaction();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface TransactionWrapper {
export interface TransactionWrapper extends AsyncDisposable {
rollback(): Promise<void>;
tryCommit(): Promise<void>;
unwrap(): unknown;
Expand Down

0 comments on commit fc10b5e

Please sign in to comment.