Ver código fonte

PROD-1221: oadrQueryRegistration implementation

* oadrQueryRegistration EiRegisterParty service
* Add polymorphic parsing of different EiRegisterParty request payloads
Blake Schneider 5 anos atrás
pai
commit
dd318833c3

+ 14 - 1
__tests__/integration/ven-registration.spec.js

@@ -22,9 +22,22 @@ describe('VEN registration', function() {
       ven = new Ven(`http://127.0.0.1:${port}`, clientCrtPem, 'aabbccddeeff', '17:32:59:FD:0E:B5:99:31:27:9C', 'ven.js1');
     });
 
-    it('should successfully register and receive a vtnId', async () => {
+    it('should successfully return a vtnId from queryRegistration', async () => {
+      const queryResponse = await ven.queryRegistration();
+      expect(queryResponse.vtnId).to.be.a('string');
+    });
+
+    it('should successfully register and receive a vtnId and registrationId', async () => {
       const registrationResponse = await ven.register();
       expect(registrationResponse.vtnId).to.be.a('string');
+      expect(registrationResponse.registrationId).to.be.a('string');
+    });
+
+    it('should successfully return a registrationId and venId from queryRegistration', async () => {
+      const queryResponse = await ven.queryRegistration();
+      expect(queryResponse.vtnId).to.be.a('string');
+      expect(queryResponse.registrationId).to.be.a('string');
+      expect(queryResponse.venId).to.be.a('string');
     });
 
     after(async () => {

+ 45 - 0
__tests__/unit/processes/registration.spec.js

@@ -5,6 +5,7 @@ const { v4 } = require('uuid');
 const { sequelize } = require('../../../db');
 
 const {
+  query,
   registerParty,
 } = require('../../../processes/registration');
 
@@ -116,5 +117,49 @@ describe('VEN registration', function() {
       expect(exception.message).to.eql('Ven already exists with that CN.');
     });
 
+    describe('query', function() {
+
+      let venId, commonName, queryResponse;
+
+      before(async () => {
+        venId = v4().replace(/-/g, '').substring(0, 20).toUpperCase().match(/.{2}/g).join(':');
+        const requestId = v4().replace(/-/g, '');
+        commonName = v4().replace(/-/g, '').substring(0, 12);
+        const request = {
+          requestId: requestId
+        };
+        queryResponse = await query(request, commonName, venId);
+      });
+
+      it('does not return venId or registrationId for new device', async () => {
+        expect(queryResponse.registrationId).to.be.undefined;
+        expect(queryResponse.venId).to.be.undefined;
+      });
+
+      it('returns registrationId if already registered', async () => {
+        venId = v4().replace(/-/g, '').substring(0,20).toUpperCase().match(/.{2}/g).join(':');
+        const requestId = v4().replace(/-/g, '');
+        commonName = v4().replace(/-/g, '').substring(0, 12);
+        const registerRequest = {
+          requestId: requestId,
+          venId: venId,
+          oadrProfileName: '2.0b',
+          oadrTransportName: 'simplehttp',
+          oadrReportOnly: false,
+          oadrXmlSignature: false,
+          oadrVenName: `VEN ${commonName}`,
+          oadrHttpPullModel: true
+        };
+        registrationResponse = await registerParty(registerRequest, commonName, venId);
+        const initialRegistrationId = registrationResponse.registrationId;
+
+        const queryRequest = {
+          requestId: requestId
+        };
+        queryResponse = await query(queryRequest, commonName, venId);
+        expect(queryResponse.registrationId).to.eql(initialRegistrationId);
+        expect(queryResponse.venId).to.eql(venId);
+      });
+    });
   });
 });

+ 1 - 1
__tests__/unit/xml/create-party-registration.spec.js

@@ -2,7 +2,7 @@
 
 const { expect } = require('chai');
 
-const { parse, serialize } = require('../../../xml/create-party-registration');
+const { parse, serialize } = require('../../../../xml/register-party/create-party-registration');
 const { createPartyRegistration1Xml, malformedXml, missingOadrXmlSignatureXml, illegalBooleanValueXml } = require('./xml-requests');
 const { createPartyRegistration1 } = require('./js-requests');
 

+ 1 - 1
__tests__/unit/xml/created-party-registration.spec.js

@@ -2,7 +2,7 @@
 
 const { expect } = require('chai');
 
-const { parse, serialize } = require('../../../xml/created-party-registration');
+const { parse, serialize } = require('../../../../xml/register-party/created-party-registration');
 const { createdPartyRegistration1Xml } = require('./xml-requests');
 const { createdPartyRegistration1 } = require('./js-responses');
 

+ 8 - 1
__tests__/unit/xml/js-requests.js

