diff --git a/.gitignore b/.gitignore index 408f16dd282c1265bd4a261ba086613fa3bb76d5..0a20bda443ea89da7e3abcaa41a1bdfdc4c3104a 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ desktop.ini .flooignore .idea/ coverage/* +junit.xml # Default # /!\ KEEP THIS SECTION THE LAST ONE !.gitkeep diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1ecbeffe212bf59e5699fd0fd98116492ffda2fd..129896626ffb490e705bfcddac4b4d7f75c208b6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,6 +32,22 @@ build-dev: - git config --global credential.helper store - yarn deploy-dev +unit-test: + image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/node:16.14.2-alpine3.14 + stage: test + before_script: + - apk add git + - apk add bash + script: + - yarn + - yarn test --ci --reporters=default --reporters=jest-junit + coverage: "/All files[^|]*\\|[^|]*\\s+([\\d\\.]+)/" + artifacts: + when: always + reports: + junit: + - junit.xml + build: image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/node:16.14.2-alpine3.14 stage: build diff --git a/__tests__/aggregate.spec.js b/__tests__/aggregate.spec.js index 5f304ad4560e92d7b5c78830e8272ed0d68fa2fb..0b43f693f937ad3f72476e9220f92431eed00f40 100644 --- a/__tests__/aggregate.spec.js +++ b/__tests__/aggregate.spec.js @@ -1,7 +1,89 @@ const { buildAgregatedData } = require('../src/aggregate') +const { cozyClient } = require('cozy-konnector-libs') + describe('buildAgregatedData', () => { it('should return empty', async () => { const reply = await buildAgregatedData([], 'com.enedis.day') expect(reply).toEqual([]) }) + it('should return year value', async () => { + const reply = await buildAgregatedData( + { + '2022': 36, + }, + 'com.grandlyon.enedis.year' + ) + expect(reply).toEqual([ + { + day: 0, + hour: 0, + load: 36, + minute: 0, + month: 1, + year: 2022, + }, + ]) + }) + it('should return year value with doc existing', async () => { + const spy = jest.spyOn(cozyClient.data, 'findAll') + spy.mockResolvedValueOnce([{ year: 2022, month: 8, day: 1, load: 1 }]) + const reply = await buildAgregatedData( + { + '2022': 36, + }, + 'com.grandlyon.enedis.year' + ) + expect(reply).toEqual([ + { + day: 0, + hour: 0, + load: 37, + minute: 0, + month: 1, + year: 2022, + }, + ]) + }) + it('should return month value', async () => { + const spy = jest.spyOn(cozyClient.data, 'findAll') + spy.mockResolvedValueOnce([{ year: 2022, month: 8, day: 1, load: 1 }]) + const reply = await buildAgregatedData( + { + '2022-08': 36, + }, + 'com.grandlyon.enedis.month' + ) + expect(reply).toEqual([ + { + day: 0, + hour: 0, + load: 37, + minute: 0, + month: 8, + year: 2022, + }, + ]) + }) + it('should return daily value', async () => { + const spy = jest.spyOn(cozyClient.data, 'findAll') + spy.mockResolvedValueOnce([ + { year: 2022, month: 8, day: 1, load: 1, hour: 13 }, + ]) + const reply = await buildAgregatedData( + { + '2022-08-01-13:39:25+00:00': 36, + }, + 'com.grandlyon.enedis.minute' + ) + expect(reply).toEqual([ + { + day: 1, + hour: 13, + load: 37, + minute: 0, + month: 8, + year: 2022, + }, + ]) + }) }) diff --git a/__tests__/requests/insee.spec.js b/__tests__/requests/insee.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..2be3245aa5957e09a7f1bc7604e6041aee8b8cdc --- /dev/null +++ b/__tests__/requests/insee.spec.js @@ -0,0 +1,22 @@ +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 return null for post code 69290 when city is not provided', async () => { + expect(await getInseeCode(69290)).toEqual(null) + }) + + it('should return Craponne insee code for post code 69290', async () => { + expect(await getInseeCode(69290, 'CRAPONNE')).toEqual('69069') + }) + + it('should return Pollionnay insee code for post code 69290', async () => { + expect(await getInseeCode(69290, 'POLLIONNAY')).toEqual('69154') + }) +}) diff --git a/importedData.json b/importedData.json new file mode 100644 index 0000000000000000000000000000000000000000..9e26dfeeb6e641a33dae4961196235bdb965b21b --- /dev/null +++ b/importedData.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/package.json b/package.json index 113d04644a03b4094eea878762d9f984af90c67d..eb3c5f85ada17a90a7928cd8d91bd1490381b56a 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "travisDeployKey": "./bin/generate_travis_deploy_key" }, "dependencies": { + "axios": "^0.27.2", "cozy-konnector-libs": "4.54.0", "easy-soap-request": "^4.7.0", "jest": "^28.1.3", @@ -52,6 +53,7 @@ "cozy-konnector-build": "1.3.4", "eslint-config-cozy-app": "1.3.3", "eslint-plugin-prettier": "^4.0.0", - "git-directory-deploy": "1.5.1" + "git-directory-deploy": "1.5.1", + "jest-junit": "^14.0.0" } } diff --git a/src/aggregate.js b/src/aggregate.js index 808d34f0c36877aa7ab15856851e60a8a9168965..00a66199a0ab12948a565b035171fa7ac60c1b29 100644 --- a/src/aggregate.js +++ b/src/aggregate.js @@ -9,7 +9,7 @@ async function buildAgregatedData(data, doctype) { let agregatedData = [] // eslint-disable-next-line no-unused-vars for (let [key, value] of Object.entries(data)) { - const data = await buildDataFromKey(doctype, key, value) + const data = buildDataFromKey(doctype, key, value) const oldValue = await resetInProgressAggregatedData(data, doctype) data.load += oldValue agregatedData.push(data) @@ -23,7 +23,7 @@ async function buildAgregatedData(data, doctype) { * For year doctype: key = "YYYY" * For month doctype: key = "YYYY-MM" */ -async function buildDataFromKey(doctype, key, value) { +function buildDataFromKey(doctype, key, value) { let year, month, day, hour if (doctype === 'com.grandlyon.enedis.year') { year = key diff --git a/src/index.js b/src/index.js index 9a6fc252f5d07abf70d747b5d818ded9c325b965..1d51dce19a79931161028e7f05bf51fe02003936 100644 --- a/src/index.js +++ b/src/index.js @@ -17,6 +17,7 @@ const { formateDataForDoctype, parseTags, parseValue, + parseUserPdl, } = require('./parsing') const { consulterDonneesTechniquesContractuelles, @@ -80,7 +81,9 @@ async function start(fields, cozyParameters) { log('info', 'User Logging...') if (await isFirstStart()) { - if (!(await verifyUserIdentity(fields))) { + if ( + !(await verifyUserIdentity(fields, baseUrl, apiAuthKey, loginUtilisateur)) + ) { throw errors.LOGIN_FAILED } await createBoConsent() @@ -109,16 +112,30 @@ async function start(fields, cozyParameters) { /** * Verify user identity * @param {object} fields + * @param {string} baseUrl + * @param {string} apiAuthKey + * @param {string} loginUtilisateur */ -async function verifyUserIdentity(fields) { - const inseeCode = getInseeCode(fields.postalCode) - const user = await findUser( +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 !== user.pointId) { + + if (fields.pointId != pdl) { log('error', 'PointId does not match') return false } @@ -170,14 +187,14 @@ async function gatherData(baseUrl, apiAuthKey, loginUtilisateur, pointId) { */ async function getDataStartDate(url, apiAuthKey, userLogin, pointId) { log('info', 'Fetching data start date') - const sampleHeaders = { + const sgeHeaders = { 'Content-Type': 'text/xml;charset=UTF-8', apikey: apiAuthKey, } const { response } = await soapRequest({ url: url, - headers: sampleHeaders, + headers: sgeHeaders, xml: consulterDonneesTechniquesContractuelles(pointId, userLogin), }).catch(err => { log('error', 'technicalDataResponse') @@ -205,7 +222,7 @@ async function getDataStartDate(url, apiAuthKey, userLogin, pointId) { */ async function getData(url, apiAuthKey, userLogin, pointId) { log('info', 'Fetching data') - const sampleHeaders = { + const sgeHeaders = { 'Content-Type': 'text/xml;charset=UTF-8', apikey: apiAuthKey, } @@ -214,7 +231,7 @@ async function getData(url, apiAuthKey, userLogin, pointId) { const { response } = await soapRequest({ url: url, - headers: sampleHeaders, + headers: sgeHeaders, xml: consultationMesuresDetaillees( pointId, userLogin, @@ -249,7 +266,7 @@ async function getData(url, apiAuthKey, userLogin, pointId) { */ async function getMaxPowerData(url, apiAuthKey, userLogin, pointId) { log('info', 'Fetching Max Power data') - const sampleHeaders = { + const sgeHeaders = { 'Content-Type': 'text/xml;charset=UTF-8', apikey: apiAuthKey, } @@ -258,7 +275,7 @@ async function getMaxPowerData(url, apiAuthKey, userLogin, pointId) { const { response } = await soapRequest({ url: url, - headers: sampleHeaders, + headers: sgeHeaders, xml: consultationMesuresDetailleesMaxPower( pointId, userLogin, @@ -306,7 +323,7 @@ function setStartDate() { */ async function getDataHalfHour(url, apiAuthKey, userLogin, pointId) { log('info', 'Fetching data') - const sampleHeaders = { + const sgeHeaders = { 'Content-Type': 'text/xml;charset=UTF-8', apikey: apiAuthKey, } @@ -326,7 +343,7 @@ async function getDataHalfHour(url, apiAuthKey, userLogin, pointId) { .format('YYYY-MM-DD') const { response } = await soapRequest({ url: url, - headers: sampleHeaders, + headers: sgeHeaders, xml: consultationMesuresDetaillees( pointId, userLogin, @@ -455,14 +472,15 @@ async function agregateMonthAndYearData(data) { * @returns {boolean} */ function isFirstStart() { + console.log('isFirstStart') //TODO: Implement - return false + return true } /** - * @return {User} + * @return {Promise<string>} User Pdl */ -async function findUser( +async function findUserPdl( url, apiAuthKey, appLogin, @@ -472,15 +490,14 @@ async function findUser( inseeCode ) { log('info', 'Fetching user data') - const sampleHeaders = { + const sgeHeaders = { 'Content-Type': 'text/xml;charset=UTF-8', apikey: apiAuthKey, } - const { response } = await soapRequest({ url: url, - headers: sampleHeaders, - xml: rechercherPoint(appLogin, name, addresse, postalCode, inseeCode), + headers: sgeHeaders, + xml: rechercherPoint(appLogin, name, postalCode, inseeCode, addresse), }).catch(err => { log('error', 'rechercherPointResponse') log('error', err) @@ -488,14 +505,12 @@ async function findUser( //TODO: handling code error SGT4F6 and SGT432 into USER_ACTIon_NEEDED }) - //TODO: handle reply - xml2js.parseString( - response.body, - { - tagNameProcessors: [parseTags], - valueProcessors: [parseValue], - explicitArray: false, - }, - processStartDate() - ) + const parsedReply = await xml2js.parseStringPromise(response.body, { + tagNameProcessors: [parseTags], + valueProcessors: [parseValue], + explicitArray: false, + }) + + //TODO: handle errors + return parseUserPdl(parsedReply) } diff --git a/src/parsing.js b/src/parsing.js index 64e6e7e57bd7e034d79bc4aceac97bce4ec11081..05439e132dbcf03fbb96462b38e16e5b23c032f4 100644 --- a/src/parsing.js +++ b/src/parsing.js @@ -2,6 +2,19 @@ const { log } = require('cozy-konnector-libs') const moment = require('moment') +/** + * Return User PDL + * @param {string} result + * @returns {string} + */ +function parseUserPdl(result) { + log('info', 'Parsing User Pdl') + const json = JSON.stringify(result) + return JSON.parse(json)['Envelope']['Body']['rechercherPointResponse'][ + 'points' + ]['point']['$'].id +} + /** * Return start date * @param {string} result @@ -9,7 +22,7 @@ const moment = require('moment') */ function parseSgeXmlTechnicalData(result) { log('info', 'Parsing technical data') - let json = JSON.stringify(result) + const json = JSON.stringify(result) return JSON.parse(json)['Envelope']['Body'][ 'consulterDonneesTechniquesContractuellesResponse' ]['point']['donneesGenerales'][ @@ -24,7 +37,7 @@ function parseSgeXmlTechnicalData(result) { */ function parseSgeXmlData(result) { log('info', 'Parsing list of documents') - let json = JSON.stringify(result) + const json = JSON.stringify(result) return JSON.parse(json)['Envelope']['Body'][ 'consulterMesuresDetailleesResponse' ]['grandeur']['mesure'] @@ -38,7 +51,7 @@ function parseSgeXmlData(result) { async function formateDataForDoctype(data) { log('info', 'Formating data') return data.map(record => { - let date = moment(record.d, 'YYYY/MM/DD h:mm:ss') + const date = moment(record.d, 'YYYY/MM/DD h:mm:ss') return { load: record.v, year: parseInt(date.format('YYYY')), @@ -82,4 +95,5 @@ module.exports = { formateDataForDoctype, parseTags, parseValue, + parseUserPdl, } diff --git a/src/requests/insee.js b/src/requests/insee.js index fd34efe6d4f34fc63b7b74005fbe71ba1909e088..b32fef4178f98c0ddc5f60f13b746b125a9c9924 100644 --- a/src/requests/insee.js +++ b/src/requests/insee.js @@ -1,15 +1,37 @@ // @ts-check +const { default: axios } = require('axios') const { log } = require('cozy-konnector-libs') +const API_URL = 'https://apicarto.ign.fr/api/codes-postaux/communes' + /** * Return inseeCode given a postalCode * @param {string} postalCode - * @return {string} inseeCode + * @param {string} [city] + * @return {Promise<string | null>} inseeCode */ -function getInseeCode(postalCode) { - //TODO: Implement - log('info', `Query getInseeCode for postalCode ${postalCode}`) - throw new Error('Function not implemented.') +async function getInseeCode(postalCode, city) { + try { + log('info', `Query getInseeCode for postalCode ${postalCode} / ${city}`) + const response = await axios.get(`${API_URL}/${postalCode}`) + + if (response.data.length === 1) { + return response.data[0].codeCommune + } else { + if (!city) return null + + const filteredResponse = response.data.filter( + town => town.nomCommune.toLowerCase() === city.toLowerCase() + ) + return filteredResponse[0].codeCommune + } + } catch (error) { + log( + 'error', + `Query getInseeCode failed for postalCode ${postalCode} / ${city}` + ) + return null + } } module.exports = { diff --git a/src/requests/sge.js b/src/requests/sge.js index 0dd4b071d4058814360efd651f4b8b13621e3885..22669bdcb1f4fe6dc9b1b083da64cb6f8b213fbe 100644 --- a/src/requests/sge.js +++ b/src/requests/sge.js @@ -125,11 +125,15 @@ function consulterDonneesTechniquesContractuelles(pointId, appLogin) { * @param {string} name * @param {string} postalCode * @param {string} inseeCode - * @param {string} address + * @param {string} [address] * @returns {string} PDL */ function rechercherPoint(appLogin, name, postalCode, inseeCode, address) { - log('info', `Query rechercherPoint - postal code: ${postalCode}`) + log( + 'info', + `Query rechercherPoint - postal code / insee code: ${postalCode} / ${inseeCode}` + ) + //TODO: handle address return `<?xml version='1.0' encoding='utf-8'?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v2="http://www.enedis.fr/sge/b2b/services/rechercherpoint/v2.0" diff --git a/yarn.lock b/yarn.lock index 59b7dbb0eaa5f2a32cb08b2efdafaba44b18511f..30265ef2e1efa24124c1ac721c2d478e079a7242 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2385,6 +2385,14 @@ axios@^0.26.1: dependencies: follow-redirects "^1.14.8" +axios@^0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== + dependencies: + follow-redirects "^1.14.9" + form-data "^4.0.0" + babel-eslint@10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed" @@ -2925,7 +2933,7 @@ colors@^1.1.2: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -4382,11 +4390,25 @@ follow-redirects@^1.14.8: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== +follow-redirects@^1.14.9: + version "1.15.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" + integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -5284,6 +5306,16 @@ jest-haste-map@^28.1.3: optionalDependencies: fsevents "^2.3.2" +jest-junit@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-14.0.0.tgz#f69fc31bab32224848f443480c2c808fccb2a802" + integrity sha512-kALvBDegstTROfDGXH71UGD7k5g7593Y1wuX1wpWT+QTYcBbmtuGOA8UlAt56zo/B2eMIOcaOVEON3j0VXVa4g== + dependencies: + mkdirp "^1.0.4" + strip-ansi "^6.0.1" + uuid "^8.3.2" + xml "^1.0.1" + jest-leak-detector@^28.1.3: version "28.1.3" resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz#a6685d9b074be99e3adee816ce84fd30795e654d" @@ -5911,7 +5943,7 @@ mkdirp@^0.5.1: dependencies: minimist "^1.2.6" -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== @@ -7899,6 +7931,11 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" @@ -8138,6 +8175,11 @@ xml2js@^0.4.23: sax ">=0.6.0" xmlbuilder "~11.0.0" +xml@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== + xmlbuilder@~11.0.0: version "11.0.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"