1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +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)
.from(TABLE)
.limit(100)
.whereRaw("data ->> 'name' = ?", [name])
.where('type', name)
.andWhere(
'id',
'>=',

View File

@ -2,30 +2,67 @@ import { IUnleashConfig } from '../types/option';
import { IUnleashStores } from '../types/stores';
import { Logger } from '../logger';
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 {
private logger: Logger;
private settingStore: ISettingStore;
private eventStore: IEventStore;
constructor(
{ settingStore }: Pick<IUnleashStores, 'settingStore'>,
{
settingStore,
eventStore,
}: Pick<IUnleashStores, 'settingStore' | 'eventStore'>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
) {
this.logger = getLogger('services/setting-service.ts');
this.settingStore = settingStore;
this.eventStore = eventStore;
}
async get<T>(id: string): Promise<T> {
return this.settingStore.get(id);
}
async insert(id: string, value: object): Promise<void> {
return this.settingStore.insert(id, value);
async insert(id: string, value: object, createdBy: string): Promise<void> {
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> {
return this.settingStore.delete(id);
async delete(id: string, createdBy: string): Promise<void> {
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 SettingService from './setting-service';
import FakeSettingStore from '../../test/fixtures/fake-setting-store';
import FakeEventStore from '../../test/fixtures/fake-event-store';
const config: IUnleashConfig = createTestConfig();
@ -31,7 +32,10 @@ test('Should create new user', async () => {
const sessionService = new SessionService({ sessionStore }, config);
const emailService = new EmailService(config.email, config.getLogger);
const settingService = new SettingService(
{ settingStore: new FakeSettingStore() },
{
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config,
);
@ -71,7 +75,10 @@ test('Should create default user', async () => {
const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config);
const settingService = new SettingService(
{ settingStore: new FakeSettingStore() },
{
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config,
);
@ -103,7 +110,10 @@ test('Should be a valid password', async () => {
const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config);
const settingService = new SettingService(
{ settingStore: new FakeSettingStore() },
{
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config,
);
@ -133,7 +143,10 @@ test('Password must be at least 10 chars', async () => {
const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config);
const settingService = new SettingService(
{ settingStore: new FakeSettingStore() },
{
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config,
);
@ -165,7 +178,10 @@ test('The password must contain at least one uppercase letter.', async () => {
const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config);
const settingService = new SettingService(
{ settingStore: new FakeSettingStore() },
{
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config,
);
@ -199,7 +215,10 @@ test('The password must contain at least one number', async () => {
const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config);
const settingService = new SettingService(
{ settingStore: new FakeSettingStore() },
{
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config,
);
@ -232,7 +251,10 @@ test('The password must contain at least one special character', async () => {
const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config);
const settingService = new SettingService(
{ settingStore: new FakeSettingStore() },
{
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config,
);
@ -265,7 +287,10 @@ test('Should be a valid password with special chars', async () => {
const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config);
const settingService = new SettingService(
{ settingStore: new FakeSettingStore() },
{
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config,
);

View File

@ -64,6 +64,9 @@ export const ENVIRONMENT_IMPORT = 'environment-import';
export const SEGMENT_CREATED = 'segment-created';
export const SEGMENT_UPDATED = 'segment-updated';
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';
@ -437,3 +440,30 @@ export class ProjectUserUpdateRoleEvent extends BaseEvent {
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 SettingService from '../../../../lib/services/setting-service';
import FakeSettingStore from '../../../fixtures/fake-setting-store';
import FakeEventStore from '../../../fixtures/fake-event-store';
let app;
let stores;
@ -56,7 +57,10 @@ beforeAll(async () => {
);
const sessionService = new SessionService({ sessionStore }, config);
const settingService = new SettingService(
{ settingStore: new FakeSettingStore() },
{
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
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 SettingService from '../../../lib/services/setting-service';
import FakeSettingStore from '../../fixtures/fake-setting-store';
import FakeEventStore from '../../fixtures/fake-event-store';
const config: IUnleashConfig = createTestConfig();
@ -31,7 +32,10 @@ beforeAll(async () => {
sessionService = new SessionService(stores, config);
const emailService = new EmailService(undefined, config.getLogger);
const settingService = new SettingService(
{ settingStore: new FakeSettingStore() },
{
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config,
);

View File

@ -2,6 +2,11 @@ import SettingService from '../../../lib/services/setting-service';
import { createTestConfig } from '../../config/test-config';
import dbInit from '../helpers/database-init';
import { IUnleashStores } from '../../../lib/types/stores';
import {
SETTING_CREATED,
SETTING_DELETED,
SETTING_UPDATED,
} from '../../../lib/types/events';
let stores: IUnleashStores;
let db;
@ -13,23 +18,51 @@ beforeAll(async () => {
stores = db.stores;
service = new SettingService(stores, config);
});
beforeEach(async () => {
await stores.eventStore.deleteAll();
});
afterAll(async () => {
await db.destroy();
});
test('Can create new setting', async () => {
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');
expect(actual).toStrictEqual(someData);
const { eventStore } = stores;
const createdEvents = await eventStore.getEventsFilterByType(
SETTING_CREATED,
);
expect(createdEvents).toHaveLength(1);
});
test('Can delete setting', async () => {
const someData = { some: 'blob' };
await service.insert('some-setting', someData);
await service.delete('some-setting');
await service.insert('some-setting', someData, 'test-user');
await service.delete('some-setting', 'test-user');
const actual = await service.get('some-setting');
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 {
APPLICATION_CREATED,
FEATURE_CREATED,
FEATURE_DELETED,
FeatureCreatedEvent,
FeatureDeletedEvent,
IEvent,
} from '../../../lib/types/events';
@ -19,6 +22,9 @@ beforeAll(async () => {
eventStore = stores.eventStore;
});
beforeEach(async () => {
await eventStore.deleteAll();
});
afterAll(async () => {
if (db) {
await db.destroy();
@ -140,6 +146,7 @@ test('Should delete stored event', async () => {
},
};
await eventStore.store(event);
await eventStore.store(event);
const events = await eventStore.getAll();
const lastEvent = events[0];
await eventStore.delete(lastEvent.id);
@ -178,3 +185,36 @@ test('Should delete all stored events', async () => {
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);
});