Skip to content

Latest commit

 

History

History
1138 lines (758 loc) · 32.6 KB

File metadata and controls

1138 lines (758 loc) · 32.6 KB

< Tutorial Sprint 1

Tutorial - Implement the Server Side Message Queue.

Overview

In our proposed design, our server side component will listen for messages generated by the system, and create new Approval Entries as a result.

Once an Approval Entry is updated, then another message will be generated to respond back to the calling component and alert it of an updated object.

In this step we will implement both sides of this process, as well as setup a continuously generating stream of requests to help us in our development environment.

Let's write some tests

Let's start with a test that makes sure a successful posting to the Message Queue will actually result in a new entry in our PARequest data.

This feature isn't directly tied to one of our Models or Controllers, so let's create a new test for our Queue:

// [plugin]/test/unit/queue.js
var assert = require('chai').assert;

var AD = require('ad-utils');
var request = null; 

describe('Message Queue', function() {

    before(function(done) {
 
        request = AD.test.request(function(err){
            done(err);
        });

    });

    it('should add a new entry after a new message : ', function(done) {


    });

});

To begin with, let's read in the current number of PARequests in our db:

// [plugin]/test/unit/queue.js

    it('should add a new entry after a new message : ', function(done) {

        // Step 1) get the current number of PARequest entries
        PARequest.find()
        .exec(function(err, originalList){

           if (err) { done(err); return; }
           

        })
    });

NOTE: normally in development, I use the promises returned from the Sails Models ( PARequest.find().catch().then()). But in writing tests, I use the .exec() callback, so the promise's don't catch the assert errors. This helps keep my unit tests more similar by calling done() or done(err).

Now we will make the message queue call:

// [plugin]/test/unit/queue.js
var assert = require('chai').assert;

var testTransaction = {
    menu:{},
    form:{},
    related:{},
    callback:{
        message:'test.tool.callback',
        reference:{ uuid: 'test.entry.1' }
    },
    permission:{
        actionKey:'test.action',
        userID: 'user.1'
    }
}

var AD = require('ad-utils');
var request = null; 

describe('Message Queue', function() {

    before(function(done) {
 
        request = AD.test.request(function(err){
            done(err);
        });

    });

    it('should add a new entry after a new message : ', function(done) {

        // Step 1) get the current number of PARequest entries
        PARequest.find()
        .exec(function(err, originalList){

            if (err) { done(err); return; }
           
            // Step 2) Make a new Request on the Queue
            ADCore.queue.publish('opsportal.approval.create', testTransaction);

        })
    });

});

Then follow up and make sure our new entry is in the DB :

// [plugin]/test/unit/queue.js

    it('should add a new entry after a new message : ', function(done) {

        // Step 1) get the current number of PARequest entries
        PARequest.find()
        .exec(function(err, originalList){

            if (err) { done(err); return; }
           
            // Step 2) Make a new Request on the Queue
            ADCore.queue.publish('opsportal.approval.create', testTransaction);

            var checkIt = function() {

                PARequest.find()
                .exec(function(err, newList){

                    if (err) { done(err); return; }

                    assert(newList.length > 0, ' ---> we got a value back ');
                    assert.notEqual(originalList.length, newList.length, ' ---> the lengths are different');
                    assert(newList.length > originalList.length, ' ---> new list is longer ');

                    var found = _.where(newList, { callback:testTransaction.callback.message});

                    assert(found.length > 0, ' --> found our test transaction');
                    done();
                })

            }

            // this will take some time after the message, so delay a bit before checking:
            setTimeout(checkIt, 200);  // 200 ms ok?

        })
    });

Now run our test and see if it fails:

$ npm test

> opstool-process-approval@0.0.0 test /sails/node_modules/opstool-process-approval
> make test

  ․․․․․․․․․․․

  10 passing (12s)
  1 failing

  1) Message Queue should add a new entry after a new message : :
     Uncaught AssertionError:  ---> the lengths are different: expected 4 to not equal 4
      at test/unit/queue.js:41:28
      at wrapper (/Users/codingMonkey/.npm-packages/lib/node_modules/sails/node_modules/waterline/node_modules/lodash/index.js:3602:19)

      ### a ton of stack traces removed here ###



