mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-10 17:53:36 +02:00
add methods for checking if a store is expecting to be in transactional mode
This commit is contained in:
parent
dd955c6ede
commit
914955e9d7
@ -9,8 +9,7 @@ import Group, {
|
|||||||
IGroupUser,
|
IGroupUser,
|
||||||
IGroupUserModel,
|
IGroupUserModel,
|
||||||
} from '../types/group';
|
} from '../types/group';
|
||||||
import Transaction = Knex.Transaction;
|
import { expectTransaction, Transactor } from './transactional';
|
||||||
import { Transactor } from './transactional';
|
|
||||||
|
|
||||||
const T = {
|
const T = {
|
||||||
GROUPS: 'groups',
|
GROUPS: 'groups',
|
||||||
@ -178,7 +177,6 @@ export default class GroupStore
|
|||||||
groupId: number,
|
groupId: number,
|
||||||
users: IGroupUserModel[],
|
users: IGroupUserModel[],
|
||||||
userName: string,
|
userName: string,
|
||||||
transaction?: Transaction,
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const rows = users.map((user) => {
|
const rows = users.map((user) => {
|
||||||
return {
|
return {
|
||||||
@ -187,14 +185,11 @@ export default class GroupStore
|
|||||||
created_by: userName,
|
created_by: userName,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return (transaction || this.db).batchInsert(T.GROUP_USER, rows);
|
return this.db.batchInsert(T.GROUP_USER, rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteUsersFromGroup(
|
async deleteUsersFromGroup(deletableUsers: IGroupUser[]): Promise<void> {
|
||||||
deletableUsers: IGroupUser[],
|
return this.db(T.GROUP_USER)
|
||||||
transaction?: Transaction,
|
|
||||||
): Promise<void> {
|
|
||||||
return (transaction || this.db)(T.GROUP_USER)
|
|
||||||
.whereIn(
|
.whereIn(
|
||||||
['group_id', 'user_id'],
|
['group_id', 'user_id'],
|
||||||
deletableUsers.map((user) => [user.groupId, user.userId]),
|
deletableUsers.map((user) => [user.groupId, user.userId]),
|
||||||
@ -209,10 +204,9 @@ export default class GroupStore
|
|||||||
deletableUsers: IGroupUser[],
|
deletableUsers: IGroupUser[],
|
||||||
userName: string,
|
userName: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.db.transaction(async (tx) => {
|
expectTransaction(this.db);
|
||||||
await this.addUsersToGroup(groupId, newUsers, userName, tx);
|
await this.addUsersToGroup(groupId, newUsers, userName);
|
||||||
await this.deleteUsersFromGroup(deletableUsers, tx);
|
await this.deleteUsersFromGroup(deletableUsers);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNewGroupsForExternalUser(
|
async getNewGroupsForExternalUser(
|
||||||
|
@ -20,3 +20,17 @@ export abstract class Transactor<T> implements Transactional<T> {
|
|||||||
return clone as T;
|
return clone as T;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const expectTransaction = (db: Knex | Knex.Transaction): void => {
|
||||||
|
if (db.isTransaction) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isRunningInTest = process.env.NODE_ENV === 'test';
|
||||||
|
const errorMessage =
|
||||||
|
'A store method that was expected to be run in a transaction was run outside of a transaction';
|
||||||
|
if (isRunningInTest) {
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
} else {
|
||||||
|
console.error(errorMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -15,6 +15,7 @@ import { IEventStore } from '../types/stores/event-store';
|
|||||||
import NameExistsError from '../error/name-exists-error';
|
import NameExistsError from '../error/name-exists-error';
|
||||||
import { IUserStore } from '../types/stores/user-store';
|
import { IUserStore } from '../types/stores/user-store';
|
||||||
import { IUser } from '../types/user';
|
import { IUser } from '../types/user';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
export class GroupService {
|
export class GroupService {
|
||||||
private groupStore: IGroupStore;
|
private groupStore: IGroupStore;
|
||||||
@ -25,14 +26,17 @@ export class GroupService {
|
|||||||
|
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
|
private db: Knex;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
stores: Pick<IUnleashStores, 'groupStore' | 'eventStore' | 'userStore'>,
|
stores: Pick<IUnleashStores, 'groupStore' | 'eventStore' | 'userStore'>,
|
||||||
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
|
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>, // db: Knex,
|
||||||
) {
|
) {
|
||||||
this.logger = getLogger('service/group-service.js');
|
this.logger = getLogger('service/group-service.js');
|
||||||
this.groupStore = stores.groupStore;
|
this.groupStore = stores.groupStore;
|
||||||
this.eventStore = stores.eventStore;
|
this.eventStore = stores.eventStore;
|
||||||
this.userStore = stores.userStore;
|
this.userStore = stores.userStore;
|
||||||
|
// this.db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAll(): Promise<IGroupModel[]> {
|
async getAll(): Promise<IGroupModel[]> {
|
||||||
@ -118,19 +122,21 @@ export class GroupService {
|
|||||||
);
|
);
|
||||||
const deletableUserIds = deletableUsers.map((g) => g.userId);
|
const deletableUserIds = deletableUsers.map((g) => g.userId);
|
||||||
|
|
||||||
await this.groupStore.updateGroupUsers(
|
this.db.transaction(async (tx) => {
|
||||||
newGroup.id,
|
await this.groupStore.transactional(tx).updateGroupUsers(
|
||||||
group.users.filter(
|
newGroup.id,
|
||||||
(user) => !existingUserIds.includes(user.user.id),
|
group.users.filter(
|
||||||
),
|
(user) => !existingUserIds.includes(user.user.id),
|
||||||
group.users.filter(
|
),
|
||||||
(user) =>
|
group.users.filter(
|
||||||
existingUserIds.includes(user.user.id) &&
|
(user) =>
|
||||||
!deletableUserIds.includes(user.user.id),
|
existingUserIds.includes(user.user.id) &&
|
||||||
),
|
!deletableUserIds.includes(user.user.id),
|
||||||
deletableUsers,
|
),
|
||||||
userName,
|
deletableUsers,
|
||||||
);
|
userName,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
await this.eventStore.store({
|
await this.eventStore.store({
|
||||||
type: GROUP_UPDATED,
|
type: GROUP_UPDATED,
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
} from '../../lib/types/stores/feature-toggle-store';
|
} from '../../lib/types/stores/feature-toggle-store';
|
||||||
import NotFoundError from '../../lib/error/notfound-error';
|
import NotFoundError from '../../lib/error/notfound-error';
|
||||||
import { FeatureToggle, FeatureToggleDTO, IVariant } from 'lib/types/model';
|
import { FeatureToggle, FeatureToggleDTO, IVariant } from 'lib/types/model';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
export default class FakeFeatureToggleStore implements IFeatureToggleStore {
|
export default class FakeFeatureToggleStore implements IFeatureToggleStore {
|
||||||
features: FeatureToggle[] = [];
|
features: FeatureToggle[] = [];
|
||||||
@ -135,4 +136,11 @@ export default class FakeFeatureToggleStore implements IFeatureToggleStore {
|
|||||||
feature.variants = newVariants;
|
feature.variants = newVariants;
|
||||||
return feature;
|
return feature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transactional(
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
transaction: Knex.Transaction<any, any[]>,
|
||||||
|
): IFeatureToggleStore {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,102 @@ test('should actually do something transactional mode', async () => {
|
|||||||
expect(createdGroup).toBeDefined();
|
expect(createdGroup).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should fail if we run a method that requires transactional outside of a transaction', async () => {
|
||||||
|
const newUser = await stores.userStore.insert({
|
||||||
|
name: 'Tyler Durden',
|
||||||
|
});
|
||||||
|
const oldUser = await stores.userStore.insert({
|
||||||
|
name: 'Bob Poulson',
|
||||||
|
});
|
||||||
|
|
||||||
|
const group = await stores.groupStore.create({
|
||||||
|
name: 'TestGroup',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(async () =>
|
||||||
|
stores.groupStore.updateGroupUsers(
|
||||||
|
group.id,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
user: newUser,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
groupId: group.id,
|
||||||
|
userId: oldUser.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'David Fincher',
|
||||||
|
),
|
||||||
|
).rejects.toThrow(Error);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not fail if we run a method that requires transactional outside of a transaction in prod mode', async () => {
|
||||||
|
const newUser = await stores.userStore.insert({
|
||||||
|
name: 'Tyler Durden',
|
||||||
|
});
|
||||||
|
const oldUser = await stores.userStore.insert({
|
||||||
|
name: 'Bob Poulson',
|
||||||
|
});
|
||||||
|
|
||||||
|
const group = await stores.groupStore.create({
|
||||||
|
name: 'TestGroup',
|
||||||
|
});
|
||||||
|
|
||||||
|
process.env.NODE_ENV = 'prod';
|
||||||
|
|
||||||
|
await stores.groupStore.updateGroupUsers(
|
||||||
|
group.id,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
user: newUser,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
groupId: group.id,
|
||||||
|
userId: oldUser.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'David Fincher',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not fail if we run a method that requires transactional inside of a transaction in test mode', async () => {
|
||||||
|
const newUser = await stores.userStore.insert({
|
||||||
|
name: 'Tyler Durden',
|
||||||
|
});
|
||||||
|
const oldUser = await stores.userStore.insert({
|
||||||
|
name: 'Bob Poulson',
|
||||||
|
});
|
||||||
|
|
||||||
|
const group = await stores.groupStore.create({
|
||||||
|
name: 'TestGroup',
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.db.transaction(async (trx) => {
|
||||||
|
await stores.groupStore.transactional(trx).updateGroupUsers(
|
||||||
|
group.id,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
user: newUser,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
groupId: group.id,
|
||||||
|
userId: oldUser.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'David Fincher',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('should fail entire transaction if encountering an error', async () => {
|
test('should fail entire transaction if encountering an error', async () => {
|
||||||
await db.db.transaction(async (trx) => {
|
await db.db.transaction(async (trx) => {
|
||||||
const featureDTO = {
|
const featureDTO = {
|
||||||
|
Loading…
Reference in New Issue
Block a user