diff --git a/__tests__/findUserPdl.spec.js b/__tests__/findUserPdl.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..fbf854b832d6362b409f90c044383236b16fbc85 --- /dev/null +++ b/__tests__/findUserPdl.spec.js @@ -0,0 +1,51 @@ +const xml2js = require('xml2js') +const { errors } = require('cozy-konnector-libs') +const { findUserPdl } = require('../src/findUserPdl') + +const mockSoapRequest = jest.fn() +jest.mock('easy-soap-request', () => async () => mockSoapRequest()) + +jest.spyOn(xml2js, 'parseStringPromise').mockResolvedValue('response') + +const mockParseUserPdl = jest.fn() +jest.mock('../src/parsing', () => ({ + parseUserPdl: () => mockParseUserPdl(), +})) + +const responseMock = { + response: { + body: 'mockedBody', + }, +} + +describe('recherchePoint', () => { + it('should throw LOGIN_FAILED for too many responses', async () => { + mockSoapRequest.mockResolvedValue(responseMock) + mockParseUserPdl.mockImplementationOnce(() => { + throw new Error('fail') + }) + + try { + await findUserPdl() + expect(true).toBe(false) + } catch (error) { + expect(error).toBe(errors.LOGIN_FAILED) + } + }) + + it('should throw LOGIN_FAIL if soapRequest fails', async () => { + mockSoapRequest.mockRejectedValueOnce('reject') + try { + await findUserPdl() + expect(true).toBe(false) + } catch (error) { + expect(error).toBe(errors.LOGIN_FAILED) + } + }) + + it('should return a correct pdl number', async () => { + mockSoapRequest.mockResolvedValue(responseMock) + mockParseUserPdl.mockResolvedValue('12345') + expect(await findUserPdl()).toBe('12345') + }) +}) diff --git a/__tests__/requests/bo.spec.js b/__tests__/requests/bo.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..7e2102956c86c644a604a5de28d3e6aafcbca11b --- /dev/null +++ b/__tests__/requests/bo.spec.js @@ -0,0 +1,93 @@ +const { createBoConsent, getBoConsent } = require('../../src/requests/bo') +const { default: axios } = require('axios') +const { errors } = require('cozy-konnector-libs') + +jest.mock('axios') +describe('Backoffice routes', () => { + describe('createBoConsent', () => { + it('should send consent to BO', async () => { + axios.post.mockResolvedValueOnce({ id: 1 }) + const consent = await createBoConsent({ + pdl: 11111111111111, + name: 'POUET', + adresse: '20 rue du lac', + postalCode: '69003', + inseeCode: '69383', + }) + expect(consent).toBe({ id: 1 }) + }) + it('should handle unavailable BO', async () => { + axios.post.mockImplementationOnce(() => + Promise.reject(errors.MAINTENANCE) + ) + try { + await createBoConsent({ + pdl: 11111111111111, + name: 'POUET', + adresse: '20 rue du lac', + postalCode: '69003', + inseeCode: '69383', + }) + expect(true).toBe(false) + } catch (e) { + expect(e).toBe(errors.MAINTENANCE) + } + }) + }) + describe('getBoConsent', () => { + it('should get consent from BO', async () => { + axios.get.mockResolvedValueOnce({ + id: 1, + pointId: 11111111111111, + name: 'POUET', + adresse: '20 rue du lac', + postalCode: '69003', + inseeCode: '69383', + }) + const consent = await getBoConsent(1) + expect(consent).toBe({ + pointId: 11111111111111, + name: 'POUET', + adresse: '20 rue du lac', + postalCode: '69003', + inseeCode: '69383', + }) + }) + it('should get consent from BO with service id', async () => { + axios.get.mockResolvedValueOnce({ + id: 1, + pointId: 11111111111111, + name: 'POUET', + adresse: '20 rue du lac', + postalCode: '69003', + inseeCode: '69383', + serviceId: 'abcde', + }) + const consent = await getBoConsent(1) + expect(consent.serviceId).toBeTruthy() + expect(consent).toBe({ + pointId: 11111111111111, + name: 'POUET', + adresse: '20 rue du lac', + postalCode: '69003', + inseeCode: '69383', + serviceId: 'abcde', + }) + }) + it('should handle unavailable BO', async () => { + axios.get.mockImplementationOnce(() => Promise.reject(errors.MAINTENANCE)) + try { + await createBoConsent({ + pointId: 11111111111111, + name: 'POUET', + adresse: '20 rue du lac', + postalCode: '69003', + inseeCode: '69383', + }) + expect(true).toBe(false) + } catch (e) { + expect(e).toBe(errors.MAINTENANCE) + } + }) + }) +}) diff --git a/__tests__/requests/insee.spec.js b/__tests__/requests/insee.spec.js index 2be3245aa5957e09a7f1bc7604e6041aee8b8cdc..c8154a3ac93c639aea0534a603c1aeee3d82b39c 100644 --- a/__tests__/requests/insee.spec.js +++ b/__tests__/requests/insee.spec.js @@ -1,15 +1,24 @@ +const { errors } = require('cozy-konnector-libs') const { getInseeCode } = require('../../src/requests/insee') describe('getInseeCode', () => { it('should return a valid insee code for Lyon 7', async () => { expect(await getInseeCode(69007)).toEqual('69387') }) - it('should return null for a unexisting post code', async () => { - expect(await getInseeCode(69069)).toEqual(null) + it('should throw USER_ACTION_NEEDED for a unexisting post code', async () => { + try { + await getInseeCode(69069) + } catch (error) { + expect(error).toEqual(errors.USER_ACTION_NEEDED) + } }) - it('should return null for post code 69290 when city is not provided', async () => { - expect(await getInseeCode(69290)).toEqual(null) + it('should throw USER_ACTION_NEEDED for post code 69290 when city is not provided', async () => { + try { + await getInseeCode(69290) + } catch (error) { + expect(error).toEqual(errors.USER_ACTION_NEEDED) + } }) it('should return Craponne insee code for post code 69290', async () => { diff --git a/__tests__/requests/sge.spec.js b/__tests__/requests/sge.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..28770b2b649b4aa16a4fc2149a449585baa9de6a --- /dev/null +++ b/__tests__/requests/sge.spec.js @@ -0,0 +1,6 @@ +describe('Sge routes', () => { + //TODO: write TU + it('should be true', () => { + expect(true).toBe(true) + }) +}) diff --git a/__tests__/verifyUserIdentity.spec.js b/__tests__/verifyUserIdentity.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..98263253c4a140dee01ec3300581336f4bb762d2 --- /dev/null +++ b/__tests__/verifyUserIdentity.spec.js @@ -0,0 +1,55 @@ +const { errors } = require('cozy-konnector-libs') +const { verifyUserIdentity } = require('../src/verifyUserIdentity') + +jest.mock('../src/requests/insee', () => ({ + getInseeCode: jest.fn().mockResolvedValue(69), +})) + +jest.mock('../src/findUserPdl', () => ({ + findUserPdl: jest.fn().mockResolvedValue('12345'), +})) + +jest.mock('../src/index', () => ({ + start: jest.fn(), +})) + +describe('verifyUserIdentity', () => { + it('should throw LOGIN_FAILED when pdl give and recieved are NOT matching 🚫', async () => { + try { + await verifyUserIdentity( + { + name: 'John', + address: '1 street', + pointId: 987654321, + postalCode: '69069', + }, + 'azertyuiop', + 'apiKey', + 'login@user.com' + ) + expect(true).toBe(false) + } catch (error) { + expect(error).toBe(errors.LOGIN_FAILED) + } + }) + + it('should return void when pdl give and recieved are matching ✅', async () => { + expect.assertions(1) + try { + await verifyUserIdentity( + { + name: 'John', + address: '1 street', + pointId: '12345', + postalCode: '69069', + }, + 'azertyuiop', + 'apiKey', + 'login@user.com' + ) + expect(true).toBeTruthy() + } catch (error) { + expect(true).toBe(false) + } + }) +}) diff --git a/importedData.json b/importedData.json index 9e26dfeeb6e641a33dae4961196235bdb965b21b..5b3d3e991426163b55da470e84c7a4e80fab9b56 100644 --- a/importedData.json +++ b/importedData.json @@ -1 +1,6 @@ -{} \ No newline at end of file +{ + "io.cozy.files": [], + "com.grandlyon.enedis.year": [], + "com.grandlyon.enedis.month": [], + "com.grandlyon.enedis.minute": [] +} \ No newline at end of file diff --git a/src/findUserPdl.js b/src/findUserPdl.js new file mode 100644 index 0000000000000000000000000000000000000000..ba1871244813edb30b3f7ff343ccbe9cdf19e613 --- /dev/null +++ b/src/findUserPdl.js @@ -0,0 +1,56 @@ +// @ts-check +const { log, errors } = require('cozy-konnector-libs') +const soapRequest = require('easy-soap-request') +const { parseUserPdl, parseTags, parseValue } = require('./parsing') +const { rechercherPoint } = require('./requests/sge') +const xml2js = require('xml2js') + +/** + * @param {string} url + * @param {string} apiAuthKey + * @param {string} appLogin + * @param {string} name + * @param {string} address + * @param {string} postalCode + * @param {string} inseeCode + * @return {Promise<string | null>} User Pdl + */ +async function findUserPdl( + url, + apiAuthKey, + appLogin, + name, + address, + postalCode, + inseeCode +) { + log('info', 'Fetching user data') + const sgeHeaders = { + 'Content-Type': 'text/xml;charset=UTF-8', + apikey: apiAuthKey, + } + + const { response } = await soapRequest({ + url: url, + headers: sgeHeaders, + xml: rechercherPoint(appLogin, name, postalCode, inseeCode, address), + }).catch(err => { + log('error', 'rechercherPointResponse') + log('error', err) + throw errors.LOGIN_FAILED + }) + + const parsedReply = await xml2js.parseStringPromise(response.body, { + tagNameProcessors: [parseTags], + valueProcessors: [parseValue], + explicitArray: false, + }) + + try { + return parseUserPdl(parsedReply) + } catch (error) { + throw errors.LOGIN_FAILED + } +} + +module.exports = { findUserPdl } diff --git a/src/index.js b/src/index.js index 1d51dce19a79931161028e7f05bf51fe02003936..20790eeda46e1e5895a2ce5dcfb0cd9171eecd5b 100644 --- a/src/index.js +++ b/src/index.js @@ -17,13 +17,12 @@ const { formateDataForDoctype, parseTags, parseValue, - parseUserPdl, } = require('./parsing') const { consulterDonneesTechniquesContractuelles, consultationMesuresDetailleesMaxPower, consultationMesuresDetaillees, - rechercherPoint, + commanderCollectePublicationMesures, commanderArretServiceSouscritMesures, } = require('./requests/sge') @@ -33,7 +32,7 @@ const { getBoConsent, deleteBoConsent, } = require('./requests/bo') -const { getInseeCode } = require('./requests/insee') +const { verifyUserIdentity } = require('./verifyUserIdentity') moment.locale('fr') // set the language moment.tz.setDefault('Europe/Paris') // set the timezone @@ -81,11 +80,8 @@ async function start(fields, cozyParameters) { log('info', 'User Logging...') if (await isFirstStart()) { - if ( - !(await verifyUserIdentity(fields, baseUrl, apiAuthKey, loginUtilisateur)) - ) { - throw errors.LOGIN_FAILED - } + await verifyUserIdentity(fields, baseUrl, apiAuthKey, loginUtilisateur) + await createBoConsent() //TODO: remove because useless ? Done later in code // const startDate = await getDataStartDate( @@ -97,6 +93,7 @@ async function start(fields, cozyParameters) { await commanderCollectePublicationMesures() await updateBoConsent() } else { + //AlternateStart await getBoConsent() if (!(await verifyUserIdentity(fields))) { await deleteBoConsent() @@ -109,39 +106,6 @@ async function start(fields, cozyParameters) { await gatherData(baseUrl, apiAuthKey, loginUtilisateur, fields.pointId) } -/** - * Verify user identity - * @param {object} fields - * @param {string} baseUrl - * @param {string} apiAuthKey - * @param {string} loginUtilisateur - */ -async function verifyUserIdentity( - fields, - baseUrl, - apiAuthKey, - loginUtilisateur -) { - const inseeCode = await getInseeCode(fields.postalCode) - if (!inseeCode) throw errors.USER_ACTION_NEEDED - - const pdl = await findUserPdl( - `${baseUrl}/enedis_SDE_recherche-point/1.0`, - apiAuthKey, - loginUtilisateur, - fields.name, - fields.addresse, - fields.postalCode, - inseeCode - ) - - if (fields.pointId != pdl) { - log('error', 'PointId does not match') - return false - } - return true -} - /** * Main method for gathering data * @param {string} baseUrl @@ -476,41 +440,3 @@ function isFirstStart() { //TODO: Implement return true } - -/** - * @return {Promise<string>} User Pdl - */ -async function findUserPdl( - url, - apiAuthKey, - appLogin, - name, - addresse, - postalCode, - inseeCode -) { - log('info', 'Fetching user data') - const sgeHeaders = { - 'Content-Type': 'text/xml;charset=UTF-8', - apikey: apiAuthKey, - } - const { response } = await soapRequest({ - url: url, - headers: sgeHeaders, - xml: rechercherPoint(appLogin, name, postalCode, inseeCode, addresse), - }).catch(err => { - log('error', 'rechercherPointResponse') - log('error', err) - throw errors.LOGIN_FAILED - //TODO: handling code error SGT4F6 and SGT432 into USER_ACTIon_NEEDED - }) - - const parsedReply = await xml2js.parseStringPromise(response.body, { - tagNameProcessors: [parseTags], - valueProcessors: [parseValue], - explicitArray: false, - }) - - //TODO: handle errors - return parseUserPdl(parsedReply) -} diff --git a/src/requests/insee.js b/src/requests/insee.js index b32fef4178f98c0ddc5f60f13b746b125a9c9924..a616d9d8d9e48dd03db69aaecf8eafda3558d5b4 100644 --- a/src/requests/insee.js +++ b/src/requests/insee.js @@ -1,6 +1,6 @@ // @ts-check const { default: axios } = require('axios') -const { log } = require('cozy-konnector-libs') +const { log, errors } = require('cozy-konnector-libs') const API_URL = 'https://apicarto.ign.fr/api/codes-postaux/communes' @@ -8,7 +8,7 @@ const API_URL = 'https://apicarto.ign.fr/api/codes-postaux/communes' * Return inseeCode given a postalCode * @param {string} postalCode * @param {string} [city] - * @return {Promise<string | null>} inseeCode + * @return {Promise<string>} inseeCode */ async function getInseeCode(postalCode, city) { try { @@ -18,7 +18,7 @@ async function getInseeCode(postalCode, city) { if (response.data.length === 1) { return response.data[0].codeCommune } else { - if (!city) return null + if (!city) throw errors.USER_ACTION_NEEDED const filteredResponse = response.data.filter( town => town.nomCommune.toLowerCase() === city.toLowerCase() @@ -30,7 +30,7 @@ async function getInseeCode(postalCode, city) { 'error', `Query getInseeCode failed for postalCode ${postalCode} / ${city}` ) - return null + throw errors.USER_ACTION_NEEDED } } diff --git a/src/types.js b/src/types.js index b2d1f4016528c80d44ce27bc9b6c461fd1c170c6..b5cff384d39d1698486456f6abbf49fce321ca55 100644 --- a/src/types.js +++ b/src/types.js @@ -15,12 +15,24 @@ * @property {string} d */ +// /** +// * User definition +// * @typedef {object} User +// * @property {string} name +// * @property {string} address +// * @property {string} postalCode +// * @property {string} pointId +// * @property {string} [inseeCode] +// */ + /** - * User definition - * @typedef {object} User + * Consent definition + * @typedef {object} Consent + * @property {number} pointId * @property {string} name - * @property {string} address + * @property {string} adresse * @property {string} postalCode - * @property {string} pointId - * @property {string} [inseeCode] + * @property {string} inseeCode + * @property {string} [serviceId] + * @property {number} [id] */ diff --git a/src/verifyUserIdentity.js b/src/verifyUserIdentity.js new file mode 100644 index 0000000000000000000000000000000000000000..2d46ceda30cdf1ef223cfbef110b6dbaa6b9d2d5 --- /dev/null +++ b/src/verifyUserIdentity.js @@ -0,0 +1,38 @@ +// @ts-check +const { log, errors } = require('cozy-konnector-libs') +const { findUserPdl } = require('./findUserPdl') +const { getInseeCode } = require('./requests/insee') + +/** + * Verify user identity + * @param {object} fields + * @param {string} baseUrl + * @param {string} apiAuthKey + * @param {string} loginUtilisateur + * @returns {Promise<void>} + */ +async function verifyUserIdentity( + fields, + baseUrl, + apiAuthKey, + loginUtilisateur +) { + const inseeCode = await getInseeCode(fields.postalCode) + + const pdl = await findUserPdl( + `${baseUrl}/enedis_SDE_recherche-point/1.0`, + apiAuthKey, + loginUtilisateur, + fields.name, + fields.address, + fields.postalCode, + inseeCode + ) + + if (fields.pointId != pdl) { + log('error', 'PointId does not match') + throw errors.LOGIN_FAILED + } +} + +module.exports = { verifyUserIdentity } diff --git a/yarn.lock b/yarn.lock index 30265ef2e1efa24124c1ac721c2d478e079a7242..2c0a6a47f2fc138ea1cd9225767cf9cd11146602 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5943,7 +5943,7 @@ mkdirp@^0.5.1: dependencies: minimist "^1.2.6" -mkdirp@^1.0.4: +mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==