diff --git a/.travis.yml b/.travis.yml index 9c10c08b..17c64444 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,14 +3,27 @@ dist: trusty language: node_js node_js: - "8" - - "7" before_install: - npm install -g npm@5 - npm install -g greenkeeper-lockfile@1 -before_script: greenkeeper-lockfile-update +before_script: + - greenkeeper-lockfile-update + - sudo mount --make-shared / + - sudo service docker stop + - sudo sed -i 's/DOCKER_OPTS=\"/DOCKER_OPTS=\"--insecure-registry 172.30.0.0\/16 /' /etc/default/docker + - sudo service docker start + - wget https://github.com/openshift/origin/releases/download/v3.9.0-alpha.3/openshift-origin-client-tools-v3.9.0-alpha.3-78ddc10-linux-64bit.tar.gz + - tar xvzOf openshift-origin-client-tools-v3.9.0-alpha.3-78ddc10-linux-64bit.tar.gz > oc.bin + - sudo mv oc.bin /usr/bin/oc + - sudo chmod 755 /usr/bin/oc script: - npm run ci + - oc cluster up + - sleep 10 + - oc new-app -e POSTGRESQL_USER=luke -ePOSTGRESQL_PASSWORD=secret -ePOSTGRESQL_DATABASE=my_data openshift/postgresql-92-centos7 --name=my-database + - sleep 10 + - npm run test:integration # Only the node version 8 job will upload the lockfile after_script: greenkeeper-lockfile-upload branches: diff --git a/package-lock.json b/package-lock.json index 7e8da973..6aa8fdb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6694,6 +6694,17 @@ "through": "2.3.8" } }, + "rhoaster": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/rhoaster/-/rhoaster-0.2.0.tgz", + "integrity": "sha512-VaM5pawaPprWtXmATEK1by1QIPQLtj2u/j55Dx28OntpDi+AUdsUp3GE5eAC0zohaFU1dnRfyS3Ngs9SMo071Q==", + "dev": true, + "requires": { + "nodeshift": "1.5.1", + "openshift-config-loader": "0.4.0", + "openshift-rest-client": "1.0.1" + } + }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", diff --git a/package.json b/package.json index e7f86562..4a5f1cd1 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "license": "Apache-2.0", "scripts": { "test": "tape test/*.js | tap-spec", + "test:integration": "tape test/integration/*.js | tap-spec", + "test:integration:undeploy": "nodeshift --strictSSL=false undeploy", "lint": "eslint test/*.js app.js bin/*", "prepare": "echo 'To confirm CVE compliance, run \"npm run security-check\"' ", "security-check": "nsp check", @@ -56,6 +58,7 @@ "nsp": "~3.2.1", "nyc": "~11.6.0", "proxyquire": "^2.0.0", + "rhoaster": "~0.2.0", "standard-version": "^4.3.0", "supertest": "^3.0.0", "szero": "^1.0.0", diff --git a/test/integration/fruits-integration-test.js b/test/integration/fruits-integration-test.js new file mode 100644 index 00000000..c6c7ff89 --- /dev/null +++ b/test/integration/fruits-integration-test.js @@ -0,0 +1,135 @@ +'use strict'; + +'use strict'; + +const test = require('tape'); +const supertest = require('supertest'); +const rhoaster = require('rhoaster'); + +const testEnvironment = rhoaster({ + deploymentName: 'nodejs-rest-http-crud', + nodeVersion: '8.x' +}); + +testEnvironment.deploy() + .then(runTests) + .then(_ => test.onFinish(testEnvironment.undeploy)) + .catch(console.error); + +function runTests (route) { + // GET fruits + test('/api/fruits', t => { + t.plan(2); + supertest(route) + .get('/api/fruits') + .expect(200) + .expect('Content-Type', /json/) + .then(response => { + t.equal(Array.isArray(response.body), true, 'response is an Array'); + t.equal(response.body.length, 3, 'should be initialized with 3 items'); + t.end(); + }); + }); + + // GET 1 fruit + test('/api/fruit/:id', t => { + t.plan(4); + supertest(route) + .get('/api/fruits/1') + .expect(200) + .expect('Content-Type', /json/) + .then(response => { + t.equal(Array.isArray(response.body), false, 'not an array returned'); + t.equal(response.body.id, 1, 'id is 1'); + t.equal(response.body.name, 'Apple', 'name is Apple'); + t.equal(response.body.stock, '10', 'stock is 10'); + t.end(); + }); + }); + + // GET 1 fruit that doesn't exist + test('/api/fruit/:id - does not exist', t => { + t.plan(1); + supertest(route) + .get('/api/fruits/10') + .expect(404) + .expect('Content-Type', /text/) + .then(response => { + t.equal(response.text, 'Item 10 not found', 'return not found string'); + t.end(); + }); + }); + + // POST a fruit + test('POST /api/fruit/', t => { + t.plan(4); + const fruitData = { + name: 'Banana', + stock: '10' + }; + + supertest(route) + .post('/api/fruits') + .send(fruitData) + .expect(201) + .expect('Content-Type', /json/) + .then(response => { + t.equal(Array.isArray(response.body), false, 'not an array returned'); + t.ok(response.body.id, 'has a new id field'); + t.equal(response.body.name, fruitData.name, `name is ${fruitData.name}`); + t.equal(response.body.stock, fruitData.stock, `stock is ${fruitData.stock}`); + + // clean up + supertest(route).delete(`/api/fruits/${response.body.id}`).then(_ => ({})); + t.end(); + }); + }); + + // POST a fruit + test('POST /api/fruit/ - send non json', t => { + const fruitData = '{name: \'Banana\', stock: \'10\'}'; + + supertest(route) + .post('/api/fruits') + .send(fruitData) + .expect(422) + .then(response => { + t.pass('should fail with 422'); + t.end(); + }); + }); + + // PUT a fruit + test('PUT /api/fruit/:id', t => { + // t.plan(0); + const fruitData = { + name: 'put fruit', + stock: '10' + }; + + // First create the new fruit + supertest(route) + .post('/api/fruits') + .send(fruitData) + .expect(201) + .then(response => { + const id = response.body.id; + + const updatedFruit = { + name: response.body.name, + stock: '20' + }; + + supertest(route) + .put(`/api/fruits/${id}`) + .send(updatedFruit) + .expect(204) + .then(putResponse => { + // clean up + t.pass('should return with an empty response'); + supertest(route).delete(`/api/fruits/${response.body.id}`).then(_ => ({})); + t.end(); + }); + }); + }); +}