@@ -1,6 +1,7 @@
 'use strict';
 
 const createPartyRegistration1 = {
+  _type: 'oadrCreatePartyRegistration',
   requestId: '2233',
   registrationId: '3bd3c02dc6965c8b9240',
   venId: '3f59d85fbdf3997dbeb1',
@@ -12,8 +13,14 @@ const createPartyRegistration1 = {
   oadrHttpPullModel: true
 };
 
+const queryRegistration1 = {
+  _type: 'oadrQueryRegistration',
+  requestId: '12345'
+};
+
 module.exports = {
-  createPartyRegistration1
+  createPartyRegistration1,
+  queryRegistration1
 };
 
 

__tests__/unit/xml/js-responses.js → __tests__/unit/xml/register-party/js-responses.js


+ 40 - 0
__tests__/unit/xml/register-party/query-registration.spec.js

@@ -0,0 +1,40 @@
+'use strict';
+
+const { expect } = require('chai');
+
+const { parse, serialize } = require('../../../../xml/register-party/query-registration');
+const { queryRegistration1Xml } = require('./xml-requests');
+const { queryRegistration1 } = require('./js-requests');
+
+describe('Query Registration', function() {
+  describe('serialize', function() {
+
+    let serializedRequest;
+
+    before(async () => {
+      serializedRequest = await serialize(queryRegistration1);
+    });
+
+    it ('successfully parses valid message', function() {
+      expect(serializedRequest).to.eql('<oadr2b:oadrPayload xmlns:oadr2b="http://openadr.org/oadr-2.0b/2012/07"><oadr2b:oadrSignedObject><oadr2b:oadrQueryRegistration xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ei:schemaVersion="2.0b"><pyld:requestID xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads">12345</pyld:requestID></oadr2b:oadrQueryRegistration></oadr2b:oadrSignedObject></oadr2b:oadrPayload>');
+    });
+  });
+
+  describe('parse', function() {
+    let parsedRequest;
+
+    before(async () => {
+      parsedRequest = await parse(queryRegistration1Xml);
+    });
+
+    it ('successfully parses valid message', function() {
+      expect(parsedRequest.requestId).to.eql('4323');
+    });
+
+    it ('successfully parses serialized value', async function() {
+      const serialized = serialize(queryRegistration1);
+      const parsedResponse = await parse(serialized);
+      expect(parsedResponse).to.eql(queryRegistration1);
+    });
+  });
+});

Diferenças do arquivo suprimidas por serem muito extensas
+ 10 - 1
__tests__/unit/xml/xml-requests.js


+ 44 - 18
client/ven.js

@@ -2,10 +2,16 @@
 
 const {
   serialize: serializeCreatePartyRegistration,
-} = require('../xml/create-party-registration');
+} = require('../xml/register-party/create-party-registration');
+
+const {
+  serialize: serializeQueryRegistration,
+} = require('../xml/register-party/query-registration');
+
 const {
   parse: parseCreatedPartyRegistration,
-} = require('../xml/created-party-registration');
+} = require('../xml/register-party/created-party-registration');
+
 const axios = require('axios');
 const { escape } = require('querystring');
 
@@ -27,6 +33,23 @@ class Ven {
     this.commonName = commonName;
   }
 
