Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
version: 2

jobs:
test_dashboard_server:
machine:
image: ubuntu-1604:202007-01
steps:
- checkout
- run:
command: ./test.sh dashboard-server --is-ci
test_dean:
machine:
image: ubuntu-1604:202007-01
steps:
- checkout
- run:
command: ./test.sh dean --is-ci
test_dinersclub:
machine:
image: ubuntu-1604:202007-01
Expand All @@ -15,6 +29,27 @@ jobs:
- checkout
- run:
command: ./test.sh formcentral --is-ci
test_linksniffer:
machine:
image: ubuntu-1604:202007-01
steps:
- checkout
- run:
command: ./test.sh linksniffer --is-ci
test_replybot:
machine:
image: ubuntu-1604:202007-01
steps:
- checkout
- run:
command: ./test.sh replybot --is-ci
test_scribble:
machine:
image: ubuntu-1604:202007-01
steps:
- checkout
- run:
command: ./test.sh scribble --is-ci
test_e2e:
machine:
image: ubuntu-1604:202007-01
Expand Down Expand Up @@ -46,7 +81,12 @@ workflows:
version: 2
test:
jobs:
- test_dashboard_server
- test_dean
- test_dinersclub
- test_formcentral
- test_linksniffer
- test_replybot
- test_scribble
- test_e2e:
context: secrets
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,44 @@ helm install fly vlab -f values/production.yaml

## Development

### Testing locally

Fly is made up of multiple services, where each service is responsible for a specific _scope of work_.

For convenience, we have created tools to make it easy to add features and bug fixes to services, while maintaining the expected behaviors of previous features. We took inspiration from test-driven-development to enable developers to write new business logic while testing continuously in a hot-reload fashion. This closed feedback loop allows early detection of potential problems.

Because services are written using two programming languages (`javascript` and `go`), the test suite naturally behaves differently for each of them (we use `mocha` for `javascript` and `testing` for `go`), but they are _initialized_ in the same way.

For services written in `javascript` or `go`, you can start the local test suite by executing in your terminal:

```
./test.sh {NAME_OF_SERVICE}
```

where `{NAME_OF_SERVICE}` corresponds to the name of the service (the name of the directory). For example, if you want to run the `scribble` test suite, you can type in your terminal:

```
./test.sh scribble
```

The `scribble` service is written in `go`, but you can use any service name, even those written in `javascript`.

Once it's running, you can edit the service code and the test suite will run all the tests for that service every time you save your changes. _How cool!_

#### Running a single test

Sometimes, it's convinient to run only one test at the time.

For tests executed by `mocha`, you can add `.only()` to the test. For more information on `only()`, visit https://mochajs.org/#exclusive-tests.

For tests written in `go`, you can add the name of the test that you want to run exclusively, to the command that _initializes_ the test suite. For example, if the name of the test is `TestCreateUserReturnsSuccess`, the command you will execute in your terminal will look like this:

```
./test.sh scribble TestCreateUserReturnsSuccess
```

### Testing inside Kubernetes

Make sure you have KIND installed.

Then run:
Expand All @@ -22,3 +60,4 @@ Then run:
cd devops
./dev-cluster.sh
```

12 changes: 6 additions & 6 deletions dashboard-server/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
FROM node:10-stretch

WORKDIR /usr/src/app
RUN mkdir /app
WORKDIR /app

COPY package.json .
RUN npm i

COPY . .
COPY package.json /app
RUN npm install

COPY . /app
EXPOSE 3000

CMD [ "npm", "start"]
CMD [ "npm", "start" ]
16 changes: 5 additions & 11 deletions dashboard-server/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const jwks = require('jwks-rsa');

const envVarsSchema = joi
.object({
NODE_ENV: joi.string().allow(['development', 'production', 'test']),
API_VERSION: joi.number(),
AUTH0_HOST: joi.string(),
DB_USER: joi.string(),
Expand All @@ -31,12 +30,7 @@ if (error) {
throw new Error(`Config validation error: ${error.message}`);
}

const isTest = () => envVars.NODE_ENV === 'test';

const config = {
ENV: envVars.NODE_ENV,
IS_DEVELOPMENT: envVars.NODE_ENV === 'development',
IS_TEST: isTest(),
FORMCENTRAL: {
url: envVars.FORMCENTRAL_URL,
},
Expand Down Expand Up @@ -69,11 +63,11 @@ const config = {
secret: envVars.AUTH0_DASHBOARD_SECRET
},
DATABASE_CONFIG: {
user: isTest() ? 'root' : envVars.DB_USER || 'postgres',
host: isTest() ? 'localhost' : envVars.DB_HOST || 'localhost',
database: isTest() ? 'chatroach' : envVars.DB_DATABASE || 'postgres',
password: isTest() ? undefined : envVars.DB_PASSWORD || undefined,
port: isTest() ? 5433 : envVars.DB_PORT || 5432,
user: envVars.DB_USER,
host: envVars.DB_HOST,
database: envVars.DB_DATABASE,
password: envVars.DB_PASSWORD,
port: envVars.DB_PORT,
},
};

Expand Down
2 changes: 1 addition & 1 deletion dashboard-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"lint": "eslint .",
"precommit": "npm run lint",
"start": "node index.js",
"test": "NODE_ENV=test mocha --colors --timeout 5000 './{,!(node_modules)/**}/*.test.js'"
"test": "mocha --colors --timeout 5000 './{,!(node_modules)/**}/*.test.js'"
},
"repository": {
"type": "git",
Expand Down
5 changes: 4 additions & 1 deletion dashboard-server/queries/responses/response.queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ async function all() {
SELECT DISTINCT ON (1) userid, timestamp AS last_timestamp, response AS last_response, surveyid
FROM responses
ORDER BY 1,2 DESC
) l USING (userid)`;
) l
USING (userid)
ORDER BY first_timestamp DESC

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is basically a copy and paste from, #59 (comment)

