diff --git a/src/lib/db/feature-toggle-store.ts b/src/lib/db/feature-toggle-store.ts index 91db0006aa..7edfecb318 100644 --- a/src/lib/db/feature-toggle-store.ts +++ b/src/lib/db/feature-toggle-store.ts @@ -6,6 +6,7 @@ import NotFoundError from '../error/notfound-error'; import { Logger, LogProvider } from '../logger'; import { FeatureToggle, FeatureToggleDTO, IVariant } from '../types/model'; import { IFeatureToggleStore } from '../types/stores/feature-toggle-store'; +import { Transactor } from './transactional'; const FEATURE_COLUMNS = [ 'name', @@ -36,14 +37,18 @@ export interface FeaturesTable { const TABLE = 'features'; -export default class FeatureToggleStore implements IFeatureToggleStore { - private db: Knex; +export default class FeatureToggleStore + extends Transactor + implements IFeatureToggleStore +{ + private db: Knex | Knex.Transaction; private logger: Logger; private timer: Function; constructor(db: Knex, eventBus: EventEmitter, getLogger: LogProvider) { + super(db, eventBus, getLogger); this.db = db; this.logger = getLogger('feature-toggle-store.ts'); this.timer = (action) => diff --git a/src/lib/db/group-store.ts b/src/lib/db/group-store.ts index b91ed844b6..1a7995b2ba 100644 --- a/src/lib/db/group-store.ts +++ b/src/lib/db/group-store.ts @@ -10,7 +10,7 @@ import Group, { IGroupUserModel, } from '../types/group'; import Transaction = Knex.Transaction; -import { AbstractTransactional } from './transactional'; +import { Transactor } from './transactional'; const T = { GROUPS: 'groups', @@ -62,10 +62,10 @@ const groupToRow = (group: IStoreGroup) => ({ }); export default class GroupStore - extends AbstractTransactional + extends Transactor implements IGroupStore { - private db: Knex; + private db: Knex | Knex.Transaction; constructor(db: Knex) { super(); diff --git a/src/lib/db/transactional.ts b/src/lib/db/transactional.ts index 45db097479..864855f22c 100644 --- a/src/lib/db/transactional.ts +++ b/src/lib/db/transactional.ts @@ -1,9 +1,18 @@ import { Knex } from 'knex'; import { Transactional } from 'lib/types/stores/transactional'; -export abstract class AbstractTransactional implements Transactional { +export abstract class Transactor implements Transactional { + args: any[]; + + // eslint-disable-next-line + constructor(...args: any) { + this.args = args; + } + transactional(transaction: Knex.Transaction): T { - let clone = new (this.constructor as { new (): any })(); + let clone = new (this.constructor as { new (...args: any[]): any })( + ...this.args, + ); for (const attribute in this) { clone[attribute] = this[attribute]; } diff --git a/src/lib/types/stores/feature-toggle-store.ts b/src/lib/types/stores/feature-toggle-store.ts index 77e5626607..bc755597c1 100644 --- a/src/lib/types/stores/feature-toggle-store.ts +++ b/src/lib/types/stores/feature-toggle-store.ts @@ -1,4 +1,5 @@ import { FeatureToggle, FeatureToggleDTO, IVariant } from '../model'; +import { Transactional } from './transactional'; import { Store } from './store'; export interface IFeatureToggleQuery { @@ -7,7 +8,9 @@ export interface IFeatureToggleQuery { stale: boolean; } -export interface IFeatureToggleStore extends Store { +export interface IFeatureToggleStore + extends Store, + Transactional { count(query?: Partial): Promise; setLastSeen(toggleNames: string[]): Promise; getProjectId(name: string): Promise; diff --git a/src/test/transactional.test.ts b/src/test/transactional.test.ts index 9beecad1f5..5b5a8d9b98 100644 --- a/src/test/transactional.test.ts +++ b/src/test/transactional.test.ts @@ -5,25 +5,8 @@ let stores; let db: ITestDb; beforeAll(async () => { - db = await dbInit('group_service_serial', getLogger); + db = await dbInit('transactional_serial', getLogger); stores = db.stores; - - await stores.groupStore.create({ - name: 'dev_group', - description: 'dev_group', - mappingsSSO: ['dev'], - }); - await stores.groupStore.create({ - name: 'maintainer_group', - description: 'maintainer_group', - mappingsSSO: ['maintainer'], - }); - - await stores.groupStore.create({ - name: 'admin_group', - description: 'admin_group', - mappingsSSO: ['admin'], - }); }); afterAll(async () => { @@ -45,3 +28,20 @@ test('should actually do something transactional mode', async () => { }); expect(createdGroup).toBeDefined(); }); + +test('should fail entire transaction if encountering an error', async () => { + await db.db.transaction(async (trx) => { + const featureDTO = { + name: 'SomeUniqueNameThatSoonWontBeUnique', + }; + + await stores.featureToggleStore + .transactional(trx) + .create('default', featureDTO); + await stores.featureToggleStore + .transactional(trx) + .create('default', featureDTO); + }); + const toggles = await stores.featureToggleStore.getAll(); + expect(toggles.length).toBe(0); +});