Skip to content

Commit 5a77fb4

Browse files
author
Artem
committed
#RI-4489 add migrations + start UTests
1 parent 04854c2 commit 5a77fb4

File tree

8 files changed

+140
-41
lines changed

8 files changed

+140
-41
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { MigrationInterface, QueryRunner } from "typeorm";
2+
3+
export class feature1684175820824 implements MigrationInterface {
4+
name = 'feature1684175820824'
5+
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
await queryRunner.query(`CREATE TABLE "features" ("name" varchar PRIMARY KEY NOT NULL, "flag" boolean NOT NULL)`);
8+
await queryRunner.query(`CREATE TABLE "features_config" ("id" varchar PRIMARY KEY NOT NULL, "controlNumber" integer, "data" varchar NOT NULL, "updatedAt" datetime NOT NULL DEFAULT (datetime('now')))`);
9+
}
10+
11+
public async down(queryRunner: QueryRunner): Promise<void> {
12+
await queryRunner.query(`DROP TABLE "features_config"`);
13+
await queryRunner.query(`DROP TABLE "features"`);
14+
}
15+
16+
}

redisinsight/api/migration/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { databaseCompressor1678182722874 } from './1678182722874-database-compre
3131
import { customTutorials1677135091633 } from './1677135091633-custom-tutorials';
3232
import { databaseRecommendations1681900503586 } from './1681900503586-database-recommendations';
3333
import { databaseRecommendationParams1683006064293 } from './1683006064293-database-recommendation-params';
34+
import { feature1684175820824 } from './1684175820824-feature';
3435

