Skip to content

Commit

Permalink
Merge branch 'master' into multiplex
Browse files Browse the repository at this point in the history
  • Loading branch information
willgraf committed Sep 16, 2020
2 parents 61a1c72 + cdcc8e9 commit aaeb7a5
Show file tree
Hide file tree
Showing 29 changed files with 3,100 additions and 84 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ node_modules

dist/
*.log
coverage/
8 changes: 5 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ dist: trusty
language: node_js

node_js:
- 8
- 10
- lts/*
- node

cache:
Expand All @@ -17,10 +16,13 @@ cache:
before_install:
- travis_retry curl -o- -L https://yarnpkg.com/install.sh | bash -s
- export PATH="$HOME/.yarn/bin:$PATH"
install: true

install:
- yarn

script:
- yarn test
- yarn coveralls

jobs:
include:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# ![DeepCell Kiosk Banner](https://raw.githubusercontent.com/vanvalenlab/kiosk-console/master/docs/images/DeepCell_Kiosk_Banner.png)

[![Build Status](https://travis-ci.com/vanvalenlab/kiosk-frontend.svg?branch=master)](https://travis-ci.com/vanvalenlab/kiosk-frontend)
[![Coverage Status](https://coveralls.io/repos/github/vanvalenlab/kiosk-frontend/badge.svg)](https://coveralls.io/github/vanvalenlab/kiosk-frontend)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](/LICENSE)

The `kiosk-frontend` serves as the main interaction point for end users of the DeepCell Kiosk. The NodeJS backend API and the React frontend allows them to easily create jobs through their web browser.
Expand Down
1 change: 1 addition & 0 deletions __mocks__/fileMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = 'test-file-stub';
1 change: 1 addition & 0 deletions __mocks__/styleMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {};
17 changes: 17 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module.exports = {
moduleNameMapper: {
'\\.(css|less|sass|scss)$': '<rootDir>/__mocks__/styleMock.js',
'\\.(gif|ttf|eot|svg)$': '<rootDir>/__mocks__/fileMock.js'
},
transform: {
'^.+\\.jsx?$': 'babel-jest'
},
verbose: true,
collectCoverage: true,
collectCoverageFrom: [
'server/**/*.{js,jsx}',
'src/**/*.{js,jsx}',
'!**/node_modules/**',
'!**/vendor/**'
]
};
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
},
"main": "index.js",
"scripts": {
"test": "echo \"No tests specified\" && exit 0",
"test": "NODE_ENV=test jest --detectOpenHandles --no-cache",
"coveralls": "NODE_ENV=test jest --coverage && cat ./coverage/lcov.info | coveralls",
"clean": "rm -rf ./dist",
"build:client": "webpack --mode production --config webpack.prod.js",
"build:server": "babel -d ./dist/server ./server -s",
Expand Down Expand Up @@ -65,18 +66,27 @@
"@babel/polyfill": "^7.10.1",
"@babel/preset-env": "^7.10.2",
"@babel/preset-react": "^7.10.1",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.0.4",
"axios": "^0.19.0",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.3.0",
"babel-loader": "^8.1.0",
"compression-webpack-plugin": "^4.0.0",
"concurrently": "4.1.2",
"coveralls": "^3.1.0",
"css-loader": "^3.6.0",
"eslint": "7.2.0",
"eslint-plugin-import": "2.21.2",
"eslint-plugin-react": "7.20.0",
"html-webpack-plugin": "4.3.0",
"ioredis-mock": "^4.21.3",
"jest": "^26.4.2",
"nodemon": "^2.0.4",
"react-test-renderer": "^16.13.1",
"style-loader": "1.2.1",
"supertest": "^4.0.2",
"tmp": "^0.2.1",
"webpack": "4.43.0",
"webpack-cli": "3.3.11",
"webpack-dev-server": "3.11.0"
Expand Down
5 changes: 2 additions & 3 deletions server/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ const envVarsSchema = Joi.object({
CLOUD_PROVIDER: Joi.string()
.description('The cloud platform to interact with.')
.valid('gke', 'aws')
.default('aws')
.required(),
.default('aws'),
MODEL_PREFIX: Joi.string()
.description('S3 Folder in which models are saved')
.default('models'),
Expand All @@ -36,7 +35,7 @@ const envVarsSchema = Joi.object({
.default('deepcell-output'),
HOSTNAME: Joi.string()
.description('Kubernetes pod name'),
REDIS_HOST: Joi.string().required()
REDIS_HOST: Joi.string().default('localhost')
.description('Redis DB host url'),
REDIS_PORT: Joi.number()
.default(6379),
Expand Down
7 changes: 5 additions & 2 deletions server/config/redis.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,12 @@ function getClient() {
return createBasicClient();
}

const client = getClient();

function isArray(a) {
return (!!a) && (a.constructor === Array);
}

async function hget(key, field) {
const client = getClient();
const hgetAsync = promisify(client.hget).bind(client);
try {
const value = await hgetAsync(key, field);
Expand All @@ -48,6 +47,7 @@ async function hget(key, field) {
}

async function hmget(key, fields) {
const client = getClient();
const hmgetAsync = promisify(client.hmget).bind(client);
try {
const value = await hmgetAsync(key, ...fields);
Expand All @@ -60,6 +60,7 @@ async function hmget(key, fields) {
}

async function expire(key, expireTime) {
const client = getClient();
const expireAsync = promisify(client.expire).bind(client);
try {
const value = await expireAsync(key, expireTime);
Expand All @@ -72,6 +73,7 @@ async function expire(key, expireTime) {
}

async function lpush(queueName, redisKey) {
const client = getClient();
const lpushAsync = promisify(client.lpush).bind(client);
try {
let response;
Expand All @@ -89,6 +91,7 @@ async function lpush(queueName, redisKey) {
}

async function hmset(redisHash, values) {
const client = getClient();
const hmsetAsync = promisify(client.hmset).bind(client);
try {
const response = await hmsetAsync([redisHash, ...values]);
Expand Down
160 changes: 160 additions & 0 deletions server/config/redis.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// import MockRedis from 'ioredis-mock';

import redis from './redis';
import config from './config';

const mocks = { redis: null };

jest.mock('ioredis', () => {
const Redis = require('ioredis-mock');
if (typeof Redis === 'object') {
// the first mock is an ioredis shim because ioredis-mock depends on it
// https://github.com/stipsan/ioredis-mock/blob/master/src/index.js#L101-L111
return {
Command: { _transformer: { argument: {}, reply: {} } }
};
}
// second mock for our code
return function(...args) {
const dummyData = {
data: {
jobId: {
'status': 'done',
'otherKey': 'testValue'
}
}
};
const instance = new Redis({...args, ...dummyData});
mocks.redis = instance;
return instance;
}
});

jest.mock('../config/config', () => {
return {
redis: {
sentinelEnabled: false
}
};
});

describe('Redis tests', () => {

beforeEach(() => {
if (mocks.redis != null) {
mocks.redis.hmset('jobId', ['status', 'done', 'otherKey', 'testValue']);
}
});

afterEach(() => {
jest.resetAllMocks();
});

describe('Test HGET', () => {

it('should get the correct value', async done => {
let value = await redis.hget('jobId', 'status');
expect(value).toBe('done');

config.redis.sentinelEnabled = true;
value = await redis.hget('jobId', 'status');
expect(value).toBe('done');

done();
});

});

describe('Test HMGET', () => {

it('should get the correct values', async done => {
let value = await redis.hmget('jobId', ['status', 'otherKey']);
expect(value).toMatchObject(['done', 'testValue']);

config.redis.sentinelEnabled = true;
value = await redis.hmget('jobId', ['status', 'otherKey']);
expect(value).toMatchObject(['done', 'testValue']);
done();
});

});

describe('Test EXPIRE', () => {

it('should expire the key', async done => {
let value = await redis.expire('jobId', 1);
expect(value).toBe(1);

config.redis.sentinelEnabled = true;
value = await redis.expire('jobId', 1);
expect(value).toBe(1);
done();
});

it('should return 0 if no valid key', async done => {
let value = await redis.expire('otherKey', 1);
expect(value).toBe(0);

config.redis.sentinelEnabled = true;
value = await redis.expire('anotherKey', 1);
expect(value).toBe(0);
done();
});
});

describe('Test LPUSH', () => {

it('should push a single value to a queue', async done => {
let value = await redis.lpush('testQueue0', 'newKey');
expect(value).toBe(1);

let response = await mocks.redis.llen('testQueue0');
expect(response).toBe(1);

config.redis.sentinelEnabled = true;
value = await redis.lpush('testQueue1', 'newKey');
expect(value).toBe(1);

response = await mocks.redis.llen('testQueue1');
expect(response).toBe(1);
done();
});

it('should push an array of values to a queue', async done => {
let value = await redis.lpush('testQueue2', ['newKey', 'otherNewKey']);
expect(value).toBe(2);

let response = await mocks.redis.llen('testQueue2');
expect(response).toBe(2);

config.redis.sentinelEnabled = true;
value = await redis.lpush('testQueue3', ['newKey', 'otherNewKey']);
expect(value).toBe(2);

response = await mocks.redis.llen('testQueue3');
expect(response).toBe(2);
done();
});
});

describe('Test HMSET', () => {

it('should set multiple values', async done => {
const newStatus = 'success';
let value = await redis.hmset('jobId', ['status', newStatus]);
expect(value).toBe('OK');

let response = await mocks.redis.hget('jobId', 'status');
expect(response).toBe(newStatus);

config.redis.sentinelEnabled = true;
value = await redis.hmset('jobId', ['status', newStatus]);
expect(value).toBe('OK');

response = await mocks.redis.hget('jobId', 'status');
expect(response).toBe(newStatus);
done();
});
});

});
4 changes: 1 addition & 3 deletions server/config/winston.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ const logger = winston.createLogger({
});

// eslint-disable-next-line no-unused-vars
logger.stream.write = (message, encoding) => {
logger.info(message);
};
logger.stream.write = (message, encoding) => logger.info(message);

export default logger;
35 changes: 35 additions & 0 deletions server/controllers/misc.controller.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import supertest from 'supertest';

import app from '../index';
import swaggerSpec from '../config/swagger';

describe('Miscellaneous Controller Tests', () => {

describe('GET /api/swagger.json', () => {

it('should return a JSON Swagger spec', async done => {
const request = supertest(app);
const response = await request.get('/api/swagger.json');

expect(response.status).toBe(200);
expect(response.body).toMatchObject(swaggerSpec);
done();
});

});

describe('GET /api/health-check', () => {

it('should return 200 OK', async done => {
const request = supertest(app);
const response = await request.get('/api/health-check');

expect(response.status).toBe(200);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toBe('OK');
done();
});

});

});
12 changes: 10 additions & 2 deletions server/controllers/model.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function getModelObject(allModels) {
const cleanModels = {};
for (let i = 0; i < allModels.length; ++i) {
let modelVersion = allModels[i].replace(modelPrefix, '').split('/', 2);
if (cleanModels.hasOwnProperty(modelVersion[0])) {
if (Object.prototype.hasOwnProperty.call(cleanModels, modelVersion[0])) {
cleanModels[modelVersion[0]].push(modelVersion[1]);
} else {
cleanModels[modelVersion[0]] = [modelVersion[1]];
Expand Down Expand Up @@ -107,6 +107,14 @@ async function getGcpModels(req, res) {
}
}

const getModels = config.cloud == 'aws' ? getAwsModels : getGcpModels;
async function getModels(req, res) {
let response;
if (config.cloud === 'aws') {
response = await getAwsModels(req, res);
} else {
response = await getGcpModels(req, res);
}
return response;
}

export default { getModels };

0 comments on commit aaeb7a5

Please sign in to comment.