Skip to content

Commit 4db23c7

Browse files
committed
Merge branch 'release/1.0.0-beta.1'
2 parents f9cfac1 + a806b6a commit 4db23c7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+13056
-1352
lines changed

.codeclimate.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
version: "2"
2+
plugins:
3+
eslint:
4+
enabled: true
5+
channel: "eslint-6"
6+
exclude_patterns:
7+
- ".idea/"
8+
- ".*.json"
9+
- ".*.yml"
10+
- ".*ignore"
11+
- "node_modules/"
12+
- "__tests__/"
13+
- "*.js"
14+
- "*.json"

.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
DEBUG=1
2+
DB_HOST=localhost
3+
DB_USER=ad_display
4+
DB_PASSWORD=
5+
DB_NAME=ad_display
6+
DB_PREFIX=ad_
7+
#PORT=3000

.eslintignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.idea/
2+
jest.config.js

.eslintrc.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"env": {
3+
"commonjs": true,
4+
"es6": true,
5+
"node": true
6+
},
7+
"extends": [
8+
"standard"
9+
],
10+
"globals": {
11+
"Atomics": "readonly",
12+
"SharedArrayBuffer": "readonly"
13+
},
14+
"parserOptions": {
15+
"ecmaVersion": 2018
16+
},
17+
"root": true,
18+
"rules": {
19+
}
20+
}

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
node_modules
22
.idea
33
.env
4+
build
5+
coverage
6+
ext-display
7+
ext-console

.travis.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
language: node_js
2+
node_js:
3+
- 14
4+
- 12
5+
- 10
6+
7+
branches:
8+
except:
9+
- /^feature\/[-\w]+$/
10+
11+
env:
12+
global:
13+
- CC_TEST_REPORTER_ID=c1c885065adfd5bb4901e0bb11111b73d3b71833597bc1759741e02f89b1be7b
14+
15+
jobs:
16+
include:
17+
- name: "ESLint"
18+
before_script: skip
19+
script: npm run lint
20+
after_script: skip
21+
22+
before_script:
23+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
24+
- chmod +x ./cc-test-reporter
25+
- ./cc-test-reporter before-build
26+
after_script:
27+
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
28+
29+
notifications:
30+
email:
31+
on_success: never
32+
on_failure: always

LICENSE

