From 395e19197034d2814f3177d9be547c62f6cd092f Mon Sep 17 00:00:00 2001 From: Pierre-Gilles Leymarie Date: Fri, 29 Mar 2024 09:28:13 +0100 Subject: [PATCH] Enedis: Better catch error when user consent is no longer found --- core/enedis/enedis.js | 41 ++++++++++++------ .../enedis.dailyRefreshAllUsers.test.js | 42 +++++++++++++++++++ 2 files changed, 71 insertions(+), 12 deletions(-) diff --git a/core/enedis/enedis.js b/core/enedis/enedis.js index 52ee260..eebfca2 100644 --- a/core/enedis/enedis.js +++ b/core/enedis/enedis.js @@ -24,6 +24,17 @@ const { const ENEDIS_GRANT_ACCESS_TOKEN_REDIS_PREFIX = 'enedis-grant-access-token:'; +const getDevicesWithEnedisActivated = ` + SELECT DISTINCT t_user.id, t_user.account_id, + t_device.id as device_id, t_device.provider_refresh_token + FROM t_user + INNER JOIN t_device ON t_user.id = t_device.user_id + WHERE t_user.account_id = $1 + AND t_device.revoked = false + AND t_device.is_deleted = false + AND t_device.client_id = $2; + `; + module.exports = function EnedisModel(logger, db, redisClient) { const { ENEDIS_GRANT_CLIENT_ID, ENEDIS_GRANT_CLIENT_SECRET, ENEDIS_BACKEND_URL, ENEDIS_GLADYS_PLUS_REDIRECT_URI } = process.env; @@ -69,16 +80,7 @@ module.exports = function EnedisModel(logger, db, redisClient) { if (accessTokenInRedis) { return accessTokenInRedis; } - const getDevicesWithEnedisActivated = ` - SELECT DISTINCT t_user.id, t_user.account_id, - t_device.id as device_id, t_device.provider_refresh_token - FROM t_user - INNER JOIN t_device ON t_user.id = t_device.user_id - WHERE t_user.account_id = $1 - AND t_device.revoked = false - AND t_device.is_deleted = false - AND t_device.client_id = $2; - `; + const devices = await db.query(getDevicesWithEnedisActivated, [accountId, ENEDIS_GRANT_CLIENT_ID]); if (devices.length === 0) { logger.warn(`Forbidden: Enedis Oauth process was not done`); @@ -226,6 +228,11 @@ module.exports = function EnedisModel(logger, db, redisClient) { async function getContract(accountId, usagePointId) { logger.info(`Enedis - get contract for usagePoint = ${usagePointId}`); const accessToken = await getAccessToken(accountId); + const devices = await db.query(getDevicesWithEnedisActivated, [accountId, ENEDIS_GRANT_CLIENT_ID]); + if (devices.length === 0) { + logger.warn(`Forbidden: Enedis Oauth process was not done`); + throw new ForbiddenError(); + } const data = { usage_point_id: usagePointId, }; @@ -233,7 +240,13 @@ module.exports = function EnedisModel(logger, db, redisClient) { try { response = await makeRequest('/customers_upc/v5/usage_points/contracts', data, accessToken); } catch (e) { - logger.error(e); + // if status is 403, error is "No consent can be found for this customer and this usage point" + // Revoke device to avoid re-hitting this error + if (get(e, 'response.status') === 403) { + await db.t_device.update(devices[0].device_id, { + revoked: true, + }); + } throw e; } const lastActivationDate = get(response, 'customer.usage_points.0.contracts.last_activation_date'); @@ -328,7 +341,11 @@ module.exports = function EnedisModel(logger, db, redisClient) { const oneWeekAgo = dayjs().subtract(6, 'day'); logger.info(`Enedis: Daily refresh of all users. Refreshing ${usersToRefresh.length} users`); await Promise.each(usersToRefresh, async (userToRefresh) => { - await refreshAllData({ userId: userToRefresh.id, start: oneWeekAgo }); + try { + await refreshAllData({ userId: userToRefresh.id, start: oneWeekAgo }); + } catch (e) { + logger.error(`Failed to refresh user = ${userToRefresh.id}`); + } }); } async function enedisSyncData(job) { diff --git a/test/core/enedis/enedis.dailyRefreshAllUsers.test.js b/test/core/enedis/enedis.dailyRefreshAllUsers.test.js index 4a267a6..72c9be8 100644 --- a/test/core/enedis/enedis.dailyRefreshAllUsers.test.js +++ b/test/core/enedis/enedis.dailyRefreshAllUsers.test.js @@ -43,6 +43,39 @@ describe('EnedisWorker.dailyRefreshAllUsers', function Describe() { await shutdown(); }); it('should publish 2 jobs', async () => { + // Insert broken enedis user data + await TEST_DATABASE_INSTANCE.t_account.insert({ + id: 'ab9c205a-d090-4c97-84b5-d2a9eb932201', + name: 'user_with_broken_enedis_sync@test.fr', + current_period_end: new Date('2050-11-19T16:00:00.000Z'), + status: 'active', + }); + await TEST_DATABASE_INSTANCE.t_user.insert({ + id: '1258b0b1-4f5a-4ea6-926b-755ab725eeb3', + email_confirmation_token_hash: 'dlkflmdskflmdskfmldskfkdsfldsfldksmfkdslfksdf', + email: 'user_with_broken_enedis_sync@test.fr', + email_confirmed: true, + srp_salt: 'ddd', + srp_verifier: 'sdfsdf', + two_factor_enabled: false, + role: 'admin', + account_id: 'ab9c205a-d090-4c97-84b5-d2a9eb932201', + language: 'fr', + name: 'Broken Enedis', + }); + await TEST_DATABASE_INSTANCE.t_device.insert({ + id: '002a93b7-e21b-48bb-a414-29bcc31615ba', + user_id: '1258b0b1-4f5a-4ea6-926b-755ab725eeb3', + client_id: process.env.ENEDIS_GRANT_CLIENT_ID, + name: 'Enedis', + refresh_token_hash: 'a43fc595dfbf78cf52e644cd30e4cb45ad1f4ef2472c6819cf4546afdece8888', + created_at: '2024-10-16T02:21:25.901Z', + last_seen: '2024-10-16T02:21:25.901Z', + }); + await TEST_DATABASE_INSTANCE.t_enedis_usage_point.insert({ + account_id: 'ab9c205a-d090-4c97-84b5-d2a9eb932201', + usage_point_id: 'broken-usage-point', + }); // First, finalize Enedis Oauth process nock(`https://${process.env.ENEDIS_BACKEND_URL}`) .post('/oauth2/v3/token', (body) => { @@ -63,10 +96,18 @@ describe('EnedisWorker.dailyRefreshAllUsers', function Describe() { usage_points_id: '16401220101758,16401220101710,16401220101720', apigo_client_id: '73cd2d7f-e361-b7f6-48359493ed2c', }); + nock(`https://${process.env.ENEDIS_BACKEND_URL}`) .get('/customers_upc/v5/usage_points/contracts') .query(contractQueryParams) .reply(200, contractData); + nock(`https://${process.env.ENEDIS_BACKEND_URL}`) + .get('/customers_upc/v5/usage_points/contracts') + .query({ usage_point_id: 'broken-usage-point' }) + .reply(403, { + error: 'ERRE001150', + error_description: 'No consent can be found for this customer and this usage point.', + }); await request(TEST_BACKEND_APP) .post('/enedis/finalize') .send({ @@ -78,6 +119,7 @@ describe('EnedisWorker.dailyRefreshAllUsers', function Describe() { .expect('Content-Type', /json/) .expect(200); mockAccessTokenRefresh(); + mockAccessTokenRefresh(); await enedisModel.dailyRefreshOfAllUsers(); const counts = await enedisModel.queue.getJobCounts('wait', 'completed', 'failed'); expect(counts).to.deep.equal({ wait: 2, completed: 0, failed: 0 });