make: *** [test] Error 2
npm ERR! Test failed.  See above for more details.

Great! We have a failing test that shows our requests are not being processed. Now let's fix that:

Listening to the Message Queue

We want our app to listen to the incoming Message Queue requests and respond to them. We'll setup our listener in our config/bootstrap.js. Currently it looks like:

//[plugin]/config/bootstrap.js
var path = require('path');
var AD = require('ad-utils');
module.exports = function (cb) {

  AD.module.permissions(path.join(__dirname, '..', 'setup', 'permissions'), cb);

};

Lets add a routine to subscribe to our message queue:

//[plugin]/config/bootstrap.js
module.exports = function (cb) {

  AD.module.permissions(path.join(__dirname, '..', 'setup', 'permissions'), cb);

  ADCore.queue.subscribe('opsportal.approval.create', function(message, data){


  });

};

NOTE: this routine is not asynchronous, so we are going to leave the calling of the cb to the existing AD.module.permissions() routine.

Now we need to modify the incoming data and store it in the PARequest table.

//[plugin]/config/bootstrap.js
module.exports = function (cb) {

  AD.module.permissions(path.join(__dirname, '..', 'setup', 'permissions'), cb);

  ADCore.queue.subscribe('opsportal.approval.create', function(message, data){

      var paData = {};
      paData.objectData = data;
      paData.actionKey = data.permission.actionKey;
      paData.userID = data.permission.userID;
      paData.callback = data.callback.message;
      paData.status = 'pending';

      PARequest.create(paData)
      .exec(function(){});

  });

};

Check our Unit Test now:

$ npm test

> opstool-process-approval@0.0.0 test /sails/node_modules/opstool-process-approval
> make test

  ․․․․․․․․․․․

  11 passing (12s)
  

That's nice, but we forgot to check for what happens if we send invalid data to the Queue. So let's write a test that checks for bad transactions to not get processed:

// [plugin]/test/unit/queue.js
    it('should not accept invalid data sent to the Queue : ', function(done){

        // create some invalid transactions
        var invalidTransactions = [];
        invalidTransactions.push(_.omit(_.clone(testTransaction, true), 'permission'));
        invalidTransactions.push(_.omit(_.clone(testTransaction, true), 'callback'));

        var t = _.clone(testTransaction, true);
        delete t.permission.actionKey;
        invalidTransactions.push(t);

        t = _.clone(testTransaction, true);
        delete t.permission.userID;
        invalidTransactions.push(t);

        t = _.clone(testTransaction, true);
        delete t.callback.message;
        invalidTransactions.push(t);


        // Step 1: find out how many PARequests there are now
        PARequest.find()
        .exec(function(err, originalList){

            if (err) { done(err); return; }


            // Step 2) Make all these requests on the Queue
            invalidTransactions.forEach(function(transaction) {
                ADCore.queue.publish('opsportal.approval.create', transaction);
            })
            

            var checkIt = function() {

                PARequest.find()
                .exec(function(err, newList){

                    if (err) { done(err); return; }

                    assert.equal(originalList.length, newList.length, ' ---> the lengths are same');
                    done();
                })

            }

            // this will take some time after our messages, so delay a bit before checking:
            setTimeout(checkIt, 200);  // is 200 ms ok?

        })
    });

Now run our tests:

$ npm test

> opstool-process-approval@0.0.0 test /sails/node_modules/opstool-process-approval
> make test

  ․․․․․․․․․․․․

  11 passing (13s)
  1 failing

  1) Message Queue should not accept invalid data sent to the Queue : :
     Uncaught TypeError: Cannot read property 'actionKey' of undefined
      at config/bootstrap.js:41:38
      at EventEmitter.tempCB (/Users/codingMonkey/Sites/web/sails/node_modules/appdev-core/api/services/adcore/queue.js:164:13)

      #### stack trace removed for brevity #### 



make: *** [test] Error 1
npm ERR! Test failed.  See above for more details.

Our test did fail, but not because of our assertions. They failed because our Queue handler crashed trying to access values on an object that wasn't there. So let's fix this:

