mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
feat: add user create/update/delete events (#807)
This commit is contained in:
parent
d0b17af770
commit
886e0bb008
@ -32,6 +32,9 @@ const {
|
||||
APPLICATION_CREATED,
|
||||
FEATURE_STALE_ON,
|
||||
FEATURE_STALE_OFF,
|
||||
USER_CREATED,
|
||||
USER_UPDATED,
|
||||
USER_DELETED,
|
||||
} = require('./event-type');
|
||||
|
||||
const strategyTypes = [
|
||||
@ -63,6 +66,8 @@ const contextTypes = [
|
||||
CONTEXT_FIELD_UPDATED,
|
||||
];
|
||||
|
||||
const userTypes = [USER_CREATED, USER_UPDATED, USER_DELETED];
|
||||
|
||||
const tagTypes = [TAG_CREATED, TAG_DELETED];
|
||||
|
||||
const tagTypeTypes = [TAG_TYPE_CREATED, TAG_TYPE_DELETED];
|
||||
@ -88,23 +93,34 @@ function baseTypeFor(event) {
|
||||
if (tagTypeTypes.indexOf(event.type) !== -1) {
|
||||
return 'tag-type';
|
||||
}
|
||||
if (userTypes.indexOf(event.type) !== -1) {
|
||||
return 'user';
|
||||
}
|
||||
if (event.type === APPLICATION_CREATED) {
|
||||
return 'application';
|
||||
}
|
||||
return event.type;
|
||||
}
|
||||
|
||||
const uniqueFieldForType = baseType => {
|
||||
if (baseType === 'user') {
|
||||
return 'id';
|
||||
}
|
||||
return 'name';
|
||||
};
|
||||
|
||||
function groupByBaseTypeAndName(events) {
|
||||
const groups = {};
|
||||
|
||||
events.forEach(event => {
|
||||
const baseType = baseTypeFor(event);
|
||||
const uniqueField = uniqueFieldForType(baseType);
|
||||
|
||||
groups[baseType] = groups[baseType] || {};
|
||||
groups[baseType][event.data.name] =
|
||||
groups[baseType][event.data.name] || [];
|
||||
groups[baseType][event.data[uniqueField]] =
|
||||
groups[baseType][event.data[uniqueField]] || [];
|
||||
|
||||
groups[baseType][event.data.name].push(event);
|
||||
groups[baseType][event.data[uniqueField]].push(event);
|
||||
});
|
||||
|
||||
return groups;
|
||||
|
@ -42,4 +42,7 @@ module.exports = {
|
||||
ADDON_CONFIG_UPDATED: 'addon-config-updated',
|
||||
ADDON_CONFIG_DELETED: 'addon-config-deleted',
|
||||
DB_POOL_UPDATE: 'db-pool-update',
|
||||
USER_CREATED: 'user-created',
|
||||
USER_UPDATED: 'user-updated',
|
||||
USER_DELETED: 'user-deleted',
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
const REQUEST_TIME = 'request_time';
|
||||
const DB_TIME = 'db_time';
|
||||
const DB_TIME = 'db_time';
|
||||
|
||||
export {REQUEST_TIME, DB_TIME};
|
||||
export { REQUEST_TIME, DB_TIME };
|
||||
|
@ -6,13 +6,11 @@ import { AccessService } from '../../services/access-service';
|
||||
import { Logger } from '../../logger';
|
||||
import { handleErrors } from './util';
|
||||
import { IUnleashConfig } from '../../types/option';
|
||||
import { EmailService, MAIL_ACCEPTED } from '../../services/email-service';
|
||||
import { EmailService } from '../../services/email-service';
|
||||
import ResetTokenService from '../../services/reset-token-service';
|
||||
import { IUnleashServices } from '../../types/services';
|
||||
import SessionService from '../../services/session-service';
|
||||
|
||||
const getCreatorUsernameOrPassword = req => req.user.username || req.user.email;
|
||||
|
||||
export default class UserAdminController extends Controller {
|
||||
private userService: UserService;
|
||||
|
||||
@ -46,8 +44,8 @@ export default class UserAdminController extends Controller {
|
||||
super(config);
|
||||
this.userService = userService;
|
||||
this.accessService = accessService;
|
||||
this.logger = config.getLogger('routes/user-controller.ts');
|
||||
this.emailService = emailService;
|
||||
this.logger = config.getLogger('routes/user-controller.ts');
|
||||
this.resetTokenService = resetTokenService;
|
||||
this.sessionService = sessionService;
|
||||
|
||||
@ -64,12 +62,12 @@ export default class UserAdminController extends Controller {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async resetPassword(req, res): Promise<void> {
|
||||
const { user } = req;
|
||||
try {
|
||||
const requester = getCreatorUsernameOrPassword(req);
|
||||
const receiver = req.body.id;
|
||||
const resetPasswordUrl = await this.userService.createResetPasswordEmail(
|
||||
receiver,
|
||||
requester,
|
||||
user,
|
||||
);
|
||||
res.json({ resetPasswordUrl });
|
||||
} catch (e) {
|
||||
@ -124,12 +122,15 @@ export default class UserAdminController extends Controller {
|
||||
const { user } = req;
|
||||
|
||||
try {
|
||||
const createdUser = await this.userService.createUser({
|
||||
username,
|
||||
email,
|
||||
name,
|
||||
rootRole: Number(rootRole),
|
||||
});
|
||||
const createdUser = await this.userService.createUser(
|
||||
{
|
||||
username,
|
||||
email,
|
||||
name,
|
||||
rootRole: Number(rootRole),
|
||||
},
|
||||
user,
|
||||
);
|
||||
|
||||
const inviteLink = await this.resetTokenService.createNewUserUrl(
|
||||
createdUser.id,
|
||||
@ -163,17 +164,22 @@ export default class UserAdminController extends Controller {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async updateUser(req, res): Promise<void> {
|
||||
const { id } = req.params;
|
||||
const { name, email, rootRole } = req.body;
|
||||
const { user, params, body } = req;
|
||||
|
||||
const { id } = params;
|
||||
const { name, email, rootRole } = body;
|
||||
|
||||
try {
|
||||
const user = await this.userService.updateUser({
|
||||
id: Number(id),
|
||||
name,
|
||||
email,
|
||||
rootRole: Number(rootRole),
|
||||
});
|
||||
res.status(200).send({ ...user, rootRole });
|
||||
const updateUser = await this.userService.updateUser(
|
||||
{
|
||||
id: Number(id),
|
||||
name,
|
||||
email,
|
||||
rootRole: Number(rootRole),
|
||||
},
|
||||
user,
|
||||
);
|
||||
res.status(200).send({ ...updateUser, rootRole });
|
||||
} catch (e) {
|
||||
this.logger.warn(e.message);
|
||||
res.status(400).send([{ msg: e.message }]);
|
||||
@ -182,10 +188,11 @@ export default class UserAdminController extends Controller {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async deleteUser(req, res): Promise<void> {
|
||||
const { id } = req.params;
|
||||
const { user, params } = req;
|
||||
const { id } = params;
|
||||
|
||||
try {
|
||||
await this.userService.deleteUser(+id);
|
||||
await this.userService.deleteUser(+id, user);
|
||||
res.status(200).send();
|
||||
} catch (error) {
|
||||
this.logger.warn(error);
|
||||
|
@ -21,7 +21,6 @@ interface SessionRequest<PARAMS, QUERY, BODY, K>
|
||||
user?;
|
||||
}
|
||||
|
||||
const UNLEASH = 'Unleash';
|
||||
class ResetPasswordController extends Controller {
|
||||
private userService: UserService;
|
||||
|
||||
@ -43,7 +42,7 @@ class ResetPasswordController extends Controller {
|
||||
const { email } = req.body;
|
||||
|
||||
try {
|
||||
await this.userService.createResetPasswordEmail(email, UNLEASH);
|
||||
await this.userService.createResetPasswordEmail(email);
|
||||
res.status(200).end();
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import test from 'ava';
|
||||
import UserService from './user-service';
|
||||
import UserStoreMock from '../../test/fixtures/fake-user-store';
|
||||
import EventStoreMock from '../../test/fixtures/fake-event-store';
|
||||
import AccessServiceMock from '../../test/fixtures/access-service-mock';
|
||||
import { ResetTokenStoreMock } from '../../test/fixtures/fake-reset-token-store';
|
||||
import ResetTokenService from './reset-token-service';
|
||||
@ -10,11 +11,15 @@ import { IUnleashConfig } from '../types/option';
|
||||
import { createTestConfig } from '../../test/config/test-config';
|
||||
import SessionService from './session-service';
|
||||
import FakeSessionStore from '../../test/fixtures/fake-session-store';
|
||||
import User from '../types/user';
|
||||
|
||||
const config: IUnleashConfig = createTestConfig();
|
||||
|
||||
const systemUser = new User({ id: -1, username: 'system' });
|
||||
|
||||
test('Should create new user', async t => {
|
||||
const userStore = new UserStoreMock();
|
||||
const eventStore = new EventStoreMock();
|
||||
const accessService = new AccessServiceMock();
|
||||
const resetTokenStore = new ResetTokenStoreMock();
|
||||
const resetTokenService = new ResetTokenService(
|
||||
@ -25,16 +30,19 @@ test('Should create new user', async t => {
|
||||
const sessionService = new SessionService({ sessionStore }, config);
|
||||
const emailService = new EmailService(config.email, config.getLogger);
|
||||
|
||||
const service = new UserService({ userStore }, config, {
|
||||
const service = new UserService({ userStore, eventStore }, config, {
|
||||
accessService,
|
||||
resetTokenService,
|
||||
emailService,
|
||||
sessionService,
|
||||
});
|
||||
const user = await service.createUser({
|
||||
username: 'test',
|
||||
rootRole: 1,
|
||||
});
|
||||
const user = await service.createUser(
|
||||
{
|
||||
username: 'test',
|
||||
rootRole: 1,
|
||||
},
|
||||
systemUser,
|
||||
);
|
||||
const storedUser = await userStore.get(user);
|
||||
const allUsers = await userStore.getAll();
|
||||
|
||||
@ -46,6 +54,7 @@ test('Should create new user', async t => {
|
||||
|
||||
test('Should create default user', async t => {
|
||||
const userStore = new UserStoreMock();
|
||||
const eventStore = new EventStoreMock();
|
||||
const accessService = new AccessServiceMock();
|
||||
const resetTokenStore = new ResetTokenStoreMock();
|
||||
const resetTokenService = new ResetTokenService(
|
||||
@ -56,7 +65,7 @@ test('Should create default user', async t => {
|
||||
const sessionStore = new FakeSessionStore();
|
||||
const sessionService = new SessionService({ sessionStore }, config);
|
||||
|
||||
const service = new UserService({ userStore }, config, {
|
||||
const service = new UserService({ userStore, eventStore }, config, {
|
||||
accessService,
|
||||
resetTokenService,
|
||||
emailService,
|
||||
@ -71,6 +80,7 @@ test('Should create default user', async t => {
|
||||
|
||||
test('Should be a valid password', async t => {
|
||||
const userStore = new UserStoreMock();
|
||||
const eventStore = new EventStoreMock();
|
||||
const accessService = new AccessServiceMock();
|
||||
const resetTokenStore = new ResetTokenStoreMock();
|
||||
const resetTokenService = new ResetTokenService(
|
||||
@ -82,7 +92,7 @@ test('Should be a valid password', async t => {
|
||||
const sessionStore = new FakeSessionStore();
|
||||
const sessionService = new SessionService({ sessionStore }, config);
|
||||
|
||||
const service = new UserService({ userStore }, config, {
|
||||
const service = new UserService({ userStore, eventStore }, config, {
|
||||
accessService,
|
||||
resetTokenService,
|
||||
emailService,
|
||||
@ -96,6 +106,7 @@ test('Should be a valid password', async t => {
|
||||
|
||||
test('Password must be at least 10 chars', async t => {
|
||||
const userStore = new UserStoreMock();
|
||||
const eventStore = new EventStoreMock();
|
||||
const accessService = new AccessServiceMock();
|
||||
const resetTokenStore = new ResetTokenStoreMock();
|
||||
const resetTokenService = new ResetTokenService(
|
||||
@ -106,7 +117,7 @@ test('Password must be at least 10 chars', async t => {
|
||||
const sessionStore = new FakeSessionStore();
|
||||
const sessionService = new SessionService({ sessionStore }, config);
|
||||
|
||||
const service = new UserService({ userStore }, config, {
|
||||
const service = new UserService({ userStore, eventStore }, config, {
|
||||
accessService,
|
||||
resetTokenService,
|
||||
emailService,
|
||||
@ -121,6 +132,7 @@ test('Password must be at least 10 chars', async t => {
|
||||
|
||||
test('The password must contain at least one uppercase letter.', async t => {
|
||||
const userStore = new UserStoreMock();
|
||||
const eventStore = new EventStoreMock();
|
||||
const accessService = new AccessServiceMock();
|
||||
const resetTokenStore = new ResetTokenStoreMock();
|
||||
const resetTokenService = new ResetTokenService(
|
||||
@ -131,7 +143,7 @@ test('The password must contain at least one uppercase letter.', async t => {
|
||||
const sessionStore = new FakeSessionStore();
|
||||
const sessionService = new SessionService({ sessionStore }, config);
|
||||
|
||||
const service = new UserService({ userStore }, config, {
|
||||
const service = new UserService({ userStore, eventStore }, config, {
|
||||
accessService,
|
||||
resetTokenService,
|
||||
emailService,
|
||||
@ -146,6 +158,7 @@ test('The password must contain at least one uppercase letter.', async t => {
|
||||
|
||||
test('The password must contain at least one number', async t => {
|
||||
const userStore = new UserStoreMock();
|
||||
const eventStore = new EventStoreMock();
|
||||
const accessService = new AccessServiceMock();
|
||||
const resetTokenStore = new ResetTokenStoreMock();
|
||||
const resetTokenService = new ResetTokenService(
|
||||
@ -157,7 +170,7 @@ test('The password must contain at least one number', async t => {
|
||||
const sessionStore = new FakeSessionStore();
|
||||
const sessionService = new SessionService({ sessionStore }, config);
|
||||
|
||||
const service = new UserService({ userStore }, config, {
|
||||
const service = new UserService({ userStore, eventStore }, config, {
|
||||
accessService,
|
||||
resetTokenService,
|
||||
emailService,
|
||||
@ -172,6 +185,7 @@ test('The password must contain at least one number', async t => {
|
||||
|
||||
test('The password must contain at least one special character', async t => {
|
||||
const userStore = new UserStoreMock();
|
||||
const eventStore = new EventStoreMock();
|
||||
const accessService = new AccessServiceMock();
|
||||
const resetTokenStore = new ResetTokenStoreMock();
|
||||
const resetTokenService = new ResetTokenService(
|
||||
@ -182,7 +196,7 @@ test('The password must contain at least one special character', async t => {
|
||||
const sessionStore = new FakeSessionStore();
|
||||
const sessionService = new SessionService({ sessionStore }, config);
|
||||
|
||||
const service = new UserService({ userStore }, config, {
|
||||
const service = new UserService({ userStore, eventStore }, config, {
|
||||
accessService,
|
||||
resetTokenService,
|
||||
emailService,
|
||||
@ -197,6 +211,7 @@ test('The password must contain at least one special character', async t => {
|
||||
|
||||
test('Should be a valid password with special chars', async t => {
|
||||
const userStore = new UserStoreMock();
|
||||
const eventStore = new EventStoreMock();
|
||||
const accessService = new AccessServiceMock();
|
||||
const resetTokenStore = new ResetTokenStoreMock();
|
||||
const resetTokenService = new ResetTokenService(
|
||||
@ -207,7 +222,7 @@ test('Should be a valid password with special chars', async t => {
|
||||
const sessionStore = new FakeSessionStore();
|
||||
const sessionService = new SessionService({ sessionStore }, config);
|
||||
|
||||
const service = new UserService({ userStore }, config, {
|
||||
const service = new UserService({ userStore, eventStore }, config, {
|
||||
accessService,
|
||||
resetTokenService,
|
||||
emailService,
|
||||
|
@ -19,6 +19,10 @@ import SessionService from './session-service';
|
||||
import { IUnleashServices } from '../types/services';
|
||||
import { IUnleashStores } from '../types/stores';
|
||||
import PasswordUndefinedError from '../error/password-undefined';
|
||||
import EventStore from '../db/event-store';
|
||||
import { USER_UPDATED, USER_CREATED, USER_DELETED } from '../event-type';
|
||||
|
||||
const systemUser = new User({ id: -1, username: 'system' });
|
||||
|
||||
export interface ICreateUser {
|
||||
name?: string;
|
||||
@ -56,6 +60,8 @@ class UserService {
|
||||
|
||||
private store: UserStore;
|
||||
|
||||
private eventStore: EventStore;
|
||||
|
||||
private accessService: AccessService;
|
||||
|
||||
private resetTokenService: ResetTokenService;
|
||||
@ -65,7 +71,7 @@ class UserService {
|
||||
private emailService: EmailService;
|
||||
|
||||
constructor(
|
||||
stores: Pick<IUnleashStores, 'userStore'>,
|
||||
stores: Pick<IUnleashStores, 'userStore' | 'eventStore'>,
|
||||
{
|
||||
getLogger,
|
||||
authentication,
|
||||
@ -85,6 +91,7 @@ class UserService {
|
||||
) {
|
||||
this.logger = getLogger('service/user-service.js');
|
||||
this.store = stores.userStore;
|
||||
this.eventStore = stores.eventStore;
|
||||
this.accessService = accessService;
|
||||
this.resetTokenService = resetTokenService;
|
||||
this.emailService = emailService;
|
||||
@ -164,13 +171,10 @@ class UserService {
|
||||
return this.store.get({ email });
|
||||
}
|
||||
|
||||
async createUser({
|
||||
username,
|
||||
email,
|
||||
name,
|
||||
password,
|
||||
rootRole,
|
||||
}: ICreateUser): Promise<User> {
|
||||
async createUser(
|
||||
{ username, email, name, password, rootRole }: ICreateUser,
|
||||
updatedBy?: User,
|
||||
): Promise<User> {
|
||||
assert.ok(username || email, 'You must specify username or email');
|
||||
|
||||
if (email) {
|
||||
@ -195,15 +199,32 @@ class UserService {
|
||||
await this.store.setPasswordHash(user.id, passwordHash);
|
||||
}
|
||||
|
||||
await this.updateChangeLog(USER_CREATED, user, updatedBy);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async updateUser({
|
||||
id,
|
||||
name,
|
||||
email,
|
||||
rootRole,
|
||||
}: IUpdateUser): Promise<User> {
|
||||
private async updateChangeLog(
|
||||
type: string,
|
||||
user: User,
|
||||
updatedBy: User = systemUser,
|
||||
): Promise<void> {
|
||||
await this.eventStore.store({
|
||||
type,
|
||||
createdBy: updatedBy.username || updatedBy.email,
|
||||
data: {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async updateUser(
|
||||
{ id, name, email, rootRole }: IUpdateUser,
|
||||
updatedBy?: User,
|
||||
): Promise<User> {
|
||||
if (email) {
|
||||
Joi.assert(email, Joi.string().email(), 'Email');
|
||||
}
|
||||
@ -212,7 +233,11 @@ class UserService {
|
||||
await this.accessService.setUserRootRole(id, rootRole);
|
||||
}
|
||||
|
||||
return this.store.update(id, { name, email });
|
||||
const user = await this.store.update(id, { name, email });
|
||||
|
||||
await this.updateChangeLog(USER_UPDATED, user, updatedBy);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async loginUser(usernameOrEmail: string, password: string): Promise<User> {
|
||||
@ -269,7 +294,8 @@ class UserService {
|
||||
return this.store.setPasswordHash(userId, passwordHash);
|
||||
}
|
||||
|
||||
async deleteUser(userId: number): Promise<void> {
|
||||
async deleteUser(userId: number, updatedBy?: User): Promise<void> {
|
||||
const user = await this.store.get({ id: userId });
|
||||
const roles = await this.accessService.getRolesForUser(userId);
|
||||
await Promise.all(
|
||||
roles.map(role =>
|
||||
@ -278,6 +304,8 @@ class UserService {
|
||||
);
|
||||
|
||||
await this.store.delete(userId);
|
||||
|
||||
await this.updateChangeLog(USER_DELETED, user, updatedBy);
|
||||
}
|
||||
|
||||
async getUserForToken(token: string): Promise<ITokenUser> {
|
||||
@ -322,7 +350,7 @@ class UserService {
|
||||
|
||||
async createResetPasswordEmail(
|
||||
receiverEmail: string,
|
||||
requester: string,
|
||||
user: User = systemUser,
|
||||
): Promise<URL> {
|
||||
const receiver = await this.getByEmail(receiverEmail);
|
||||
if (!receiver) {
|
||||
@ -330,7 +358,7 @@ class UserService {
|
||||
}
|
||||
const resetLink = await this.resetTokenService.createResetPasswordUrl(
|
||||
receiver.id,
|
||||
requester,
|
||||
user.username || user.email,
|
||||
);
|
||||
|
||||
await this.emailService.sendResetMail(
|
||||
|
@ -6,11 +6,14 @@ import User from '../../../../lib/types/user';
|
||||
import UserStore from '../../../../lib/db/user-store';
|
||||
import { AccessStore, IRole } from '../../../../lib/db/access-store';
|
||||
import { RoleName } from '../../../../lib/services/access-service';
|
||||
import EventStore from '../../../../lib/db/event-store';
|
||||
import eventType from '../../../../lib/event-type';
|
||||
|
||||
let stores;
|
||||
let db;
|
||||
|
||||
let userStore: UserStore;
|
||||
let eventStore: EventStore;
|
||||
let accessStore: AccessStore;
|
||||
let editorRole: IRole;
|
||||
let adminRole: IRole;
|
||||
@ -20,6 +23,7 @@ test.before(async () => {
|
||||
stores = db.stores;
|
||||
userStore = stores.userStore;
|
||||
accessStore = stores.accessStore;
|
||||
eventStore = stores.eventStore;
|
||||
const roles = await accessStore.getRootRoles();
|
||||
editorRole = roles.find(r => r.name === RoleName.EDITOR);
|
||||
adminRole = roles.find(r => r.name === RoleName.ADMIN);
|
||||
@ -242,3 +246,66 @@ test.serial(
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial('generates USER_CREATED event', async t => {
|
||||
t.plan(5);
|
||||
const email = 'some@getunelash.ai';
|
||||
const name = 'Some Name';
|
||||
const request = await setupApp(stores);
|
||||
const { body } = await request
|
||||
.post('/api/admin/user-admin')
|
||||
.send({
|
||||
email,
|
||||
name,
|
||||
password: 'some-strange-pass-123-GH',
|
||||
rootRole: adminRole.id,
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(201);
|
||||
|
||||
const events = await eventStore.getEvents();
|
||||
|
||||
t.is(events[0].type, eventType.USER_CREATED);
|
||||
t.is(events[0].data.email, email);
|
||||
t.is(events[0].data.name, name);
|
||||
t.is(events[0].data.id, body.id);
|
||||
t.falsy(events[0].data.password);
|
||||
});
|
||||
|
||||
test.serial('generates USER_DELETED event', async t => {
|
||||
t.plan(3);
|
||||
const request = await setupApp(stores);
|
||||
|
||||
const user = await userStore.insert({ email: 'some@mail.com' });
|
||||
await request.delete(`/api/admin/user-admin/${user.id}`);
|
||||
|
||||
const events = await eventStore.getEvents();
|
||||
t.is(events[0].type, eventType.USER_DELETED);
|
||||
t.is(events[0].data.id, user.id);
|
||||
t.is(events[0].data.email, user.email);
|
||||
});
|
||||
|
||||
test.serial('generates USER_UPDATED event', async t => {
|
||||
t.plan(3);
|
||||
const request = await setupApp(stores);
|
||||
const { body } = await request
|
||||
.post('/api/admin/user-admin')
|
||||
.send({
|
||||
email: 'some@getunelash.ai',
|
||||
name: 'Some Name',
|
||||
rootRole: editorRole.id,
|
||||
})
|
||||
.set('Content-Type', 'application/json');
|
||||
|
||||
await request
|
||||
.put(`/api/admin/user-admin/${body.id}`)
|
||||
.send({
|
||||
name: 'New name',
|
||||
})
|
||||
.set('Content-Type', 'application/json');
|
||||
|
||||
const events = await eventStore.getEvents();
|
||||
t.is(events[0].type, eventType.USER_UPDATED);
|
||||
t.is(events[0].data.id, body.id);
|
||||
t.is(events[0].data.name, 'New name');
|
||||
});
|
||||
|
@ -1,21 +1,22 @@
|
||||
'use strict';
|
||||
import EventStore, { IEvent } from '../../lib/db/event-store';
|
||||
import noLoggerProvider from './no-logger';
|
||||
|
||||
const { EventEmitter } = require('events');
|
||||
class FakeEventStore extends EventStore {
|
||||
events: IEvent[];
|
||||
|
||||
class EventStore extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
super(undefined, noLoggerProvider);
|
||||
this.setMaxListeners(0);
|
||||
this.events = [];
|
||||
}
|
||||
|
||||
store(event) {
|
||||
store(event: IEvent): Promise<void> {
|
||||
this.events.push(event);
|
||||
this.emit(event.type, event);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
batchStore(events) {
|
||||
batchStore(events: IEvent[]): Promise<void> {
|
||||
events.forEach(event => {
|
||||
this.events.push(event);
|
||||
this.emit(event.type, event);
|
||||
@ -23,9 +24,10 @@ class EventStore extends EventEmitter {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
getEvents() {
|
||||
getEvents(): Promise<IEvent[]> {
|
||||
return Promise.resolve(this.events);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EventStore;
|
||||
module.exports = FakeEventStore;
|
||||
export default FakeEventStore;
|
Loading…
Reference in New Issue
Block a user