`;
const { rows } = await this.query(GET_ALL);
return rows;
}
Expand Down
124 changes: 66 additions & 58 deletions dashboard-server/queries/responses/response.test.js
Original file line number Diff line number Diff line change
@@ -1,86 +1,94 @@
const { Pool } = require('pg');
require('chai').should();
require('mocha');
const axios = require('axios');

const model = require('./response.queries');
const responseModel = require('./response.queries');
const surveyModel = require('../surveys/survey.queries');
const userModel = require('../users/user.queries');

const { DATABASE_CONFIG } = require('../../config');

describe('Response queries', () => {
let pool;
let Response;
let vlabPool;
let Survey;
let User;

before(async () => {
let pool = new Pool({
user: 'postgres',
host: 'localhost',
database: 'postgres',
password: undefined,
port: 5432,
});
pool = new Pool(DATABASE_CONFIG);
Response = responseModel.queries(pool);
Survey = surveyModel.queries(pool);
User = userModel.queries(pool);
});

try {
await pool.query('CREATE DATABASE vlab_dashboard_test');
} catch (e) {}
beforeEach(async () => {
await axios.get('http://system/resetdb');
});

vlabPool = new Pool(DATABASE_CONFIG);
describe('.all()', () => {
it('should get the list of the first and last responses for each user', async () => {
const user1 = await User.create({
token: 'AAAA',
email: 'test1@vlab.com',
});

await vlabPool.query(
`CREATE TABLE responses(
parent_surveyid UUID NOT NULL,
parent_shortcode INT NOT NULL,
surveyid UUID NOT NULL,
shortcode INT NOT NULL,
flowid INT NOT NULL,
userid VARCHAR NOT NULL,
question_ref VARCHAR NOT NULL,
question_idx INT NOT NULL,
question_text VARCHAR NOT NULL,
response VARCHAR NOT NULL,
seed INT NOT NULL,
timestamp TIMESTAMPTZ NOT NULL,
PRIMARY KEY (userid, timestamp)
)`,
);
await vlabPool.query('DELETE FROM responses');
const user2 = await User.create({
token: 'BBBB',
email: 'test2@vlab.com',
});

Response = model.queries(vlabPool);
});
const user3 = await User.create({
token: 'CCCC',
email: 'test3@vlab.com',
});

afterEach(async () => {
await vlabPool.query('DELETE FROM responses');
});
const survey1 = await Survey.create({
created: new Date(),
formid: 'DDD',
form: '{"form": "form detail 1"}',
shortcode: 123,
userid: user1.id,
title: 'New User Title 1',
metadata: '{}',
survey_name: "test 1",
translation_conf: '{}',
});

after(async () => {
await vlabPool.query('DROP TABLE responses');
});
const survey2 = await Survey.create({
created: new Date(),
formid: 'EEE',
form: '{"form": "form detail 2"}',
shortcode: 567,
userid: user2.id,
title: 'New User Title 2',
metadata: '{}',
survey_name: "test 2",
translation_conf: '{}',
});

describe('.all()', () => {
it('should get the list of the first and last responses for each user', async () => {
const MOCK_QUERY = `INSERT INTO responses(parent_surveyid, parent_shortcode, surveyid, shortcode, flowid, userid, question_ref, question_idx, question_text, response, seed, timestamp)
VALUES
('b8b960ca-3c0d-4a64-a058-2140ee89596b', '101', 'b8b960ca-3c0d-4a64-a058-2140ee89596b', '101', 100001, '124', 'ref', 10, 'text', '{ "text": "last" }', '6789', current_date + interval '14 hour')
,('f5de09c8-f2c3-49ac-847d-33c8bf5be427', '202', 'f5de09c8-f2c3-49ac-847d-33c8bf5be427', '202', 100003, '123', 'ref', 10, 'text', '{ "text": "last" }', '6789', date '2019-04-18' + interval '12 hour')
,('b8b960ca-3c0d-4a64-a058-2140ee89596b', '101', 'b8b960ca-3c0d-4a64-a058-2140ee89596b', '101', 100004, '124', 'ref', 10, 'text', '{ "text": "first" }', '6789', current_date + interval '10 hour')
,('f5de09c8-f2c3-49ac-847d-33c8bf5be427', '202', 'f5de09c8-f2c3-49ac-847d-33c8bf5be427', '202', 100005, '123', 'ref', 10, 'text', '{ "text": "first" }', '6789', date '2019-04-18' + interval '8 hour')
,('f5de09c8-f2c3-49ac-847d-33c8bf5be427', '202', 'f5de09c8-f2c3-49ac-847d-33c8bf5be427', '202', 100003, '125', 'ref', 10, 'text', '{ "text": "last" }', '6789', date '2019-04-18' + interval '12 hour')
,('b8b960ca-3c0d-4a64-a058-2140ee89596b', '101', 'b8b960ca-3c0d-4a64-a058-2140ee89596b', '101', 100004, '125', 'ref', 10, 'text', '{ "text": "first" }', '6789', date '2019-04-18' + interval '10 hour')
,('f5de09c8-f2c3-49ac-847d-33c8bf5be427', '202', 'f5de09c8-f2c3-49ac-847d-33c8bf5be427', '202', 100005, '125', 'ref', 10, 'text', '{ "text": "first" }', '6789', date '2019-04-18' + interval '8 hour')
,('b8b960ca-3c0d-4a64-a058-2140ee89596b', '101', 'b8b960ca-3c0d-4a64-a058-2140ee89596b', '101', 100006, '124', 'ref', 10, 'text', '{ "text": "middle" }', '6789', current_date + interval '12 hour')`;
const MOCK_QUERY = `
INSERT INTO responses(parent_surveyid, parent_shortcode, surveyid, shortcode, flowid, userid, question_ref, question_idx, question_text, response, seed, timestamp)
VALUES
('${survey1.id}', '${survey1.shortcode}', '${survey1.id}', '${survey1.shortcode}', 100001, '${user1.id}', 'ref', 10, 'text', '{ "text": "last" }', '6789', '2019-05-29T14:00:01.00Z'),
('${survey2.id}', '${survey2.shortcode}', '${survey2.id}', '${survey2.shortcode}', 100003, '${user2.id}', 'ref', 10, 'text', '{ "text": "last" }', '6789', '2010-05-29T12:00:01.00Z'),
('${survey1.id}', '${survey1.shortcode}', '${survey1.id}', '${survey1.shortcode}', 100004, '${user1.id}', 'ref', 10, 'text', '{ "text": "first" }', '6789', '2019-05-29T10:20:01.00Z'),
('${survey2.id}', '${survey2.shortcode}', '${survey2.id}', '${survey2.shortcode}', 100005, '${user2.id}', 'ref', 10, 'text', '{ "text": "first" }', '6789', '2010-05-29T08:00:01.00Z'),
('${survey2.id}', '${survey2.shortcode}', '${survey2.id}', '${survey2.shortcode}', 100003, '${user3.id}', 'ref', 10, 'text', '{ "text": "last" }', '6789', '2010-05-29T12:00:01.00Z'),
('${survey1.id}', '${survey1.shortcode}', '${survey1.id}', '${survey1.shortcode}', 100004, '${user3.id}', 'ref', 10, 'text', '{ "text": "first" }', '6789', '2010-05-29T10:00:01.00Z'),
('${survey2.id}', '${survey2.shortcode}', '${survey2.id}', '${survey2.shortcode}', 100005, '${user3.id}', 'ref', 10, 'text', '{ "text": "first" }', '6789', '2010-05-29T08:00:01.00Z'),
('${survey1.id}', '${survey1.shortcode}', '${survey1.id}', '${survey1.shortcode}', 100006, '${user1.id}', 'ref', 10, 'text', '{ "text": "middle" }', '6789', '2019-05-29T12:00:01.00Z')
`;

await vlabPool.query(MOCK_QUERY);
await pool.query(MOCK_QUERY);
const responses = await Response.all();

responses[0].first_response.should.equal('{ "text": "first" }');
responses[0].last_response.should.equal('{ "text": "last" }');
responses[0].surveyid.should.equal(
'f5de09c8-f2c3-49ac-847d-33c8bf5be427',
);
responses[0].surveyid.should.equal(survey1.id);
responses[1].first_response.should.equal('{ "text": "first" }');
responses[1].last_response.should.equal('{ "text": "last" }');
responses[1].surveyid.should.equal(
'b8b960ca-3c0d-4a64-a058-2140ee89596b',
);
responses[1].surveyid.should.equal(survey2.id);
});
});
});
Loading