//[plugin]/config/bootstrap.js
module.exports = function (cb) {

  AD.module.permissions(path.join(__dirname, '..', 'setup', 'permissions'), cb);

  ADCore.queue.subscribe('opsportal.approval.create', function(message, data){

    var requiredProperties = ['permission.actionKey', 'permission.userID', 'callback.message'];
    var allPropertiesFound = true;
    requiredProperties.forEach(function(prop){

      /// NOTE: once Sails uses lodash v3.10.1, we can simply do this:
      /// allPropertiesFound = allPropertiesFound && _.has(data, prop);

      /// currently Sails uses v2.4.2 so we do this for now:
      var props = prop.split('.');
      var currData = data;

      while(props.length>0) {
        var currentProp = props.shift();
        allPropertiesFound = allPropertiesFound  && _.has(currData, currentProp);
        if (currData) currData = currData[currentProp];
      }

    });

    if (!allPropertiesFound) {

      // data provided not in valid format

    } else {

      // reformat the incoming data into a PARequest format:
      var paData = {};
      paData.objectData = data;
      paData.actionKey = data.permission.actionKey;
      paData.userID = data.permission.userID;
      paData.callback = data.callback.message;
      paData.status = 'pending';

      PARequest.create(paData)
      .exec(function(err, newRequest){

      })
    }


  });

};

Now run our test again:

$ npm test

> opstool-process-approval@0.0.0 test /sails/node_modules/opstool-process-approval
> make test

  ․․․․․․․․․․․․

  12 passing (13s)

OK, one more thing here. Notice that when I actually create the new PARequest, I'm not checking to see if anything went wrong? Well, that's not a good idea. Let's make sure that if a basic .create() operation fails, we alert our developers:

      PARequest.create(paData)
      .catch(function(err){
        ADCore.error.log('unable to create this PARequest entry', {
          error:err,
          data:paData,
          module:'opstool-process-approval'
        })
      });

Make sure you read up on our Error reporting guidelines. The key here is to make sure you include enough additional information for this error that a developer can figure out what the issue is.

Responding to a processed Approval (the Test)

OK, the final step in the process of an approval being processed, is to notify the calling application of the state of the request. So, lets write a test that expects to get called when a request is approved or rejected.

// [plugin]/test/unit/queue.js
it('should call the return queue on an approval : ', function(done) {

  done();

});

Now lets create a new transaction for testing an approved Transaction:

// [plugin]/test/unit/queue.js
it('should call the return queue on an approval : ', function(done) {

  var approveTransaction = _.clone(testTransaction, true);
  approveTransaction.callback.message += '.approve';
  approveTransaction.callback.reference.uuid = 'test.approved';
  done();

});

Now subscribe to the indicated callback.message and keep track if this routine was called:

// [plugin]/test/unit/queue.js
it('should call the return queue on an approval : ', function(done) {

  var approveTransaction = _.clone(testTransaction, true);
  approveTransaction.callback.message += '.approve';
  approveTransaction.callback.reference.uuid = 'test.approved';

  var didCall = false;

  // subscribe to our callback queue
  ADCore.queue.subscribe(approveTransaction.callback.message, function(message, data){

      didCall = true;
      assert(data.reference.uuid == approveTransaction.callback.reference.uuid, ' --> the returned uuid is the one we just referenced! ');
  });

  done();

});

Now let's create the entry by publishing the transaction to the opsportal.approval.create queue:

// [plugin]/test/unit/queue.js
it('should call the return queue on an approval : ', function(done) {

  var approveTransaction = _.clone(testTransaction, true);
  approveTransaction.callback.message += '.approve';
  approveTransaction.callback.reference.uuid = 'test.approved';

  var didCall = false;

  // subscribe to our callback queue
  ADCore.queue.subscribe(approveTransaction.callback.message, function(message, data){

      didCall = true;
      assert(data.reference.uuid == approveTransaction.callback.reference.uuid, ' --> the returned uuid is the one we just referenced! ');
  });


  // Step 1) create the entry in the Queue:
  ADCore.queue.publish('opsportal.approval.create', approveTransaction);
  
  done();

});

Like our previous tests, we will wait for that operation to complete before we start looking if the data is in the DB:

