Skip to content

Commit 868e66e

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 fa8e55f commit 868e66e

File tree

10 files changed

+167
-264
lines changed

10 files changed

+167
-264
lines changed

.eslintrc-node.js renamed to .eslintrc.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ module.exports = {
1919
"camelcase": "off",
2020
"no-var": "error",
2121
"prefer-const": "error",
22+
"array-bracket-spacing": ["error", "always", {"objectsInArrays": false}],
23+
"object-curly-spacing": ["error", "always"],
2224

2325
// Override some eslint base rules because we're using node.
2426
"no-console": "off",

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/blog.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ exports.synchronize = async function () {
9494
blog.topics = await Promise.all(topicsPromises);
9595
const now = Date.now();
9696
metrics.set(blog, 'updated', now);
97-
metrics.push(blog, 'pull-time', [now, now - time]);
97+
metrics.push(blog, 'pull-time', [ now, now - time ]);
9898
db.save();
9999
return { count: topics.length };
100100
};

lib/proxy-heuristics.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ exports.handleProxyUrls = function (request, response, next) {
2626
// Look for a container ID and port in `request.url`.
2727
let match = exports.proxyUrlPrefix.exec(request.url);
2828
if (match) {
29-
[url, containerId, port, path] = match;
29+
[ url, containerId, port, path ] = match;
3030

3131
// We want the proxied `path` to always begin with a '/'.
3232
// However `path` is empty in URLs like '/abc123/8080?p=1', so we redirect
@@ -52,7 +52,7 @@ exports.handleProxyUrls = function (request, response, next) {
5252
const referer = nodeurl.parse(request.headers.referer);
5353
match = exports.proxyUrlPrefix.exec(referer.pathname);
5454
if (match) {
55-
[url, containerId, port, path] = match;
55+
[ url, containerId, port, path ] = match;
5656
}
5757
}
5858

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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
"env": {
3+
"jest": true
4+
},
5+
"extends": [
6+
"../.eslintrc.js"
7+
]
8+
};

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)