Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ screenshots
*.pem
rabblerouser-core.sublime-project
rabblerouser-core.sublime-workspace
/e2e/cypress/videos
.Trash-1001/
.Trash-0/
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ services:

install: yarn

script: ./go.sh yarn test
script:
- ./go.sh yarn test
- ./bin/e2e.sh

after_success:
- if [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_BRANCH" == "master" ]; then bin/docker-build-and-deploy.sh; fi
40 changes: 23 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,42 @@ To find out more about the Rabble Rouser project, check out our [documentation r

2. Clone the project:

git clone https://github.com/rabblerouser/core.git
```sh
git clone https://github.com/rabblerouser/core.git
```

3. Start a Docker container to develop in (this also starts containers for dependent services):

./go.sh # For Mac/Linux
# Windows not supported yet :(
```sh
./go.sh # For Mac/Linux
# Windows not supported yet :(
```

4. Install/compile the project, seed the database, run the tests, then start the app

yarn
yarn seed
yarn test
yarn start
```sh
yarn
yarn seed
yarn test
yarn start
```

5. Verify that the app works:
1. Register a new member at `http://localhost:3000`
2. Log in at `http://localhost:3000/login`, with `superadmin@rabblerouser.team`/`password1234`

* Register a new member at `http://localhost:3000`

* Log in at `http://localhost:3000/login`, with `superadmin@rabblerouser.team`/`password1234`

## Bonus commands

To run a single command inside the container, rather than interactive mode:

```sh
./go.sh yarn test
```

To watch container logs (e.g. for debugging):

```sh
# This will show all container logs, colour-coded
docker-compose logs -f
Expand Down Expand Up @@ -73,18 +83,15 @@ This repository is split into these sub-directories:
* `bin`: Utility scripts, mostly for build/deploy tasks
* `frontend`: The frontend React.js web app
* `backend`: The backend node.js API
* `e2e`: End-to-end tests built with casperjs (broken right now, ignore them)
* `e2e`: End-to-end tests built with cypress.

The frontend, backend, and E2E tests are all written in JavaScript, so each one has a `package.json` file for
dependencies and tasks. There is also another `package.json` at the top-level of the repo, which mainly orchestrates the
tasks contained within the sub-projects.
The frontend, backend, and E2E tests are all written in JavaScript, so each one has a `package.json` file for dependencies and tasks. There is also another `package.json` at the top-level of the repo, which mainly orchestrates the tasks contained within the sub-projects.

Each of these directories also has its own README file, with more instructions for how to work on its code.

## Linting

We use ESLint to maintain a consistent style and detect common sources of bugs, and this is run as part of the build
system. To run ESLint just do `yarn lint` in one of the frontend, backend, or e2e directories.
We use ESLint to maintain a consistent style and detect common sources of bugs, and this is run as part of the build system. To run ESLint just do `yarn lint` in one of the frontend, backend, or e2e directories.

To lint just a specific file or directory:

Expand All @@ -95,6 +102,5 @@ You can even add `--fix` to the end of that command to automatically fix things
If you're not sure how to fix an ESLint error, you can look up the docs for specific rules using a URL like:
http://eslint.org/docs/rules/arrow-parens. In this case, `arrow-parens` is the name of the rule.

We're using the [airbnb style](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb) (slightly
modified), which encourages use of many ES6 features. If you're not up to speed on ES6, this reference may come in
We're using the [airbnb style](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb) (slightly modified), which encourages use of many ES6 features. If you're not up to speed on ES6, this reference may come in
handy: http://es6-features.org/.
172 changes: 121 additions & 51 deletions backend/bin/e2e-seed.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,121 @@
const uuid = require('node-uuid');

const models = require('../src/models');
const Branch = models.Branch;
const AdminUser = models.AdminUser;
const Member = models.Member;
const Group = models.Group;

const branchId = uuid();
let branch;
let group;

console.log('Running script to seed data necessary for E2E tests');

Promise.all([Branch, AdminUser, Group]
.map(model => model.destroy({ truncate: true, cascade: true, force: true }))
)
.then(() => process.stdout.write('Database cleared of branches, groups, admins, organisers.\n'))

.then(() => AdminUser.create({ email: 'admin@rr.com', password: 'apassword', type: 'SUPER' }))
.then(() => process.stdout.write('Database seeded with admin: admin@rr.com.\n'))

.then(() => Branch.create({ id: branchId, name: 'A branch' }))
.then(created => { branch = created; })
.then(() => process.stdout.write('Database seeded with branch: \'A branch\'.\n'))

.then(() => AdminUser.create({ email: 'organiser@rr.com', password: 'apassword', branchId, type: 'BRANCH' }))
.then(() => process.stdout.write('Database seeded with organiser: organiser@rr.com.\n'))

.then(() => Group.create({ name: 'A group with member', description: 'This is a description' }))
.then(created => { group = created; })
.then(() => branch.addGroup(group))
.then(() => process.stdout.write('Database seeded with group: \'a group with a member\'.\n'))

.then(() => Member.create(
{ firstName: 'A name', lastName: 'surname', gender: 'social-construct',
memberSince: new Date(), email: 'a@name.com', primaryPhoneNumber: '0',
secondaryPhoneNumber: '1', residentialAddress: null, postalAddress: null, membershipType: 'full',
branchId, additionalInfo: '' })
)
.then(created => {
group.addMember(created);
return created.addGroup(group);
})
.then(() => process.stdout.write('Database seeded with member: \'A name\'.\n'))

.then(() => process.exit(0))
.catch(err => {
process.stdout.write(`Error: ${err}`);
process.exit(1);
});
'use strict';

/* eslint no-console: off */
/* eslint import/no-extraneous-dependencies: off */

// This script can be run manually via yarn to prepare data for the app
// - It publishes a "branch-created" event onto the rabblerouser kinesis stream
// - It publishes an "admin-created" event onto the rabblerouser kinesis stream
// - In development, it also ensures that the kinesis stream and event S3 bucket have been created
// (In production, these are created by terraform before the app is deployed)

const AWS = require('aws-sdk');
const branchesController = require('../src/controllers/branchesController');
const adminController = require('../src/controllers/adminController');
const adminType = require('../src/security/adminType');

const createStream = () => {
console.log('Creating kinesis stream for development');

const kinesis = new AWS.Kinesis({
endpoint: process.env.KINESIS_ENDPOINT,
region: 'ap-southeast-2',
accessKeyId: 'FAKE',
secretAccessKey: 'ALSO FAKE',
});

const StreamName = process.env.STREAM_NAME;
return kinesis.createStream({ StreamName, ShardCount: 1 }).promise().then(
() => console.log(`${StreamName} created`),
err => {
// Swallow these errors, but re-throw all others
if (err.message.includes('already exists')) {
console.log(`${StreamName} already exists`);
return;
}
throw new Error(`Could not create stream: ${err.message}`);
}
);
};

const createArchiveBucket = () => {
console.log('Creating archive S3 bucket for development');

const s3 = new AWS.S3({
endpoint: process.env.S3_ENDPOINT,
region: 'ap-southeast-2',
accessKeyId: 'FAKE',
secretAccessKey: 'ALSO FAKE',
});

return s3.createBucket({ Bucket: process.env.ARCHIVE_BUCKET }).promise().then(
() => console.log(`${process.env.ARCHIVE_BUCKET} created`),
err => { throw new Error(`Could not create bucket: ${err.message}`); }
);
};

const createStreamAndBucket = () => {
if (process.env.NODE_ENV === 'production') {
console.log('Not creating kinesis stream or S3 bucket. These should already be there in production.');
return undefined;
}
return Promise.all([createStream(), createArchiveBucket()]);
};

const fakeRes = () => {
const res = {
lastStatus: null,
lastJson: null,
};
res.status = status => { res.lastStatus = status; return res; };
res.sendStatus = status => { res.lastStatus = status; return res; };
res.json = json => { res.lastJson = json; return res; };
return res;
};

const createDefaultBranch = () => {
console.log('Creating default branch.');
const req = { body: { name: 'A branch' } };
const res = fakeRes();
return branchesController.createBranch(req, res)
.then(() => {
if (res.lastStatus === 200) {
console.log('Default branch created');
return Promise.resolve();
}
console.log('Failed to create default branch');
return Promise.reject({ status: res.lastStatus, json: res.lastJson });
});
};

const createDefaultAdmin = () => {
console.log('Creating default admin');
const req = { params: {}, body: { email: 'admin@rr.com', name: 'Default Super Admin', password: 'password4321' } };
const res = fakeRes();
return adminController.createAdmin(adminType.super)(req, res)
.then(() => {
if (res.lastStatus === 200) {
console.log(`Default admin created - username: ${req.body.email}, password: ${req.body.password}`);
return Promise.resolve();
}
console.log('Failed to create default admin');
return Promise.reject({ status: res.lastStatus, json: res.lastJson });
});
};

const wait = () => {
console.log('Waiting for a few seconds for things to be ready...');
return new Promise(resolve => setTimeout(resolve, 5000));
};

console.log('Running seed script');
Promise.resolve()
.then(createStreamAndBucket)
.then(wait)
.then(createDefaultBranch)
.then(createDefaultAdmin)
.then(() => console.log('Seeded successfully.'))
.catch(error => {
console.error('Seed failed:', error);
process.exit(1);
});
5 changes: 3 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
"ci": "yarn lint && node bin/test-seed.js && yarn test",
"dev": "yarn nodemon -L index.js",
"lint": "yarn eslint src spec",
"e2e-seed": "NODE_ENV=test node bin/e2e-seed.js",
"e2e-seed": "node bin/e2e-seed.js",
"test": "NODE_ENV=test yarn mocha --reporter dot --check-leaks --timeout 10000 -r spec/specGlobals --recursive spec",
"tdd": "NODE_ENV=test yarn mocha --reporter dot --check-leaks --timeout 10000 -r spec/specGlobals --recursive spec/unit --watch",
"seed": "node bin/seed.js && node bin/test-seed.js",
"start": "node index.js"
"start": "node index.js",
"start-test": "NODE_ENV=test node index.js"
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm hesitant to add more scripts to the package.json, as it's already a bit out of control. Can we just do NODE_ENV=test yarn start anywhere we need this?

Copy link
Author

Choose a reason for hiding this comment

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

Personal preference - I see what you mean, but, this one feels like a useful shorthand that would be used relatively commonly, and also serves as documentation for future devs.

},
"repository": {
"type": "git",
Expand Down
21 changes: 4 additions & 17 deletions bin/e2e.sh
Original file line number Diff line number Diff line change
@@ -1,21 +1,8 @@
#!/bin/sh
set -e
set -x

export NODE_ENV=test
docker-compose -f docker-compose.yml -f docker-compose.test.yml up -d

yarn start &

echo "Will wait for the server to be available for testing..."
attempts=0
until $(curl --output /dev/null --silent --head --fail http://localhost:3000); do
attempts=$((attempts+1))
if [ "$attempts" = '100' ]; then
echo "Tried to contact the site, but it's taking too long. Possibly it's broken. Either way it's ignoring me and I don't appreciate this."
exit 1
fi
sleep 5
done
echo "Oh hai server!"

yarn --cwd backend e2e-seed && yarn --cwd e2e ci-test
docker logs -f --since=1m core_test_1
docker logs -f --since=1m core_core_1
docker logs -f --since=1m core_kinesis_1
19 changes: 19 additions & 0 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: '2'
services:
core:
command: >
sh -c "yarn --cwd backend
&& yarn --cwd backend e2e-seed
&& ./bin/copy_assets.sh
&& yarn start"
test:
image: cypress/browsers:chrome65-ff57
volumes:
- '.:/app'
links:
- core
command: >
bash -c "yarn --cwd app/e2e
&& yarn --cwd app/e2e test-command"
depends_on:
- core
2 changes: 2 additions & 0 deletions e2e/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
./cypress/screenshots
./cypress/videos
15 changes: 12 additions & 3 deletions e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@ End to end tests for Rabble Rouser - A pluggable, extensible membership database
## Tech

* Webpack + Babel
* Casperjs
* Cypress

## Running the tests

`yarn e2e`
Running the script `./bin/e2e.sh` from the project root on your local machine is the best way to run the tests. This will also spin up the relevant containers and populate data.

This will also run the prep-data task which sets up the backend with required data before kicking off the tests.
To load up cypress to run the end-to-end tests locally, do:

```sh
$ [core]/bin/e2e.sh # make sure you have run this first to install key dependencies
$ cd e2e # make sure you're in the e2e directory
$ $(yarn bin)/cypress install # install the correct cypress binary for your system (if not linux)
$ bin/run_e2e_locally.sh # fire up cypress locally to run the e2e tests
```

Tests live in `cypress/integration`. See `docs.cypress.io` for more usage information.

## Linting

Expand Down
3 changes: 3 additions & 0 deletions e2e/bin/run_e2e_locally.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env bash

$(yarn bin)/cypress open --env configFile=development
3 changes: 3 additions & 0 deletions e2e/config/development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"baseUrl": "http://localhost:3000"
}
3 changes: 3 additions & 0 deletions e2e/config/e2e.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"baseUrl": "http://core:3000"
}
3 changes: 3 additions & 0 deletions e2e/cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"baseUrl": "http://core:3000"
}
Loading