// [plugin]/test/unit/queue.js
it('should call the return queue on an approval : ', function(done) {

  var approveTransaction = _.clone(testTransaction, true);
  approveTransaction.callback.message += '.approve';
  approveTransaction.callback.reference.uuid = 'test.approved';

  var didCall = false;

  // subscribe to our callback queue
  ADCore.queue.subscribe(approveTransaction.callback.message, function(message, data){

      didCall = true;
      assert(data.reference.uuid == approveTransaction.callback.reference.uuid, ' --> the returned uuid is the one we just referenced! ');
  });


  // Step 1) create the entry in the Queue:
  ADCore.queue.publish('opsportal.approval.create', approveTransaction);
  
  var waitForIt = function() {

    done();

  }

  setTimeout(waitForIt, 200);

});

Now we need to find this approval entry in the DB, so we can find it's id value:

// [plugin]/test/unit/queue.js
it('should call the return queue on an approval : ', function(done) {

  var approveTransaction = _.clone(testTransaction, true);
  approveTransaction.callback.message += '.approve';
  approveTransaction.callback.reference.uuid = 'test.approved';

  var didCall = false;

  // subscribe to our callback queue
  ADCore.queue.subscribe(approveTransaction.callback.message, function(message, data){

      didCall = true;
      assert(data.reference.uuid == approveTransaction.callback.reference.uuid, ' --> the returned uuid is the one we just referenced! ');
  });


  // Step 1) create the entry in the Queue:
  ADCore.queue.publish('opsportal.approval.create', approveTransaction);
  
  var waitForIt = function() {

    // Step 2) create an update request for this entry:
    PARequest.find({ callback:approveTransaction.callback.message })
    .exec(function(err, paEntry){

        assert(paEntry.length > 0, ' --> should have created our approveTransaction');

        if (err) { done(err); return; }

        done();

    })

  }

  setTimeout(waitForIt, 200);

});

Once we get here, we are able to make the RESTful update call that should approve the entry and then call our callback queue:

// [plugin]/test/unit/queue.js
it('should call the return queue on an approval : ', function(done) {

  var approveTransaction = _.clone(testTransaction, true);
  approveTransaction.callback.message += '.approve';
  approveTransaction.callback.reference.uuid = 'test.approved';

  var didCall = false;

  // subscribe to our callback queue
  ADCore.queue.subscribe(approveTransaction.callback.message, function(message, data){

      didCall = true;
      assert(data.reference.uuid == approveTransaction.callback.reference.uuid, ' --> the returned uuid is the one we just referenced! ');
  });


  // Step 1) create the entry in the Queue:
  ADCore.queue.publish('opsportal.approval.create', approveTransaction);
  
  var waitForIt = function() {

    // Step 2) create an update request for this entry:
    PARequest.find({ callback:approveTransaction.callback.message })
    .exec(function(err, paEntry){

        assert(paEntry.length > 0, ' --> should have created our approveTransaction');

        if (err) { done(err); return; }

        request
          .put('/opstool-process-approval/parequest/'+paEntry[0].id)
          .field('status', 'approved')
          .end(function(err, res) {

              assert(didCall, ' --> should have called our Queue');
              done(err);
          });

    })

  }

  setTimeout(waitForIt, 200);

});

Whew! Now let's run our tests:

$ npm test

> opstool-process-approval@0.0.0 test /sails/node_modules/opstool-process-approval
> make test

  ․․․․․․․․․․․․․

  12 passing (14s)
  1 failing

  1) Message Queue should call the return queue on an approval : :
     Uncaught AssertionError:  --> should have called our Queue
      at Test.<anonymous> (test/unit/queue.js:160:25)
      at Test.assert (node_modules/ad-utils/node_modules/supertest/lib/test.js:156:6)
      at Server.assert (node_modules/ad-utils/node_modules/supertest/lib/test.js:127:12)
      at net.js:1419:10



make: *** [test] Error 1
npm ERR! Test failed.  See above for more details.

Yep, that's what we wanted. Now let's add the code to update the calling queues with the result of the operation.

Responding to a processed Approval (the Code)

It is when a PARequest model is updated with a status of either approved or rejected that we should notify the calling Application queue. So let's place the logic in the PARequest model:

