From a9276181ed940ac71430c44cdadfaff924cae177 Mon Sep 17 00:00:00 2001
From: Yoan VALLET <ext.sopra.yvallet@grandlyon.com>
Date: Mon, 8 Feb 2021 15:46:40 +0100
Subject: [PATCH] fix: end of duel

---
 .../Challenge/ChallengeCardOnGoing.tsx        |   1 +
 .../Challenge/challengeCardOnGoing.scss       |   1 +
 src/components/Duel/DuelResultModal.tsx       |  20 +-
 src/db/duelEntity.json                        |   2 +-
 src/locales/fr.json                           |   4 +-
 src/services/challenge.service.spec.ts        | 250 +++++++++++++-----
 src/services/challenge.service.ts             |  24 +-
 7 files changed, 216 insertions(+), 86 deletions(-)

diff --git a/src/components/Challenge/ChallengeCardOnGoing.tsx b/src/components/Challenge/ChallengeCardOnGoing.tsx
index 9a182d5ba..368d7cb21 100644
--- a/src/components/Challenge/ChallengeCardOnGoing.tsx
+++ b/src/components/Challenge/ChallengeCardOnGoing.tsx
@@ -233,6 +233,7 @@ const ChallengeCardOnGoing: React.FC<ChallengeCardOnGoingProps> = ({
             <span>{t('challenge.card.ongoing.duelDone')}</span>
           </div>
           <StyledIcon className="duelLocked" icon={challengeIcon} size={60} />
+          <div className="notifChallenge">1</div>
         </div>
       ) : (
         <div className={'smallCard duelCard'}>
diff --git a/src/components/Challenge/challengeCardOnGoing.scss b/src/components/Challenge/challengeCardOnGoing.scss
index a9ed15eb3..2b54c4989 100644
--- a/src/components/Challenge/challengeCardOnGoing.scss
+++ b/src/components/Challenge/challengeCardOnGoing.scss
@@ -114,6 +114,7 @@
   border: solid 1px rgba(97, 240, 242, 0.5);
   align-items: center;
   justify-content: space-between;
+  position: relative;
   &.active {
     background: $grey-linear-gradient-background;
   }
diff --git a/src/components/Duel/DuelResultModal.tsx b/src/components/Duel/DuelResultModal.tsx
index 53113cef4..7a6fbc42f 100644
--- a/src/components/Duel/DuelResultModal.tsx
+++ b/src/components/Duel/DuelResultModal.tsx
@@ -26,7 +26,7 @@ const DuelResultModal: React.FC<DuelResultModalProps> = ({
   const { t } = useI18n()
   const [winIcon, setWinIcon] = useState<string>(defaultIcon)
   const [lossIcon, setLossIcon] = useState<string>(defaultIcon)
-  const [emptyIcon, setEmptyIcon] = useState<string>(defaultIcon)
+  const [, setEmptyIcon] = useState<string>(defaultIcon)
   const result: string | number = formatNumberValues(
     Math.abs(userChallenge.duel.threshold - userChallenge.duel.userConsumption)
   )
@@ -49,15 +49,11 @@ const DuelResultModal: React.FC<DuelResultModalProps> = ({
         <div className="em-content">
           <StyledIcon
             className="imgResult"
-            icon={empty ? emptyIcon : win ? winIcon : lossIcon}
+            icon={win ? winIcon : lossIcon}
             size={208}
           />
           <div className="text-28-normal-uppercase title">
-            {empty
-              ? t('duel.empty.title')
-              : win
-              ? t('duel.sucess.title')
-              : t('duel.lost.title')}
+            {win ? t('duel.sucess.title') : t('duel.lost.title')}
           </div>
           <div className="text-18-normal">
             {empty
@@ -67,9 +63,7 @@ const DuelResultModal: React.FC<DuelResultModalProps> = ({
               : t('duel.lost.message1') + result + ' €'}
           </div>
           <div className="text-18-normal">
-            {empty
-              ? t('duel.empty.message2') + userChallenge.title
-              : win
+            {win
               ? t('duel.sucess.message2') + userChallenge.title
               : t('duel.lost.message2') + userChallenge.title + '...'}
           </div>
@@ -81,11 +75,7 @@ const DuelResultModal: React.FC<DuelResultModalProps> = ({
               label: 'text-16-normal',
             }}
           >
-            {empty
-              ? t('duel.empty.button')
-              : win
-              ? t('duel.sucess.button')
-              : t('duel.lost.button')}
+            {win ? t('duel.sucess.button') : t('duel.lost.button')}
           </MuiButton>
         </div>
       </div>
diff --git a/src/db/duelEntity.json b/src/db/duelEntity.json
index f446ee274..19a418a0d 100644
--- a/src/db/duelEntity.json
+++ b/src/db/duelEntity.json
@@ -2,7 +2,7 @@
   {
     "_id": "DUEL001",
     "title": "Nicolas Hublot",
-    "description": "Je parie un ours polaire que vous ne pouvez pas consommer moins que #CONSUMPTION € en 1 semaine",
+    "description": "Je vous défie de consommer moins que #CONSUMPTION € en 1 semaine",
     "duration": { "days": 7 }
   },
   {
diff --git a/src/locales/fr.json b/src/locales/fr.json
index f24f7ccdd..e8d0dbdec 100644
--- a/src/locales/fr.json
+++ b/src/locales/fr.json
@@ -371,8 +371,8 @@
     "lost": {
       "title": "Presque !",
       "message1": "Vous avez dépassé de ",
-      "message2": "et presque manqué le badge ",
-      "button": "Dommage..."
+      "message2": "et manqué le badge ",
+      "button": "Zut alors"
     },
     "empty": {
       "title": "Félicitations !",
diff --git a/src/services/challenge.service.spec.ts b/src/services/challenge.service.spec.ts
index f658048b4..010de953e 100644
--- a/src/services/challenge.service.spec.ts
+++ b/src/services/challenge.service.spec.ts
@@ -6,6 +6,7 @@ import {
   UserChallenge,
   QuizEntity,
   ExplorationEntity,
+  Dataload,
 } from 'models'
 import {
   UserChallengeState,
@@ -46,6 +47,7 @@ import {
   UserExplorationUnlocked,
 } from '../../test/__mocks__/explorationData.mock'
 import { fluidStatusData } from '../../test/__mocks__/fluidStatusData.mock'
+import { cloneDeep } from 'lodash'
 
 const mockGetExplorationEntityById = jest.fn()
 const mockParseExplorationEntityToUserExploration = jest.fn()
@@ -152,7 +154,6 @@ describe('Challenge service', () => {
       skip: 0,
     }
     it('should return all user challenge', async () => {
-      mockClient
       mockClient.query.mockResolvedValueOnce(mockQueryResult)
       mockClient.query.mockResolvedValueOnce(mockQueryResultExploration)
       mockClient.query.mockResolvedValueOnce(mockQueryResultQuiz)
@@ -170,14 +171,14 @@ describe('Challenge service', () => {
       expect(result).toEqual(userChallengeData)
     })
     it('should return all user challenge plus create one missing', async () => {
-      const mockQueryResult: QueryResult<ChallengeEntity[], DuelEntity[]> = {
+      const _mockQueryResult: QueryResult<ChallengeEntity[], DuelEntity[]> = {
         data: allChallengeEntityData,
         included: allDuelEntity,
         bookmark: '',
         next: false,
         skip: 0,
       }
-      mockClient.query.mockResolvedValueOnce(mockQueryResult)
+      mockClient.query.mockResolvedValueOnce(_mockQueryResult)
       mockClient.query.mockResolvedValueOnce(mockQueryResultQuiz)
       mockClient.query.mockResolvedValueOnce(mockQueryResultExploration)
       mockClient.query.mockResolvedValueOnce(mockQueryResultUser)
@@ -326,6 +327,15 @@ describe('Challenge service', () => {
       )
       expect(result).toEqual(userChallengeData[0])
     })
+    it('should throw error when failed to save the updateUserChallenge', () => {
+      mockClient.save.mockRejectedValueOnce(new Error())
+      expect(
+        challengeService.updateUserChallenge(
+          userChallengeData[0],
+          UserChallengeUpdateFlag.DUEL_START
+        )
+      ).rejects.toEqual(new Error())
+    })
   })
 
   describe('getUserChallengeDataload method', () => {
@@ -361,71 +371,193 @@ describe('Challenge service', () => {
   })
 
   describe('isChallengeDone method', () => {
-    const userChallenge = {
-      ...userChallengeData[0],
-      state: UserChallengeState.DUEL,
-      duel: {
-        ...userChallengeData[0].duel,
-        state: UserDuelState.ONGOING,
-        duration: Duration.fromObject({ day: 3 }),
-        threshold: 200,
-        userConsumption: 199,
-        startDate: DateTime.local()
-          .setZone('utc', {
-            keepLocalTime: true,
-          })
-          .minus({ days: 5 }),
-      },
-    }
-    const dataloads = graphData.actualData
-    dataloads[2].value = 50
-    it('should return isDone = true and isWin = true', async () => {
-      const result = await challengeService.isChallengeDone(
-        userChallenge,
-        dataloads
-      )
-      expect(result).toEqual({ isDone: true, isWin: true, isEmpty: false })
-    })
-    it('should return isDone = true and isWin = false when threshold < consumption ', async () => {
-      const updatedUserChallenge = {
-        ...userChallenge,
+    describe('case date + lags + 1 is reached', () => {
+      const userChallenge = {
+        ...userChallengeData[0],
+        state: UserChallengeState.DUEL,
         duel: {
-          ...userChallenge.duel,
-          threshold: 100,
-          userConsumption: 200,
+          ...userChallengeData[0].duel,
+          state: UserDuelState.ONGOING,
+          duration: Duration.fromObject({ day: 3 }),
+          threshold: 200,
+          userConsumption: 199,
+          startDate: DateTime.local()
+            .setZone('utc', {
+              keepLocalTime: true,
+            })
+            .minus({ days: 5 }),
         },
       }
-      const result = await challengeService.isChallengeDone(
-        updatedUserChallenge,
-        dataloads
-      )
-      expect(result).toEqual({ isDone: true, isWin: false, isEmpty: false })
-    })
-    it('should return isDone = false and isWin = true with last dataload = -1', async () => {
-      const updatedDataloads = [...dataloads]
-      updatedDataloads[2].value = -1
-      updatedDataloads[2].valueDetail = null
-      const result = await challengeService.isChallengeDone(
-        userChallenge,
-        updatedDataloads
-      )
-      expect(result).toEqual({ isDone: true, isWin: true, isEmpty: true })
+      const dataloads: Dataload[] = [
+        {
+          date: DateTime.local()
+            .setZone('utc', {
+              keepLocalTime: true,
+            })
+            .minus({ days: 5 }),
+          value: 69.18029999999999,
+          valueDetail: [
+            45.127739999999996,
+            0.9048899999999999,
+            23.147669999999998,
+          ],
+        },
+        {
+          date: DateTime.local()
+            .setZone('utc', {
+              keepLocalTime: true,
+            })
+            .minus({ days: 4 }),
+          value: 61.65554999999999,
+          valueDetail: [
+            40.21918999999999,
+            0.8064649999999999,
+            20.629894999999998,
+          ],
+        },
+        {
+          date: DateTime.local()
+            .setZone('utc', {
+              keepLocalTime: true,
+            })
+            .minus({ days: 3 }),
+          value: 50.0,
+          valueDetail: [25.0, 5.0, 20.0],
+        },
+      ]
+      it('should return isDone = true, isWin = true and isEmpty=false when userConsumption < threshold', async () => {
+        const result = await challengeService.isChallengeDone(
+          userChallenge,
+          dataloads
+        )
+        expect(result).toEqual({ isDone: true, isWin: true, isEmpty: false })
+      })
+      it('should return isDone = true, isWin = true and isEmpty=true when missing value and userConsumption < threshold', async () => {
+        const updatedDataloads = cloneDeep(dataloads)
+        updatedDataloads[2].value = -1
+        updatedDataloads[2].valueDetail = null
+        const result = await challengeService.isChallengeDone(
+          userChallenge,
+          updatedDataloads
+        )
+        expect(result).toEqual({ isDone: true, isWin: true, isEmpty: true })
+      })
+      it('should return isDone = true, isWin = false and isEmpty=false when userConsumption >= threshold ', async () => {
+        const updatedUserChallenge = {
+          ...userChallenge,
+          duel: {
+            ...userChallenge.duel,
+            threshold: 100,
+            userConsumption: 200,
+          },
+        }
+        const result = await challengeService.isChallengeDone(
+          updatedUserChallenge,
+          dataloads
+        )
+        expect(result).toEqual({ isDone: true, isWin: false, isEmpty: false })
+      })
     })
-    it('should return isDone = false and isWin = false with dataload not complete', async () => {
-      const updatedUserChallenge = {
-        ...userChallenge,
+
+    describe('case date + lags + 1 is not reached', () => {
+      const userChallenge = {
+        ...userChallengeData[0],
+        state: UserChallengeState.DUEL,
         duel: {
-          ...userChallenge.duel,
-          duration: Duration.fromObject({ day: 4 }),
+          ...userChallengeData[0].duel,
+          state: UserDuelState.ONGOING,
+          duration: Duration.fromObject({ day: 3 }),
+          threshold: 200,
+          userConsumption: 199,
+          startDate: DateTime.local()
+            .setZone('utc', {
+              keepLocalTime: true,
+            })
+            .minus({ days: 3 }),
         },
       }
-      const result = await challengeService.isChallengeDone(
-        updatedUserChallenge,
-        dataloads
-      )
-      expect(result).toEqual({ isDone: false, isWin: false, isEmpty: false })
+      const dataloads: Dataload[] = [
+        {
+          date: DateTime.local()
+            .setZone('utc', {
+              keepLocalTime: true,
+            })
+            .minus({ days: 3 }),
+          value: 69.18029999999999,
+          valueDetail: [
+            45.127739999999996,
+            0.9048899999999999,
+            23.147669999999998,
+          ],
+        },
+        {
+          date: DateTime.local()
+            .setZone('utc', {
+              keepLocalTime: true,
+            })
+            .minus({ days: 2 }),
+          value: 61.65554999999999,
+          valueDetail: [
+            40.21918999999999,
+            0.8064649999999999,
+            20.629894999999998,
+          ],
+        },
+        {
+          date: DateTime.local()
+            .setZone('utc', {
+              keepLocalTime: true,
+            })
+            .minus({ days: 1 }),
+          value: 50,
+          valueDetail: [25.0, 5.0, 20.0],
+        },
+      ]
+      it('should return isDone = true, isWin = true and isEmpty=false when all data are available and userConsumption < threshold', async () => {
+        const result = await challengeService.isChallengeDone(
+          userChallenge,
+          dataloads
+        )
+        expect(result).toEqual({ isDone: true, isWin: true, isEmpty: false })
+      })
+
+      it('should return isDone = true and isWin = false and isEmpty=false when all data are available and userConsumption >= threshold ', async () => {
+        const updatedUserChallenge = {
+          ...userChallenge,
+          duel: {
+            ...userChallenge.duel,
+            threshold: 100,
+            userConsumption: 200,
+          },
+        }
+        const result = await challengeService.isChallengeDone(
+          updatedUserChallenge,
+          dataloads
+        )
+        expect(result).toEqual({ isDone: true, isWin: false, isEmpty: false })
+      })
+      it('should return isDone = false and isWin = false and isEmpty = false when last data is not available', async () => {
+        const updatedDataloads = cloneDeep(dataloads)
+        updatedDataloads[2].value = -1
+        updatedDataloads[2].valueDetail = null
+        const result = await challengeService.isChallengeDone(
+          userChallenge,
+          updatedDataloads
+        )
+        expect(result).toEqual({ isDone: false, isWin: false, isEmpty: false })
+      })
+      it('should return isDone = false and isWin = false and isEmpty = false when data in the middle is not available', async () => {
+        const updatedDataloads = cloneDeep(dataloads)
+        updatedDataloads[1].valueDetail = [20.0, -1, 10.0]
+        const result = await challengeService.isChallengeDone(
+          userChallenge,
+          updatedDataloads
+        )
+        expect(result).toEqual({ isDone: false, isWin: false, isEmpty: false })
+      })
     })
   })
+
   describe('loopVerificationExplorationCondition method', () => {
     it('should return updated userChallenge with non-conditional exploration', async () => {
       mockGetExplorationEntityById.mockResolvedValue(explorationEntity)
diff --git a/src/services/challenge.service.ts b/src/services/challenge.service.ts
index bcfc7f5d2..130fbaee5 100644
--- a/src/services/challenge.service.ts
+++ b/src/services/challenge.service.ts
@@ -146,8 +146,6 @@ export default class ChallengeService {
       fluidStatus[fluidCondition[0]].status !== FluidState.NOT_CONNECTED
     ) {
       isValid = true
-    } else {
-      isValid = false
     }
     return isValid
   }
@@ -759,14 +757,14 @@ export default class ChallengeService {
       if (diffFromNow >= fullDuration) {
         isDone = true
         dataloads.forEach((d: Dataload) => {
-          if (d.value === -1) isEmpty = true
-          else if (d.valueDetail) {
-            d.valueDetail.forEach((detail: number) => {
-              if (detail === -1) isEmpty = true
-            })
+          if (d.value === -1 || (d.valueDetail && d.valueDetail.includes(-1))) {
+            isEmpty = true
           }
         })
-        if (userChallenge.duel.userConsumption < userChallenge.duel.threshold) {
+        if (
+          isDone &&
+          userChallenge.duel.userConsumption < userChallenge.duel.threshold
+        ) {
           isWin = true
         }
       } else {
@@ -775,9 +773,17 @@ export default class ChallengeService {
           dataloads.length === duration &&
           dataloads[duration - 1].value !== -1
         ) {
-          console.log(dataloads[duration - 1].value)
           isDone = true
+          dataloads.forEach((d: Dataload) => {
+            if (
+              d.value === -1 ||
+              (d.valueDetail && d.valueDetail.includes(-1))
+            ) {
+              isDone = false
+            }
+          })
           if (
+            isDone &&
             userChallenge.duel.userConsumption < userChallenge.duel.threshold
           ) {
             isWin = true
-- 
GitLab