big refactor - move into src/worker and src/server
parent
2922b16918
commit
bff3f3ca5e
|
@ -11,4 +11,4 @@ RUN npm ci
|
|||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["node", "-r", "esm", "server.js"]
|
||||
CMD ["node", "-r", "esm", "src/server"]
|
||||
|
|
12
README.md
12
README.md
|
@ -7,8 +7,8 @@ If you don't want to host your own instance, check out https://api.retropilot.or
|
|||
|
||||
The server consists of 2 node scripts.
|
||||
|
||||
`server.js` is using expressjs and runs the backend (file upload / communication with openpilot) and the useradmin dashboard to manage / view / download drives & logs.
|
||||
`worker.js` is a background worker that is processing drives (analyzing video files & logs) to prepare drives for playback in cabana and to gather statistics. It automatically terminates itself after 60 minutes to make sure the video/log libraries do not cause memory leaks.
|
||||
`src/server` is using expressjs and runs the backend (file upload / communication with openpilot) and the useradmin dashboard to manage / view / download drives & logs.
|
||||
`src/worker` is a background worker that is processing drives (analyzing video files & logs) to prepare drives for playback in cabana and to gather statistics. It automatically terminates itself after 60 minutes to make sure the video/log libraries do not cause memory leaks.
|
||||
|
||||
Both scripts can be started with a cronjob each minute, they use locking to make sure they run exclusively.
|
||||
|
||||
|
@ -27,15 +27,15 @@ cp database.empty.sqlite database.sqlite
|
|||
### [Server] Running
|
||||
|
||||
```
|
||||
node -r esm server.js
|
||||
node -r esm src/server
|
||||
```
|
||||
```
|
||||
node -r esm worker.js
|
||||
node -r esm src/worker
|
||||
```
|
||||
|
||||
|
||||
### [Server] CABANA Support
|
||||
A compiled version of a custom cabana fork (https://github.com/florianbrede-ayet/retropilot-cabana) is directly bundled in the `cabana/` subdirectory and will be served by the express app. After starting `server.js`, cabana is ready to use.
|
||||
A compiled version of a custom cabana fork (https://github.com/florianbrede-ayet/retropilot-cabana) is directly bundled in the `cabana/` subdirectory and will be served by the express app. After starting `index.js`, cabana is ready to use.
|
||||
|
||||
-----
|
||||
|
||||
|
@ -96,4 +96,4 @@ The athena websockets interface is not implemented yet, so the comma app and ath
|
|||
Launch with:
|
||||
```
|
||||
docker-compose -f docker-compose.yml -f docker-compose.uat.yml up -d
|
||||
```
|
||||
```
|
||||
|
|
|
@ -12,7 +12,7 @@ services:
|
|||
- "4040:4040"
|
||||
worker:
|
||||
build: .
|
||||
command: node -r esm worker.js
|
||||
command: node -r esm src/worker
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- db
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
module.exports = [{
|
||||
name: 'server',
|
||||
script: 'server.js',
|
||||
script: 'src/server',
|
||||
node_args: '-r esm',
|
||||
}, {
|
||||
name: 'worker',
|
||||
script: 'worker.js',
|
||||
script: 'src/worker',
|
||||
node_args: '-r esm',
|
||||
}];
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
"name": "retropilot-server",
|
||||
"version": "1.0.0",
|
||||
"description": "replacement for comma.ai backend and useradmin dashboard. can be combined with a modified cabana instance.",
|
||||
"main": "server.js",
|
||||
"main": "src/server/index.js",
|
||||
"scripts": {
|
||||
"test": "mocha",
|
||||
"start": "node --es-module-specifier-resolution=node server.js",
|
||||
"start": "node -r esm src/server",
|
||||
"lint": "eslint . --ext .js",
|
||||
"lint:fix": "eslint . --ext .js --fix"
|
||||
},
|
||||
|
|
129
server.js
129
server.js
|
@ -1,129 +0,0 @@
|
|||
/* eslint-disable global-require */
|
||||
import 'dotenv/config'
|
||||
|
||||
import log4js from 'log4js';
|
||||
import lockfile from 'proper-lockfile';
|
||||
import http from 'http';
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import storageController from './controllers/storage.js';
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
import athena from './websocket/athena/index.js';
|
||||
import routers from './routes/index.js';
|
||||
import controllers from './controllers/index.js';
|
||||
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
|
||||
process.on('unhandledRejection', (error, p) => {
|
||||
console.log('=== UNHANDLED REJECTION ===');
|
||||
console.log(error.promise, p);
|
||||
console.dir(error.stack);
|
||||
});
|
||||
|
||||
log4js.configure({
|
||||
appenders: { logfile: { type: 'file', filename: 'server.log' }, out: { type: 'console' } /* {type: "file", filename: "server1.log"} */ },
|
||||
categories: { default: { appenders: ['out', 'logfile'], level: 'info' } },
|
||||
});
|
||||
|
||||
const logger = log4js.getLogger('default');
|
||||
// TODO evaluate if this is the best way to determine the root of project
|
||||
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
global.__basedir = __dirname;
|
||||
|
||||
function runAsyncWrapper(callback) {
|
||||
return function wrapper(req, res, next) {
|
||||
callback(req, res, next)
|
||||
.catch(next);
|
||||
};
|
||||
}
|
||||
|
||||
const web = async () => {
|
||||
const app = express();
|
||||
|
||||
app.use((req, res, next) => {
|
||||
res.header('Access-Control-Allow-Origin', `${process.env.BASE_URL}`);
|
||||
res.header('Access-Control-Allow-Credentials', true);
|
||||
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
|
||||
next();
|
||||
});
|
||||
|
||||
storageController.initializeStorage();
|
||||
await storageController.updateTotalStorageUsed();
|
||||
|
||||
app.use(routers.api);
|
||||
app.use(routers.useradmin);
|
||||
app.use(routers.authenticationApi);
|
||||
|
||||
if (process.env.ATHENA_ENABLED) {
|
||||
const athenaRateLimit = rateLimit({
|
||||
windowMs: 30000,
|
||||
max: process.env.ATHENA_API_RATE_LIMIT,
|
||||
});
|
||||
|
||||
app.use((req, res, next) => {
|
||||
req.athenaWebsocketTemp = athena;
|
||||
return next();
|
||||
});
|
||||
|
||||
app.use('/admin', routers.admin);
|
||||
app.use('/realtime', athenaRateLimit);
|
||||
app.use('/realtime', routers.realtime);
|
||||
//app.use(routers.oauthAuthenticator)
|
||||
} else {
|
||||
logger.log('Athena disabled');
|
||||
}
|
||||
|
||||
app.use(cors({ origin: 'http://localhost:3000' }));
|
||||
app.use(cookieParser());
|
||||
app.use('/favicon.ico', express.static('static/favicon.ico'));
|
||||
app.use(process.env.BASE_DRIVE_DOWNLOAD_PATH_MAPPING, express.static(process.env.STORAGE_PATH));
|
||||
|
||||
app.use(routers.deviceApi);
|
||||
|
||||
app.use('/.well-known', express.static('.well-known'));
|
||||
|
||||
app.use('/cabana', express.static('cabana/'));
|
||||
|
||||
app.get('/', async (req, res) => {
|
||||
res.redirect('/useradmin')
|
||||
});
|
||||
|
||||
app.get('*', runAsyncWrapper(async (req, res) => {
|
||||
logger.error(`HTTP.GET unhandled request: ${controllers.helpers.simpleStringify(req)}, ${controllers.helpers.simpleStringify(res)}`);
|
||||
res.status(404);
|
||||
res.send('Not Implemented');
|
||||
}));
|
||||
|
||||
app.post('*', runAsyncWrapper(async (req, res) => {
|
||||
logger.error(`HTTP.POST unhandled request: ${controllers.helpers.simpleStringify(req)}, ${controllers.helpers.simpleStringify(res)}`);
|
||||
res.status(404);
|
||||
res.send('Not Implemented');
|
||||
}));
|
||||
|
||||
return app;
|
||||
};
|
||||
|
||||
lockfile.lock('retropilot_server', { realpath: false, stale: 30000, update: 2000 })
|
||||
.then(async () => {
|
||||
console.log('STARTING SERVER...');
|
||||
const app = await web();
|
||||
const httpServer = http.createServer(app);
|
||||
|
||||
httpServer.listen(process.env.HTTP_PORT, () => {
|
||||
logger.info(`RetroPilot Server listening at ${process.env.BASE_URL}`);
|
||||
});
|
||||
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
process.exit();
|
||||
});
|
|
@ -0,0 +1,84 @@
|
|||
import cookieParser from 'cookie-parser';
|
||||
import cors from 'cors';
|
||||
import express from 'express';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import log4js from 'log4js';
|
||||
|
||||
import storageController from './controllers/storage';
|
||||
import athena from '../websocket/athena';
|
||||
import controllers from './controllers';
|
||||
import routers from './routes';
|
||||
|
||||
const logger = log4js.getLogger('default');
|
||||
|
||||
function runAsyncWrapper(callback) {
|
||||
return function wrapper(req, res, next) {
|
||||
callback(req, res, next)
|
||||
.catch(next);
|
||||
};
|
||||
}
|
||||
|
||||
const tasks = [];
|
||||
const app = express();
|
||||
|
||||
app.use((req, res, next) => {
|
||||
res.header('Access-Control-Allow-Origin', `${process.env.BASE_URL}`);
|
||||
res.header('Access-Control-Allow-Credentials', true);
|
||||
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
|
||||
next();
|
||||
});
|
||||
|
||||
storageController.initializeStorage();
|
||||
tasks.push(storageController.updateTotalStorageUsed());
|
||||
|
||||
app.use(routers.api);
|
||||
app.use(routers.useradmin);
|
||||
app.use(routers.authenticationApi);
|
||||
|
||||
if (process.env.ATHENA_ENABLED) {
|
||||
const athenaRateLimit = rateLimit({
|
||||
windowMs: 30000,
|
||||
max: process.env.ATHENA_API_RATE_LIMIT,
|
||||
});
|
||||
|
||||
app.use((req, res, next) => {
|
||||
req.athenaWebsocketTemp = athena;
|
||||
return next();
|
||||
});
|
||||
|
||||
app.use('/admin', routers.admin);
|
||||
app.use('/realtime', athenaRateLimit);
|
||||
app.use('/realtime', routers.realtime);
|
||||
// app.use(routers.oauthAuthenticator)
|
||||
} else {
|
||||
logger.log('Athena disabled');
|
||||
}
|
||||
|
||||
app.use(cors({ origin: 'http://localhost:3000' }));
|
||||
app.use(cookieParser());
|
||||
app.use('/favicon.ico', express.static('static/favicon.ico'));
|
||||
app.use(process.env.BASE_DRIVE_DOWNLOAD_PATH_MAPPING, express.static(process.env.STORAGE_PATH));
|
||||
|
||||
app.use(routers.deviceApi);
|
||||
|
||||
app.use('/.well-known', express.static('.well-known'));
|
||||
|
||||
app.use('/cabana', express.static('cabana/'));
|
||||
|
||||
app.get('/', async (req, res) => {
|
||||
res.redirect('/useradmin');
|
||||
});
|
||||
|
||||
app.get('*', runAsyncWrapper(async (req, res) => {
|
||||
logger.error(`HTTP.GET unhandled request: ${controllers.helpers.simpleStringify(req)}, ${controllers.helpers.simpleStringify(res)}`);
|
||||
res.status(404);
|
||||
res.send('Not Implemented');
|
||||
}));
|
||||
|
||||
app.post('*', runAsyncWrapper(async (req, res) => {
|
||||
logger.error(`HTTP.POST unhandled request: ${controllers.helpers.simpleStringify(req)}, ${controllers.helpers.simpleStringify(res)}`);
|
||||
res.status(404);
|
||||
res.send('Not Implemented');
|
||||
}));
|
||||
|
||||
export default Promise.all(tasks).then(() => app);
|
|
@ -1,4 +1,4 @@
|
|||
import orm from '../models/index.model';
|
||||
import orm from '../../models/index.model';
|
||||
|
||||
// TODO move everythijng away from this dumb intertwined style
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import crypto from 'crypto';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import log4js from 'log4js';
|
||||
import orm from '../../models/index.model';
|
||||
import orm from '../../../models/index.model';
|
||||
|
||||
const logger = log4js.getLogger('default');
|
||||
|
|
@ -6,7 +6,7 @@ import {
|
|||
AUTH_2FA_ENROLLED,
|
||||
AUTH_2FA_BAD_TOKEN,
|
||||
} from '../../consistency/terms';
|
||||
import orm from '../../models/index.model';
|
||||
import orm from '../../../models/index.model';
|
||||
|
||||
export async function twoFactorOnboard(account) {
|
||||
if (!account || !account.dataValues) { return { success: false, ...AUTH_2FA_BAD_ACCOUNT }; }
|
|
@ -2,7 +2,7 @@ import sanitizeFactory from 'sanitize';
|
|||
import crypto from 'crypto';
|
||||
import dirTree from 'directory-tree';
|
||||
import log4js from 'log4js';
|
||||
import orm from '../models/index.model';
|
||||
import orm from '../../models/index.model';
|
||||
|
||||
import { readJWT, validateJWT } from './authentication';
|
||||
import { getAccountFromId } from './users';
|
|
@ -1,6 +1,6 @@
|
|||
import crypto from 'crypto';
|
||||
import log4js from 'log4js';
|
||||
import orm from '../models/index.model';
|
||||
import orm from '../../models/index.model';
|
||||
|
||||
const logger = log4js.getLogger('default');
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import 'dotenv/config';
|
||||
import http from 'http';
|
||||
import log4js from 'log4js';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
log4js.configure({
|
||||
appenders: { logfile: { type: 'file', filename: 'server.log' }, out: { type: 'console' } /* {type: "file", filename: "server1.log"} */ },
|
||||
categories: { default: { appenders: ['out', 'logfile'], level: 'info' } },
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (error, p) => {
|
||||
console.log('=== UNHANDLED REJECTION ===');
|
||||
console.log(error.promise, p);
|
||||
console.dir(error.stack);
|
||||
});
|
||||
|
||||
// TODO evaluate if this is the best way to determine the root of project
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
global.__basedir = __dirname;
|
||||
|
||||
const main = async () => {
|
||||
const logger = log4js.getLogger('default');
|
||||
const httpServer = http.createServer(await require('./app').default);
|
||||
|
||||
httpServer.listen(process.env.HTTP_PORT, () => {
|
||||
logger.info(`RetroPilot Server listening at ${process.env.BASE_URL}`);
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
|
@ -5,8 +5,8 @@ import crypto from 'crypto';
|
|||
import log4js from 'log4js';
|
||||
import storageController from '../controllers/storage';
|
||||
import deviceController from '../controllers/devices';
|
||||
import authenticationController from './../controllers/authentication';
|
||||
import userController from './../controllers/users';
|
||||
import authenticationController from '../controllers/authentication';
|
||||
import userController from '../controllers/users';
|
||||
|
||||
const logger = log4js.getLogger('default');
|
||||
const router = express.Router();
|
||||
|
@ -314,14 +314,14 @@ async function upload(req, res) {
|
|||
})
|
||||
|
||||
await deviceController.updateOrCreateDriveSegment(dongleId, driveName, segment, {
|
||||
duration: 0,
|
||||
distance_meters: 0,
|
||||
upload_complete: false,
|
||||
is_processed: false,
|
||||
is_stalled: false,
|
||||
duration: 0,
|
||||
distance_meters: 0,
|
||||
upload_complete: false,
|
||||
is_processed: false,
|
||||
is_stalled: false,
|
||||
created: Date.now()
|
||||
})
|
||||
|
||||
|
||||
|
||||
logger.info(`HTTP.UPLOAD_URL updated existing drive: ${JSON.stringify(drive)}`);
|
||||
}
|
|
@ -4,7 +4,7 @@ import authenticationController from '../../controllers/authentication';
|
|||
|
||||
import userController from '../../controllers/users';
|
||||
import deviceController from '../../controllers/devices';
|
||||
import models from '../../models/index.model';
|
||||
import models from '../../../models/index.model';
|
||||
/* eslint-enable no-unused-vars */
|
||||
const router = express.Router();
|
||||
const whitelistParams = {
|
|
@ -601,11 +601,11 @@ router.get('/useradmin/drive/:dongleId/:driveIdentifier', runAsyncWrapper(async
|
|||
var qlog = '--';
|
||||
var rlog = '--';
|
||||
for (var c in directoryTree.children[i].children) {
|
||||
if (directoryTree.children[i].children[c].name == 'fcamera.hevc') fcamera = '<a target="_blank" href="' + driveUrl + segment + '/' + directoryTree.children[i].children[c].name + '">' + directoryTree.children[i].children[c].name + '</a>';
|
||||
if (directoryTree.children[i].children[c].name == 'dcamera.hevc') fcamera = '<a target="_blank" href="' + driveUrl + segment + '/' + directoryTree.children[i].children[c].name + '">' + directoryTree.children[i].children[c].name + '</a>';
|
||||
if (directoryTree.children[i].children[c].name == 'qcamera.ts') qcamera = '<a target="_blank" href="' + driveUrl + segment + '/' + directoryTree.children[i].children[c].name + '">' + directoryTree.children[i].children[c].name + '</a>';
|
||||
if (directoryTree.children[i].children[c].name == 'qlog.bz2') qlog = '<a target="_blank" href="' + driveUrl + segment + '/' + directoryTree.children[i].children[c].name + '">' + directoryTree.children[i].children[c].name + '</a>';
|
||||
if (directoryTree.children[i].children[c].name == 'rlog.bz2') rlog = '<a target="_blank" href="' + driveUrl + segment + '/' + directoryTree.children[i].children[c].name + '">' + directoryTree.children[i].children[c].name + '</a>';
|
||||
if (directoryTree.children[i].children[c].name == 'fcamera.hevc') fcamera = '' + driveUrl + segment + '<a target="_blank" href="/">' + directoryTree.children[i].children[c].name + '' + directoryTree.children[i].children[c].name + '</a>';
|
||||
if (directoryTree.children[i].children[c].name == 'dcamera.hevc') fcamera = '' + driveUrl + segment + '<a target="_blank" href="/">' + directoryTree.children[i].children[c].name + '' + directoryTree.children[i].children[c].name + '</a>';
|
||||
if (directoryTree.children[i].children[c].name == 'qcamera.ts') qcamera = '' + driveUrl + segment + '<a target="_blank" href="/">' + directoryTree.children[i].children[c].name + '' + directoryTree.children[i].children[c].name + '</a>';
|
||||
if (directoryTree.children[i].children[c].name == 'qlog.bz2') qlog = '' + driveUrl + segment + '<a target="_blank" href="/">' + directoryTree.children[i].children[c].name + '' + directoryTree.children[i].children[c].name + '</a>';
|
||||
if (directoryTree.children[i].children[c].name == 'rlog.bz2') rlog = '' + driveUrl + segment + '<a target="_blank" href="/">' + directoryTree.children[i].children[c].name + '' + directoryTree.children[i].children[c].name + '</a>';
|
||||
}
|
||||
|
||||
var isProcessed = '?';
|
|
@ -7,7 +7,7 @@ import log4js from 'log4js';
|
|||
import models from '../../models/index.model';
|
||||
import helperFunctions from './helpers';
|
||||
|
||||
import deviceController from '../../controllers/devices';
|
||||
import deviceController from '../../server/controllers/devices';
|
||||
|
||||
const logger = log4js.getLogger('default');
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
// eslint-disable-next-line no-unused-vars
|
||||
import authenticationController from '../../controllers/authentication';
|
||||
import authenticationController from '../../server/controllers/authentication';
|
||||
|
||||
import deviceController from '../../controllers/devices';
|
||||
import athenaRealtime from '../athena/index';
|
||||
import deviceController from '../../server/controllers/devices';
|
||||
import athenaRealtime from '../athena';
|
||||
|
||||
// Checks if device is currently online in Athena
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import deviceController from '../../controllers/devices';
|
||||
import deviceController from '../../server/controllers/devices';
|
||||
|
||||
let wss;
|
||||
|
|
@ -2,7 +2,7 @@ import { WebSocketServer } from 'ws';
|
|||
import cookie from 'cookie';
|
||||
import httpServer from 'http';
|
||||
import log4js from 'log4js';
|
||||
import authenticationController from '../../controllers/authentication';
|
||||
import authenticationController from '../../server/controllers/authentication';
|
||||
import athenaRealtime from '../athena';
|
||||
|
||||
import controlsFunction from './controls';
|
|
@ -12,7 +12,7 @@ import Reader from '@commaai/log_reader';
|
|||
import ffprobe from 'ffprobe';
|
||||
import ffprobeStatic from 'ffprobe-static';
|
||||
|
||||
import orm from './models/index.model';
|
||||
import orm from '../models/index.model';
|
||||
|
||||
let lastCleaningTime = 0;
|
||||
let startTime = Date.now();
|
|
@ -1,74 +1,69 @@
|
|||
import request from 'supertest';
|
||||
import dummyGenerator from './../dummyGenerator';
|
||||
let app;
|
||||
|
||||
export default (app) => {
|
||||
describe('/v2/pilotauth/ - Testing device registration', function () {
|
||||
it('Returns dongle ID on valid registration', function (done) {
|
||||
request(app)
|
||||
.post('/v2/pilotauth/')
|
||||
.query({
|
||||
imei: dummyGenerator.getImei(),
|
||||
serial: dummyGenerator.getSerial(),
|
||||
public_key: dummyGenerator.devicePubKey,
|
||||
register_token: dummyGenerator.makeJWT(),
|
||||
})
|
||||
|
||||
|
||||
export default (server) => {
|
||||
app = server;
|
||||
|
||||
describe('/v2/pilotauth/ - Testing device registration', function() {
|
||||
it('Returns dongle ID on valid registration', function(done) {
|
||||
request(server)
|
||||
.post('/v2/pilotauth/')
|
||||
.query({
|
||||
imei: dummyGenerator.getImei(),
|
||||
serial: dummyGenerator.getSerial(),
|
||||
public_key: dummyGenerator.devicePubKey,
|
||||
register_token: dummyGenerator.makeJWT()
|
||||
})
|
||||
|
||||
.set('Accept', 'application/x-www-form-urlencoded')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
if (!res.body.dongle_id) throw new Error("API Failed to return dongle_id on status 200")
|
||||
})
|
||||
.end(done)
|
||||
});
|
||||
|
||||
it('Returns 400 when incorrect public key given', function(done) {
|
||||
request(server)
|
||||
.post('/v2/pilotauth/')
|
||||
.query({
|
||||
imei: dummyGenerator.getImei(),
|
||||
serial: dummyGenerator.getSerial(),
|
||||
public_key: dummyGenerator.rougePublicKey,
|
||||
register_token: dummyGenerator.makeJWT()
|
||||
})
|
||||
|
||||
.set('Accept', 'application/x-www-form-urlencoded')
|
||||
.expect('Content-Type', /text/)
|
||||
.expect(400)
|
||||
.end(done)
|
||||
});
|
||||
|
||||
it('Returns 400 when missing register_token', function(done) {
|
||||
request(server)
|
||||
.post('/v2/pilotauth/')
|
||||
.query({
|
||||
imei: dummyGenerator.getImei(),
|
||||
serial: dummyGenerator.getSerial(),
|
||||
public_key: dummyGenerator.rougePublicKey,
|
||||
register_token: ""
|
||||
})
|
||||
|
||||
.set('Accept', 'application/x-www-form-urlencoded')
|
||||
.expect('Content-Type', /text/)
|
||||
.expect(400)
|
||||
.end(done)
|
||||
});
|
||||
|
||||
it('Returns 400 when missing query', function(done) {
|
||||
request(server)
|
||||
.post('/v2/pilotauth/')
|
||||
|
||||
.set('Accept', 'application/x-www-form-urlencoded')
|
||||
.expect('Content-Type', /text/)
|
||||
.expect(400)
|
||||
.end(done)
|
||||
});
|
||||
.set('Accept', 'application/x-www-form-urlencoded')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
if (!res.body.dongle_id) {
|
||||
throw new Error('API Failed to return dongle_id on status 200');
|
||||
}
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('Returns 400 when incorrect public key given', function (done) {
|
||||
request(app)
|
||||
.post('/v2/pilotauth/')
|
||||
.query({
|
||||
imei: dummyGenerator.getImei(),
|
||||
serial: dummyGenerator.getSerial(),
|
||||
public_key: dummyGenerator.rougePublicKey,
|
||||
register_token: dummyGenerator.makeJWT(),
|
||||
})
|
||||
|
||||
.set('Accept', 'application/x-www-form-urlencoded')
|
||||
.expect('Content-Type', /text/)
|
||||
.expect(400)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('Returns 400 when missing register_token', function (done) {
|
||||
request(app)
|
||||
.post('/v2/pilotauth/')
|
||||
.query({
|
||||
imei: dummyGenerator.getImei(),
|
||||
serial: dummyGenerator.getSerial(),
|
||||
public_key: dummyGenerator.rougePublicKey,
|
||||
register_token: '',
|
||||
})
|
||||
|
||||
.set('Accept', 'application/x-www-form-urlencoded')
|
||||
.expect('Content-Type', /text/)
|
||||
.expect(400)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('Returns 400 when missing query', function (done) {
|
||||
request(app)
|
||||
.post('/v2/pilotauth/')
|
||||
|
||||
.set('Accept', 'application/x-www-form-urlencoded')
|
||||
.expect('Content-Type', /text/)
|
||||
.expect(400)
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,46 +1,34 @@
|
|||
import request from 'supertest';
|
||||
import dummyGenerator from './../dummyGenerator';
|
||||
let app;
|
||||
|
||||
|
||||
|
||||
export default (server) => {
|
||||
app = server;
|
||||
|
||||
describe('/api', function() {
|
||||
it('Load general server stats', function (done) {
|
||||
request(server)
|
||||
.get('/retropilot/0/useradmin')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((req) => {
|
||||
const body = req.body;
|
||||
|
||||
try {
|
||||
if (
|
||||
body.hasOwnProperty('success') && body.success === true &&
|
||||
body.hasOwnProperty('data') &&
|
||||
body.data.hasOwnProperty('serverStats') &&
|
||||
body.data.serverStats.hasOwnProperty('config') &&
|
||||
typeof body.data.serverStats.config.registerAllowed === "boolean" &&
|
||||
typeof body.data.serverStats.process.env.WELCOME_MESSAGE === "string" &&
|
||||
typeof body.data.serverStats['accounts'] === "number" &&
|
||||
typeof body.data.serverStats['devices'] === "number" &&
|
||||
typeof body.data.serverStats['drives'] === "number" &&
|
||||
(typeof body.data.serverStats['storageUsed'] === "number" || body.data.serverStats['storageUsed'] === "Unsupported Platform"))
|
||||
{
|
||||
return true;
|
||||
} else {
|
||||
throw new Error('Invalid returned parameters in GET /retropilot/0/useradmin')
|
||||
}
|
||||
} catch (exception) {
|
||||
throw new Error('Invalid returned parameters in GET /retropilot/0/useradmin ')
|
||||
}
|
||||
})
|
||||
.end(done)
|
||||
});
|
||||
|
||||
export default (app) => {
|
||||
describe('/api', function () {
|
||||
it('Load general app stats', function (done) {
|
||||
request(app)
|
||||
.get('/retropilot/0/useradmin')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((req) => {
|
||||
const body = req.body;
|
||||
|
||||
try {
|
||||
if (
|
||||
body.hasOwnProperty('success') && body.success === true &&
|
||||
body.hasOwnProperty('data') &&
|
||||
body.data.hasOwnProperty('appStats') &&
|
||||
body.data.appStats.hasOwnProperty('config') &&
|
||||
typeof body.data.appStats.config.registerAllowed === 'boolean' &&
|
||||
typeof body.data.appStats.process.env.WELCOME_MESSAGE === 'string' &&
|
||||
typeof body.data.appStats['accounts'] === 'number' &&
|
||||
typeof body.data.appStats['devices'] === 'number' &&
|
||||
typeof body.data.appStats['drives'] === 'number' &&
|
||||
(typeof body.data.appStats['storageUsed'] === 'number' || body.data.appStats['storageUsed'] === 'Unsupported Platform')) {
|
||||
return true;
|
||||
}
|
||||
} catch (ignored) {
|
||||
}
|
||||
throw new Error('Invalid returned parameters in GET /retropilot/0/useradmin ');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,52 +1,46 @@
|
|||
import request from 'supertest';
|
||||
import dummyGenerator from './../dummyGenerator';
|
||||
let app;
|
||||
|
||||
|
||||
|
||||
export default (server) => {
|
||||
app = server;
|
||||
|
||||
describe('/useradmin', function() {
|
||||
it('Page load', function (done) {
|
||||
request(server)
|
||||
.get('/useradmin')
|
||||
.expect('Content-Type', /html/)
|
||||
.expect(200)
|
||||
.end(done)
|
||||
});
|
||||
|
||||
it('Redirect on existing session', function(done) {
|
||||
request(server)
|
||||
.get('/useradmin')
|
||||
// pull sessions from a store
|
||||
.set('Cookie', ['session=s%3Aj%3A%7B%22account%22%3A%22adam%40adamblack.us%22%2C%22expires%22%3A1653171350726%7D.cRX19pNfx6mCGZ9ZYHcUIyy5CAQVMDgKrp%2F%2Bf7NFVYA;'])
|
||||
.expect('Location', '/useradmin/overview')
|
||||
.expect(302)
|
||||
.end(done)
|
||||
});
|
||||
export default (app) => {
|
||||
describe('/useradmin', function () {
|
||||
it('Page load', function (done) {
|
||||
request(app)
|
||||
.get('/useradmin')
|
||||
.expect('Content-Type', /html/)
|
||||
.expect(200)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
describe('/useradmin/register/token', function() {
|
||||
it('No duplicate emails', function (done) {
|
||||
request(server)
|
||||
.post('/useradmin/register/token')
|
||||
// TODO add dedicated DB/user account for tests to run on
|
||||
.send(`email=${dummyGenerator.alreadyRegisteredEmail}`)
|
||||
.set('Accept', 'application/x-www-form-urlencoded')
|
||||
.expect('Location', `/useradmin/register?status=${encodeURIComponent('Email is already registered')}`)
|
||||
.end(done)
|
||||
});
|
||||
|
||||
it('Accepts new accounts', function (done) {
|
||||
request(server)
|
||||
.post('/useradmin/register/token')
|
||||
// TODO add dedicated DB/user account for tests to run on
|
||||
.send(`email=${dummyGenerator.newUserEmail}`)
|
||||
.set('Accept', 'application/x-www-form-urlencoded')
|
||||
.expect(200)
|
||||
.end(done)
|
||||
});
|
||||
|
||||
it('Redirect on existing session', function (done) {
|
||||
request(app)
|
||||
.get('/useradmin')
|
||||
// pull sessions from a store
|
||||
.set('Cookie', ['session=s%3Aj%3A%7B%22account%22%3A%22adam%40adamblack.us%22%2C%22expires%22%3A1653171350726%7D.cRX19pNfx6mCGZ9ZYHcUIyy5CAQVMDgKrp%2F%2Bf7NFVYA;'])
|
||||
.expect('Location', '/useradmin/overview')
|
||||
.expect(302)
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('/useradmin/register/token', function () {
|
||||
it('No duplicate emails', function (done) {
|
||||
request(app)
|
||||
.post('/useradmin/register/token')
|
||||
// TODO add dedicated DB/user account for tests to run on
|
||||
.send(`email=${dummyGenerator.alreadyRegisteredEmail}`)
|
||||
.set('Accept', 'application/x-www-form-urlencoded')
|
||||
.expect('Location', `/useradmin/register?status=${encodeURIComponent('Email is already registered')}`)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('Accepts new accounts', function (done) {
|
||||
request(app)
|
||||
.post('/useradmin/register/token')
|
||||
// TODO add dedicated DB/user account for tests to run on
|
||||
.send(`email=${dummyGenerator.newUserEmail}`)
|
||||
.set('Accept', 'application/x-www-form-urlencoded')
|
||||
.expect(200)
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
12
test/test.js
12
test/test.js
|
@ -1,20 +1,20 @@
|
|||
import request from 'supertest';
|
||||
import server from '../server';
|
||||
import app from '../src/server/app';
|
||||
|
||||
// TODO better way to only run tests once server is up
|
||||
describe('loading express', () => {
|
||||
it('responds to /', (done) => {
|
||||
request(server)
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, done);
|
||||
});
|
||||
it('404 everything else', (done) => {
|
||||
request(server)
|
||||
request(app)
|
||||
.get('/foo/bar')
|
||||
.expect(404, done);
|
||||
});
|
||||
});
|
||||
|
||||
require('./routes/api.test')(server);
|
||||
require('./routes/useradmin.test')(server);
|
||||
if (process.env.USE_USER_ADMIN_API) require('./routes/userAdminApi.test')(server);
|
||||
require('./routes/api.test')(app);
|
||||
require('./routes/useradmin.test')(app);
|
||||
if (process.env.USE_USER_ADMIN_API) require('./routes/userAdminApi.test')(app);
|
||||
|
|
Loading…
Reference in New Issue