From cb51ec048504e47a6102c32d827ee0208e8a241e Mon Sep 17 00:00:00 2001 From: Yoan Vallet <yoan.vallet@gmail.com> Date: Sun, 15 Nov 2020 18:29:25 +0100 Subject: [PATCH] feat: tests for query runner --- src/models/dataload.model.ts | 13 + src/services/__mocks__/enedisDayData.json | 50 ++++ src/services/__mocks__/enedisMinuteData.json | 74 ++++++ src/services/queryRunner.service.spec.ts | 257 +++++++++++++++---- src/services/queryRunner.service.ts | 248 +++++++++--------- 5 files changed, 464 insertions(+), 178 deletions(-) create mode 100644 src/services/__mocks__/enedisDayData.json create mode 100644 src/services/__mocks__/enedisMinuteData.json diff --git a/src/models/dataload.model.ts b/src/models/dataload.model.ts index bb4be319d..06fd28408 100644 --- a/src/models/dataload.model.ts +++ b/src/models/dataload.model.ts @@ -5,3 +5,16 @@ export interface Dataload { value: number valueDetail?: number[] | null } + +export interface DataloadEntity { + id: string + _id?: string + _type?: string + _rev?: string + day: number + hour: number + load: number + minute: number + month: number + year: number +} diff --git a/src/services/__mocks__/enedisDayData.json b/src/services/__mocks__/enedisDayData.json new file mode 100644 index 000000000..fa0395bbc --- /dev/null +++ b/src/services/__mocks__/enedisDayData.json @@ -0,0 +1,50 @@ +[ + { + "id": "bf1ce3a5774e140056714c4c200c093e", + "_id": "bf1ce3a5774e140056714c4c200c093e", + "_type": "com.grandlyon.enedis.day", + "_rev": "1-4fe971dff073a3c3c6cc12a0246e642e", + "day": 1, + "hour": 0, + "load": 25.25, + "minute": 0, + "month": 11, + "year": 2020 + }, + { + "id": "cf7dc6f44a19b354f99b01ba1a0b4840", + "_id": "cf7dc6f44a19b354f99b01ba1a0b4840", + "_type": "com.grandlyon.enedis.day", + "_rev": "1-4fe971dff073a3c3c6cc12a0246e642e", + "day": 2, + "hour": 0, + "load": 20.5, + "minute": 0, + "month": 11, + "year": 2020 + }, + { + "id": "cf7dc6f44a19b354f99b01ba1a0b4840", + "_id": "cf7dc6f44a19b354f99b01ba1a0b4840", + "_type": "com.grandlyon.enedis.day", + "_rev": "1-4fe971dff073a3c3c6cc12a0246e642e", + "day": 3, + "hour": 0, + "load": 30.33, + "minute": 0, + "month": 11, + "year": 2020 + }, + { + "id": "cf7dc6f44a19b354f99b01ba1a0b4840", + "_id": "cf7dc6f44a19b354f99b01ba1a0b4840", + "_type": "com.grandlyon.enedis.day", + "_rev": "1-4fe971dff073a3c3c6cc12a0246e642e", + "day": 4, + "hour": 0, + "load": 1.22, + "minute": 0, + "month": 11, + "year": 2020 + } +] diff --git a/src/services/__mocks__/enedisMinuteData.json b/src/services/__mocks__/enedisMinuteData.json new file mode 100644 index 000000000..65e093d9c --- /dev/null +++ b/src/services/__mocks__/enedisMinuteData.json @@ -0,0 +1,74 @@ +[ + { + "id": "bf1ce3a5774e140056714c4c200c093e", + "_id": "bf1ce3a5774e140056714c4c200c093e", + "_type": "com.grandlyon.enedis.minute", + "_rev": "1-4fe971dff073a3c3c6cc12a0246e642e", + "day": 1, + "hour": 23, + "load": 2.25, + "minute": 30, + "month": 11, + "year": 2020 + }, + { + "id": "cf7dc6f44a19b354f99b01ba1a0b4840", + "_id": "cf7dc6f44a19b354f99b01ba1a0b4840", + "_type": "com.grandlyon.enedis.minute", + "_rev": "1-4fe971dff073a3c3c6cc12a0246e642e", + "day": 2, + "hour": 0, + "load": 4.5, + "minute": 0, + "month": 11, + "year": 2020 + }, + { + "id": "cf7dc6f44a19b354f99b01ba1a0b4840", + "_id": "cf7dc6f44a19b354f99b01ba1a0b4840", + "_type": "com.grandlyon.enedis.minute", + "_rev": "1-4fe971dff073a3c3c6cc12a0246e642e", + "day": 2, + "hour": 0, + "load": 1.33, + "minute": 30, + "month": 11, + "year": 2020 + }, + { + "id": "cf7dc6f44a19b354f99b01ba1a0b4840", + "_id": "cf7dc6f44a19b354f99b01ba1a0b4840", + "_type": "com.grandlyon.enedis.minute", + "_rev": "1-4fe971dff073a3c3c6cc12a0246e642e", + "day": 2, + "hour": 1, + "load": 3.22, + "minute": 0, + "month": 11, + "year": 2020 + }, + { + "id": "cf7dc6f44a19b354f99b01ba1a0b4840", + "_id": "cf7dc6f44a19b354f99b01ba1a0b4840", + "_type": "com.grandlyon.enedis.minute", + "_rev": "1-4fe971dff073a3c3c6cc12a0246e642e", + "day": 2, + "hour": 1, + "load": 7.82, + "minute": 30, + "month": 11, + "year": 2020 + }, + { + "id": "cf7dc6f44a19b354f99b01ba1a0b4840", + "_id": "cf7dc6f44a19b354f99b01ba1a0b4840", + "_type": "com.grandlyon.enedis.minute", + "_rev": "1-4fe971dff073a3c3c6cc12a0246e642e", + "day": 2, + "hour": 2, + "load": 1.23, + "minute": 0, + "month": 11, + "year": 2020 + } +] diff --git a/src/services/queryRunner.service.spec.ts b/src/services/queryRunner.service.spec.ts index 0f7792930..28cf73a4f 100644 --- a/src/services/queryRunner.service.spec.ts +++ b/src/services/queryRunner.service.spec.ts @@ -1,88 +1,239 @@ +import { QueryResult } from 'cozy-client' +import { FluidType } from 'enum/fluid.enum' import { TimeStep } from 'enum/timeStep.enum' import { DateTime } from 'luxon' +import { DataloadEntity } from 'models' import QueryRunner from './queryRunner.service' import mockClient from './__mocks__/client' - -const mockTimePeriod = { - startDate: DateTime.fromISO('2020-11-01T00:00:00.000+01:00'), - endDate: DateTime.fromISO('2020-11-30T23:59:59.999+01:00'), -} - -const queryResult = { - data: [ - { - id: 'bf1ce3a5774e140056714c4c200c093e', - _id: 'bf1ce3a5774e140056714c4c200c093e', - _type: 'com.grandlyon.egl.day', - _rev: '1-4fe971dff073a3c3c6cc12a0246e642e', - day: 1, - hour: 0, - load: 257.25, - minute: 0, - month: 11, - year: 2020, - }, - { - id: 'cf7dc6f44a19b354f99b01ba1a0b4840', - _id: 'cf7dc6f44a19b354f99b01ba1a0b4840', - _type: 'com.grandlyon.egl.day', - _rev: '1-4fe971dff073a3c3c6cc12a0246e642e', - day: 1, - hour: 0, - load: 257.25, - minute: 0, - month: 11, - year: 2020, - }, - ], - next: false, - skip: 0, - bookmark: - 'g2wAAAACaAJkAA5zdGFydGtleV9kb2NpZG0AAAAgY2Y3ZGM2ZjQ0YTE5YjM1NGY5OWIwMWJhMWEwYjI3ZjhoAmQACHN0YXJ0a2V5bAAAAANiAAAH5GELYQRqag', -} +import enedisDayData from './__mocks__/enedisDayData.json' +import enedisMinuteData from './__mocks__/enedisMinuteData.json' describe('queryRunner service', () => { const queryRunner = new QueryRunner(mockClient) - const fluidType = 0 - describe('fetchFluidData', () => { + + describe('fetchFluidData method', () => { + const mockTimePeriod = { + startDate: DateTime.fromISO('2020-11-01T00:00:00.000+01:00'), + endDate: DateTime.fromISO('2020-11-30T23:59:59.999+01:00'), + } const expectedResult = [ { date: DateTime.fromISO('2020-11-01T00:00:00.000+01:00'), - value: 257.25, + value: 25.25, }, { - date: DateTime.fromISO('2020-11-01T00:00:00.000+01:00'), - value: 257.25, + date: DateTime.fromISO('2020-11-02T00:00:00.000+01:00'), + value: 20.5, + }, + { + date: DateTime.fromISO('2020-11-03T00:00:00.000+01:00'), + value: 30.33, + }, + { + date: DateTime.fromISO('2020-11-04T00:00:00.000+01:00'), + value: 1.22, }, ] - it('should return the data of the selected Fluid', async () => { - mockClient.query.mockImplementation(() => Promise.resolve(queryResult)) + it('should return the data of the selected Fluid', async () => { + const mockQueryResult: QueryResult<DataloadEntity[]> = { + data: enedisDayData, + bookmark: '', + next: false, + skip: 0, + } + mockClient.query.mockResolvedValue(mockQueryResult) const result = await queryRunner.fetchFluidData( mockTimePeriod, TimeStep.DAY, - fluidType + FluidType.ELECTRICITY ) expect(result).toEqual(expectedResult) }) - it('should return null', async () => { - mockClient.query.mockImplementationOnce(() => Promise.resolve(null)) + + it('should return null when fetch data failed', async () => { + mockClient.query.mockRejectedValue(new Error()) const result = await queryRunner.fetchFluidData( mockTimePeriod, TimeStep.DAY, - fluidType + FluidType.ELECTRICITY ) expect(result).toBeNull() }) - }) - describe('fetchFluidMaxData', () => { - it('should return the max data of the selected Fluid', async () => { + + it('should return null when unknown fluid type', async () => { + const result = await queryRunner.fetchFluidData( + mockTimePeriod, + TimeStep.DAY, + 99 + ) + expect(result).toBeNull() + }) + + it('should return null when unknown time step', async () => { const result = await queryRunner.fetchFluidData( + mockTimePeriod, + 99, + FluidType.ELECTRICITY + ) + expect(result).toBeNull() + }) + }) + + describe('fetchFluidMaxData method', () => { + it('should return the max load for elec fluid with day timestep', async () => { + const mockTimePeriod = { + startDate: DateTime.fromISO('2020-11-01T00:00:00.000+01:00'), + endDate: DateTime.fromISO('2020-11-30T23:59:59.999+01:00'), + } + const mockQueryResult: QueryResult<DataloadEntity[]> = { + data: enedisDayData.sort((a, b) => b.load - a.load), + bookmark: '', + next: false, + skip: 0, + } + mockClient.query.mockResolvedValue(mockQueryResult) + const result = await queryRunner.fetchFluidMaxData( + mockTimePeriod, + TimeStep.DAY, + FluidType.ELECTRICITY + ) + expect(result).toBe(30.33) + }) + + it('should return the max load for elec fluid with half an hour timestep', async () => { + const mockTimePeriod = { + startDate: DateTime.fromISO('2020-11-02T00:00:00.000+01:00'), + endDate: DateTime.fromISO('2020-11-2T23:59:59.999+01:00'), + } + const mockQueryResult: QueryResult<DataloadEntity[]> = { + data: [enedisMinuteData[4]], + bookmark: '', + next: false, + skip: 0, + } + const mockQueryResult2: QueryResult<DataloadEntity[]> = { + data: [enedisMinuteData[0]], + bookmark: '', + next: false, + skip: 0, + } + mockClient.query + .mockResolvedValueOnce(mockQueryResult) + .mockResolvedValueOnce(mockQueryResult2) + const result = await queryRunner.fetchFluidMaxData( + mockTimePeriod, + TimeStep.HALF_AN_HOUR, + FluidType.ELECTRICITY + ) + expect(result).toBe(7.82) + }) + + it('should return null when fetch data failed', async () => { + const mockTimePeriod = { + startDate: DateTime.fromISO('2020-11-01T00:00:00.000+01:00'), + endDate: DateTime.fromISO('2020-11-30T23:59:59.999+01:00'), + } + mockClient.query.mockRejectedValue(new Error()) + const result = await queryRunner.fetchFluidMaxData( mockTimePeriod, TimeStep.DAY, - fluidType + FluidType.ELECTRICITY ) expect(result).toBeNull() }) + + it('should return null when unknown fluid type', async () => { + const mockTimePeriod = { + startDate: DateTime.fromISO('2020-11-01T00:00:00.000+01:00'), + endDate: DateTime.fromISO('2020-11-30T23:59:59.999+01:00'), + } + const result = await queryRunner.fetchFluidMaxData( + mockTimePeriod, + TimeStep.DAY, + 99 + ) + expect(result).toBeNull() + }) + + it('should return null when unknown time step', async () => { + const mockTimePeriod = { + startDate: DateTime.fromISO('2020-11-01T00:00:00.000+01:00'), + endDate: DateTime.fromISO('2020-11-30T23:59:59.999+01:00'), + } + const result = await queryRunner.fetchFluidMaxData( + mockTimePeriod, + 99, + FluidType.ELECTRICITY + ) + expect(result).toBeNull() + }) + }) + + describe('getLastDateData method', () => { + it('should return the last load for elec fluid', async () => { + const lastMockData = [...enedisDayData] + lastMockData.sort( + (a, b) => + (b.year - a.year) * 100 + (b.month - a.month) * 10 + (b.day - a.day) + ) + const mockQueryResult: QueryResult<DataloadEntity[]> = { + data: [lastMockData[0]], + bookmark: '', + next: false, + skip: 0, + } + mockClient.query.mockResolvedValue(mockQueryResult) + const result = await queryRunner.getLastDateData(FluidType.ELECTRICITY) + expect(result).toEqual( + DateTime.local( + enedisDayData[3].year, + enedisDayData[3].month, + enedisDayData[3].day + ) + ) + }) + + it('should return null when fetch data failed', async () => { + mockClient.query.mockRejectedValue(new Error()) + const result = await queryRunner.getLastDateData(FluidType.ELECTRICITY) + expect(result).toBeNull() + }) + }) + + describe('getEntries method', () => { + it('should return query result for elec fluid with day timestep', async () => { + const mockQueryResult: QueryResult<DataloadEntity[]> = { + data: enedisDayData, + bookmark: '', + next: false, + skip: 0, + } + mockClient.query.mockResolvedValue(mockQueryResult) + const result = await queryRunner.getEntries( + FluidType.ELECTRICITY, + TimeStep.DAY + ) + expect(result).toEqual(mockQueryResult) + }) + + it('should return null when fetch data failed', async () => { + mockClient.query.mockRejectedValue(new Error()) + const result = await queryRunner.getEntries( + FluidType.ELECTRICITY, + TimeStep.DAY + ) + expect(result).toBeNull() + }) + + it('should return null when unknown fluid type', async () => { + const result = await queryRunner.getEntries(99, TimeStep.DAY) + expect(result).toBeNull() + }) + + it('should return null when unknown time step', async () => { + const result = await queryRunner.getEntries(FluidType.ELECTRICITY, 99) + expect(result).toBeNull() + }) }) }) diff --git a/src/services/queryRunner.service.ts b/src/services/queryRunner.service.ts index 051d2fec1..f4a4e9b30 100644 --- a/src/services/queryRunner.service.ts +++ b/src/services/queryRunner.service.ts @@ -62,103 +62,54 @@ export default class QueryRunner { this._client = _client } - public async fetchFluidData( - timePeriod: TimePeriod, + private buildListQuery( timeStep: TimeStep, - fluidType: FluidType - ): Promise<Dataload[] | null> { - const result = await this.fetchData( - this.buildListQuery(timeStep, timePeriod, fluidType, this._max_limit) - ) - if (result && result.data) { - const filteredResult = this.filterDataList(result, timePeriod) - const mappedResult = this.mapDataList(filteredResult) - - return mappedResult - } + timePeriod: TimePeriod, + fluidType: FluidType, + limit: number + ) { + const doctype = this.getRelevantDoctype(fluidType, timeStep) - return null + return Q(doctype) + .where(this.getPredicate(timePeriod, timeStep)) + .limitBy(limit) } - public async fetchFluidMaxData( - maxTimePeriod: TimePeriod, + private buildMaxQuery( timeStep: TimeStep, - fluidType: FluidType - ): Promise<number | null> { - const result = await this.fetchData( - this.buildMaxQuery(timeStep, maxTimePeriod, fluidType, this._max_limit) - ) + maxTimePeriod: TimePeriod, + fluidType: FluidType, + limit: number + ) { + const doctype = this.getRelevantDoctype(fluidType, timeStep) if (timeStep === TimeStep.HALF_AN_HOUR) { - const lastDayOfPreviousMonth = { - startDate: maxTimePeriod.startDate.plus({ day: -1 }), - endDate: maxTimePeriod.startDate.plus({ day: -1 }).endOf('days'), - } - const lastDayOfPreviousMonthResult = await this.fetchData( - this.buildMaxQuery( - timeStep, - lastDayOfPreviousMonth, - fluidType, - this._max_limit - ) - ) - return Math.max( - lastDayOfPreviousMonthResult.data[0] - ? lastDayOfPreviousMonthResult.data[0].load - : 0, - result.data[0] ? result.data[0].load : 0 - ) - } - - if (result && result.data) { - const filteredResult = this.filterDataList(result, maxTimePeriod) - const mappedResult = this.mapDataList(filteredResult) - return mappedResult && mappedResult[0] && mappedResult[0].value + return Q(doctype) + .where(this.getPredicate(maxTimePeriod, TimeStep.HALF_AN_HOUR)) + .limitBy(1) + .sortBy([{ load: 'desc' }]) } - return null + return Q(doctype) + .where(this.getPredicate(maxTimePeriod, timeStep)) + .limitBy(limit) + .sortBy([{ load: 'desc' }]) } - public async getLastDateData(fluidType: FluidType): Promise<DateTime | null> { - const result = await this.fetchData(this.buildLastDateQuery(fluidType, 1)) - - if ( - result && - result.data && - result.data[0] && - result.data[0].year && - result.data[0].month && - result.data[0].day - ) { - return DateTime.local( - result.data[0].year, - result.data[0].month, - result.data[0].day - ) - } - - return null + private buildLastDateQuery(fluidType: FluidType, limit: number) { + const doctype = this.getRelevantDoctype(fluidType, TimeStep.DAY) + return Q(doctype) + .where({}) + .indexFields(['year', 'month', 'day']) + .sortBy([{ year: 'desc' }, { month: 'desc' }, { day: 'desc' }]) + .limitBy(limit) } private async fetchData(query: QueryDefinition) { let result = null try { result = await this._client.query(query) - console.log(result) } catch (error) { // log stuff - //throw new Error("Cozy Cient Error"); - - return null - } - return result - } - - public async getEntries(fluidType: FluidType, timeStep: TimeStep) { - let result = null - const doctype = this.getRelevantDoctype(fluidType, timeStep) - try { - result = await this._client.query(this._client.find(doctype).where({})) - } catch (error) { - return null + // throw new Error('Fetch data failed in query runner') } return result } @@ -193,40 +144,6 @@ export default class QueryRunner { return mappedResult } - private buildListQuery( - timeStep: TimeStep, - timePeriod: TimePeriod, - fluidType: FluidType, - limit: number - ) { - const doctype = this.getRelevantDoctype(fluidType, timeStep) - - return Q(doctype) - .where(this.getPredicate(timePeriod, timeStep)) - .limitBy(limit) - } - - private buildMaxQuery( - timeStep: TimeStep, - maxTimePeriod: TimePeriod, - fluidType: FluidType, - limit: number - ) { - const doctype = this.getRelevantDoctype(fluidType, timeStep) - if (timeStep === TimeStep.HALF_AN_HOUR) { - return this._client - .find(doctype) - .where(this.getPredicate(maxTimePeriod, 20)) - .limitBy(1) - .sortBy([{ load: 'desc' }]) - } - return this._client - .find(doctype) - .where(this.getPredicate(maxTimePeriod, timeStep)) - .limitBy(limit) - .sortBy([{ load: 'desc' }]) - } - private withinDateBoundaries(dateTime: DateTime, timePeriod: TimePeriod) { return dateTime <= timePeriod.endDate && dateTime >= timePeriod.startDate } @@ -273,17 +190,6 @@ export default class QueryRunner { return dayList } - private buildLastDateQuery(fluidType: FluidType, limit: number) { - const doctype = this.getRelevantDoctype(fluidType, TimeStep.DAY) - - return this._client - .find(doctype) - .where({}) - .indexFields(['year', 'month', 'day']) - .sortBy([{ year: 'desc' }, { month: 'desc' }, { day: 'desc' }]) - .limitBy(limit) - } - private getPredicate(timePeriod: TimePeriod, timeStep: TimeStep) { let predicate = {} @@ -409,4 +315,96 @@ export default class QueryRunner { return doctype } + + public async fetchFluidData( + timePeriod: TimePeriod, + timeStep: TimeStep, + fluidType: FluidType + ): Promise<Dataload[] | null> { + const query: QueryDefinition = this.buildListQuery( + timeStep, + timePeriod, + fluidType, + this._max_limit + ) + const result = await this.fetchData(query) + if (result && result.data) { + const filteredResult = this.filterDataList(result, timePeriod) + const mappedResult = this.mapDataList(filteredResult) + return mappedResult + } + return null + } + + public async fetchFluidMaxData( + maxTimePeriod: TimePeriod, + timeStep: TimeStep, + fluidType: FluidType + ): Promise<number | null> { + const query: QueryDefinition = this.buildMaxQuery( + timeStep, + maxTimePeriod, + fluidType, + this._max_limit + ) + const result = await this.fetchData(query) + if (timeStep === TimeStep.HALF_AN_HOUR) { + const lastDayOfPreviousMonth = { + startDate: maxTimePeriod.startDate.plus({ day: -1 }), + endDate: maxTimePeriod.startDate.plus({ day: -1 }).endOf('days'), + } + const lastDayOfPreviousMonthQuery: QueryDefinition = this.buildMaxQuery( + timeStep, + lastDayOfPreviousMonth, + fluidType, + this._max_limit + ) + const lastDayOfPreviousMonthResult = await this.fetchData( + lastDayOfPreviousMonthQuery + ) + return Math.max( + lastDayOfPreviousMonthResult && lastDayOfPreviousMonthResult.data[0] + ? lastDayOfPreviousMonthResult.data[0].load + : 0, + result && result.data[0] ? result.data[0].load : 0 + ) + } + if (result && result.data) { + const filteredResult = this.filterDataList(result, maxTimePeriod) + const mappedResult = this.mapDataList(filteredResult) + return mappedResult && mappedResult[0] && mappedResult[0].value + } + return null + } + + public async getLastDateData(fluidType: FluidType): Promise<DateTime | null> { + const query: QueryDefinition = this.buildLastDateQuery(fluidType, 1) + const result = await this.fetchData(query) + if ( + result && + result.data && + result.data[0] && + result.data[0].year && + result.data[0].month && + result.data[0].day + ) { + return DateTime.local( + result.data[0].year, + result.data[0].month, + result.data[0].day + ) + } + return null + } + + public async getEntries(fluidType: FluidType, timeStep: TimeStep) { + const doctype = this.getRelevantDoctype(fluidType, timeStep) + try { + const query = Q(doctype).where({}) + const result = await this._client.query(query) + return result + } catch (error) { + return null + } + } } -- GitLab