Browse Source

Merge branch 'feature/link-db-ws' into develop

rigwild 2 years ago
parent
commit
6576fc505c

+ 1 - 1
config.js

@@ -16,7 +16,7 @@ export const imageServedUrl = apiPrefix + '/images'
 export const serverPort = parseInt(process.env.PORT, 10) || 5000
 
 // MongoDB database connection URI
-export const mongoDatabaseURI = process.env.MONGO_URI || 'mongodb://mongo:27017/webexpe'
+export const mongoDatabaseURI = process.env.MONGO_URI || 'mongodb://localhost:27017/webexpe'
 
 // The directory where the images are stored
 export const imagesPath = TEST_MODE

+ 2 - 1
docker-compose.yml

@@ -23,9 +23,10 @@ services:
         ports:
           - "${PORT:-5000}:5000"
         environment:
-            NODE_ENV: production
+            NODE_ENV: "production"
             SERVE_CLIENT: "${SERVE_CLIENT:-true}"
             PORT: "5000"
+            MONGO_URI: "${MONGO_URI:-mongodb://mongo:27017/webexpe}"
         links:
             - mongo
         volumes:

+ 13 - 5
server/database/controllers/Data.js

@@ -1,24 +1,32 @@
 'use strict'
 
 import DataModel from '../models/Data'
