From 669ecaa8767ed1ba3ac1a5d9877400665eca247b Mon Sep 17 00:00:00 2001 From: Hugo NOUTS <hnouts@grandlyon.com> Date: Wed, 2 Feb 2022 10:29:28 +0000 Subject: [PATCH] feat(fluidPrices): Replaced migration system with hash system --- src/components/Splash/SplashRoot.tsx | 3 + src/db/profileData.json | 2 + src/migrations/migration.data.ts | 26 +---- src/models/profile.model.ts | 1 + src/services/fluidsPrices.service.ts | 4 +- src/services/initialization.service.spec.ts | 113 ++++++++++++++++++++ src/services/initialization.service.ts | 66 ++++++++++++ tests/__mocks__/profile.mock.ts | 1 + 8 files changed, 192 insertions(+), 24 deletions(-) diff --git a/src/components/Splash/SplashRoot.tsx b/src/components/Splash/SplashRoot.tsx index c1305efcb..12719870f 100644 --- a/src/components/Splash/SplashRoot.tsx +++ b/src/components/Splash/SplashRoot.tsx @@ -135,6 +135,7 @@ const SplashRoot = ({ setValidExploration(UserExplorationID.EXPLORATION007) const [ ecogestureHash, + fluidPricesHash, duelHash, quizHash, challengeHash, @@ -142,6 +143,7 @@ const SplashRoot = ({ analysisResult, ] = await Promise.all([ initializationService.initEcogesture(profile.ecogestureHash), + initializationService.initFluidPrices(profile.fluidPricesHash), initializationService.initDuelEntity(profile.duelHash), initializationService.initQuizEntity(profile.quizHash), initializationService.initExplorationEntity(profile.challengeHash), @@ -149,6 +151,7 @@ const SplashRoot = ({ initializationService.initAnalysis(profile), ]) profile.ecogestureHash = ecogestureHash + profile.fluidPricesHash = fluidPricesHash profile.duelHash = duelHash profile.quizHash = quizHash profile.challengeHash = challengeHash diff --git a/src/db/profileData.json b/src/db/profileData.json index bbd3db0ef..27e8408d2 100644 --- a/src/db/profileData.json +++ b/src/db/profileData.json @@ -5,6 +5,7 @@ "mailToken": "", "duelHash": "", "quizHash": "", + "fluidPricesHash": "", "isFirstConnection": true, "lastConnectionDate": "0000-01-01T00:00:00.000Z", "haveSeenLastAnalysis": true, @@ -13,6 +14,7 @@ "sendConsumptionAlert": false, "waterDailyConsumptionLimit": 0, "isProfileTypeCompleted": false, + "isProfileEcogestureCompleted": false, "onboarding": { "isWelcomeSeen": false }, diff --git a/src/migrations/migration.data.ts b/src/migrations/migration.data.ts index 014a1a104..a99bd4e99 100644 --- a/src/migrations/migration.data.ts +++ b/src/migrations/migration.data.ts @@ -284,20 +284,12 @@ export const migrations: Migration[] = [ baseSchemaVersion: 8, targetSchemaVersion: 9, appVersion: '1.6.0', - description: 'Init new doctype fluidPrices', + description: 'Init new doctype fluidPrices --deprecated--', releaseNotes: null, docTypes: FLUIDPRICES_DOCTYPE, isCreate: true, run: async (_client: Client, docs: any[]): Promise<any> => { - if (docs.length > 0) { - console.log('collection already exists, delete all') - docs.forEach(async doc => { - await _client.destroy(doc) - }) - } - for (const fluidPrice of fluidsPricesData) { - await _client.create(FLUIDPRICES_DOCTYPE, fluidPrice) - } + return null }, }, { @@ -502,22 +494,12 @@ export const migrations: Migration[] = [ baseSchemaVersion: 17, targetSchemaVersion: 18, appVersion: '1.7.0', - description: 'Init new fluidPrices for water', + description: 'Init new fluidPrices for water -- deprecated --', releaseNotes: null, docTypes: FLUIDPRICES_DOCTYPE, run: async (_client: Client, docs: any[]): Promise<any> => { - const waterPricesData: any[] = fluidsPricesData.filter(fluidPriceData => { - return fluidPriceData.fluidType === FluidType.WATER - }) - const createWaterPricesData = waterPricesData.map( - (waterPriceData: any) => { - waterPriceData.createAction = true - waterPriceData.doctype = FLUIDPRICES_DOCTYPE - return waterPriceData - } - ) - return createWaterPricesData + return null }, }, { diff --git a/src/models/profile.model.ts b/src/models/profile.model.ts index 5f1741939..3073cdd0e 100644 --- a/src/models/profile.model.ts +++ b/src/models/profile.model.ts @@ -11,6 +11,7 @@ export interface ProfileEntity { duelHash: string quizHash: string explorationHash: string + fluidPricesHash: string isFirstConnection: boolean lastConnectionDate: string haveSeenLastAnalysis: boolean diff --git a/src/services/fluidsPrices.service.ts b/src/services/fluidsPrices.service.ts index 2304a2f51..9fc5be5ab 100644 --- a/src/services/fluidsPrices.service.ts +++ b/src/services/fluidsPrices.service.ts @@ -94,8 +94,8 @@ export default class FluidPricesService { public async deleteAllFluidsPrices(): Promise<boolean> { const fluidsPrices = await this.getAllPrices() try { - for (let index = 0; index < fluidsPrices.length; index++) { - await this._client.destroy(fluidsPrices[index]) + for (const price of fluidsPrices) { + await this._client.destroy(price) } return true } catch (error) { diff --git a/src/services/initialization.service.spec.ts b/src/services/initialization.service.spec.ts index a79575e9f..ea766400e 100644 --- a/src/services/initialization.service.spec.ts +++ b/src/services/initialization.service.spec.ts @@ -9,6 +9,7 @@ import challengeEntityData from 'db/challengeEntity.json' import duelEntityData from 'db/duelEntity.json' import quizEntityData from 'db/quizEntity.json' import explorationEntityData from 'db/explorationEntity.json' +import fluidPrices from 'db/fluidPrices.json' import { hashFile } from 'utils/hash' import { getActualAnalysisDate } from 'utils/date' @@ -22,6 +23,7 @@ import { allChallengeEntityData } from '../../tests/__mocks__/challengeEntity.mo import { allDuelEntity } from '../../tests/__mocks__/duelData.mock' import { allQuizEntities } from '../../tests/__mocks__/quizData.mock' import { allExplorationEntities } from '../../tests/__mocks__/explorationData.mock' +import { fluidPrices as allFluidPrices } from '../../tests/__mocks__/fluidPrice.mock' import { mockOutdatedTerm, mockUpToDateTerm, @@ -84,6 +86,17 @@ jest.mock('./challenge.service', () => { }) }) +const mockGetAllPrices = jest.fn() +const mockDeleteAllFluidsPrices = jest.fn() +jest.mock('./fluidsPrices.service', () => { + return jest.fn(() => { + return { + getAllPrices: mockGetAllPrices, + deleteAllFluidsPrices: mockDeleteAllFluidsPrices, + } + }) +}) + const mockGetAllDuelEntities = jest.fn() const mockDeleteAllDuelEntities = jest.fn() jest.mock('./duel.service', () => { @@ -340,6 +353,106 @@ describe('Initialization service', () => { }) }) + describe('initFluidPrices method', () => { + beforeEach(() => { + mockGetAllPrices.mockClear() + mockDeleteAllFluidsPrices.mockClear() + }) + it('should return hash when fluidPrices hash is already up to date', async () => { + mockGetAllPrices.mockResolvedValueOnce(fluidPrices) + const hash = hashFile(fluidPrices) + await expect( + initializationService.initFluidPrices(hash) + ).resolves.toEqual(hash) + }) + it('should return hash when fluidPrices are created', async () => { + mockGetAllPrices + .mockResolvedValueOnce(null) + .mockResolvedValueOnce(fluidPrices) + const mockQueryResult: QueryResult<boolean> = { + data: true, + bookmark: '', + next: false, + skip: 0, + } + mockClient.create.mockResolvedValue(mockQueryResult) + const hash = hashFile(fluidPrices) + await expect( + initializationService.initFluidPrices(hash) + ).resolves.toEqual(hash) + }) + it('should throw an error when fluidPrices entities should be created and created fluidPrices entities number does not match', async () => { + mockGetAllPrices + .mockResolvedValueOnce(null) + .mockResolvedValueOnce(allFluidPrices) + const mockQueryResult: QueryResult<boolean> = { + data: true, + bookmark: '', + next: false, + skip: 0, + } + mockClient.create.mockResolvedValue(mockQueryResult) + await expect( + initializationService.initFluidPrices(hashFile(fluidPrices)) + ).rejects.toThrow( + new Error( + 'initFluidPrices: Created fluidPrices type entities does not match' + ) + ) + }) + it('should throw an error when fluidPrices should be created and creation failed', async () => { + mockGetAllPrices.mockResolvedValueOnce(null) + mockClient.create.mockRejectedValue(new Error()) + await expect( + initializationService.initChallengeEntity(hashFile(fluidPrices)) + ).rejects.toThrow(new Error()) + }) + it('should return hash when fluidPrices are updated', async () => { + mockGetAllPrices + .mockResolvedValueOnce(fluidPrices) + .mockResolvedValueOnce(fluidPrices) + mockDeleteAllFluidsPrices.mockResolvedValue(true) + const mockQueryResult: QueryResult<boolean> = { + data: true, + bookmark: '', + next: false, + skip: 0, + } + mockClient.create.mockResolvedValue(mockQueryResult) + await expect(initializationService.initFluidPrices('')).resolves.toEqual( + hashFile(fluidPrices) + ) + }) + it('should throw an error when fluidPrices should be updated and fluidPrices entities number does not match', async () => { + mockGetAllPrices + .mockResolvedValueOnce('') + .mockResolvedValueOnce(allFluidPrices) + mockDeleteAllFluidsPrices.mockResolvedValue(true) + const mockQueryResult: QueryResult<boolean> = { + data: true, + bookmark: '', + next: false, + skip: 0, + } + mockClient.create.mockResolvedValue(mockQueryResult) + await expect(initializationService.initFluidPrices('')).rejects.toThrow( + new Error( + 'initFluidPrices: Created fluidPrices type entities does not match' + ) + ) + }) + it('should throw an error whenfluidPrices should be updated and fluidPrices entities creation failed', async () => { + mockGetAllPrices + .mockResolvedValueOnce(fluidPrices) + .mockResolvedValueOnce(fluidPrices) + mockDeleteAllFluidsPrices.mockResolvedValue(true) + mockClient.create.mockRejectedValueOnce(new Error()) + expect(initializationService.initFluidPrices('')).rejects.toThrow( + new Error() + ) + }) + }) + describe('initChallengeEntity method', () => { beforeEach(() => { mockGetAllChallengeEntities.mockClear() diff --git a/src/services/initialization.service.ts b/src/services/initialization.service.ts index 923c9836d..6c606a1ae 100644 --- a/src/services/initialization.service.ts +++ b/src/services/initialization.service.ts @@ -16,6 +16,7 @@ import { DUEL_DOCTYPE, QUIZ_DOCTYPE, EXPLORATION_DOCTYPE, + FLUIDPRICES_DOCTYPE, } from 'doctypes' import { FluidType } from 'enum/fluid.enum' @@ -35,6 +36,7 @@ import challengeEntityData from 'db/challengeEntity.json' import duelEntityData from 'db/duelEntity.json' import quizEntityData from 'db/quizEntity.json' import explorationEntityData from 'db/explorationEntity.json' +import fluidPrices from 'db/fluidPrices.json' import ProfileService from 'services/profile.service' import profileData from 'db/profileData.json' @@ -58,6 +60,7 @@ import TermsService from './terms.service' import log from 'utils/logger' import { ProfileEcogesture } from 'models/profileEcogesture.model' import ProfileEcogestureService from './profileEcogesture.service' +import FluidPricesService from './fluidsPrices.service' export default class InitializationService { private readonly _client: Client @@ -287,6 +290,69 @@ export default class InitializationService { } } + public async initFluidPrices(hash: string): Promise<string> { + const hashFluidPricesType = hashFile(fluidPrices) + const fpService = new FluidPricesService(this._client) + // Populate data if none ecogesture exists + const loadedPrices = await fpService.getAllPrices() + if (!loadedPrices || (loadedPrices && loadedPrices.length === 0)) { + // Populate the doctype with data + + try { + for (const price of fluidPrices) { + await this._client.create(FLUIDPRICES_DOCTYPE, price) + } + // Check of created document based on count + const checkCount = await fpService.getAllPrices() + if ( + !checkCount || + (checkCount && checkCount.length !== fluidPrices.length) + ) { + throw new Error( + 'initFluidPrices: Created fluidPrices type entities does not match' + ) + } + log.info('[Initialization] FluidPrices list created') + return hashFluidPricesType + } catch (error) { + log.error('Initialization error - initFluidPrices: ', error) + throw error + } + } + // Update if the hash is not the same as the one from profile + if (hash !== hashFluidPricesType) { + // Update the doctype + try { + // Deletion of all documents + await fpService.deleteAllFluidsPrices() + // Population with the data + for (const price of fluidPrices) { + await this._client.create(FLUIDPRICES_DOCTYPE, price) + } + // Check of created document based on count + const checkCount = await fpService.getAllPrices() + + if ( + !checkCount || + (checkCount && checkCount.length !== fluidPrices.length) + ) { + throw new Error( + 'initFluidPrices: Created fluidPrices type entities does not match' + ) + } + log.info('[Initialization] FluidPrices updated') + return hashFluidPricesType + } catch (error) { + log.error('Initialization error - initFluidPrices: ', error) + throw error + } + } else { + // Doctype already up to date + log.info('[Initialization] FluidPrices already up-to-date') + return hashFluidPricesType + } + } + public async initChallengeEntity(hash: string): Promise<string> { const challengeHash = hashFile(challengeEntityData) const challengeService = new ChallengeService(this._client) diff --git a/tests/__mocks__/profile.mock.ts b/tests/__mocks__/profile.mock.ts index aaee9099c..57ea3c609 100644 --- a/tests/__mocks__/profile.mock.ts +++ b/tests/__mocks__/profile.mock.ts @@ -6,6 +6,7 @@ export const profileData: Profile = { _rev: '16-57473da4fc26315247c217083175dfa0', id: '4d9403218ef13e65b2e3a8ad1700bc41', ecogestureHash: '9798a0aaccb47cff906fc4931a2eff5f9371dd8b', + fluidPricesHash: '9798a0aaccb47cff906fc4931a2eff5f9371dd8a', challengeHash: '1136feb6185c7643e071d14180c0e95782aa4ba3', duelHash: '1136feb6185c7643e071d14180c0e95782aa4ba3', quizHash: '1136feb6185c7643e071d14180c0e95782aa4ba3', -- GitLab