1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-04 00:18:01 +01:00

task: Add events for setting-service (#1814)

* task: Add events for setting-service
This commit is contained in:
Christopher Kolstad 2022-07-21 15:40:31 +02:00 committed by GitHub
parent 012da8469f
commit 09fa031e0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 192 additions and 19 deletions

View File

@ -121,7 +121,7 @@ class EventStore extends EventEmitter implements IEventStore {
.select(EVENT_COLUMNS) .select(EVENT_COLUMNS)
.from(TABLE) .from(TABLE)
.limit(100) .limit(100)
.whereRaw("data ->> 'name' = ?", [name]) .where('type', name)
.andWhere( .andWhere(
'id', 'id',
'>=', '>=',

View File

@ -2,30 +2,67 @@ import { IUnleashConfig } from '../types/option';
import { IUnleashStores } from '../types/stores'; import { IUnleashStores } from '../types/stores';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { ISettingStore } from '../types/stores/settings-store'; import { ISettingStore } from '../types/stores/settings-store';
import { IEventStore } from '../types/stores/event-store';
import {
SettingCreatedEvent,
SettingDeletedEvent,
SettingUpdatedEvent,
} from '../types/events';
export default class SettingService { export default class SettingService {
private logger: Logger; private logger: Logger;
private settingStore: ISettingStore; private settingStore: ISettingStore;
private eventStore: IEventStore;
constructor( constructor(
{ settingStore }: Pick<IUnleashStores, 'settingStore'>, {
settingStore,
eventStore,
}: Pick<IUnleashStores, 'settingStore' | 'eventStore'>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>, { getLogger }: Pick<IUnleashConfig, 'getLogger'>,
) { ) {
this.logger = getLogger('services/setting-service.ts'); this.logger = getLogger('services/setting-service.ts');
this.settingStore = settingStore; this.settingStore = settingStore;
this.eventStore = eventStore;
} }
async get<T>(id: string): Promise<T> { async get<T>(id: string): Promise<T> {
return this.settingStore.get(id); return this.settingStore.get(id);
} }
async insert(id: string, value: object): Promise<void> { async insert(id: string, value: object, createdBy: string): Promise<void> {
return this.settingStore.insert(id, value); const exists = await this.settingStore.exists(id);
if (exists) {
await this.settingStore.updateRow(id, value);
await this.eventStore.store(
new SettingUpdatedEvent({
createdBy,
data: { id },
}),
);
} else {
await this.settingStore.insert(id, value);
await this.eventStore.store(
new SettingCreatedEvent({
createdBy,
data: { id },
}),
);
}
} }
async delete(id: string): Promise<void> { async delete(id: string, createdBy: string): Promise<void> {
return this.settingStore.delete(id); await this.settingStore.delete(id);
await this.eventStore.store(
new SettingDeletedEvent({
createdBy,
data: {
id,
},
}),
);
} }
} }

View File

@ -13,6 +13,7 @@ import User from '../types/user';
import FakeResetTokenStore from '../../test/fixtures/fake-reset-token-store'; import FakeResetTokenStore from '../../test/fixtures/fake-reset-token-store';
import SettingService from './setting-service'; import SettingService from './setting-service';
import FakeSettingStore from '../../test/fixtures/fake-setting-store'; import FakeSettingStore from '../../test/fixtures/fake-setting-store';
import FakeEventStore from '../../test/fixtures/fake-event-store';
const config: IUnleashConfig = createTestConfig(); const config: IUnleashConfig = createTestConfig();
@ -31,7 +32,10 @@ test('Should create new user', async () => {
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 settingService = new SettingService( const settingService = new SettingService(
{ settingStore: new FakeSettingStore() }, {
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config, config,
); );
@ -71,7 +75,10 @@ test('Should create default user', async () => {
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const settingService = new SettingService( const settingService = new SettingService(
{ settingStore: new FakeSettingStore() }, {
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config, config,
); );
@ -103,7 +110,10 @@ test('Should be a valid password', async () => {
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const settingService = new SettingService( const settingService = new SettingService(
{ settingStore: new FakeSettingStore() }, {
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config, config,
); );
@ -133,7 +143,10 @@ test('Password must be at least 10 chars', async () => {
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const settingService = new SettingService( const settingService = new SettingService(
{ settingStore: new FakeSettingStore() }, {
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config, config,
); );
@ -165,7 +178,10 @@ test('The password must contain at least one uppercase letter.', async () => {
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const settingService = new SettingService( const settingService = new SettingService(
{ settingStore: new FakeSettingStore() }, {
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config, config,
); );
@ -199,7 +215,10 @@ test('The password must contain at least one number', async () => {
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const settingService = new SettingService( const settingService = new SettingService(
{ settingStore: new FakeSettingStore() }, {
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config, config,
); );
@ -232,7 +251,10 @@ test('The password must contain at least one special character', async () => {
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const settingService = new SettingService( const settingService = new SettingService(
{ settingStore: new FakeSettingStore() }, {
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config, config,
); );
@ -265,7 +287,10 @@ test('Should be a valid password with special chars', async () => {
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const settingService = new SettingService( const settingService = new SettingService(
{ settingStore: new FakeSettingStore() }, {
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config, config,
); );

View File

@ -64,6 +64,9 @@ export const ENVIRONMENT_IMPORT = 'environment-import';
export const SEGMENT_CREATED = 'segment-created'; export const SEGMENT_CREATED = 'segment-created';
export const SEGMENT_UPDATED = 'segment-updated'; export const SEGMENT_UPDATED = 'segment-updated';
export const SEGMENT_DELETED = 'segment-deleted'; export const SEGMENT_DELETED = 'segment-deleted';
export const SETTING_CREATED = 'setting-created';
export const SETTING_UPDATED = 'setting-updated';
export const SETTING_DELETED = 'setting-deleted';
export const CLIENT_METRICS = 'client-metrics'; export const CLIENT_METRICS = 'client-metrics';
@ -437,3 +440,30 @@ export class ProjectUserUpdateRoleEvent extends BaseEvent {
this.preData = preData; this.preData = preData;
} }
} }
export class SettingCreatedEvent extends BaseEvent {
readonly data: any;
constructor(eventData: { createdBy: string; data: any }) {
super(SETTING_CREATED, eventData.createdBy);
this.data = eventData.data;
}
}
export class SettingDeletedEvent extends BaseEvent {
readonly data: any;
constructor(eventData: { createdBy: string; data: any }) {
super(SETTING_DELETED, eventData.createdBy);
this.data = eventData.data;
}
}
export class SettingUpdatedEvent extends BaseEvent {
readonly data: any;
constructor(eventData: { createdBy: string; data: any }) {
super(SETTING_UPDATED, eventData.createdBy);
this.data = eventData.data;
}
}

View File

@ -15,6 +15,7 @@ import SessionService from '../../../../lib/services/session-service';
import { RoleName } from '../../../../lib/types/model'; import { RoleName } from '../../../../lib/types/model';
import SettingService from '../../../../lib/services/setting-service'; import SettingService from '../../../../lib/services/setting-service';
import FakeSettingStore from '../../../fixtures/fake-setting-store'; import FakeSettingStore from '../../../fixtures/fake-setting-store';
import FakeEventStore from '../../../fixtures/fake-event-store';
let app; let app;
let stores; let stores;
@ -56,7 +57,10 @@ beforeAll(async () => {
); );
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const settingService = new SettingService( const settingService = new SettingService(
{ settingStore: new FakeSettingStore() }, {
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config, config,
); );
userService = new UserService(stores, config, { userService = new UserService(stores, config, {

View File

@ -11,6 +11,7 @@ import InvalidTokenError from '../../../lib/error/invalid-token-error';
import { IUser } from '../../../lib/types/user'; import { IUser } from '../../../lib/types/user';
import SettingService from '../../../lib/services/setting-service'; import SettingService from '../../../lib/services/setting-service';
import FakeSettingStore from '../../fixtures/fake-setting-store'; import FakeSettingStore from '../../fixtures/fake-setting-store';
import FakeEventStore from '../../fixtures/fake-event-store';
const config: IUnleashConfig = createTestConfig(); const config: IUnleashConfig = createTestConfig();
@ -31,7 +32,10 @@ beforeAll(async () => {
sessionService = new SessionService(stores, config); sessionService = new SessionService(stores, config);
const emailService = new EmailService(undefined, config.getLogger); const emailService = new EmailService(undefined, config.getLogger);
const settingService = new SettingService( const settingService = new SettingService(
{ settingStore: new FakeSettingStore() }, {
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config, config,
); );

View File

@ -2,6 +2,11 @@ import SettingService from '../../../lib/services/setting-service';
import { createTestConfig } from '../../config/test-config'; import { createTestConfig } from '../../config/test-config';
import dbInit from '../helpers/database-init'; import dbInit from '../helpers/database-init';
import { IUnleashStores } from '../../../lib/types/stores'; import { IUnleashStores } from '../../../lib/types/stores';
import {
SETTING_CREATED,
SETTING_DELETED,
SETTING_UPDATED,
} from '../../../lib/types/events';
let stores: IUnleashStores; let stores: IUnleashStores;
let db; let db;
@ -13,23 +18,51 @@ beforeAll(async () => {
stores = db.stores; stores = db.stores;
service = new SettingService(stores, config); service = new SettingService(stores, config);
}); });
beforeEach(async () => {
await stores.eventStore.deleteAll();
});
afterAll(async () => { afterAll(async () => {
await db.destroy(); await db.destroy();
}); });
test('Can create new setting', async () => { test('Can create new setting', async () => {
const someData = { some: 'blob' }; const someData = { some: 'blob' };
await service.insert('some-setting', someData); await service.insert('some-setting', someData, 'test-user');
const actual = await service.get('some-setting'); const actual = await service.get('some-setting');
expect(actual).toStrictEqual(someData); expect(actual).toStrictEqual(someData);
const { eventStore } = stores;
const createdEvents = await eventStore.getEventsFilterByType(
SETTING_CREATED,
);
expect(createdEvents).toHaveLength(1);
}); });
test('Can delete setting', async () => { test('Can delete setting', async () => {
const someData = { some: 'blob' }; const someData = { some: 'blob' };
await service.insert('some-setting', someData); await service.insert('some-setting', someData, 'test-user');
await service.delete('some-setting'); await service.delete('some-setting', 'test-user');
const actual = await service.get('some-setting'); const actual = await service.get('some-setting');
expect(actual).toBeUndefined(); expect(actual).toBeUndefined();
const { eventStore } = stores;
const createdEvents = await eventStore.getEventsFilterByType(
SETTING_DELETED,
);
expect(createdEvents).toHaveLength(1);
});
test('Can update setting', async () => {
const { eventStore } = stores;
const someData = { some: 'blob' };
await service.insert('updated-setting', someData, 'test-user');
await service.insert(
'updated-setting',
{ ...someData, test: 'fun' },
'test-user',
);
const updatedEvents = await eventStore.getEventsFilterByType(
SETTING_UPDATED,
);
expect(updatedEvents).toHaveLength(1);
}); });

View File

@ -1,6 +1,9 @@
import { import {
APPLICATION_CREATED, APPLICATION_CREATED,
FEATURE_CREATED, FEATURE_CREATED,
FEATURE_DELETED,
FeatureCreatedEvent,
FeatureDeletedEvent,
IEvent, IEvent,
} from '../../../lib/types/events'; } from '../../../lib/types/events';
@ -19,6 +22,9 @@ beforeAll(async () => {
eventStore = stores.eventStore; eventStore = stores.eventStore;
}); });
beforeEach(async () => {
await eventStore.deleteAll();
});
afterAll(async () => { afterAll(async () => {
if (db) { if (db) {
await db.destroy(); await db.destroy();
@ -140,6 +146,7 @@ test('Should delete stored event', async () => {
}, },
}; };
await eventStore.store(event); await eventStore.store(event);
await eventStore.store(event);
const events = await eventStore.getAll(); const events = await eventStore.getAll();
const lastEvent = events[0]; const lastEvent = events[0];
await eventStore.delete(lastEvent.id); await eventStore.delete(lastEvent.id);
@ -178,3 +185,36 @@ test('Should delete all stored events', async () => {
expect(events).toHaveLength(0); expect(events).toHaveLength(0);
}); });
test('Should get all events of type', async () => {
const data = { name: 'someName', project: 'test-project' };
await Promise.all(
[0, 1, 2, 3, 4, 5].map(async (id) => {
const event =
id % 2 == 0
? new FeatureCreatedEvent({
project: data.project,
featureName: data.name,
createdBy: 'test-user',
data,
tags: [],
})
: new FeatureDeletedEvent({
project: data.project,
preData: data,
featureName: data.name,
createdBy: 'test-user',
tags: [],
});
return eventStore.store(event);
}),
);
const featureCreatedEvents = await eventStore.getEventsFilterByType(
FEATURE_CREATED,
);
expect(featureCreatedEvents).toHaveLength(3);
const featureDeletedEvents = await eventStore.getEventsFilterByType(
FEATURE_DELETED,
);
expect(featureDeletedEvents).toHaveLength(3);
});