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

tests: add more tests for the lifecycle avg calculation query (#8698)

This PR adds more tests to check a few more cases for the lifecycle
calculation query. Specifically, it tests that:
- If we don't have any data for a stage, we return `null`.
- We filter on projects
- It correctly takes `0` days into account when calculating averages.
This commit is contained in:
Thomas Heartman 2024-11-08 11:29:17 +01:00 committed by GitHub
parent 5733f91347
commit 0a250a7526
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 118 additions and 24 deletions

View File

@ -1,4 +1,4 @@
import { addDays } from 'date-fns'; import { addDays, addMinutes } from 'date-fns';
import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init'; import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init';
import getLogger from '../../../test/fixtures/no-logger'; import getLogger from '../../../test/fixtures/no-logger';
import { ProjectLifecycleSummaryReadModel } from './project-lifecycle-summary-read-model'; import { ProjectLifecycleSummaryReadModel } from './project-lifecycle-summary-read-model';
@ -17,6 +17,12 @@ afterAll(async () => {
} }
}); });
afterEach(async () => {
await db.stores.projectStore.deleteAll();
await db.stores.featureToggleStore.deleteAll();
await db.stores.featureLifecycleStore.deleteAll();
});
const updateFeatureStageDate = async ( const updateFeatureStageDate = async (
flagName: string, flagName: string,
stage: string, stage: string,
@ -30,22 +36,22 @@ const updateFeatureStageDate = async (
describe('Average time calculation', () => { describe('Average time calculation', () => {
test('it calculates the average time for each stage', async () => { test('it calculates the average time for each stage', async () => {
const project1 = await db.stores.projectStore.create({ const project = await db.stores.projectStore.create({
name: 'project1', name: 'project',
id: 'project1', id: randomId(),
}); });
const now = new Date(); const now = new Date();
const flags = [ const flags = [
{ name: randomId(), offsets: [2, 5, 6, 10] }, { name: randomId(), offsets: [2, 5, 6, 10] },
{ name: randomId(), offsets: [1, null, 4, 7] }, { name: randomId(), offsets: [1, null, 4, 7] },
{ name: randomId(), offsets: [12, 25, 8, 9] }, { name: randomId(), offsets: [12, 25, 0, 9] },
{ name: randomId(), offsets: [1, 2, 3, null] }, { name: randomId(), offsets: [1, 2, 3, null] },
]; ];
for (const { name, offsets } of flags) { for (const { name, offsets } of flags) {
const created = await db.stores.featureToggleStore.create( const created = await db.stores.featureToggleStore.create(
project1.id, project.id,
{ {
name, name,
createdByUserId: 1, createdByUserId: 1,
@ -79,27 +85,115 @@ describe('Average time calculation', () => {
await updateFeatureStageDate( await updateFeatureStageDate(
created.name, created.name,
stage, stage,
addMinutes(
addDays(now, offsetFromInitial), addDays(now, offsetFromInitial),
1 * (index + 1),
),
); );
} }
} }
const readModel = new ProjectLifecycleSummaryReadModel(db.rawDatabase); const readModel = new ProjectLifecycleSummaryReadModel(db.rawDatabase);
const result = await readModel.getAverageTimeInEachStage(project1.id); const result = await readModel.getAverageTimeInEachStage(project.id);
expect(result).toMatchObject({ expect(result).toMatchObject({
initial: 4, // (2 + 1 + 12 + 1) / 4 = 4 initial: 4, // (2 + 1 + 12 + 1) / 4 = 4
'pre-live': 9, // (5 + 25 + 2 + 4) / 4 = 9 'pre-live': 9, // (5 + 4 + 25 + 2) / 4 = 9
live: 6, // (6 + 8 + 3) / 3 ~= 5.67 ~= 6 live: 3, // (6 + 0 + 3) / 3 = 3
completed: 9, // (10 + 7 + 9) / 3 ~= 8.67 ~= 9 completed: 9, // (10 + 7 + 9) / 3 ~= 8.67 ~= 9
}); });
}); });
test('it returns `null` if it has no data for something', async () => {}); test('it returns `null` if it has no data for something', async () => {
test('it rounds to the nearest whole number', async () => {}); const project = await db.stores.projectStore.create({
test('it ignores flags in other projects', async () => {}); name: 'project',
test('it ignores flags in other projects', async () => {}); id: randomId(),
});
test("it ignores rows that don't have a next stage", async () => {}); const readModel = new ProjectLifecycleSummaryReadModel(db.rawDatabase);
const result1 = await readModel.getAverageTimeInEachStage(project.id);
expect(result1).toMatchObject({
initial: null,
'pre-live': null,
live: null,
completed: null,
});
const flag = await db.stores.featureToggleStore.create(project.id, {
name: randomId(),
createdByUserId: 1,
});
await db.stores.featureLifecycleStore.insert([
{
feature: flag.name,
stage: 'initial',
},
]);
await db.stores.featureLifecycleStore.insert([
{
feature: flag.name,
stage: 'pre-live',
},
]);
await updateFeatureStageDate(
flag.name,
'pre-live',
addDays(new Date(), 5),
);
const result2 = await readModel.getAverageTimeInEachStage(project.id);
expect(result2).toMatchObject({
initial: 5,
'pre-live': null,
live: null,
completed: null,
});
});
test('it ignores flags in other projects', async () => {
const project = await db.stores.projectStore.create({
name: 'project',
id: randomId(),
});
const readModel = new ProjectLifecycleSummaryReadModel(db.rawDatabase);
const flag = await db.stores.featureToggleStore.create(project.id, {
name: randomId(),
createdByUserId: 1,
});
await db.stores.featureLifecycleStore.insert([
{
feature: flag.name,
stage: 'initial',
},
]);
await db.stores.featureLifecycleStore.insert([
{
feature: flag.name,
stage: 'pre-live',
},
]);
await updateFeatureStageDate(
flag.name,
'pre-live',
addDays(new Date(), 5),
);
const result =
await readModel.getAverageTimeInEachStage('some-other-project');
expect(result).toMatchObject({
initial: null,
'pre-live': null,
live: null,
completed: null,
});
});
}); });

View File

@ -38,10 +38,10 @@ export class ProjectLifecycleSummaryReadModel
} }
async getAverageTimeInEachStage(projectId: string): Promise<{ async getAverageTimeInEachStage(projectId: string): Promise<{
initial: number; initial: number | null;
'pre-live': number; 'pre-live': number | null;
live: number; live: number | null;
completed: number; completed: number | null;
}> { }> {
const q = this.db const q = this.db
.with( .with(
@ -80,10 +80,10 @@ export class ProjectLifecycleSummaryReadModel
return acc; return acc;
}, },
{ {
initial: 0, initial: null,
'pre-live': 0, 'pre-live': null,
live: 0, live: null,
completed: 0, completed: null,
}, },
); );
} }

View File

@ -294,7 +294,7 @@ const config: Config = {
routeBasePath: '/', routeBasePath: '/',
remarkPlugins: [[pluginNpm2Yarn, { sync: true }]], remarkPlugins: [[pluginNpm2Yarn, { sync: true }]],
docItemComponent: '@theme/ApiItem', docItemComponent: '@theme/ApiItem',
sidebarPath: './sidebars.ts' sidebarPath: './sidebars.ts',
}, },
theme: { theme: {
customCss: './src/css/custom.css', customCss: './src/css/custom.css',