// [plugin]/api/models/PARequest.js
module.exports = {

  // connection:"appdev_default",

  tableName:"pa_request",   

  attributes: {

    actionKey : { type: 'string' },

    userID : { type: 'string' },

    callback : { type: 'string' },

    status : { type: 'string' },

    objectData : { type: 'json' },

    updatedValues: { type: 'json' },

    comments: {
      collection:'pacomment', 
      via:'request'
    }
  },

  afterUpdate: function(updatedRecord, cb) {

    // if not one of the 'in process' statuses:
    if (['pending', 'requesting'].indexOf(updatedRecord.status) == -1) {

      // then this entry is finished being processed

      // Compile data to return to the calling application
      // 1) return our status
      var returnData = {};
      returnData.status = updatedRecord.status;


      // 2) if a reference value was given then include it too
      if (updatedRecord.objectData && updatedRecord.objectData.callback && updatedRecord.objectData.callback.reference) {
        returnData.reference = updatedRecord.objectData.callback.reference;
      }


      // 3) add in any changed data submitted from the UI:
      returnData.data = updatedRecord.updatedValues;


      // update the registered application :
      ADCore.queue.publish(updatedRecord.callback, returnData);

    }

    cb();  // <-- make sure to call this or your app will just 'hang'
  }

Now let's run our tests again:

$ npm test

> opstool-process-approval@0.0.0 test /sails/node_modules/opstool-process-approval
> make test

  ․․․․․․․․․․․․․

  13 passing (14s)

That passes, but we are not done yet.

Currently we haven't prevented the UI from updating fields in our model that we don't want it to update. The UI should only update status, comments, and updatedValues. So we should prevent the remaining values from being changed.

Let's write a test that attempts to change these values and expects the DB value to remain unchanged:

    it('should only allow status, comments and updatedValues fields to be modified : ', function(done) {

        // another test approval
        var approveTransaction = _.clone(testTransaction, true);
        approveTransaction.callback.message += '.noupdateinvalidfields';

        // Step 1) create the entry in the Queue:
        ADCore.queue.publish('opsportal.approval.create', approveTransaction);

        var waitForIt = function() {

            // Step 2) find this entry so we can try to modify it
            PARequest.find({ callback:approveTransaction.callback.message })
            .exec(function(err, paEntry){

                assert(paEntry.length > 0, ' --> should have created our approveTransaction');

                if (err) { done(err); return; }


                // Step 3) try to modify it:
                request
                    .put('/opstool-process-approval/parequest/'+paEntry[0].id)
                    .field('actionKey', 'no.change')
                    .field('userID', 'no.change')
                    .field('callback', 'no.change')
                    .field('objectData', JSON.stringify({ no:'change'}))
                    .end(function(err, res) {


                        // Step 4) now lookup the Entry again and check for any changes in our values:
                        PARequest.findOne(paEntry[0].id)
                        .exec(function(err, paFinal){
                            var a = paEntry[0];
                            var b = paFinal;

                            ['actionKey', 'userID', 'callback'].forEach(function(key){
                                assert.equal(a[key], b[key], ' --> '+key+' should remain unchanged ');
                            })

                            assert(_.isEqual(a.objectData, b.objectData), ' --> objectData should remain unchanged ');
                            done(err);

                        })
                    });
            })

        }

        setTimeout(waitForIt, 200);

    });

Run our tests:

$ npm test

> opstool-process-approval@0.0.0 test /sails/node_modules/opstool-process-approval
> make test

  ․․․․․․․․․․․․․

  13 passing (14s)
  1 failing

  1) Message Queue should only allow status, comments and updatedValues fields to be modified : :
     Uncaught AssertionError:  --> actionKey should remain unchanged : expected 'test.action' to equal 'no.change'
      at test/unit/queue.js:212:40
      at Array.forEach (native)
      #### Stack Trace removed for brevity ####



make: *** [test] Error 1
npm ERR! Test failed.  See above for more details.

Yeah, that is catching the problem. So let's fix it. We'll fix it by modifying the PARequest model to remove any changes to these fields when processing an update:

// [plugin]/api/models/PARequest.js
  beforeUpdate: function(valuesToUpdate, cb) {

    // don't allow updates to these fields:
    ['actionKey', 'userID', 'callback', 'objectData'].forEach(function(key){
      if (valuesToUpdate[key]) delete valuesToUpdate[key];  
    });

    cb();
  },

