Lab 4 Testing Sails, and Advanced git operations

Lab 4.1 Testing in Sails

Mocha: A testing framework for Node.js application

Mocha is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases. Hosted on GitHub.

Configuration

In the project directory, we run npm install mocha --save-dev to add mocha library in development dependencies.

cd your_project_location
npm install mocha --save-dev

If you don’t have project in your local machine, please clone your Sails project from Github, or create a new Sails project using sails new command.

sails new testingApp
# or
git clone https://your_project_git

test/ directory structure

We would like to put all testing related stuffs in test folder in our project. I have created the folder skeleton for you. Please download test.zip from BUMoodle and extract in your sails project . After that, your sails project file structure would look similar to the followings.

./myApp
├── api/
├── assets/
├── ...
├── test/
│  ├── integration/
│  │  ├── controllers/
│  │  │  └── ...
│  │  ├── models/
│  │     └── ...
│  ├── fixtures/
|  │  └── ...
│  ├── lifecycle.test.js
│  └── mocha.opts
├── ...
└── views/

Example: Unit test for User Models

Let’s develop a unit test for User model in our Sails app.

Optional: Generating User Model and configure blueprints

If you don’t have api/models/User.js in your project, please run sails generate api to generate it.

sails generate api User

Please modify config/blueprints.js as follows.

actions: true,
rest: false,
shortcuts: false,

Adding attributes for User model

For simplicity, we only define necessary attribute for User model. Please add the following attributes in api/models/User.js.

username: {
  type: "string"
},
password: {
  type: "string"
},

Adding admin account

In config/bootstrap.js, we hard-coded an administrator account for our system. Please put the following lines right before return done();.

await User.createEach([
    { "username": "admin", "password": "123456" },
]);

Writing the unit test


describe('User (model)', function() {

  describe('Admin account', function() {
    it('should find an User with username "admin"', function (done) {
      User.find({username:'admin'})
      .then(function(users) {

        if (users.length == 0) {
          return done(new Error(
            'Should return 1 user -- the admin '+
            'But instead, got: no user found'
          ));
        }
        return done();
      })
      .catch(done);
    });
  });

});

Run tests

To run all tests, please issue the following command in terminal.

node ./node_modules/mocha/bin/mocha test/lifecycle.test.js test/integration/**/*.test.js

If you would like to simplify the previous command, we need to edit package.json for running the tests with npm test. Please comment out this line.

"test": "npm run lint && npm run custom-tests && echo 'Done.'",

Then add the following line.

"test": "node ./node_modules/mocha/bin/mocha test/lifecycle.test.js test/integration/**/*.test.js",

//...
  "scripts": {
	//"test": "npm run lint && npm run custom-tests && echo 'Done.'",
	"test": "node ./node_modules/mocha/bin/mocha test/lifecycle.test.js test/integration/**/*.test.js",
  },
//...

After that, we could simply run the tests by npm test in terminal.

npm test

Supertest: for testing HTTP request and response

Supertest is a Javascript module to provide a high-level abstraction for testing HTTP. We could easy to make a HTTP request to our Sails app and check the result for testing. It works great with Mocha.

There are several examples on its official Github, please have a look.

Configuration

cd your_project_location
npm install supertest --save-dev

Example 1: Testing controller actions

Adding login and logout actions in UserController

Let’s add login and logout action to api/controllers/UserController.js for testing.

login: async function (req, res) {

    if (req.method == "GET") return res.view('user/login');

    if (!req.body.username) return res.badRequest();
    if (!req.body.password) return res.badRequest();

    var user = await User.findOne({ username: req.body.username });

    if (!user) {
        res.status(401);
        return res.send("User not found");
    }

    if (user.password != req.body.password) {
        res.status(401);
        return res.send("Wrong Password");
    }

    req.session.regenerate(function (err) {
        if (err) return res.serverError(err);
        req.session.username = req.body.username;
        sails.log("Session: " + JSON.stringify(req.session) );
        return res.ok("Login successfully");
    });
},

logout: async function (req, res) {

    req.session.destroy(function (err) {
        if (err) return res.serverError(err);
        return res.ok("Log out successfully");
    });
},

Writing Unit test for login and logout action

Please create a new Javascript file attest/integration/controllers/UserController.test.js with the following codes.

var supertest = require('supertest');

describe('UserController', function() {

  describe('#login() with admin account', function() {
    it('should return status 200 with "Login successfully" in body', function (done) {
      supertest(sails.hooks.http.app)
      .post('/user/login')
      .send({ username: 'admin', password: '123456' })
      .expect(200, 'Login successfully', done);
    });
  });

  describe('#login() with non-exists account', function() {
    it('should return status 401 with "User not found" in body', function (done) {
      supertest(sails.hooks.http.app)
      .post('/user/login')
      .send({ username: 'user', password: '123456' })
      .expect(401, 'User not found', done);
    });
  });

  describe('#logout()', function() {
    it('should return status 200 with "Log out successfully" in body', function (done) {
      supertest(sails.hooks.http.app)
      .get('/user/logout')
      .expect(200, 'Log out successfully', done);
    });
  });

});

Then run tests by npm test.

Example 2: Testing Dialogflow fulfillment Webhook

In case you don’t have a Sails project for Dialogflow, you may fork this sample project and clone it in your machine. The upcoming steps are already done in this sample project. You may have a look.

Getting a fulfillment request from Dialogflow

Let’s direct to the Dialogflow Console at https://console.dialogflow.com.

enter image description here

Please input a query in Try it now input box. When there is response, click on Diagnostic info. You should able to see Fulfillment Request and Fulfillment Response in JSON format.

enter image description here

Writing Unit test

