diff --git a/.eslintrc.json b/.eslintrc.json index 3133bf6b..23deaa49 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,15 +3,16 @@ "ecmaVersion": 2020 }, "root": true, - "plugins": ["@swrlab/eslint-plugin-swr"], - "extends": ["plugin:@swrlab/eslint-plugin-swr/recommended"], + "plugins": ["@swrlab/eslint-plugin-swr", "chai-friendly"], + "extends": ["plugin:@swrlab/eslint-plugin-swr/recommended", "plugin:chai-friendly/recommended"], "ignorePatterns": ["docs/_SIDEBAR.md"], "rules": { "radix": 0, "no-unused-vars": [ "error", { - "argsIgnorePattern": "next" + "argsIgnorePattern": "next", + "varsIgnorePattern": "should" } ], "no-param-reassign": [2, { "props": false }], diff --git a/CHANGELOG.md b/CHANGELOG.md index ccc15262..e1e8b580 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.7] - 2021-03-18 + +### Changes + +- Add auth verification for unit tests +- Check `content-type` in unit tests +- Update unit tests for new ESLint config + ## [0.1.6] - 2021-03-17 ### Changes diff --git a/README.md b/README.md index a0f2ef75..bcd7ec17 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ This source code is provided under EUPL v1.2, except for the [`spdx-exceptions`] | NPM DEV | `docsify-cli` | [MIT](https://github.com/docsifyjs/docsify-cli/blob/master/LICENSE) | | NPM DEV | `eslint` | [MIT](https://github.com/eslint/eslint/blob/master/LICENSE) | | NPM DEV | `eslint-plugin-swr` | [ISC](https://github.com/swrlab/eslint-plugin-swr/blob/main/package.json) | +| NPM DEV | `eslint-plugin-chai-friendly` | [MIT](https://github.com/ihordiachenko/eslint-plugin-chai-friendly/blob/master/LICENSE) | | NPM DEV | `license-compliance` | [MIT](https://github.com/tmorell/license-compliance/blob/master/LICENSE) | | NPM DEV | `mocha` | [MIT](https://github.com/mochajs/mocha/blob/master/LICENSE) | | NPM DEV | `nodemon` | [MIT](https://github.com/remy/nodemon/blob/master/LICENSE) | diff --git a/openapi.json b/openapi.json index c6fb44cd..ba34c7eb 100644 --- a/openapi.json +++ b/openapi.json @@ -11,7 +11,7 @@ "name": "European Union Public License 1.2", "url": "https://spdx.org/licenses/EUPL-1.2.html" }, - "version": "0.1.6" + "version": "0.1.7" }, "externalDocs": { "description": "ARD-Eventhub Documentation", diff --git a/openapi.yaml b/openapi.yaml index d63b8171..e6a13775 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -10,7 +10,7 @@ info: license: name: European Union Public License 1.2 url: 'https://spdx.org/licenses/EUPL-1.2.html' - version: 0.1.6 + version: 0.1.7 externalDocs: description: ARD-Eventhub Documentation url: 'https://swrlab.github.io/ard-eventhub/' diff --git a/package.json b/package.json index 9c64e3ff..a8db56a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ard-eventhub", - "version": "0.1.6", + "version": "0.1.7", "description": "ARD system to distribute real-time (live) metadata for primarily radio broadcasts.", "main": "./src/ingest/index.js", "scripts": { @@ -41,6 +41,7 @@ "chai-http": "^4.3.0", "docsify-cli": "^4.4.3", "eslint": "^7.22.0", + "eslint-plugin-chai-friendly": "^0.6.0", "eslint-plugin-swr": "0.0.5", "license-compliance": "^1.0.3", "mocha": "^8.3.2", diff --git a/test/README.md b/test/README.md index 0fdb4285..7c56fbfd 100644 --- a/test/README.md +++ b/test/README.md @@ -15,6 +15,7 @@ It needs several environment variables to work: - OPTIONAL `PORT` - override server port setting, default is 8080 - REQUIRED `TEST_USER` - test user email - REQUIRED `TEST_USER_PW` - test user password +- OPTIONAL `TEST_USER_RESET` - test email reset (request limit) ## Setup diff --git a/test/example.test.js b/test/example.test.js index 0c6c20da..aaf1e0cc 100644 --- a/test/example.test.js +++ b/test/example.test.js @@ -7,7 +7,10 @@ */ -const expect = require('chai').expect +// Add eslint exceptions for chai +/* global describe it */ + +const { expect } = require('chai') describe('Simple Math Test', () => { it('1 + 1 = 2', () => { diff --git a/test/ingest.test.js b/test/ingest.test.js index 556f0b98..c1140d63 100644 --- a/test/ingest.test.js +++ b/test/ingest.test.js @@ -7,22 +7,37 @@ */ +// Add eslint exceptions +/* eslint-disable object-shorthand */ +/* global describe it before */ + // Require dependencies const chai = require('chai') const chaiHttp = require('chai-http') const server = require('../src/ingest/index') -// Init functions +// Init chai functions +const { expect } = chai const should = chai.should() -const expect = chai.expect // Use chaiHttp chai.use(chaiHttp) +// define general tests +function testResponse(res, status) { + expect(res).to.be.json + expect(res).to.have.status(status) +} + +function testAuth(res) { + expect(res).to.have.status(403) +} + /* AUTH - Authentication services for Eventhub */ +const loginPath = '/auth/login' let accessToken, refreshToken function testAuthKeys(body) { @@ -34,10 +49,9 @@ function testAuthKeys(body) { body.should.have.property('user').to.be.a('object') body.user.should.have.property('user_id').to.be.a('string') body.user.should.have.property('email_verified').to.be.a('boolean') - //body.should.have.property('trace').eql(null); } -describe('POST /auth/login', () => { +describe(`POST ${loginPath}`, () => { it('swap login credentials for an id-token', (done) => { const loginRequest = { email: process.env.TEST_USER, @@ -45,10 +59,10 @@ describe('POST /auth/login', () => { } chai.request(server) - .post('/auth/login') + .post(loginPath) .send(loginRequest) .end((err, res) => { - res.should.have.status(200) + testResponse(res, 200) testAuthKeys(res.body) done() // Store tokens for further tests @@ -58,17 +72,19 @@ describe('POST /auth/login', () => { }) }) -describe('POST /auth/refresh', () => { +const refreshPath = '/auth/refresh' + +describe(`POST ${refreshPath}`, () => { it('swap refresh-token for new id-token', (done) => { const refreshRequest = { refreshToken: refreshToken, } chai.request(server) - .post('/auth/refresh') + .post(refreshPath) .send(refreshRequest) .end((err, res) => { - res.should.have.status(200) + testResponse(res, 200) testAuthKeys(res.body) done() // Store new token for further tests @@ -78,18 +94,20 @@ describe('POST /auth/refresh', () => { }) // 🚨 firebase limit is 150 requests per day 🚨 +const resetPath = '/auth/reset' + if (process.env.TEST_USER_RESET) { - describe('POST /auth/reset', () => { + describe(`POST ${resetPath}`, () => { it('request password reset email', (done) => { const resetRequest = { email: process.env.TEST_USER, } chai.request(server) - .post('/auth/reset') + .post(resetPath) .send(resetRequest) .end((err, res) => { - res.should.have.status(200) + testResponse(res, 200) done() }) }) @@ -104,38 +122,52 @@ function testEventKeys(body) { body.should.be.a('object') body.should.have.property('topics').to.be.a('object') body.should.have.property('message').to.be.a('object') - //body.should.have.property('trace').eql(null); } const eventName = 'de.ard.eventhub.v1.radio.track.playing' +const eventPath = `/events/${eventName}` + +const swrTV = '990030' +const ardDE = '990140' +const event = { + event: eventName, + type: 'music', + start: '2020-01-01T06:00:00+01:00', + title: 'Unit Test Song', + serviceIds: [swrTV, ardDE], + playlistItemId: 'unit-test-playlist', +} -describe(`POST /events/${eventName}`, () => { - it('publish a new event', (done) => { - const event = { - event: eventName, - type: 'music', - start: '2020-01-19T06:00:00+01:00', - title: 'Song name', - serviceIds: ['990030', '990140'], - playlistItemId: 'swr3-5678', - } +describe(`POST ${eventPath}`, () => { + it('test auth for POST /event', (done) => { + chai.request(server) + .post(eventPath) + .set('Authorization', `Bearer invalid${accessToken}`) + .send(event) + .end((err, res) => { + testAuth(res) + done() + }) + }) + it('publish a new event', (done) => { chai.request(server) - .post(`/events/${eventName}`) + .post(eventPath) .set('Authorization', `Bearer ${accessToken}`) .send(event) .end((err, res) => { - res.should.have.status(201) + testResponse(res, 201) testEventKeys(res.body) done() }) }) }) -// /* -// TOPICS - Access to topics details -// */ +/* + TOPICS - Access to topics details +*/ +const topicPath = '/topics' let topicName function testTopicKeys(body) { @@ -146,16 +178,30 @@ function testTopicKeys(body) { body.should.have.property('labels').to.be.a('object') } -describe('GET /topics', () => { +describe(`GET ${topicPath}`, () => { + it(`test auth for GET ${topicPath}`, (done) => { + chai.request(server) + .get(topicPath) + .set('Authorization', `Bearer invalid${accessToken}`) + .end((err, res) => { + testAuth(res) + done() + }) + }) + it('list all available topics', (done) => { chai.request(server) - .get('/topics') + .get(topicPath) .set('Authorization', `Bearer ${accessToken}`) .end((err, res) => { - res.should.have.status(200) + testResponse(res, 200) res.body.should.be.a('array') res.body.every((i) => testTopicKeys(i)) - topicName = res.body[0].name + res.body.forEach((topic) => { + if (topic.name.indexOf(swrTV) !== -1) { + topicName = topic.name + } + }) done() }) }) @@ -165,6 +211,7 @@ describe('GET /topics', () => { SUBSCRIPTIONS - Access to subscription management */ +const subscriptPath = '/subscriptions' let subscriptionName function testSubscriptionKeys(body) { @@ -179,7 +226,6 @@ function testSubscriptionKeys(body) { body.topic.should.have.property('path').to.be.a('string') body.should.have.property('ackDeadlineSeconds').to.be.a('number') body.should.have.property('retainAckedMessages').to.be.a('boolean') - //body.should.have.property('retryPolicy').eql(null); body.should.have.property('serviceAccount').to.be.a('string') body.should.have.property('labels').to.be.a('object') body.labels.should.have.property('id').to.be.a('string') @@ -189,22 +235,38 @@ function testSubscriptionKeys(body) { body.should.have.property('owner').to.be.a('string') } -describe('POST /subscriptions', () => { - it('add a new subscription to this user', (done) => { - const subscription = { +describe(`POST ${subscriptPath}`, () => { + let subscription + + before((done) => { + subscription = { type: 'PUBSUB', method: 'PUSH', - url: 'https://example.com/my/webhook/for/this/subscription', - contact: 'my-emergency-and-notifications-contact@ard.de', + url: 'https://unit.test/eventhub/subscription', + contact: 'eventhub-unit-test@ard.de', topic: topicName, } + done() + }) + + it(`test auth for POST ${subscriptPath}`, (done) => { + chai.request(server) + .post(subscriptPath) + .set('Authorization', `Bearer invalid${accessToken}`) + .send(subscription) + .end((err, res) => { + testAuth(res) + done() + }) + }) + it('add a new subscription to this user', (done) => { chai.request(server) - .post('/subscriptions') + .post(subscriptPath) .set('Authorization', `Bearer ${accessToken}`) .send(subscription) .end((err, res) => { - res.should.have.status(201) + testResponse(res, 201) testSubscriptionKeys(res.body) // Store subscription name for further tests subscriptionName = res.body.name @@ -213,13 +275,23 @@ describe('POST /subscriptions', () => { }) }) -describe('GET /subscriptions', () => { +describe(`GET ${subscriptPath}`, () => { + it(`test auth for GET ${subscriptPath}`, (done) => { + chai.request(server) + .get(subscriptPath) + .set('Authorization', `Bearer invalid${accessToken}`) + .end((err, res) => { + testAuth(res) + done() + }) + }) + it('list all subscriptions for this user', (done) => { chai.request(server) - .get('/subscriptions') + .get(subscriptPath) .set('Authorization', `Bearer ${accessToken}`) .end((err, res) => { - res.should.have.status(200) + testResponse(res, 200) res.body.should.be.a('array') res.body.every((i) => testSubscriptionKeys(i)) done() @@ -227,29 +299,48 @@ describe('GET /subscriptions', () => { }) }) -describe('GET /subscriptions/{name}', () => { +describe(`GET ${subscriptPath}/{name}`, () => { + it(`test auth for GET ${subscriptPath}/{name}`, (done) => { + chai.request(server) + .get(`${subscriptPath}/${subscriptionName}`) + .set('Authorization', `Bearer invalid${accessToken}`) + .end((err, res) => { + testAuth(res) + done() + }) + }) + it('get details about single subscription from this user', (done) => { chai.request(server) - .get(`/subscriptions/${subscriptionName}`) + .get(`${subscriptPath}/${subscriptionName}`) .set('Authorization', `Bearer ${accessToken}`) .end((err, res) => { - res.should.have.status(200) + testResponse(res, 200) testSubscriptionKeys(res.body) done() }) }) }) -describe('DELETE /subscriptions/{name}', () => { +describe(`DELETE ${subscriptPath}/{name}`, () => { + it(`test auth for DELETE ${subscriptPath}/{name}`, (done) => { + chai.request(server) + .delete(`${subscriptPath}/${subscriptionName}`) + .set('Authorization', `Bearer invalid${accessToken}`) + .end((err, res) => { + testAuth(res) + done() + }) + }) + it('remove a single subscription by this user', (done) => { chai.request(server) - .delete(`/subscriptions/${subscriptionName}`) + .delete(`${subscriptPath}/${subscriptionName}`) .set('Authorization', `Bearer ${accessToken}`) .end((err, res) => { - res.should.have.status(200) + testResponse(res, 200) res.body.should.be.a('object') res.body.should.have.property('valid').eql(true) - //res.body.should.have.property('trace').eql(null); done() }) }) diff --git a/yarn.lock b/yarn.lock index 18ef09be..97a94116 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1912,6 +1912,11 @@ eslint-module-utils@^2.6.0: debug "^2.6.9" pkg-dir "^2.0.0" +eslint-plugin-chai-friendly@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.6.0.tgz#54052fab79302ed0cea76ab997351ea4809bfb77" + integrity sha512-Uvvv1gkbRGp/qfN15B0kQyQWg+oFA8buDSqrwmW3egNSk/FpqH2MjQqKOuKwmEL6w4QIQrIjDp+gg6kGGmD3oQ== + eslint-plugin-import@^2.22.0: version "2.22.1" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702"