1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-04 13:48:56 +02:00

feat: add user create/update/delete events (#807)

This commit is contained in:
Ivar Conradi Østhus 2021-04-27 20:47:11 +02:00 committed by GitHub
parent d0b17af770
commit 886e0bb008
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 205 additions and 68 deletions

View File

@ -32,6 +32,9 @@ const {
APPLICATION_CREATED, APPLICATION_CREATED,
FEATURE_STALE_ON, FEATURE_STALE_ON,
FEATURE_STALE_OFF, FEATURE_STALE_OFF,
USER_CREATED,
USER_UPDATED,
USER_DELETED,
} = require('./event-type'); } = require('./event-type');
const strategyTypes = [ const strategyTypes = [
@ -63,6 +66,8 @@ const contextTypes = [
CONTEXT_FIELD_UPDATED, CONTEXT_FIELD_UPDATED,
]; ];
const userTypes = [USER_CREATED, USER_UPDATED, USER_DELETED];
const tagTypes = [TAG_CREATED, TAG_DELETED]; const tagTypes = [TAG_CREATED, TAG_DELETED];
const tagTypeTypes = [TAG_TYPE_CREATED, TAG_TYPE_DELETED]; const tagTypeTypes = [TAG_TYPE_CREATED, TAG_TYPE_DELETED];
@ -88,23 +93,34 @@ function baseTypeFor(event) {
if (tagTypeTypes.indexOf(event.type) !== -1) { if (tagTypeTypes.indexOf(event.type) !== -1) {
return 'tag-type'; return 'tag-type';
} }
if (userTypes.indexOf(event.type) !== -1) {
return 'user';
}
if (event.type === APPLICATION_CREATED) { if (event.type === APPLICATION_CREATED) {
return 'application'; return 'application';
} }
return event.type; return event.type;
} }
const uniqueFieldForType = baseType => {
if (baseType === 'user') {
return 'id';
}
return 'name';
};
function groupByBaseTypeAndName(events) { function groupByBaseTypeAndName(events) {
const groups = {}; const groups = {};
events.forEach(event => { events.forEach(event => {
const baseType = baseTypeFor(event); const baseType = baseTypeFor(event);
const uniqueField = uniqueFieldForType(baseType);
groups[baseType] = groups[baseType] || {}; groups[baseType] = groups[baseType] || {};
groups[baseType][event.data.name] = groups[baseType][event.data[uniqueField]] =
groups[baseType][event.data.name] || []; groups[baseType][event.data[uniqueField]] || [];
groups[baseType][event.data.name].push(event); groups[baseType][event.data[uniqueField]].push(event);
}); });
return groups; return groups;

View File

@ -42,4 +42,7 @@ module.exports = {
ADDON_CONFIG_UPDATED: 'addon-config-updated', ADDON_CONFIG_UPDATED: 'addon-config-updated',
ADDON_CONFIG_DELETED: 'addon-config-deleted', ADDON_CONFIG_DELETED: 'addon-config-deleted',
DB_POOL_UPDATE: 'db-pool-update', DB_POOL_UPDATE: 'db-pool-update',
USER_CREATED: 'user-created',
USER_UPDATED: 'user-updated',
USER_DELETED: 'user-deleted',
}; };

View File

@ -1,4 +1,4 @@
const REQUEST_TIME = 'request_time'; 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 };

View File