-import { dbLogger } from '../../../config'
+import { dbLogger, TEST_MODE } from '../../../config'
 import { formatLog } from '../../functions'
 
 export default class Data {
+  static get Model() {
+    return DataModel
+  }
+
+  static log(data) {
+    if (!TEST_MODE) dbLogger.info(formatLog(data))
+  }
+
   static async add(dataObj) {
     const doc = await DataModel.create({ data: dataObj })
-    dbLogger.info(formatLog(`New document was added. id=${doc.id}`))
+    this.log(`New document was added. id=${doc.id}`)
     return doc
   }
 
   static async del(dataId) {
     const doc = await DataModel.findByIdAndDelete(dataId)
-    dbLogger.info(formatLog(`A document was deleted. id=${doc.id}`))
+    this.log(`A document was deleted. id=${doc.id}`)
   }
 
   static async update(dataId, newDataObj) {
-    const doc = await DataModel.findByIdAndUpdate(dataId, newDataObj, { new: true })
-    dbLogger.info(formatLog(`A document was updated. id=${doc.id}`))
+    const doc = await DataModel.findByIdAndUpdate(dataId, { $set: { data: newDataObj } }, { new: true })
+    this.log(`A document was updated. id=${doc.id}`)
     return doc
   }
 

+ 3 - 3
server/database/index.js

@@ -1,14 +1,14 @@
 'use strict'
 
 import mongoose from 'mongoose'
-import { mongoDatabaseURI, dbLogger } from '../../config'
+import { mongoDatabaseURI, dbLogger, TEST_MODE } from '../../config'
 import { formatLog, formatError } from '../functions'
 
 const connectDb = async () => {
-  await mongoose.connect(mongoDatabaseURI, { useNewUrlParser: true })
+  await mongoose.connect(mongoDatabaseURI, { useNewUrlParser: true, useFindAndModify: false })
   mongoose.connection.on('error', err => dbLogger.error(formatError(err)))
 
-  dbLogger.info(formatLog('The database connection was established.'))
+  if (!TEST_MODE) dbLogger.info(formatLog('The database connection was established.'))
 }
 
 export default connectDb

+ 1 - 0
server/index.js

@@ -12,6 +12,7 @@ import { apiPrefix, imageServedUrl, serverPort, serveClient, imagesPath, logger
 import startWebSocketServer from './webSocket'
 import connectDb from './database'
 const morgan = require('morgan')
+
 const app = express()
 
 // Activating logging

+ 4 - 4
server/webSocket/index.js

@@ -2,10 +2,9 @@
 
 import WebSocket from 'ws'
 import { formatLog, formatError } from '../functions'
-import { wsLogger } from '../../config'
+import { wsLogger, TEST_MODE } from '../../config'
 import messageHandler from './messageHandler'
 
-
 /**
  * @typedef {function} ErrorLogger
  * @param {Error} err an Error object
@@ -17,8 +16,9 @@ import messageHandler from './messageHandler'
  * @returns {ErrorLogger} the actual error logger
  */
 export const errorHandler = ws => err => {
-  ws.send(err.message)
-  wsLogger.error(formatError(err))
+  const errStr = formatError(err)
+  ws.send(errStr)
+  if (!TEST_MODE) wsLogger.error(errStr)
 }
 
 /**

+ 4 - 2
server/webSocket/messageHandler.js

@@ -1,7 +1,8 @@
 'use strict'
 
 import { formatLog } from '../functions'
-import { wsLogger } from '../../config'
+import { wsLogger, TEST_MODE } from '../../config'
+import DataController from '../database/controllers/Data'
 
 /**
  * @typedef {Function} MessageHandler
@@ -21,7 +22,8 @@ const messageHandler = ws => async data => {
     throw new Error('Invalid JSON data.')
   }
 
-  wsLogger.info(formatLog(json, 'message'))
+  await DataController.add(json)
+  if (!TEST_MODE) wsLogger.info(formatLog(json, 'message'))
   ws.send('ok')
 }
 

+ 27 - 5
test/api/_test_functions.js

@@ -2,9 +2,13 @@
 
 import path from 'path'
 import express from 'express'
+import WebSocket from 'ws'
 import serveStatic from 'serve-static'
 import routes from '../../server/routes'
 import { apiPrefix, imageServedUrl, imagesPath } from '../../config'
+import connectDb from '../../server/database'
+import { errorHandler as wsErrorHandler } from '../../server/webSocket'
+import wsMessageHandler from '../../server/webSocket/messageHandler'
 
 // Path to `test` directory
 export const testDir = path.resolve(__dirname, '..')
@@ -13,15 +17,17 @@ export const testDir = path.resolve(__dirname, '..')
 export const json = obj => 'JSON DATA : ' + (JSON.stringify(obj, null, 2) || obj)
 
 /**
- * Uses supertest to open an Express server on an ephemeral port.
+ * Open an Express server not listening to any port.
  * The server serves images in `test/images`, all api routes and
  * uses a custom error handler (no logging to stdout).
  *
  * Using `request` (supertest) on this object will start the server
+ * on an ephemeral port.
  *
+ * @param {PluginConfig} plugins plugins that should be loaded with the server
  * @returns {object} an Express server
  */
-export const serve = () => {
+export const getHttpServer = () => {
   const app = express()
   app.use(imageServedUrl, serveStatic(imagesPath))
   app.use(apiPrefix, routes)
@@ -34,7 +40,23 @@ export const serve = () => {
   return app
 }
 
-// Before each tests, start a server
-export const beforeEachTests = async t => {
-  t.context.server = serve()
+/**
+ * Open a WebSocket server on top of a HTTP server
+ *
+ * @param {object} httpServer a HTTP server instance (ie. Express server object)
+ * @returns {object} a WebSocket server instance
+ */
+export const getWebSocketServer = httpServer => {
+  const wss = new WebSocket.Server({ server: httpServer })
+  wss.on('error', err => {
+    throw err
+  })
+  wss.on('connection', ws => {
+    ws.on('message', data => wsMessageHandler(ws)(data).catch(wsErrorHandler(ws)))
+    ws.on('error', wsErrorHandler(ws))
+  })
+  return wss
 }
+
+/** Connect to the database */
+export { connectDb }

+ 106 - 0
test/api/databaseWebSocket.js

@@ -0,0 +1,106 @@
+'use strict'
+
+import test from 'ava'
+import WebSocket from 'ws'
+import { json, getHttpServer, getWebSocketServer, connectDb } from './_test_functions'
+import DataController from '../../server/database/controllers/Data'
+
+// Database and WebSocket testing
+
+// Before all tests, connect to the database
+test.beforeEach(async t => (t.context.server = await getHttpServer()))
+
+test('Check database is working', async t => {
+  // Connect to database
+  await connectDb()
+
+  // Add the document
+  const testData = { AUTOMATED_TEST_DB: true, TEST_DATABASE_OBJ: { msg: 'add' } }
+  const doc = await DataController.add(testData)
+  t.deepEqual(doc.data, testData, json(doc))
+
+  // Find the document
+  const findDoc = await DataController.find(doc.id)
+  t.deepEqual(findDoc.data, testData, json(findDoc))
+
+  // Update the document
+  testData.TEST_DATABASE_OBJ.msg = 'updated'
+  const updateTo = { AUTOMATED_TEST_DB: true, newObject: 'test', newProperties: { test: true } }
+  const docUpdated = await DataController.update(doc.id, updateTo)
+  t.deepEqual(docUpdated.data, updateTo, json(docUpdated))
+
+  // Delete the added document
+  await t.notThrowsAsync(DataController.del(doc.id))
+})
+
+test('Check WebSocket server is working', async t => {
+  // Connect to database
+  await connectDb()
+
+  // Start the server and get its ephemeral port
+  const server = t.context.server.listen(0)
+  const { port } = server.address()
+
+  // Start the WebSocket server
+  getWebSocketServer(server)
+
+  t.timeout(15000)
+  t.plan(11)
+
+  // Start the WebSocket client
+  const ws = new WebSocket(`ws://localhost:${port}`)
+  await new Promise((resolve, reject) => {
+    let sent = 0
+    let received = 0
+    ws.on('open', async () => {
+      // Send data on connect
+      ws.send(JSON.stringify({ AUTOMATED_TEST_WS: true, TEST_OBJECT: { msg: 'open' } }))
+      t.pass()
+      sent++
+    })
+    ws.on('message', async receivedData => {
+      received++
+      if (sent === 1) {
+        // Send data on receive
+        t.is('ok', receivedData, json(receivedData))
+        ws.send(JSON.stringify({ AUTOMATED_TEST_WS: true, TEST_OBJECT: { msg: 'message' } }))
+        t.pass()
+        sent++
+      }
+      else if (sent === 2) {
+        // Send invalid JSON data
+        t.is('ok', receivedData, json(receivedData))
+        ws.send('Not a valid JSON string')
+        t.pass()
+        sent++
+      }
+      else if (sent === 3) {
+        // Received error from server, check it is valid JSON
+        let obj = null
+        t.notThrows(() => {
+          try {
+            obj = JSON.parse(receivedData)
+          }
+          catch (err) {
+            throw new Error('Not valid JSON')
+          }
+        })
+        t.truthy(obj, json(receivedData))
+        t.is(obj.log.error, 'Invalid JSON data.', json(obj))
+        t.truthy(obj.log.stack, json(obj))
+        t.is(sent, received)
+        resolve()
+      }
+    })
+    ws.on('error', async err => {
+      // Unknown WebSocket error
+      t.fail(json(err.message))
+      reject(err)
+    })
+  })
+
+  // Delete every collected data during test
+  const db = DataController.Model
+  const found = await db.remove({ 'data.AUTOMATED_TEST_WS': true })
+  t.true(found.deletedCount >= 2)
+})

+ 2 - 2
test/api/getImage.js

@@ -3,12 +3,12 @@
 import test from 'ava'
 import request from 'supertest'
 import { apiPrefix, imageServedUrl } from '../../config'
-import { json, beforeEachTests } from './_test_functions'
+import { json, getHttpServer } from './_test_functions'
 
 // ROUTE /getImage
 
 // Before each tests, start a server
-test.beforeEach(beforeEachTests)
+test.beforeEach(async t => (t.context.server = await getHttpServer()))
 
 test('GET /getImage', async t => {
   const res = await request(t.context.server)

+ 3 - 3
test/api/getImageExtracts.js

@@ -6,12 +6,12 @@ import sharp from 'sharp'
 import fs from 'fs-extra'
 import path from 'path'
 import { apiPrefix, imageServedUrl, imagesPath } from '../../config'
-import { json, beforeEachTests } from './_test_functions'
+import { json, getHttpServer } from './_test_functions'
 
 // ROUTE /getImageExtracts
 
 // Before each tests, start a server
-test.beforeEach(beforeEachTests)
+test.beforeEach(async t => (t.context.server = await getHttpServer()))
 
 test('GET /getImageExtracts', async t => {
   const res = await request(t.context.server)
@@ -79,7 +79,7 @@ test.serial('GET /getImageExtracts?sceneName=bathroom&imageQuality=10&horizontal
   t.is(res2.header['content-type'], 'image/png', json(res2))
 })
 
-test.serial('Extracts were successfully generated', async t => {
+test.serial('Check extracts were successfully generated', async t => {
   // Check the extract on the file system
   const extracts = path.resolve(imagesPath, 'bathroom', 'extracts')
   const aBathroomConfig = path.resolve(extracts, 'x5_y2')

+ 2 - 2
test/api/listScenes.js

@@ -3,12 +3,12 @@
 import test from 'ava'
 import request from 'supertest'
 import { apiPrefix } from '../../config'
-import { json, beforeEachTests } from './_test_functions'
+import { json, getHttpServer } from './_test_functions'
 
 // ROUTE /listScenes
 
 // Before each tests, start a server
-test.beforeEach(beforeEachTests)
+test.beforeEach(async t => (t.context.server = await getHttpServer()))
 
 test('GET /listScenes', async t => {
   const res = await request(t.context.server)

+ 2 - 2
test/api/listScenesQualities.js

@@ -3,12 +3,12 @@
 import test from 'ava'
 import request from 'supertest'
 import { apiPrefix } from '../../config'
-import { json, beforeEachTests } from './_test_functions'
+import { json, getHttpServer } from './_test_functions'
 
 // ROUTE /listSceneQualities
 
 // Before each tests, start a server
-test.beforeEach(beforeEachTests)
+test.beforeEach(async t => (t.context.server = await getHttpServer()))
 
 test('GET /listSceneQualities', async t => {
   const res = await request(t.context.server)