Please create a Javascript file for the unit test at test/integration/controllers/PersonController.test.js.

var supertest = require('supertest');

describe('PersonController.fulfillment', function() {
  it('should response status 200 with JSON', function (done) {
    supertest(sails.hooks.http.app)
    .post('/person/fulfillment')
    .send(/* paste your fulfullment request json here */)
    .set('Content-Type', 'application/json')
    .set('Accept', 'application/json')
    .expect(200, /* paste your expected fulfullment response json here */, done);
  });
});

Then simply run tests by npm test.

Lab 4.2 Advanced Git operations

In this section, we talk about more git operations. You should familiar with git clone, git add, git commit and git push. You may also take a look to Pro Git book, it is a complete reference book.

Clone and checkout remote branch

Checkout directly while clone

We could checkout a particular branch while clone by adding -b branch_name option.

git clone https://your_repo_url -b branch_name

It just behave the same as following commands basically.

git clone https://your_repo_url
git checkout -b branch_name -t origin/branch_name

The -b branch_name option in git checkout means create a branch with branch_name locally. For -t origin/branch_name means set the newly created branch to track remote branch origin/branch_name.

Checkout manually after cloning

git clone https://your_repo_url
cd your_project_location
git checkout -b kenny -t origin/kenny

Pull remote changes to local: git pull vs git fetch

git fetch

git fetch download objects/commits/refs/tags from remote for all branches. If would update remote-tracked branches to latest commit without touching your modification.

cd your_project_location
git fetch

git pull

git pull is basically same as git fetch && git merge origin/current_branch_name.

Assume the following history exists and the current branch is “master”:

              A---B---C master on origin
             /
        D---E---F---G master
            ^

Then “git pull” will fetch and replay the changes from the remote master branch since it diverged from the local master (i.e., E) until its current commit (C) on top of master and record the result in a new commit along with the names of the two parent commits and a log message from the user describing the changes.

              A---B---C origin/master
             /         \
        D---E---F---G---H master

git pull would fail, if there are any conflicts in merging.

Merge branch

git stash: Save uncommitted changes

git stash is a powerful command that help us to save uncommitted changes. It always save us when git fetch fails. Here is a list of git stash sub-commands.

List stash

git stash list

It would provide output like this.

stash@{0}: On branch_name: xxxxx

View stash

git stash show

Example output would like this, showing which file changed with the number of changed lines.

 drivers/media/platform/msm/camera_v2/sensor/Makefile                             |   2 ++
 drivers/misc/tspdrv/ImmVibeSPI.c                                                 |  21 +++++++++++
 drivers/nfc/Kconfig                                                              |   2 ++
 drivers/nfc/Makefile                                                             |   1 +
 drivers/power/qpnp-charger.c                                                     |  84 +++++++++++++++++++++++++++++++++++++++++++
 firmware/synaptics/g3/PLG352-V1.05-PR1648545-DS5.5.1.0-30055185.img              | Bin 0 -> 95744 bytes
 firmware/synaptics/g3/PLG352-V1.08-PR1670515-DS5.5.1.0-30055188.img              | Bin 0 -> 95744 bytes
 firmware/synaptics/g3/PLG391-V1.05-PR1653201-DS5.5.1.0.1066_10055185_S3528A1.img | Bin 0 -> 95744 bytes
 firmware/synaptics/g3/g3_kddi_jp_limit.txt                                       | 272 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 382 insertions(+)

Output stashed changes as patch

We could use git stash show with option -p to format stash as unified patch.

git stash show -p > filename.patch

Merge code on Github

Create pull request

To create a pull request, please click on New pull request button.

enter image description here

Then you would have a screen to select forks and branches. You could click on compare across forks to select branches in other forks.

enter image description here

The merging direction is from right to left, meaning that you are merging code on the right side branch to left side branch. If you are the owner of left side repository, you could directly merge the commits to your branch. If you are not the owner, a pull request would created in the left side repository waiting for repository owner to approval. The repository owner would receive an notification for reviewing your pull request.

enter image description here

Merge code with patch locally

Getting a single commit as patch on Github

Let’s merge a security fix into our Sails project. Here is the link of commit.

https://github.com/hkbu-kennycheng/gahk/commit/c9609f501c166bbdb5739f86a49bb4621e669264

We could get it as email patch format by appending .patch in the URL.

https://github.com/hkbu-kennycheng/gahk/commit/c9609f501c166bbdb5739f86a49bb4621e669264.patch

The file is actually an email with a unified patch file, which is easy to read and debug.

From c9609f501c166bbdb5739f86a49bb4621e669264 Mon Sep 17 00:00:00 2001
From: Kenny Cheng <31645711+hkbu-kennycheng@users.noreply.github.com>
Date: Tue, 29 Jan 2019 10:41:18 +0800
Subject: [PATCH] Update package.json fixs CVE-2018-3750

---
 package.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 00f78b4..0b2ef1c 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,8 @@
     "@sailshq/connect-redis": "^3.2.1",
     "@sailshq/socket.io-redis": "^5.2.0",
     "@sailshq/lodash": "^3.10.3",
-    "async": "2.0.1"
+    "async": "2.0.1",
+    "deep-extend": ">=0.5.1"
   },
   "devDependencies": {
     "@sailshq/eslint": "^4.19.3"

Apply patch with git am

To apply email patch file directly from URL, we could use the following command.

cd your_project_location
curl -k https://github.com/hkbu-kennycheng/gahk/commit/c9609f501c166bbdb5739f86a49bb4621e669264.patch | git am

curl is a command-line HTTP client, which download the patch for us and print it on standard output. We then pipe the standard output to git am, apply it in our local repository directly.

It will create a new commit in the repository. You could see the commit history by git log command.

git log