Lines changed: 661 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Display Backend
2+
[![Build Status](https://travis-ci.com/alarmdisplay/display-backend.svg?branch=develop)](https://travis-ci.com/alarmdisplay/display-backend)
3+
4+
This component is the connection point for all Display units.
5+
While it maintains a [Socket.IO](https://socket.io/) connection with the Displays to push updates, it also offers a REST API to manage the Displays and their contents.
6+
7+
## Development
8+
In order to run a development version on your local system, you need a [Node.js](https://nodejs.org/) environment and a MariaDB instance.
9+
Clone the repository and run `npm install` inside the project folder to install all the dependencies.
10+
Then create a `.env` file based on `.env.example` and adapt it to your local setup.
11+
12+
Start the development server by running `npm run dev`, it will automatically restart when files have changed.
13+
Now you can access the server on http://localhost:3000 (may be a different port if you changed it in the `.env` file).
14+
15+
## Deployment
16+
At the moment, this project is not ready for deployment outside of a development environment.
17+
18+
## API
19+
The server offers an OpenAPI specification under `/api-docs.json` that can be used with tools like [Swagger UI](https://swagger.io/tools/swagger-ui/).

__tests__/.eslintrc.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"env": {
3+
"jest": true
4+
},
5+
"extends": [
6+
"../.eslintrc.json"
7+
]
8+
}

__tests__/api/v1/displays.test.js

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
const supertest = require('supertest')
2+
const DisplayService = require('../../../src/services/DisplayService')
3+
const NotFoundError = require('../../../src/errors/NotFoundError')
4+
5+
jest.mock('../../../src/services/DisplayService')
6+
7+
const baseUrl = '/api/v1/displays'
8+
9+
describe(baseUrl, () => {
10+
const app = require('../../../src/app')
11+
let displayService
12+
let request
13+
14+
beforeAll(() => {
15+
displayService = new DisplayService(undefined, undefined, undefined, undefined)
16+
const theApp = app(displayService, undefined, undefined)
17+
request = supertest(theApp)
18+
})
19+
20+
beforeEach(() => {
21+
DisplayService.mock.instances[0].getAllDisplays.mockClear()
22+
DisplayService.mock.instances[0].createDisplay.mockClear()
23+
DisplayService.mock.instances[0].getDisplayById.mockClear()
24+
DisplayService.mock.instances[0].updateDisplay.mockClear()
25+
DisplayService.mock.instances[0].deleteDisplay.mockClear()
26+
DisplayService.mock.instances[0].getViewsForDisplay.mockClear()
27+
})
28+
29+
describe('GET /', () => {
30+
it('should correctly call DisplayService.getAllDisplays()', () => {
31+
return request.get(`${baseUrl}/`)
32+
.expect(() => {
33+
expect(DisplayService.mock.instances[0].getAllDisplays.mock.calls).toHaveLength(1)
34+
expect(DisplayService.mock.instances[0].getAllDisplays.mock.calls[0]).toEqual([])
35+
})
36+
})
37+
38+
it('should return a JSON array', () => {
39+
DisplayService.mock.instances[0].getAllDisplays.mockResolvedValueOnce([])
40+
return request.get(`${baseUrl}/`)
41+
.expect('Content-Type', /json/)
42+
.expect(200)
43+
.expect([])
44+
})
45+
46+
it('should return the stored Displays', () => {
47+
const displays = [{ id: 1, name: 'A' }, { id: 2, name: 'B' }]
48+
DisplayService.mock.instances[0].getAllDisplays.mockResolvedValueOnce(displays)
49+
return request.get(`${baseUrl}/`)
50+
.expect('Content-Type', /json/)
51+
.expect(200)
52+
.expect(displays)
53+
})
54+
55+
it('should return 500 on internal error', () => {
56+
DisplayService.mock.instances[0].getAllDisplays.mockRejectedValueOnce(new Error('Error during GET /'))
57+
return request.get(`${baseUrl}/`)
58+
.expect('Content-Type', /json/)
59+
.expect(500)
60+
.expect({ error: { message: 'Error during GET /' } })
61+
})
62+
})
63+
64+
describe('POST /', () => {
65+
it('should correctly call DisplayService.getDisplayById()', () => {
66+
const display = { name: 'Some name', active: true, clientId: 'X29GH', description: 'Just testing', location: 'Somewhere' }
67+
return request.post(`${baseUrl}/`)
68+
.send(display)
69+
.expect(() => {
70+
expect(DisplayService.mock.instances[0].createDisplay.mock.calls).toHaveLength(1)
71+
expect(DisplayService.mock.instances[0].createDisplay.mock.calls[0]).toEqual(['Some name', true, 'X29GH', 'Just testing', 'Somewhere'])
72+
})
73+
})
74+
75+
it('should return the newly created Display with status 201', () => {
76+
const display = { id: 42, name: 'Another name', active: false, clientId: 'KJFLH2', description: 'Just testing...', location: 'Somewhere else' }
77+
DisplayService.mock.instances[0].createDisplay.mockResolvedValueOnce(display)
78+
return request.post(`${baseUrl}/`)
79+
.send({})
80+
.expect('Content-Type', /json/)
81+
.expect(201)
82+
.expect('Location', `${baseUrl}/42`)
83+
.expect(display)
84+
})
85+
86+
it('should allow to only submit the name', () => {
87+
return request.post(`${baseUrl}/`)
88+
.send({ name: 'The name' })
89+
.expect(() => {
90+
expect(DisplayService.mock.instances[0].createDisplay.mock.calls).toHaveLength(1)
91+
expect(DisplayService.mock.instances[0].createDisplay.mock.calls[0]).toEqual(['The name', false, '', '', ''])
92+
})
93+
})
94+
95+
it('should return 500 on internal error', () => {
96+
DisplayService.mock.instances[0].createDisplay.mockRejectedValueOnce(new Error('Error during POST /'))
97+
return request.post(`${baseUrl}/`)
98+
.expect('Content-Type', /json/)
99+
.expect(500)
100+
.expect({ error: { message: 'Error during POST /' } })
101+
})
102+
})
103+
104+
describe('GET /{id}', () => {
105+
it('should correctly call DisplayService.getDisplayById()', () => {
106+
return request.get(`${baseUrl}/165`)
107+
.expect(() => {
108+
expect(DisplayService.mock.instances[0].getDisplayById.mock.calls).toHaveLength(1)
109+
expect(DisplayService.mock.instances[0].getDisplayById.mock.calls[0]).toEqual([165])
110+
expect(Number.isInteger(DisplayService.mock.instances[0].getDisplayById.mock.calls[0][0])).toBeTruthy()
111+
})
112+
})
113+
114+
it('should return the Display if it exists', () => {
115+
const display = { id: 623, name: 'Name', active: false, clientId: 'KJFLH2', description: 'Just testing...', location: 'Somewhere else' }
116+
DisplayService.mock.instances[0].getDisplayById.mockResolvedValueOnce(display)
117+
return request.get(`${baseUrl}/623`)
118+
.expect('Content-Type', /json/)
119+
.expect(200)
120+
.expect(display)
121+
})
122+
123+
it('should return 404 if the Display does not exist', () => {
124+
DisplayService.mock.instances[0].getDisplayById.mockRejectedValueOnce(null)
125+
return request.get(`${baseUrl}/14`)
126+
.expect(404)
127+
})
128+
129+
it('should return 500 on internal error', () => {
130+
DisplayService.mock.instances[0].getDisplayById.mockRejectedValueOnce(new Error('Error during GET /{id}'))
131+
return request.get(`${baseUrl}/5`)
132+
.expect('Content-Type', /json/)
133+
.expect(500)
134+
.expect({ error: { message: 'Error during GET /{id}' } })
135+
})
136+
})
137+
138+
describe('PUT /{id}', () => {
139+
it('should correctly call DisplayService.updateDisplay()', () => {
140+
return request.put(`${baseUrl}/56`)
141+
.send({
142+
id: 56,
143+
name: 'Another name',
144+
active: false,
145+
clientId: 'KJFLH2',
146+
description: 'Just testing...',
147+
location: 'Somewhere else'
148+
})
149+
.expect(() => {
150+
expect(DisplayService.mock.instances[0].updateDisplay.mock.calls).toHaveLength(1)
151+
expect(DisplayService.mock.instances[0].updateDisplay.mock.calls[0]).toEqual([56, 'Another name', false, 'KJFLH2', 'Just testing...', 'Somewhere else'])
152+
})
153+
})
154+
155+
it('should return the updated Display', () => {
156+
const display = { id: 253, name: 'Hello', active: true, clientId: 'JLA73G', description: 'None', location: 'Also none' }
157+
DisplayService.mock.instances[0].updateDisplay.mockResolvedValueOnce(display)
158+
return request.put(`${baseUrl}/253`)
159+
.send({})
160+
.expect('Content-Type', /json/)
161+
.expect(200)
162+
.expect(display)
163+
})
164+
165+
it('should return 404 when Display does not exist', () => {
166+
DisplayService.mock.instances[0].updateDisplay.mockRejectedValueOnce(null)
167+
return request.put(`${baseUrl}/12`)
168+
.expect(404)
169+
})
170+
171+
it('should return 500 on internal error in DisplayService.updateDisplay()', () => {
172+
DisplayService.mock.instances[0].updateDisplay.mockRejectedValueOnce(new Error('Error during PUT /{id}'))
173+
return request.put(`${baseUrl}/5`)
174+
.expect('Content-Type', /json/)
175+
.expect(500)
176+
.expect({ error: { message: 'Error during PUT /{id}' } })
177+
})
178+
})
179+
180+
describe('DELETE /{id}', () => {
181+
it('should correctly call DisplayService.deleteDisplay()', () => {
182+
return request.delete(`${baseUrl}/1238`)
183+
.expect(() => {
184+
expect(DisplayService.mock.instances[0].deleteDisplay.mock.calls).toHaveLength(1)
185+
expect(DisplayService.mock.instances[0].deleteDisplay.mock.calls[0]).toEqual([1238])
186+
})
187+
})
188+
189+
it('should return 500 on internal error', () => {
190+
DisplayService.mock.instances[0].deleteDisplay.mockRejectedValueOnce(new Error('Error during DELETE /{id}'))
191+
return request.delete(`${baseUrl}/5`)
192+
.expect('Content-Type', /json/)
193+
.expect(500)
194+
.expect({ error: { message: 'Error during DELETE /{id}' } })
195+
})
196+
})
197+
198+
describe('GET /{id}/views', () => {
199+
it('should correctly call DisplayService.getDisplayById()', () => {
200+
return request.get(`${baseUrl}/272/views`)
201+
.expect(() => {
202+
expect(DisplayService.mock.instances[0].getDisplayById.mock.calls).toHaveLength(1)
203+
expect(DisplayService.mock.instances[0].getDisplayById.mock.calls[0]).toEqual([272])
204+
})
205+
})
206+
207+
it('should correctly call DisplayService.getViewsForDisplay()', () => {
208+
DisplayService.mock.instances[0].getDisplayById.mockResolvedValueOnce({ id: 312 })
209+
return request.get(`${baseUrl}/312/views`)
210+
.expect(() => {
211+
expect(DisplayService.mock.instances[0].getViewsForDisplay.mock.calls).toHaveLength(1)
212+
expect(DisplayService.mock.instances[0].getViewsForDisplay.mock.calls[0]).toEqual([312])
213+
})
214+
})
215+
216+
it('should return a list of Views', () => {
217+
const views = []
218+
DisplayService.mock.instances[0].getDisplayById.mockResolvedValueOnce({ id: 1268 })
219+
DisplayService.mock.instances[0].getViewsForDisplay.mockResolvedValueOnce(views)
220+
return request.get(`${baseUrl}/1268/views`)
221+
.expect('Content-Type', /json/)
222+
.expect(200)
223+
.expect(views)
224+
})
225+
})
226+
})

0 commit comments

Comments
 (0)