mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-05 17:53:12 +02:00
implement transaction creator closure and pass that to group service rather than knex directly, add some tests to prove this can be swapped out
This commit is contained in:
parent
d213df36dc
commit
3200673f1c
@ -38,3 +38,35 @@ export abstract class Transactor<T> implements Transactional<T> {
|
||||
expectTransaction(db);
|
||||
}
|
||||
}
|
||||
|
||||
export type KnexTransaction = Knex.Transaction<any, any[]>;
|
||||
|
||||
export type MockTransaction = null;
|
||||
|
||||
export type UnleashTransaction = KnexTransaction | MockTransaction;
|
||||
|
||||
export type TransactionCreator<S> = <T>(
|
||||
scope: (trx: S) => void | Promise<T>,
|
||||
) => Promise<T>;
|
||||
|
||||
export const createKnexTransactionStarter = (
|
||||
knex: Knex,
|
||||
): TransactionCreator<UnleashTransaction> => {
|
||||
function transaction<T>(
|
||||
scope: (trx: KnexTransaction) => void | Promise<T>,
|
||||
) {
|
||||
return knex.transaction(scope);
|
||||
}
|
||||
return transaction;
|
||||
};
|
||||
|
||||
export const createMockTransactionStarter =
|
||||
(): TransactionCreator<UnleashTransaction> => {
|
||||
function transaction<T>(
|
||||
scope: (trx: MockTransaction) => void | Promise<T>,
|
||||
) {
|
||||
scope(null);
|
||||
return null;
|
||||
}
|
||||
return transaction;
|
||||
};
|
||||
|
@ -15,7 +15,7 @@ import { IEventStore } from '../types/stores/event-store';
|
||||
import NameExistsError from '../error/name-exists-error';
|
||||
import { IUserStore } from '../types/stores/user-store';
|
||||
import { IUser } from '../types/user';
|
||||
import { Knex } from 'knex';
|
||||
import { TransactionCreator, UnleashTransaction } from 'lib/db/transactional';
|
||||
|
||||
export class GroupService {
|
||||
private groupStore: IGroupStore;
|
||||
@ -26,18 +26,18 @@ export class GroupService {
|
||||
|
||||
private logger: Logger;
|
||||
|
||||
private db: Knex;
|
||||
private startTransaction: TransactionCreator<UnleashTransaction>;
|
||||
|
||||
constructor(
|
||||
stores: Pick<IUnleashStores, 'groupStore' | 'eventStore' | 'userStore'>,
|
||||
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
|
||||
db: Knex,
|
||||
startTransaction: TransactionCreator<UnleashTransaction>,
|
||||
) {
|
||||
this.logger = getLogger('service/group-service.js');
|
||||
this.groupStore = stores.groupStore;
|
||||
this.eventStore = stores.eventStore;
|
||||
this.userStore = stores.userStore;
|
||||
this.db = db;
|
||||
this.startTransaction = startTransaction;
|
||||
}
|
||||
|
||||
async getAll(): Promise<IGroupModel[]> {
|
||||
@ -108,7 +108,7 @@ export class GroupService {
|
||||
|
||||
await this.validateGroup(group, preData);
|
||||
|
||||
return this.db.transaction(async (tx) => {
|
||||
return this.startTransaction(async (tx) => {
|
||||
const newGroup = await this.groupStore
|
||||
.transactional(tx)
|
||||
.update(group);
|
||||
@ -232,7 +232,7 @@ export class GroupService {
|
||||
createdBy?: string,
|
||||
): Promise<void> {
|
||||
if (Array.isArray(externalGroups)) {
|
||||
await this.db.transaction(async (trx) => {
|
||||
await this.startTransaction(async (trx) => {
|
||||
let newGroups = await this.groupStore
|
||||
.transactional(trx)
|
||||
.getNewGroupsForExternalUser(userId, externalGroups);
|
||||
|
@ -37,12 +37,14 @@ import PatService from './pat-service';
|
||||
import { PublicSignupTokenService } from './public-signup-token-service';
|
||||
import { LastSeenService } from './client-metrics/last-seen-service';
|
||||
import { Knex } from 'knex';
|
||||
import { createKnexTransactionStarter } from '../db/transactional';
|
||||
export const createServices = (
|
||||
stores: IUnleashStores,
|
||||
config: IUnleashConfig,
|
||||
db: Knex,
|
||||
): IUnleashServices => {
|
||||
const groupService = new GroupService(stores, config, db);
|
||||
let startTransaction = createKnexTransactionStarter(db);
|
||||
const groupService = new GroupService(stores, config, startTransaction);
|
||||
const accessService = new AccessService(stores, config, groupService);
|
||||
const apiTokenService = new ApiTokenService(stores, config);
|
||||
const clientInstanceService = new ClientInstanceService(stores, config);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Knex } from 'knex';
|
||||
import { UnleashTransaction } from 'lib/db/transactional';
|
||||
|
||||
export interface Transactional<T> {
|
||||
transactional(transaction: Knex.Transaction): T;
|
||||
transactional(transaction: UnleashTransaction): T;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import getLogger from '../../fixtures/no-logger';
|
||||
import { createTestConfig } from '../../config/test-config';
|
||||
import { GroupService } from '../../../lib/services/group-service';
|
||||
import GroupStore from '../../../lib/db/group-store';
|
||||
import { createKnexTransactionStarter } from '../../../lib/db/transactional';
|
||||
|
||||
let stores;
|
||||
let db: ITestDb;
|
||||
@ -21,7 +22,8 @@ beforeAll(async () => {
|
||||
const config = createTestConfig({
|
||||
getLogger,
|
||||
});
|
||||
groupService = new GroupService(stores, config, db.db);
|
||||
let startTransaction = createKnexTransactionStarter(db.db);
|
||||
groupService = new GroupService(stores, config, startTransaction);
|
||||
groupStore = stores.groupStore;
|
||||
|
||||
await stores.groupStore.create({
|
||||
|
37
src/test/fixtures/fake-group-store.ts
vendored
37
src/test/fixtures/fake-group-store.ts
vendored
@ -7,11 +7,15 @@ import Group, {
|
||||
IGroupUser,
|
||||
IGroupUserModel,
|
||||
} from '../../lib/types/group';
|
||||
import { Knex } from 'knex';
|
||||
import { UnleashTransaction } from 'lib/db/transactional';
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
export default class FakeGroupStore implements IGroupStore {
|
||||
data: IGroup[];
|
||||
|
||||
constructor() {
|
||||
this.data = [];
|
||||
}
|
||||
|
||||
async getAll(): Promise<IGroup[]> {
|
||||
return Promise.resolve(this.data);
|
||||
}
|
||||
@ -56,7 +60,7 @@ export default class FakeGroupStore implements IGroupStore {
|
||||
}
|
||||
|
||||
deleteUsersFromGroup(deletableUsers: IGroupUser[]): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
update(group: IGroupModel): Promise<IGroup> {
|
||||
@ -89,7 +93,13 @@ export default class FakeGroupStore implements IGroupStore {
|
||||
userId: number,
|
||||
externalGroups: string[],
|
||||
): Promise<IGroup[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
const mockGroups = externalGroups.map((externalGroup, index) => {
|
||||
return {
|
||||
id: index,
|
||||
name: externalGroup,
|
||||
};
|
||||
});
|
||||
return Promise.resolve(mockGroups);
|
||||
}
|
||||
|
||||
addUserToGroups(
|
||||
@ -97,21 +107,34 @@ export default class FakeGroupStore implements IGroupStore {
|
||||
groupIds: number[],
|
||||
createdBy?: string,
|
||||
): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
groupIds.forEach((groupId) => {
|
||||
this.data.push({
|
||||
id: groupId,
|
||||
name: `TestGroup-${groupId}`,
|
||||
});
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
getOldGroupsForExternalUser(
|
||||
userId: number,
|
||||
externalGroups: string[],
|
||||
): Promise<IGroupUser[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
const mockGroups = externalGroups.map((externalGroup, index) => {
|
||||
return {
|
||||
groupId: index,
|
||||
userId: index,
|
||||
joinedAt: new Date(),
|
||||
};
|
||||
});
|
||||
return Promise.resolve(mockGroups);
|
||||
}
|
||||
|
||||
getGroupsForUser(userId: number): Promise<Group[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
transactional(transaction: Knex.Transaction<any, any[]>): IGroupStore {
|
||||
throw new Error('Method not implemented.');
|
||||
transactional(transaction: UnleashTransaction): IGroupStore {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,20 @@
|
||||
import { createMockTransactionStarter } from '../lib/db/transactional';
|
||||
import { IUnleashConfig } from '../lib/server-impl';
|
||||
import { GroupService } from '../lib/services/group-service';
|
||||
import dbInit, { ITestDb } from './/e2e/helpers/database-init';
|
||||
import getLogger from './fixtures/no-logger';
|
||||
import { createTestConfig } from './config/test-config';
|
||||
import FakeGroupStore from './fixtures/fake-group-store';
|
||||
import noLoggerProvider from './fixtures/no-logger';
|
||||
|
||||
let stores;
|
||||
let db: ITestDb;
|
||||
let config: IUnleashConfig;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('transactional_serial', getLogger);
|
||||
db = await dbInit('transactional_serial', noLoggerProvider);
|
||||
config = createTestConfig({
|
||||
getLogger: noLoggerProvider,
|
||||
});
|
||||
stores = db.stores;
|
||||
});
|
||||
|
||||
@ -141,3 +150,24 @@ test('should fail entire transaction if encountering an error', async () => {
|
||||
const toggles = await stores.featureToggleStore.getAll();
|
||||
expect(toggles.length).toBe(0);
|
||||
});
|
||||
|
||||
test('should allow transactions be swapped for a different implementation', async () => {
|
||||
const mockStores = {
|
||||
groupStore: new FakeGroupStore(),
|
||||
eventStore: null,
|
||||
userStore: null,
|
||||
};
|
||||
|
||||
expect((await mockStores.groupStore.getAll()).length).toBe(0);
|
||||
|
||||
const groupService = new GroupService(
|
||||
mockStores,
|
||||
config,
|
||||
createMockTransactionStarter(),
|
||||
);
|
||||
const externalGroups = ['group-one', 'group-two'];
|
||||
|
||||
await groupService.syncExternalGroups(7, externalGroups, 'David Fincher');
|
||||
|
||||
expect((await mockStores.groupStore.getAll()).length).toBe(2);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user