Run our tests again:

$ npm test

> opstool-process-approval@0.0.0 test /sails/node_modules/opstool-process-approval
> make test

  ․․․․․․․․․․․․․․

  14 passing (14s)

While we are on the topic of validating the values coming back, we should verify that setting the status value to an invalid value is also prohibited.

The test:

    it('should prevent invalid status values : ', function(done) {

        // another test approval
        var approveTransaction = _.clone(testTransaction, true);
        approveTransaction.callback.message += '.invalidStatusValues';

        // Step 1) create the entry in the Queue:
        ADCore.queue.publish('opsportal.approval.create', approveTransaction);

        var waitForIt = function() {

            // Step 2) find this entry so we can try to modify it
            PARequest.find({ callback:approveTransaction.callback.message })
            .exec(function(err, paEntry){

                assert(paEntry.length > 0, ' --> should have created our approveTransaction');

                if (err) { done(err); return; }


                // Step 3) try to modify it with invalid status value:
                request
                    .put('/opstool-process-approval/parequest/'+paEntry[0].id)
                    .field('status', 'not.valid.value')
                    .expect(400)
                    .end(function(err, res) {
                        
                        done(err);
                    });
            })

        }

        setTimeout(waitForIt, 200);

    });

Run the test:

$ npm test

> opstool-process-approval@0.0.0 test /sails/node_modules/opstool-process-approval
> make test

  ․․․․․․․․․․․․․․․

  14 passing (15s)
  1 failing

  1) Message Queue should prevent invalid status values : :
     Error: expected 400 "Bad Request", got 200 "OK"
      at Test._assertStatus (node_modules/ad-utils/node_modules/supertest/lib/test.js:232:12)
      at Test._assertFunction (node_modules/ad-utils/node_modules/supertest/lib/test.js:247:11)
      at Test.assert (node_modules/ad-utils/node_modules/supertest/lib/test.js:148:18)
      at Server.assert (node_modules/ad-utils/node_modules/supertest/lib/test.js:127:12)
      at net.js:1419:10



make: *** [test] Error 1
npm ERR! Test failed.  See above for more details.

Now update the PARequest model to limit the values for status:

// [plugin]/api/models/PARequest.js
attributes: {

  actionKey : { type: 'string' },

  userID : { type: 'string' },

  callback : { type: 'string' },

  status : { type: 'string',
    in:[
      'pending',      // waiting for an admin to approve the request
      'requesting',   // requesting more information (comments)
      'approved',     // Admin has approved the request
      'rejected'      // Admin has rejected the request
    ] 
  },

  objectData : { type: 'json' },

  updatedValues: { type: 'json' },

  comments: {
    collection:'pacomment',  // <-- all lowercase!
    via:'request'
  }
},

Now verify our solution works:

$ npm test

> opstool-process-approval@0.0.0 test /sails/node_modules/opstool-process-approval
> make test

  ․․․․․․․․․․․․․․․

  15 passing (15s)

Finally, let's verify one more data format issue. When passing data back to the Application Queue, we want to make sure the updatedValues returned, is an actual json object, not just a string representation of an object. So let's make sure we check for this case.

Instead of writing a whole new Unit Test, lets just update an exsiting unit test to account for the case.