@ -6,13 +6,11 @@ import { AccessService } from '../../services/access-service';
import { Logger } from '../../logger'; import { Logger } from '../../logger';
import { handleErrors } from './util'; import { handleErrors } from './util';
import { IUnleashConfig } from '../../types/option'; 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 ResetTokenService from '../../services/reset-token-service';
import { IUnleashServices } from '../../types/services'; import { IUnleashServices } from '../../types/services';
import SessionService from '../../services/session-service'; import SessionService from '../../services/session-service';
const getCreatorUsernameOrPassword = req => req.user.username || req.user.email;
export default class UserAdminController extends Controller { export default class UserAdminController extends Controller {
private userService: UserService; private userService: UserService;
@ -46,8 +44,8 @@ export default class UserAdminController extends Controller {
super(config); super(config);
this.userService = userService; this.userService = userService;
this.accessService = accessService; this.accessService = accessService;
this.logger = config.getLogger('routes/user-controller.ts');
this.emailService = emailService; this.emailService = emailService;
this.logger = config.getLogger('routes/user-controller.ts');
this.resetTokenService = resetTokenService; this.resetTokenService = resetTokenService;
this.sessionService = sessionService; this.sessionService = sessionService;
@ -64,12 +62,12 @@ export default class UserAdminController extends Controller {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async resetPassword(req, res): Promise<void> { async resetPassword(req, res): Promise<void> {
const { user } = req;
try { try {
const requester = getCreatorUsernameOrPassword(req);
const receiver = req.body.id; const receiver = req.body.id;
const resetPasswordUrl = await this.userService.createResetPasswordEmail( const resetPasswordUrl = await this.userService.createResetPasswordEmail(
receiver, receiver,
requester, user,
); );
res.json({ resetPasswordUrl }); res.json({ resetPasswordUrl });
} catch (e) { } catch (e) {
@ -124,12 +122,15 @@ export default class UserAdminController extends Controller {
const { user } = req; const { user } = req;
try { try {
const createdUser = await this.userService.createUser({ const createdUser = await this.userService.createUser(
username, {
email, username,
name, email,
rootRole: Number(rootRole), name,
}); rootRole: Number(rootRole),
},
user,
);
const inviteLink = await this.resetTokenService.createNewUserUrl( const inviteLink = await this.resetTokenService.createNewUserUrl(
createdUser.id, createdUser.id,
@ -163,17 +164,22 @@ export default class UserAdminController extends Controller {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async updateUser(req, res): Promise<void> { async updateUser(req, res): Promise<void> {
const { id } = req.params; const { user, params, body } = req;
const { name, email, rootRole } = req.body;
const { id } = params;
const { name, email, rootRole } = body;
try { try {
const user = await this.userService.updateUser({ const updateUser = await this.userService.updateUser(
id: Number(id), {
name, id: Number(id),
email, name,
rootRole: Number(rootRole), email,
}); rootRole: Number(rootRole),
res.status(200).send({ ...user, rootRole }); },
user,
);
res.status(200).send({ ...updateUser, rootRole });
} catch (e) { } catch (e) {
this.logger.warn(e.message); this.logger.warn(e.message);
res.status(400).send([{ msg: 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 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async deleteUser(req, res): Promise<void> { async deleteUser(req, res): Promise<void> {
const { id } = req.params; const { user, params } = req;
const { id } = params;
try { try {
await this.userService.deleteUser(+id); await this.userService.deleteUser(+id, user);
res.status(200).send(); res.status(200).send();
} catch (error) { } catch (error) {
this.logger.warn(error); this.logger.warn(error);

View File

@ -21,7 +21,6 @@ interface SessionRequest<PARAMS, QUERY, BODY, K>
user?; user?;
} }
const UNLEASH = 'Unleash';
class ResetPasswordController extends Controller { class ResetPasswordController extends Controller {
private userService: UserService; private userService: UserService;
@ -43,7 +42,7 @@ class ResetPasswordController extends Controller {
const { email } = req.body; const { email } = req.body;
try { try {
await this.userService.createResetPasswordEmail(email, UNLEASH); await this.userService.createResetPasswordEmail(email);
res.status(200).end(); res.status(200).end();
} catch (e) { } catch (e) {
handleErrors(res, this.logger, e); handleErrors(res, this.logger, e);

View File

@ -1,6 +1,7 @@
import test from 'ava'; import test from 'ava';
import UserService from './user-service'; import UserService from './user-service';
import UserStoreMock from '../../test/fixtures/fake-user-store'; 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 AccessServiceMock from '../../test/fixtures/access-service-mock';
import { ResetTokenStoreMock } from '../../test/fixtures/fake-reset-token-store'; import { ResetTokenStoreMock } from '../../test/fixtures/fake-reset-token-store';
import ResetTokenService from './reset-token-service'; import ResetTokenService from './reset-token-service';
@ -10,11 +11,15 @@ import { IUnleashConfig } from '../types/option';
import { createTestConfig } from '../../test/config/test-config'; import { createTestConfig } from '../../test/config/test-config';
import SessionService from './session-service'; import SessionService from './session-service';
import FakeSessionStore from '../../test/fixtures/fake-session-store'; import FakeSessionStore from '../../test/fixtures/fake-session-store';
import User from '../types/user';
const config: IUnleashConfig = createTestConfig(); const config: IUnleashConfig = createTestConfig();
const systemUser = new User({ id: -1, username: 'system' });
test('Should create new user', async t => { test('Should create new user', async t => {
const userStore = new UserStoreMock(); const userStore = new UserStoreMock();
const eventStore = new EventStoreMock();
const accessService = new AccessServiceMock(); const accessService = new AccessServiceMock();
const resetTokenStore = new ResetTokenStoreMock(); const resetTokenStore = new ResetTokenStoreMock();
const resetTokenService = new ResetTokenService( const resetTokenService = new ResetTokenService(
@ -25,16 +30,19 @@ test('Should create new user', async t => {
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const emailService = new EmailService(config.email, config.getLogger); const emailService = new EmailService(config.email, config.getLogger);
const service = new UserService({ userStore }, config, { const service = new UserService({ userStore, eventStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
sessionService, sessionService,
}); });
const user = await service.createUser({ const user = await service.createUser(
username: 'test', {
rootRole: 1, username: 'test',
}); rootRole: 1,
},
systemUser,
);
const storedUser = await userStore.get(user); const storedUser = await userStore.get(user);
const allUsers = await userStore.getAll(); const allUsers = await userStore.getAll();
@ -46,6 +54,7 @@ test('Should create new user', async t => {
test('Should create default user', async t => { test('Should create default user', async t => {
const userStore = new UserStoreMock(); const userStore = new UserStoreMock();
const eventStore = new EventStoreMock();
const accessService = new AccessServiceMock(); const accessService = new AccessServiceMock();
const resetTokenStore = new ResetTokenStoreMock(); const resetTokenStore = new ResetTokenStoreMock();
const resetTokenService = new ResetTokenService( const resetTokenService = new ResetTokenService(
@ -56,7 +65,7 @@ test('Should create default user', async t => {
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const service = new UserService({ userStore }, config, { const service = new UserService({ userStore, eventStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
@ -71,6 +80,7 @@ test('Should create default user', async t => {
test('Should be a valid password', async t => { test('Should be a valid password', async t => {
const userStore = new UserStoreMock(); const userStore = new UserStoreMock();
const eventStore = new EventStoreMock();
const accessService = new AccessServiceMock(); const accessService = new AccessServiceMock();
const resetTokenStore = new ResetTokenStoreMock(); const resetTokenStore = new ResetTokenStoreMock();
const resetTokenService = new ResetTokenService( const resetTokenService = new ResetTokenService(
@ -82,7 +92,7 @@ test('Should be a valid password', async t => {
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const service = new UserService({ userStore }, config, { const service = new UserService({ userStore, eventStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
@ -96,6 +106,7 @@ test('Should be a valid password', async t => {
test('Password must be at least 10 chars', async t => { test('Password must be at least 10 chars', async t => {
const userStore = new UserStoreMock(); const userStore = new UserStoreMock();
const eventStore = new EventStoreMock();
const accessService = new AccessServiceMock(); const accessService = new AccessServiceMock();
const resetTokenStore = new ResetTokenStoreMock(); const resetTokenStore = new ResetTokenStoreMock();
const resetTokenService = new ResetTokenService( const resetTokenService = new ResetTokenService(
@ -106,7 +117,7 @@ test('Password must be at least 10 chars', async t => {
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const service = new UserService({ userStore }, config, { const service = new UserService({ userStore, eventStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, 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 => { test('The password must contain at least one uppercase letter.', async t => {
const userStore = new UserStoreMock(); const userStore = new UserStoreMock();
const eventStore = new EventStoreMock();
const accessService = new AccessServiceMock(); const accessService = new AccessServiceMock();
const resetTokenStore = new ResetTokenStoreMock(); const resetTokenStore = new ResetTokenStoreMock();
const resetTokenService = new ResetTokenService( 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 sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const service = new UserService({ userStore }, config, { const service = new UserService({ userStore, eventStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, 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 => { test('The password must contain at least one number', async t => {
const userStore = new UserStoreMock(); const userStore = new UserStoreMock();
const eventStore = new EventStoreMock();
const accessService = new AccessServiceMock(); const accessService = new AccessServiceMock();
const resetTokenStore = new ResetTokenStoreMock(); const resetTokenStore = new ResetTokenStoreMock();
const resetTokenService = new ResetTokenService( const resetTokenService = new ResetTokenService(
@ -157,7 +170,7 @@ test('The password must contain at least one number', async t => {
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const service = new UserService({ userStore }, config, { const service = new UserService({ userStore, eventStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, 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 => { test('The password must contain at least one special character', async t => {
const userStore = new UserStoreMock(); const userStore = new UserStoreMock();
const eventStore = new EventStoreMock();
const accessService = new AccessServiceMock(); const accessService = new AccessServiceMock();
const resetTokenStore = new ResetTokenStoreMock(); const resetTokenStore = new ResetTokenStoreMock();
const resetTokenService = new ResetTokenService( 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 sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const service = new UserService({ userStore }, config, { const service = new UserService({ userStore, eventStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, 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 => { test('Should be a valid password with special chars', async t => {
const userStore = new UserStoreMock(); const userStore = new UserStoreMock();
const eventStore = new EventStoreMock();
const accessService = new AccessServiceMock(); const accessService = new AccessServiceMock();
const resetTokenStore = new ResetTokenStoreMock(); const resetTokenStore = new ResetTokenStoreMock();
const resetTokenService = new ResetTokenService( const resetTokenService = new ResetTokenService(
@ -207,7 +222,7 @@ test('Should be a valid password with special chars', async t => {
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const service = new UserService({ userStore }, config, { const service = new UserService({ userStore, eventStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,

View File

@ -19,6 +19,10 @@ import SessionService from './session-service';
import { IUnleashServices } from '../types/services'; import { IUnleashServices } from '../types/services';
import { IUnleashStores } from '../types/stores'; import { IUnleashStores } from '../types/stores';
import PasswordUndefinedError from '../error/password-undefined'; 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 { export interface ICreateUser {
name?: string; name?: string;
@ -56,6 +60,8 @@ class UserService {
private store: UserStore; private store: UserStore;
private eventStore: EventStore;
private accessService: AccessService; private accessService: AccessService;
private resetTokenService: ResetTokenService; private resetTokenService: ResetTokenService;
@ -65,7 +71,7 @@ class UserService {
private emailService: EmailService; private emailService: EmailService;
constructor( constructor(
stores: Pick<IUnleashStores, 'userStore'>, stores: Pick<IUnleashStores, 'userStore' | 'eventStore'>,
{ {
getLogger, getLogger,
authentication, authentication,
@ -85,6 +91,7 @@ class UserService {
) { ) {
this.logger = getLogger('service/user-service.js'); this.logger = getLogger('service/user-service.js');
this.store = stores.userStore; this.store = stores.userStore;
this.eventStore = stores.eventStore;
this.accessService = accessService; this.accessService = accessService;
this.resetTokenService = resetTokenService; this.resetTokenService = resetTokenService;
this.emailService = emailService; this.emailService = emailService;
@ -164,13 +171,10 @@ class UserService {
return this.store.get({ email }); return this.store.get({ email });
} }
async createUser({ async createUser(
username, { username, email, name, password, rootRole }: ICreateUser,
email, updatedBy?: User,
name, ): Promise<User> {
password,
rootRole,
}: ICreateUser): Promise<User> {
assert.ok(username || email, 'You must specify username or email'); assert.ok(username || email, 'You must specify username or email');
if (email) { if (email) {
@ -195,15 +199,32 @@ class UserService {
await this.store.setPasswordHash(user.id, passwordHash); await this.store.setPasswordHash(user.id, passwordHash);
} }
await this.updateChangeLog(USER_CREATED, user, updatedBy);
return user; return user;
} }
async updateUser({ private async updateChangeLog(
id, type: string,
name, user: User,
email, updatedBy: User = systemUser,
rootRole, ): Promise<void> {
}: IUpdateUser): Promise<User> { 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) { if (email) {
Joi.assert(email, Joi.string().email(), 'Email'); Joi.assert(email, Joi.string().email(), 'Email');
} }
@ -212,7 +233,11 @@ class UserService {
await this.accessService.setUserRootRole(id, rootRole); 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> { async loginUser(usernameOrEmail: string, password: string): Promise<User> {
@ -269,7 +294,8 @@ class UserService {
return this.store.setPasswordHash(userId, passwordHash); 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); const roles = await this.accessService.getRolesForUser(userId);
await Promise.all( await Promise.all(
roles.map(role => roles.map(role =>
@ -278,6 +304,8 @@ class UserService {
); );
await this.store.delete(userId); await this.store.delete(userId);
await this.updateChangeLog(USER_DELETED, user, updatedBy);
} }
async getUserForToken(token: string): Promise<ITokenUser> { async getUserForToken(token: string): Promise<ITokenUser> {
@ -322,7 +350,7 @@ class UserService {
async createResetPasswordEmail( async createResetPasswordEmail(
receiverEmail: string, receiverEmail: string,
requester: string, user: User = systemUser,
): Promise<URL> { ): Promise<URL> {
const receiver = await this.getByEmail(receiverEmail); const receiver = await this.getByEmail(receiverEmail);
if (!receiver) { if (!receiver) {
@ -330,7 +358,7 @@ class UserService {
} }
const resetLink = await this.resetTokenService.createResetPasswordUrl( const resetLink = await this.resetTokenService.createResetPasswordUrl(
receiver.id, receiver.id,
requester, user.username || user.email,
); );
await this.emailService.sendResetMail( await this.emailService.sendResetMail(

View File

@ -6,11 +6,14 @@ import User from '../../../../lib/types/user';
import UserStore from '../../../../lib/db/user-store'; import UserStore from '../../../../lib/db/user-store';
import { AccessStore, IRole } from '../../../../lib/db/access-store'; import { AccessStore, IRole } from '../../../../lib/db/access-store';
import { RoleName } from '../../../../lib/services/access-service'; import { RoleName } from '../../../../lib/services/access-service';
import EventStore from '../../../../lib/db/event-store';
import eventType from '../../../../lib/event-type';
let stores; let stores;
let db; let db;
let userStore: UserStore; let userStore: UserStore;
let eventStore: EventStore;
let accessStore: AccessStore; let accessStore: AccessStore;
let editorRole: IRole; let editorRole: IRole;
let adminRole: IRole; let adminRole: IRole;
@ -20,6 +23,7 @@ test.before(async () => {
stores = db.stores; stores = db.stores;
userStore = stores.userStore; userStore = stores.userStore;
accessStore = stores.accessStore; accessStore = stores.accessStore;
eventStore = stores.eventStore;
const roles = await accessStore.getRootRoles(); const roles = await accessStore.getRootRoles();
editorRole = roles.find(r => r.name === RoleName.EDITOR); editorRole = roles.find(r => r.name === RoleName.EDITOR);
adminRole = roles.find(r => r.name === RoleName.ADMIN); 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');
});

View File

@ -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() { constructor() {
super(); super(undefined, noLoggerProvider);
this.setMaxListeners(0); this.setMaxListeners(0);
this.events = []; this.events = [];
} }
store(event) { store(event: IEvent): Promise<void> {
this.events.push(event); this.events.push(event);
this.emit(event.type, event); this.emit(event.type, event);
return Promise.resolve(); return Promise.resolve();
} }
batchStore(events) { batchStore(events: IEvent[]): Promise<void> {
events.forEach(event => { events.forEach(event => {
this.events.push(event); this.events.push(event);
this.emit(event.type, event); this.emit(event.type, event);
@ -23,9 +24,10 @@ class EventStore extends EventEmitter {
return Promise.resolve(); return Promise.resolve();
} }
getEvents() { getEvents(): Promise<IEvent[]> {
return Promise.resolve(this.events); return Promise.resolve(this.events);
} }
} }
module.exports = EventStore; module.exports = FakeEventStore;
export default FakeEventStore;