+  async queryRegistration() {
+    const message = {
+      requestId: '2233',
+    };
+
+    const createdResponse = this.makeRequest(
+      'EiRegisterParty',
+      message,
+      serializeQueryRegistration,
+      parseCreatedPartyRegistration,
+    );
+
+    // track registrationId for subsequent requests
+    this.registrationId = createdResponse.registrationId;
+    return createdResponse;
+  }
+
   async register() {
     const message = {
       requestId: '2233',
@@ -40,7 +63,20 @@ class Ven {
       oadrHttpPullModel: true,
     };
 
-    const xml = serializeCreatePartyRegistration(message);
+    const createdResponse = this.makeRequest(
+      'EiRegisterParty',
+      message,
+      serializeCreatePartyRegistration,
+      parseCreatedPartyRegistration,
+    );
+
+    // track registrationId for subsequent requests
+    this.registrationId = createdResponse.registrationId;
+    return createdResponse;
+  }
+
+  async makeRequest(service, message, serializer, parser) {
+    const xml = serializer(message);
 
     const config = {
       headers: {
@@ -53,30 +89,20 @@ class Ven {
 
     // axios will automatically reject HTTP response codes outside the 200-299 range
     const httpResponse = await axios.post(
-      `${this.endpoint}OpenADR2/Simple/2.0b/EiRegisterParty`,
+      `${this.endpoint}OpenADR2/Simple/2.0b/${service}`,
       xml,
       config,
     );
-    const registrationResponse = await parseCreatedPartyRegistration(
-      httpResponse.data,
-    );
+    const response = await parser(httpResponse.data);
 
     // but OpenADR provides its own response code in the XML envelope, we need to check that
-    if (
-      registrationResponse.responseCode < 200 ||
-      registrationResponse.responseCode >= 300
-    ) {
+    if (response.responseCode < 200 || response.responseCode >= 300) {
       throw new Error(
-        'Error during registration. ResponseCode=' +
-          registrationResponse.responseCode +
-          ', ResponseDescription=' +
-          registrationResponse.responseDescription,
+        `Error during ${service} call. ResponseCode=${response.responseCode}, ResponseDescription=${response.responseDescription}`,
       );
     }
 
-    // track registrationId for subsequent requests
-    this.registrationId = registrationResponse.registrationId;
-    return registrationResponse;
+    return response;
   }
 }
 

+ 39 - 0
processes/registration.js

@@ -82,6 +82,45 @@ async function registerParty(
   };
 }
 
+async function query(obj, clientCertificateCn, clientCertificateFingerprint) {
+  logger.info('query', obj, clientCertificateCn, clientCertificateFingerprint);
+
+  let registrationId, venId;
+
+  const existingDbRecordByVenId = await Ven.findOne({
+    where: { ven_id: clientCertificateFingerprint },
+  });
+
+  const existingDbRecordByCommonName = await Ven.findOne({
+    where: { common_name: clientCertificateCn },
+  });
+
+  if (existingDbRecordByVenId) {
+    if (existingDbRecordByVenId.common_name !== clientCertificateCn) {
+      const error = new Error('Client certificate CN mismatch.');
+      error.responseCode = 452;
+      throw error;
+    }
+    registrationId = existingDbRecordByVenId.data.registrationId;
+    venId = existingDbRecordByVenId.ven_id;
+  } else if (existingDbRecordByCommonName) {
+    const error = new Error('Ven already exists with that CN.');
+    error.responseCode = 452;
+    throw error;
+  }
+
+  return {
+    responseRequestId: obj.requestId || '',
+    responseCode: 200,
+    responseDescription: 'OK',
+    registrationId: registrationId,
+    venId: venId,
+    vtnId: vtnId,
+    pollFreqDuration: 'PT10S',
+  };
+}
+
 module.exports = {
+  query,
   registerParty,
 };

+ 16 - 4
server/controllers/register-party.js

@@ -1,10 +1,11 @@
 'use strict';
 
 const logger = require('../../logger');
-const { parse } = require('../../xml/create-party-registration');
-const { serialize } = require('../../xml/created-party-registration');
+const { parse } = require('../../xml/register-party');
+const { serialize } = require('../../xml/register-party/created-party-registration');
 
 const {
+  query,
   registerParty,
 } = require('../../processes/registration');
 
@@ -15,8 +16,19 @@ exports.postController = async (req, res) => {
 
   try {
     parsedRequest = await parse(xmlRequest);
-    const response = await registerParty(parsedRequest, req.clientCertificateCn, req.clientCertificateFingerprint);
-    xmlResponse = serialize(response);
+    let response;
+    switch(parsedRequest._type) {
+      case 'oadrCreatePartyRegistration':
+        response = await registerParty(parsedRequest, req.clientCertificateCn, req.clientCertificateFingerprint);
+        xmlResponse = serialize(response);
+        break;
+      case 'oadrQueryRegistration':
+        response = await query(parsedRequest, req.clientCertificateCn, req.clientCertificateFingerprint);
+        xmlResponse = serialize(response);
+        break;
+      default:
+        throw new Error(`Unknown _type: ${parsedRequest._type}`);
+    }
   } catch (e) {
     logger.warn('Error occurred processing', parsedRequest, e);
     const responseRequestId = (parsedRequest != null) ? parsedRequest.requestId : '';

+ 12 - 11
xml/create-party-registration.js

@@ -1,6 +1,6 @@
 'use strict';
 
-const { parseXML, childAttr, boolean, required } = require('./parser');
+const { parseXML, childAttr, boolean, required } = require('../parser');
 const { create, fragment } = require('xmlbuilder2');
 
 const oadrPayloadNs = 'http://www.w3.org/2000/09/xmldsig#';
@@ -18,6 +18,7 @@ async function parse(input) {
     ][0]['$$'];
 
   const result = {
+    _type: 'oadrCreatePartyRegistration',
     requestId: required(childAttr(o, 'requestID'), 'requestID'),
     oadrProfileName: required(
       childAttr(o, 'oadrProfileName'),
@@ -57,32 +58,32 @@ function serialize(obj) {
   const registrationId =
     obj.registrationId != null
       ? fragment()
-          .ele(energyInteropNs, 'ei:registrationID')
-          .txt(obj.registrationId)
+        .ele(energyInteropNs, 'ei:registrationID')
+        .txt(obj.registrationId)
       : fragment();
   const venId =
     obj.venId != null
       ? fragment()
-          .ele(energyInteropNs, 'ei:venID')
-          .txt(obj.venId)
+        .ele(energyInteropNs, 'ei:venID')
+        .txt(obj.venId)
       : fragment();
   const oadrTransportAddress =
     obj.oadrTransportAddress != null
       ? fragment()
-          .ele(oadrNs, 'oadr2b:oadrTransportAddress')
-          .txt(obj.oadrTransportAddress)
+        .ele(oadrNs, 'oadr2b:oadrTransportAddress')
+        .txt(obj.oadrTransportAddress)
       : fragment();
   const oadrVenName =
     obj.oadrVenName != null
       ? fragment()
-          .ele(oadrNs, 'oadr2b:oadrVenName')
-          .txt(obj.oadrVenName)
+        .ele(oadrNs, 'oadr2b:oadrVenName')
+        .txt(obj.oadrVenName)
       : fragment();
   const oadrHttpPullModel =
     obj.oadrHttpPullModel != null
       ? fragment()
-          .ele(oadrNs, 'oadr2b:oadrHttpPullModel')
-          .txt(obj.oadrHttpPullModel)
+        .ele(oadrNs, 'oadr2b:oadrHttpPullModel')
+        .txt(obj.oadrHttpPullModel)
       : fragment();
 
   const doc = create({

+ 1 - 1
xml/created-party-registration.js

@@ -1,7 +1,7 @@
 /* eslint-disable indent */
 'use strict';
 
-const { parseXML, childAttr, required } = require('./parser');
+const { parseXML, childAttr, required } = require('../parser');
 const { create, fragment } = require('xmlbuilder2');
 
 const oadrPayloadNs = 'http://www.w3.org/2000/09/xmldsig#';

+ 24 - 0
xml/register-party/index.js

@@ -0,0 +1,24 @@
+'use strict';
+
+const { parseXML } = require('../parser');
+
+const { parse: parseCreatePartyRegistration } = require('./create-party-registration');
+const { parse: parseQueryRegistration } = require('./query-registration');
+
+async function parse(input) {
+  const json = await parseXML(input);
+  const o = json['oadrPayload']['$$']['oadrSignedObject'][0]['$$'];
+  if (o['oadrCreatePartyRegistration']) {
+    return await parseCreatePartyRegistration(input);
+  }
+
+  if (o['oadrQueryRegistration']) {
+    return await parseQueryRegistration(input);
+  }
+
+  throw new Error(`Unexpected payload type: ${Object.keys(o)}`);
+}
+
+module.exports = {
+  parse
+};

+ 47 - 0
xml/register-party/query-registration.js

@@ -0,0 +1,47 @@
+'use strict';
+
+const { parseXML, childAttr, required } = require('../parser');
+const { create } = require('xmlbuilder2');
+
+const oadrPayloadNs = 'http://www.w3.org/2000/09/xmldsig#';
+const oadrNs = 'http://openadr.org/oadr-2.0b/2012/07';
+const energyInteropNs = 'http://docs.oasis-open.org/ns/energyinterop/201110';
+const energyInteropPayloadsNs =
+  'http://docs.oasis-open.org/ns/energyinterop/201110/payloads';
+
+async function parse(input) {
+  const json = await parseXML(input);
+  const o =
+    json['oadrPayload']['$$']['oadrSignedObject'][0]['$$'][
+      'oadrQueryRegistration'
+    ][0]['$$'];
+
+  return {
+    _type: 'oadrQueryRegistration',
+    requestId: required(childAttr(o, 'requestID'), 'requestID')
+  };
+}
+
+function serialize(obj) {
+  const doc = create({
+    namespaceAlias: {
+      ns: oadrPayloadNs,
+      oadr2b: oadrNs,
+      ei: energyInteropNs,
+      pyld: energyInteropPayloadsNs
+    },
+  })
+    .ele('@oadr2b', 'oadr2b:oadrPayload')
+    .ele('oadr2b:oadrSignedObject')
+    .ele('oadr2b:oadrQueryRegistration')
+    .att(energyInteropNs, 'ei:schemaVersion', '2.0b')
+    .ele('@pyld', 'pyld:requestID')
+    .txt(obj.requestId)
+    .doc();
+  return doc.end({ headless: true, prettyPrint: false });
+}
+
+module.exports = {
+  parse,
+  serialize,
+};