Skip to content

Commit 472961a

Browse files
committed
Move to a modern testing infrastructure
Use Jest as test framework, and restructured ESLint config files so it applies to each directory separately.
1 parent 7b8d958 commit 472961a

File tree

8 files changed

+181
-261
lines changed

8 files changed

+181
-261
lines changed
File renamed without changes.

lib/__mocks__/db.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
let store = {};
2+
3+
exports.get = function (key, defaultValue) {
4+
if (!store[key]) {
5+
store[key] = defaultValue || {};
6+
}
7+
8+
return store[key];
9+
};
10+
11+
exports.save = () => {};
12+
13+
exports.__setData = (newStore) => {
14+
store = Object.assign({}, newStore);
15+
};

lib/sessions.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const db = require('./db');
77
const log = require('./log');
88

99
const login = new EmailLogin({
10-
db: './tokens/',
10+
db: db.get('tokens', './tokens/'),
1111
mailer: db.get('mailer')
1212
});
1313
const useSecureCookies = !db.get('security').forceInsecure;

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
"scripts": {
1919
"app": "SCRIPT=app npm start",
2020
"join": "SCRIPT=join npm start",
21-
"lint": "eslint -c .eslintrc-node.js *.js api/ lib/ && eslint -c .eslintrc-browser.js static/",
22-
"lint-fix": "eslint -c .eslintrc-node.js *.js api/ lib/ --fix && eslint -c .eslintrc-browser.js static/ --fix",
21+
"lint": "eslint *.js api/ lib/ test/ static/",
22+
"lint-fix": "eslint *.js api/ lib/ test/ static/ --fix",
2323
"rebase": "git pull -q --rebase origin master && git submodule -q update --rebase && npm update",
2424
"prestart": "npm stop && touch janitor.log janitor.pid && chmod 600 janitor.log janitor.pid",
2525
"start": "if [ -z \"$SCRIPT\" ] ; then printf \"Run which Janitor script? [join/app]:\" && read SCRIPT ; fi ; node \"$SCRIPT\" >> janitor.log 2>&1 & printf \"$!\\n\" > janitor.pid",
2626
"poststart": "printf \"[$(date -uIs)] Background process started (PID $(cat janitor.pid), LOGS $(pwd)/janitor.log).\\n\"",
2727
"stop": "if [ -e janitor.pid -a -n \"$(ps h $(cat janitor.pid))\" ] ; then kill $(cat janitor.pid) && printf \"[$(date -uIs)] Background process stopped (PID $(cat janitor.pid)).\\n\" ; fi ; rm -f janitor.pid",
28-
"test": "cd tests && node tests.js",
28+
"test": "jest",
2929
"prewatch": "touch janitor.log && chmod 600 janitor.log",
3030
"watch": "watch-run --initial --pattern 'app.js,package.json,api/**,lib/**,templates/**' --stop-on-error npm run app & tail -f janitor.log -n 0"
3131
},
@@ -52,6 +52,7 @@
5252
"eslint-plugin-node": "^6.0.0",
5353
"eslint-plugin-promise": "^3.5.0",
5454
"eslint-plugin-standard": "^3.0.1",
55+
"jest": "^22.4.3",
5556
"watch-run": "^1.2.5"
5657
},
5758
"engines": {
File renamed without changes.

test/.eslintrc.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module.exports = {
2+
"env": {
3+
"browser": false,
4+
"es6": true,
5+
"node": true,
6+
"jest": true
7+
},
8+
"extends": [
9+
"eslint:recommended",
10+
"plugin:node/recommended",
11+
"standard"
12+
],
13+
"plugins": [
14+
"node"
15+
],
16+
"rules": {
17+
// Override some of standard js rules
18+
"semi": ["error", "always"],
19+
"comma-dangle": ["error", "only-multiline"],
20+
"camelcase": "off",
21+
"no-var": "error",
22+
"prefer-const": "error",
23+
24+
// Override some eslint base rules because we're using node.
25+
"no-console": "off",
26+
}
27+
};

test/api.test.js

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright © 2017 Jan Keromnes. All rights reserved.
2+
// The following code is covered by the AGPL-3.0 license.
3+
4+
'use strict';
5+
6+
const { Camp } = require('camp');
7+
const selfapi = require('selfapi');
8+
const stream = require('stream');
9+
const { promisify } = require('util');
10+
11+
describe('Janitor API self-tests', () => {
12+
jest.mock('../lib/boot');
13+
jest.mock('../lib/db');
14+
jest.mock('../lib/docker');
15+
16+
const db = require('../lib/db');
17+
db.__setData({
18+
// Tell our fake Janitor app that it runs on the fake host "example.com"
19+
hostname: 'example.com',
20+
// Disable sending any emails (for invites or signing in)
21+
mailer: {
22+
block: true
23+
},
24+
security: {
25+
// Disable Let's Encrypt HTTPS certificate generation and verification
26+
forceHttp: true,
27+
// Disable any security policies that could get in the way of testing
28+
forceInsecure: true
29+
},
30+
tokens: 'test/tokens'
31+
});
32+
33+
const boot = require('../lib/boot');
34+
boot.ensureDockerTlsCertificates.mockResolvedValue();
35+
36+
const docker = require('../lib/docker');
37+
const hosts = require('../lib/hosts');
38+
const machines = require('../lib/machines');
39+
const users = require('../lib/users');
40+
41+
// Fake several Docker methods for testing.
42+
// TODO Maybe use a real Docker Remote API server (or a full mock) for more
43+
// realistic tests?
44+
docker.pullImage.mockImplementation((parameters) => {
45+
const readable = new stream.Readable();
46+
readable.push('ok');
47+
readable.push(null); // End the stream.
48+
return Promise.resolve(readable);
49+
});
50+
docker.inspectImage.mockResolvedValue({ Created: 1500000000000 });
51+
docker.tagImage.mockResolvedValue();
52+
docker.runContainer.mockResolvedValue({ container: { id: 'abcdef0123456789' }, logs: '' });
53+
docker.copyIntoContainer.mockResolvedValue();
54+
docker.execInContainer.mockResolvedValue();
55+
docker.listChangedFilesInContainer.mockResolvedValue([
56+
{ Path: '/tmp', Kind: 0 },
57+
{ Path: '/tmp/test', Kind: 1 }
58+
]);
59+
docker.version.mockResolvedValue({ Version: '17.06.0-ce' });
60+
61+
const api = require('../api/');
62+
63+
const app = new Camp({
64+
documentRoot: 'static'
65+
});
66+
67+
beforeAll(async () => {
68+
function registerTestUser () {
69+
// Grant administrative privileges to the fake email "[email protected]".
70+
db.get('admins')['[email protected]'] = true;
71+
// Create the user "[email protected]" by "sending" them an invite email.
72+
return promisify(users.sendInviteEmail)('[email protected]');
73+
}
74+
75+
function createTestHost () {
76+
return promisify(hosts.create)('example.com', {});
77+
}
78+
79+
function createTestProject () {
80+
machines.setProject({
81+
'id': 'test-project',
82+
'/name': 'Test Project',
83+
'/docker/host': 'example.com',
84+
'/docker/image': 'image:latest',
85+
});
86+
}
87+
88+
function createTestContainer () {
89+
const user = db.get('users')['[email protected]'];
90+
// Create a new user machine for the project "test-project".
91+
return machines.spawn(user, 'test-project');
92+
}
93+
94+
await Promise.all([
95+
registerTestUser(),
96+
createTestHost(),
97+
createTestProject(),
98+
]);
99+
await createTestContainer();
100+
101+
// Authenticate test requests with a server middleware.
102+
const sessions = require('../lib/sessions');
103+
app.handle((request, response, next) => {
104+
sessions.get(request, (error, session, token) => {
105+
if (error || !session || !session.id) {
106+
console.error('[fail] session:', session, error);
107+
response.statusCode = 500; // Internal Server Error
108+
response.end();
109+
return;
110+
}
111+
request.session = session;
112+
if (!('client_secret' in request.query)) {
113+
request.user = db.get('users')['[email protected]'];
114+
}
115+
next();
116+
});
117+
});
118+
119+
// Mount the Janitor API.
120+
selfapi(app, '/api', api);
121+
122+
await promisify(app.listen).call(app, 0, '127.0.0.1');
123+
});
124+
125+
afterAll(() => {
126+
return promisify(app.close).call(app);
127+
});
128+
129+
it('follows API examples', async () => {
130+
const { passed, failed } = await promisify(api.test).call(api, `http://127.0.0.1:${app.address().port}`);
131+
console.info(`${passed.length} passed, ${failed.length} failed`);
132+
expect(failed).toHaveLength(0);
133+
});
134+
});

0 commit comments

Comments
 (0)