Find the it('should call the return queue on an approval : ', function(done) { test in our [plugin]/test/unit/queue.js file.

Now update the callback routine to verify updatedValues is an object:

// [plugin]/test/unit/queue.js
        // subscribe to our callback queue
        ADCore.queue.subscribe(approveTransaction.callback.message, function(message, data){

            didCall = true;
            assert(data.reference.uuid == approveTransaction.callback.reference.uuid, ' --> the returned uuid is correct! ');
            assert(_.isPlainObject(data.data), ' --> updatedValues should be an object ');
        });

Then make sure we send some string data back:

// [plugin]/test/unit/queue.js
request
  .put('/opstool-process-approval/parequest/'+paEntry[0].id)
  .field('status', 'approved')
  .field('updatedValues', JSON.stringify({ hello:'world' }))  // <--- this one
  .end(function(err, res) {

Now run our tests again:

$ npm test

> opstool-process-approval@0.0.0 test /sails/node_modules/opstool-process-approval
> make test

  ․․․․․․․․․․․․․․․

  14 passing (15s)
  1 failing

  1) Message Queue should call the return queue on an approval : :
     Uncaught AssertionError:  --> updatedValues should be an object 
      at test/unit/queue.js:139:13
      at EventEmitter.tempCB (/Users/codingMonkey/Sites/web/sails/node_modules/appdev-

      #### stack trace removed ####



make: *** [test] Error 1
npm ERR! Test failed.  See above for more details.

And now we go back and make sure we verify the data being stored in updateValues is a json object:

// [plugin]/api/models/PARequest.js
beforeUpdate: function(valuesToUpdate, cb) {

  // don't allow updates to these fields:
  ['actionKey', 'userID', 'callback', 'objectData'].forEach(function(key){
    if (valuesToUpdate[key]) delete valuesToUpdate[key];  
  });

  // make sure we're given a json object, not a string:
  if (valuesToUpdate.updatedValues && _.isString(valuesToUpdate.updatedValues)) {
    valuesToUpdate.updatedValues = JSON.parse(valuesToUpdate.updatedValues);
  }

  cb();
},

Rerun our tests and make sure all is right:

$ npm test

> opstool-process-approval@0.0.0 test /sails/node_modules/opstool-process-approval
> make test

  ․․․․․․․․․․․․․․․

  15 passing (15s)

Now would be a good time to go back through each of the fields of our PARequest model and enter any known validation rules. I wont write anymore about this process in the tutorial.

A helpful tool for use in development

In order to test things our on our development environment, we need a steady supply of Approval Requests coming in, so we can practice the approvals and rejections.

So I'm going to add a simple routine to make sure our Approval Request queue is always generating new requests for us to practice with.

Let's place this in our [plugin]/config/bootstrap.js file:

// [plugin]/config/bootstrap.js


// DEVELOP MODE: keep some new Approval Requests coming in:
// only do this in develop mode:
if (sails.config.environment == 'development') {

  // read in our sample fixture data
  var fixtureData = null;
  var fixtureIndex = 0;
  if (fixtureData == null){
    var data = fs.readFileSync(path.join(__dirname, '..', 'test', 'fixtures', 'PARequest.json'));
    fixtureData = JSON.parse(data);

    fixtureData.splice(fixtureData.length-1, 1);  // <-- this one isn't supposed to be returned.

  }


  // the fixtures represent the values in the DB, so 
  // reverse configure them to match a proper Request call:
  fixtureData.forEach(function(fixture){
    fixture.permission = {
      actionKey: fixture.actionKey,
      userID: fixture.userID
    };

    fixture.callback = {
      message: fixture.callback
    }

    // all the attributes stored in objectData should actually be
    // top level attributes:
    for (var o in fixture.objectData) {
      fixture[o] = fixture.objectData[o];
    }

    // .objectData is created by our MessageQueue handler.
    delete fixture.objectData;
    
  })


  // create a routine to check if we have less than our default fixture 
  var checkRequests = function() {

    PARequest.find({ status:'pending'})
    .then(function(list){
      
      if (list.length < fixtureData.length) {

        // publish a new request:
        ADCore.queue.publish('opsportal.approval.create', fixtureData[fixtureIndex]);
        fixtureIndex++;
        if (fixtureIndex >= fixtureData.length) {
          fixtureIndex = 0;
        }

      }
    });
  }

  // check every 5 seconds
  setInterval(checkRequests, 5000);

}

Now when you lift sails in development mode, this routine will continually check every 5 seconds to see how many pending requests remain in our PARequest table, and if we have less than our number of fixtures, then we add a fixture entry to our system.

It's now time to save our changes to git:

# in [plugin] directory
$ git add .
$ git commit -m '+add: server side message queue processing '
$ git push origin server_api

This also ends our planned server side updates. So let's commit these changes back to our main develop branch and get rid of our local server_api branch:

# in [plugin] directory
$ git checkout develop
$ git merge --no-ff server_api
$ git branch -d server_api
$ git push origin develop

Now, on to the Client UI ...


< step 7 : limit routes according to the user's scope Tutorial Sprint 2 >