3536
export default [
3637
initialMigration1614164490968,
@@ -66,4 +67,5 @@ export default [
6667
customTutorials1677135091633,
6768
databaseRecommendations1681900503586,
6869
databaseRecommendationParams1683006064293,
70+
feature1684175820824,
6971
];

redisinsight/api/src/__mocks__/feature.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import { FeaturesConfigEntity } from 'src/modules/feature/entities/features-config.entity';
2-
import { FeaturesConfig, FeaturesConfigData } from 'src/modules/feature/model/features-config';
3-
import { plainToClass } from 'class-transformer';
2+
import {
3+
FeatureConfig,
4+
FeatureConfigFilter,
5+
FeaturesConfig,
6+
FeaturesConfigData,
7+
} from 'src/modules/feature/model/features-config';
48
import { classToClass } from 'src/utils';
59

610
export const mockFeaturesConfigId = '1';
11+
export const mockFeaturesConfigVersion = 1.111;
712
export const mockControlNumber = 7.68;
813

914
export const mockFeaturesConfigJson = {
10-
version: 1,
15+
version: mockFeaturesConfigVersion,
1116
features: {
1217
liveRecommendations: {
1318
perc: [[0, 10]],
@@ -23,21 +28,16 @@ export const mockFeaturesConfigJson = {
2328
},
2429
};
2530

26-
export const mockFeaturesConfigData = plainToClass(FeaturesConfigData, {
27-
version: mockFeaturesConfigJson.version,
28-
features: {
29-
liveRecommendations: {
30-
perc: [[0, 10]],
31-
flag: true,
31+
export const mockFeaturesConfigData = Object.assign(new FeaturesConfigData(), {
32+
...mockFeaturesConfigJson,
33+
features: new Map(Object.entries({
34+
liveRecommendations: Object.assign(new FeatureConfig(), {
35+
...mockFeaturesConfigJson.features.liveRecommendations,
3236
filters: [
33-
{
34-
name: 'agreements.analytics',
35-
value: true,
36-
cond: 'eq',
37-
},
37+
Object.assign(new FeatureConfigFilter(), { ...mockFeaturesConfigJson.features.liveRecommendations.filters[0] }),
3838
],
39-
},
40-
},
39+
}),
40+
})),
4141
});
4242

4343
export const mockFeaturesConfig = Object.assign(new FeaturesConfig(), {
@@ -51,8 +51,8 @@ export const mockFeaturesConfigEntity = Object.assign(new FeaturesConfigEntity()
5151
});
5252

5353
export const mockFeaturesConfigRepository = jest.fn(() => ({
54-
getOrCreate: jest.fn(),
55-
update: jest.fn(),
54+
getOrCreate: jest.fn().mockResolvedValue(mockFeaturesConfig),
55+
update: jest.fn().mockResolvedValue(mockFeaturesConfig),
5656
}));
5757

5858
export const mockFeaturesConfigService = () => ({

redisinsight/api/src/modules/feature/features-config.service.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import ERROR_MESSAGES from 'src/constants/error-messages';
88
import { FeaturesConfigRepository } from 'src/modules/feature/repositories/features-config.repository';
99
import { FeatureServerEvents } from 'src/modules/feature/constants';
1010
import { Validator } from 'class-validator';
11+
import { plainToClass } from 'class-transformer';
12+
import { FeaturesConfigData } from 'src/modules/feature/model/features-config';
1113

1214
const FEATURES_CONFIG = config.get('features_config');
1315

@@ -41,9 +43,8 @@ export class FeaturesConfigService {
4143
return JSON.parse(data);
4244
} catch (error) {
4345
this.logger.error('Unable to fetch remote config', error);
46+
throw error;
4447
}
45-
46-
return null;
4748
}
4849

4950
/**
@@ -54,7 +55,8 @@ export class FeaturesConfigService {
5455
this.logger.log('Trying to sync features config...');
5556

5657
const featuresConfig = await this.repository.getOrCreate();
57-
const newConfig = await this.fetchRemoteConfig();
58+
// todo: update from default config with version > than current
59+
const newConfig = plainToClass(FeaturesConfigData, await this.fetchRemoteConfig());
5860

5961
await this.validator.validateOrReject(newConfig);
6062

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import {
2+
mockFeaturesConfig, mockFeaturesConfigEntity,
3+
mockFeaturesConfigJson,
4+
} from 'src/__mocks__';
5+
import { classToPlain, plainToClass } from 'class-transformer';
6+
import { FeaturesConfig } from 'src/modules/feature/model/features-config';
7+
import { classToClass } from 'src/utils';
8+
import { FeaturesConfigEntity } from 'src/modules/feature/entities/features-config.entity';
9+
10+
const testCases = [
11+
{
12+
plain: {
13+
...mockFeaturesConfig,
14+
data: { ...mockFeaturesConfigJson },
15+
},
16+
model: mockFeaturesConfig,
17+
entity: Object.assign(new FeaturesConfigEntity(), { ...mockFeaturesConfigEntity, id: undefined }),
18+
},
19+
{
20+
plain: {},
21+
model: {},
22+
entity: {},
23+
},
24+
{
25+
plain: null,
26+
model: null,
27+
entity: null,
28+
},
29+
{
30+
plain: undefined,
31+
model: undefined,
32+
entity: undefined,
33+
},
34+
{
35+
plain: 'incorrectdata',
36+
model: 'incorrectdata',
37+
entity: 'incorrectdata',
38+
},
39+
];
40+
41+
describe('FeaturesConfig', () => {
42+
describe('transform', () => {
43+
testCases.forEach((tc) => {
44+
it(`input ${JSON.stringify(tc.plain)}`, async () => {
45+
const modelFromPlain = plainToClass(FeaturesConfig, tc.plain);
46+
const plainFromModel = classToPlain(modelFromPlain);
47+
const entityFromModel = classToClass(FeaturesConfigEntity, modelFromPlain);
48+
const modelFromEntity = classToClass(FeaturesConfig, entityFromModel);
49+
50+
expect(tc.model).toEqual(modelFromPlain);
51+
expect(tc.plain).toEqual(plainFromModel);
52+
expect(tc.entity).toEqual(entityFromModel);
53+
expect(tc.model).toEqual(modelFromEntity);
54+
});
55+
});
56+
});
57+
});

redisinsight/api/src/modules/feature/repositories/local.features-config.repository.spec.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { LocalFeaturesConfigRepository } from 'src/modules/feature/repositories/
1212
import { FeaturesConfigEntity } from 'src/modules/feature/entities/features-config.entity';
1313
import { classToPlain, plainToClass } from 'class-transformer';
1414
import { FeaturesConfigData } from 'src/modules/feature/model/features-config';
15-
1615
describe('LocalFeaturesConfigRepository', () => {
1716
let service: LocalFeaturesConfigRepository;
1817
let repository: MockType<Repository<FeaturesConfigEntity>>;
@@ -38,11 +37,43 @@ describe('LocalFeaturesConfigRepository', () => {
3837
repository.save.mockResolvedValue(mockFeaturesConfigEntity);
3938
});
4039

40+
describe('generateControlNumber', () => {
41+
const step = 10;
42+
const iterations = 10_000;
43+
const delta = 100;
44+
45+
it('check controlNumber generation', async () => {
46+
const result = {};
47+
48+
for (let i = 0; i < 100; i += step) {
49+
result[`${i} - ${i + step}`] = 0;
50+
}
51+
52+
(new Array(iterations)).fill(1).forEach(() => {
53+
const controlNumber = service['generateControlNumber']();
54+
55+
expect(controlNumber).toBeGreaterThanOrEqual(0);
56+
expect(controlNumber).toBeLessThan(100);
57+
58+
for (let j = 0; j < 100; j += step) {
59+
if (controlNumber <= (j + step)) {
60+
result[`${j} - ${j + step}`] += 1;
61+
break;
62+
}
63+
}
64+
});
65+
66+
const amountPerGroup = iterations / step;
67+
68+
Object.entries(result).forEach(([, value]) => {
69+
expect(value).toBeGreaterThan(amountPerGroup - delta);
70+
expect(value).toBeLessThan(amountPerGroup + delta);
71+
});
72+
});
73+
});
74+
4175
describe('getOrCreate', () => {
4276
it('ttt', async () => {
43-
console.log('___ mockFeaturesConfigJson', require('util').inspect(mockFeaturesConfigJson, { depth: null }))
44-
console.log('___ mockFeaturesConfig', require('util').inspect(mockFeaturesConfig, { depth: null }))
45-
console.log('___ mockFeaturesConfigEntity', require('util').inspect(mockFeaturesConfigEntity, { depth: null }))
4677
});
4778
// it('should return existing config', async () => {
4879
// const result = await service.getOrCreate();

redisinsight/api/src/modules/feature/repositories/local.features-config.repository.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,14 @@ export class LocalFeaturesConfigRepository extends FeaturesConfigRepository {
2424
}
2525

2626
/**
27-
* Generate control group which should never be updated
27+
* Generate control number which should never be updated
2828
* @private
2929
*/
30-
private generateControlGroup(): number {
31-
this.logger.log('Getting control group');
30+
private generateControlNumber(): number {
31+
const controlNumber = Number((parseInt((Math.random() * 10_000).toString(), 10) / 100).toFixed(2));
32+
this.logger.log('Control number is generated', controlNumber);
3233

33-
const controlGroup = Number((Math.random() * 100).toFixed(2));
34-
35-
this.logger.log('Control group generated', controlGroup);
36-
37-
return controlGroup;
34+
return controlNumber;
3835
}
3936

4037
/**
@@ -51,7 +48,7 @@ export class LocalFeaturesConfigRepository extends FeaturesConfigRepository {
5148
entity = await this.repository.save(plainToClass(FeaturesConfigEntity, {
5249
id: this.id,
5350
data: defaultConfig,
54-
controlNumber: this.generateControlGroup(),
51+
controlNumber: this.generateControlNumber(),
5552
}));
5653
}
5754

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import {
2-
Entity, PrimaryGeneratedColumn, CreateDateColumn, Column,
3-
} from 'typeorm';
1+
import { Entity, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
42
import { Expose } from 'class-transformer';
53

64
@Entity('server')
@@ -12,8 +10,4 @@ export class ServerEntity {
1210
@CreateDateColumn({ type: 'datetime', nullable: false })
1311
@Expose()
1412
createDateTime: string;
15-
16-
@Expose()
17-
@Column({ nullable: true })
18-
controlGroup: number;
1913
}

0 commit comments

Comments
 (0)