From a3a59aa8f37058fff6cdf386c10e32aa180ce2ba Mon Sep 17 00:00:00 2001
From: Alex Kocharin <alex@kocharin.ru>
Date: Thu, 19 Dec 2013 19:11:54 +0400
Subject: [PATCH] reorganize tests, and add new ones

---
 .gitignore                               |   2 +-
 package.yaml                             |   2 +-
 test/basic.js                            | 120 -------------------
 test/config.js                           |  13 ---
 test/functional/basic.js                 | 140 +++++++++++++++++++++++
 test/{ => functional}/config-1.yaml      |   0
 test/{ => functional}/config-2.yaml      |   0
 test/{ => functional}/fixtures/binary    | Bin
 test/{ => functional}/fixtures/tags.json |   0
 test/functional/gh29.js                  |  74 ++++++++++++
 test/functional/index.js                 |  44 +++++++
 test/{ => functional}/lib/package.js     |   0
 test/{ => functional}/lib/server.js      |  72 ++++++------
 test/functional/lib/startup.js           |  33 ++++++
 test/functional/mirror.js                |  92 +++++++++++++++
 test/functional/race.js                  |  93 +++++++++++++++
 test/functional/security.js              |  81 +++++++++++++
 test/functional/tags.js                  |  40 +++++++
 test/gh29.js                             |  57 ---------
 test/mirror.js                           |  75 ------------
 test/race.js                             |  86 --------------
 test/start.sh                            |   2 +-
 test/startup.js                          |  42 -------
 test/tags.js                             |  34 ------
 test/tests.js                            |  27 -----
 test/unit/no_proxy.js                    |  40 +++----
 test/unit/st_merge.js                    |  20 ++--
 test/unit/transaction.js                 |  82 +++++++------
 test/unit/utils.js                       |  32 +++---
 29 files changed, 730 insertions(+), 573 deletions(-)
 delete mode 100644 test/basic.js
 delete mode 100644 test/config.js
 create mode 100644 test/functional/basic.js
 rename test/{ => functional}/config-1.yaml (100%)
 rename test/{ => functional}/config-2.yaml (100%)
 rename test/{ => functional}/fixtures/binary (100%)
 rename test/{ => functional}/fixtures/tags.json (100%)
 create mode 100644 test/functional/gh29.js
 create mode 100644 test/functional/index.js
 rename test/{ => functional}/lib/package.js (100%)
 rename test/{ => functional}/lib/server.js (61%)
 create mode 100644 test/functional/lib/startup.js
 create mode 100644 test/functional/mirror.js
 create mode 100644 test/functional/race.js
 create mode 100644 test/functional/security.js
 create mode 100644 test/functional/tags.js
 delete mode 100644 test/gh29.js
 delete mode 100644 test/mirror.js
 delete mode 100644 test/race.js
 delete mode 100644 test/startup.js
 delete mode 100644 test/tags.js
 delete mode 100644 test/tests.js

diff --git a/.gitignore b/.gitignore
index f91a6eccc..086cffe33 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,4 @@ sinopia-*.tgz
 ###
 bin/storage*
 bin/*.yaml
-test/test-storage*
+test-storage*
diff --git a/package.yaml b/package.yaml
index 7b14f4957..d82c057be 100644
--- a/package.yaml
+++ b/package.yaml
@@ -43,7 +43,7 @@ keywords:
   - server
 
 scripts:
-  test: ./test/start.sh
+  test: mocha ./test/functional ./test/unit
 
 # we depend on streams2 stuff
 # it can be replaced with isaacs/readable-stream, ask if you need to use 0.8
diff --git a/test/basic.js b/test/basic.js
deleted file mode 100644
index 956590482..000000000
--- a/test/basic.js
+++ /dev/null
@@ -1,120 +0,0 @@
-var assert = require('assert')
-  , readfile = require('fs').readFileSync
-  , crypto = require('crypto')
-  , ex = module.exports
-  , server = process.server
-  , server2 = process.server2
-
-ex['trying to fetch non-existent package'] = function(cb) {
-	server.get_package('testpkg', function(res, body) {
-		// shouldn't exist yet
-		assert.equal(res.statusCode, 404)
-		assert(~body.error.indexOf('no such package'))
-		cb()
-	})
-}
-
-ex['creating new package'] = function(cb) {
-	server.put_package('testpkg', require('./lib/package')('testpkg'), function(res, body) {
-		assert.equal(res.statusCode, 201)
-		assert(~body.ok.indexOf('created new package'))
-		cb()
-	})
-}
-
-ex['downloading non-existent tarball'] = function(cb) {
-	server.get_tarball('testpkg', 'blahblah', function(res, body) {
-		assert.equal(res.statusCode, 404)
-		assert(~body.error.indexOf('no such file'))
-		cb()
-	})
-}
-
-ex['uploading incomplete tarball'] = function(cb) {
-	server.put_tarball_incomplete('testpkg', 'blahblah1', readfile('fixtures/binary'), 3000, function(res, body) {
-		cb()
-	})
-}
-
-ex['uploading new tarball'] = function(cb) {
-	server.put_tarball('testpkg', 'blahblah', readfile('fixtures/binary'), function(res, body) {
-		assert.equal(res.statusCode, 201)
-		assert(body.ok)
-		cb()
-	})
-}
-
-ex['doubleerr test'] = function(cb) {
-	server.put_tarball('testfwd2', 'blahblah', readfile('fixtures/binary'), function(res, body) {
-		assert.equal(res.statusCode, 404)
-		assert(body.error)
-		cb()
-	})
-}
-
-ex['downloading newly created tarball'] = function(cb) {
-	server.get_tarball('testpkg', 'blahblah', function(res, body) {
-		assert.equal(res.statusCode, 200)
-		assert.deepEqual(body, readfile('fixtures/binary').toString('utf8'))
-		cb()
-	})
-}
-
-ex['uploading new package version for bad pkg'] = function(cb) {
-	server.put_version('testpxg', '0.0.1', require('./lib/package')('testpxg'), function(res, body) {
-		assert.equal(res.statusCode, 404)
-		assert(~body.error.indexOf('no such package'))
-		cb()
-	})
-}
-
-ex['uploading new package version (bad sha)'] = function(cb) {
-	var pkg = require('./lib/package')('testpkg')
-	pkg.dist.shasum = crypto.createHash('sha1').update('fake').digest('hex')
-	server.put_version('testpkg', '0.0.1', pkg, function(res, body) {
-		assert.equal(res.statusCode, 400)
-		assert(~body.error.indexOf('shasum error'))
-		cb()
-	})
-}
-
-ex['uploading new package version'] = function(cb) {
-	var pkg = require('./lib/package')('testpkg')
-	pkg.dist.shasum = crypto.createHash('sha1').update(readfile('fixtures/binary')).digest('hex')
-	server.put_version('testpkg', '0.0.1', pkg, function(res, body) {
-		assert.equal(res.statusCode, 201)
-		assert(~body.ok.indexOf('published'))
-		cb()
-	})
-}
-
-ex['downloading newly created package'] = function(cb) {
-	server.get_package('testpkg', function(res, body) {
-		assert.equal(res.statusCode, 200)
-		assert.equal(body.name, 'testpkg')
-		assert.equal(body.versions['0.0.1'].name, 'testpkg')
-		assert.equal(body.versions['0.0.1'].dist.tarball, 'http://localhost:55551/testpkg/-/blahblah')
-		assert.deepEqual(body['dist-tags'], {latest: '0.0.1'})
-		cb()
-	})
-}
-
-ex['downloading package via server2'] = function(cb) {
-	server2.get_package('testpkg', function(res, body) {
-		assert.equal(res.statusCode, 200)
-		assert.equal(body.name, 'testpkg')
-		assert.equal(body.versions['0.0.1'].name, 'testpkg')
-		assert.equal(body.versions['0.0.1'].dist.tarball, 'http://localhost:55552/testpkg/-/blahblah')
-		assert.deepEqual(body['dist-tags'], {latest: '0.0.1'})
-		cb()
-	})
-}
-
-ex['publishing package / bad ro uplink'] = function(cb) {
-	server.put_package('baduplink', require('./lib/package')('baduplink'), function(res, body) {
-		assert.equal(res.statusCode, 503)
-		assert(~body.error.indexOf('one of the uplinks is down, refuse to publish'))
-		cb()
-	})
-}
-
diff --git a/test/config.js b/test/config.js
deleted file mode 100644
index 42cee09af..000000000
--- a/test/config.js
+++ /dev/null
@@ -1,13 +0,0 @@
-var assert = require('assert')
-  , ex = module.exports
-
-ex['trying to fetch non-existent package'] = function(cb) {
-	var f = fork('../bin/sinopia', ['-c', './config/log-1.yaml'], {silent: true})
-	f.on('message', function(msg) {
-		if ('sinopia_started' in msg) {
-			f.kill()
-			cb()
-		}
-	})
-}
-
diff --git a/test/functional/basic.js b/test/functional/basic.js
new file mode 100644
index 000000000..540ce948a
--- /dev/null
+++ b/test/functional/basic.js
@@ -0,0 +1,140 @@
+require('./lib/startup')
+
+var assert = require('assert')
+  , async = require('async')
+  , crypto = require('crypto')
+  , server = process.server
+  , server2 = process.server2
+
+function readfile(x) {
+	return require('fs').readFileSync(__dirname + '/' + x)
+}
+
+module.exports = function() {
+	it('trying to fetch non-existent package', function(cb) {
+		server.get_package('testpkg', function(res, body) {
+			assert.equal(res.statusCode, 404)
+			assert(~body.error.indexOf('no such package'))
+			cb()
+		})
+	})
+
+	describe('testpkg', function() {
+		before(function(cb) {
+			server.put_package('testpkg', require('./lib/package')('testpkg'), function(res, body) {
+				assert.equal(res.statusCode, 201)
+				assert(~body.ok.indexOf('created new package'))
+				cb()
+			})
+		})
+
+		it('creating new package', function(){/* test for before() */})
+
+		it('downloading non-existent tarball', function(cb) {
+			server.get_tarball('testpkg', 'blahblah', function(res, body) {
+				assert.equal(res.statusCode, 404)
+				assert(~body.error.indexOf('no such file'))
+				cb()
+			})
+		})
+
+		it('uploading incomplete tarball', function(cb) {
+			server.put_tarball_incomplete('testpkg', 'blahblah1', readfile('fixtures/binary'), 3000, function(res, body) {
+				cb()
+			})
+		})
+
+		describe('tarball', function() {
+			before(function(cb) {
+				server.put_tarball('testpkg', 'blahblah', readfile('fixtures/binary'), function(res, body) {
+					assert.equal(res.statusCode, 201)
+					assert(body.ok)
+					cb()
+				})
+			})
+
+			it('uploading new tarball', function(){/* test for before() */})
+
+			it('downloading newly created tarball', function(cb) {
+				server.get_tarball('testpkg', 'blahblah', function(res, body) {
+					assert.equal(res.statusCode, 200)
+					assert.deepEqual(body, readfile('fixtures/binary').toString('utf8'))
+					cb()
+				})
+			})
+
+			it('uploading new package version (bad sha)', function(cb) {
+				var pkg = require('./lib/package')('testpkg')
+				pkg.dist.shasum = crypto.createHash('sha1').update('fake').digest('hex')
+				server.put_version('testpkg', '0.0.1', pkg, function(res, body) {
+					assert.equal(res.statusCode, 400)
+					assert(~body.error.indexOf('shasum error'))
+					cb()
+				})
+			})
+
+			describe('version', function() {
+				before(function(cb) {
+					var pkg = require('./lib/package')('testpkg')
+					pkg.dist.shasum = crypto.createHash('sha1').update(readfile('fixtures/binary')).digest('hex')
+					server.put_version('testpkg', '0.0.1', pkg, function(res, body) {
+						assert.equal(res.statusCode, 201)
+						assert(~body.ok.indexOf('published'))
+						cb()
+					})
+				})
+
+				it('uploading new package version', function(){/* test for before() */})
+
+				it('downloading newly created package', function(cb) {
+					server.get_package('testpkg', function(res, body) {
+						assert.equal(res.statusCode, 200)
+						assert.equal(body.name, 'testpkg')
+						assert.equal(body.versions['0.0.1'].name, 'testpkg')
+						assert.equal(body.versions['0.0.1'].dist.tarball, 'http://localhost:55551/testpkg/-/blahblah')
+						assert.deepEqual(body['dist-tags'], {latest: '0.0.1'})
+						cb()
+					})
+				})
+
+				it('downloading package via server2', function(cb) {
+					server2.get_package('testpkg', function(res, body) {
+						assert.equal(res.statusCode, 200)
+						assert.equal(body.name, 'testpkg')
+						assert.equal(body.versions['0.0.1'].name, 'testpkg')
+						assert.equal(body.versions['0.0.1'].dist.tarball, 'http://localhost:55552/testpkg/-/blahblah')
+						assert.deepEqual(body['dist-tags'], {latest: '0.0.1'})
+						cb()
+					})
+				})
+			})
+		})
+
+		require('./security')()
+	})
+
+	it('uploading new package version for bad pkg', function(cb) {
+		server.put_version('testpxg', '0.0.1', require('./lib/package')('testpxg'), function(res, body) {
+			assert.equal(res.statusCode, 404)
+			assert(~body.error.indexOf('no such package'))
+			cb()
+		})
+	})
+
+	it('doubleerr test', function(cb) {
+		server.put_tarball('testfwd2', 'blahblah', readfile('fixtures/binary'), function(res, body) {
+			assert.equal(res.statusCode, 404)
+			assert(body.error)
+			cb()
+		})
+	})
+
+	it('publishing package / bad ro uplink', function(cb) {
+		server.put_package('baduplink', require('./lib/package')('baduplink'), function(res, body) {
+			assert.equal(res.statusCode, 503)
+			assert(~body.error.indexOf('one of the uplinks is down, refuse to publish'))
+			cb()
+		})
+	})
+}
+
diff --git a/test/config-1.yaml b/test/functional/config-1.yaml
similarity index 100%
rename from test/config-1.yaml
rename to test/functional/config-1.yaml
diff --git a/test/config-2.yaml b/test/functional/config-2.yaml
similarity index 100%
rename from test/config-2.yaml
rename to test/functional/config-2.yaml
diff --git a/test/fixtures/binary b/test/functional/fixtures/binary
similarity index 100%
rename from test/fixtures/binary
rename to test/functional/fixtures/binary
diff --git a/test/fixtures/tags.json b/test/functional/fixtures/tags.json
similarity index 100%
rename from test/fixtures/tags.json
rename to test/functional/fixtures/tags.json
diff --git a/test/functional/gh29.js b/test/functional/gh29.js
new file mode 100644
index 000000000..11d509f22
--- /dev/null
+++ b/test/functional/gh29.js
@@ -0,0 +1,74 @@
+var assert = require('assert')
+  , crypto = require('crypto')
+  , ex = module.exports
+  , server = process.server
+  , server2 = process.server2
+
+function readfile(x) {
+	return require('fs').readFileSync(__dirname + '/' + x)
+}
+
+module.exports = function() {
+	it('downloading non-existent tarball #1 / srv2', function(cb) {
+		server2.get_tarball('testpkg-gh29', 'blahblah', function(res, body) {
+			assert.equal(res.statusCode, 404)
+			assert(~body.error.indexOf('no such package'))
+			cb()
+		})
+	})
+
+	describe('pkg-gh29', function() {
+		before(function(cb) {
+			server.put_package('testpkg-gh29', require('./lib/package')('testpkg-gh29'), function(res, body) {
+				assert.equal(res.statusCode, 201)
+				assert(~body.ok.indexOf('created new package'))
+				cb()
+			})
+		})
+
+		it('creating new package / srv1', function(){})
+
+		it('downloading non-existent tarball #2 / srv2', function(cb) {
+			server2.get_tarball('testpkg-gh29', 'blahblah', function(res, body) {
+				assert.equal(res.statusCode, 404)
+				assert(~body.error.indexOf('no such file'))
+				cb()
+			})
+		})
+
+		describe('tarball', function() {
+			before(function(cb) {
+				server.put_tarball('testpkg-gh29', 'blahblah', readfile('fixtures/binary'), function(res, body) {
+					assert.equal(res.statusCode, 201)
+					assert(body.ok)
+					cb()
+				})
+			})
+
+			it('uploading new tarball / srv1', function(){})
+
+			describe('pkg version', function() {
+				before(function(cb) {
+					var pkg = require('./lib/package')('testpkg-gh29')
+					pkg.dist.shasum = crypto.createHash('sha1').update(readfile('fixtures/binary')).digest('hex')
+					server.put_version('testpkg-gh29', '0.0.1', pkg, function(res, body) {
+						assert.equal(res.statusCode, 201)
+						assert(~body.ok.indexOf('published'))
+						cb()
+					})
+				})
+
+				it('uploading new package version / srv1', function(){})
+
+				it('downloading newly created tarball / srv2', function(cb) {
+					server2.get_tarball('testpkg-gh29', 'blahblah', function(res, body) {
+						assert.equal(res.statusCode, 200)
+						assert.deepEqual(body, readfile('fixtures/binary').toString('utf8'))
+						cb()
+					})
+				})
+			})
+		})
+	})
+}
+
diff --git a/test/functional/index.js b/test/functional/index.js
new file mode 100644
index 000000000..6b9b0171b
--- /dev/null
+++ b/test/functional/index.js
@@ -0,0 +1,44 @@
+require('./lib/startup')
+
+var assert = require('assert')
+  , async = require('async')
+  , crypto = require('crypto')
+  , ex = module.exports
+  , server = process.server
+  , server2 = process.server2
+
+function readfile(x) {
+	return require('fs').readFileSync(__dirname + '/' + x)
+}
+
+describe('Func', function() {
+	before(function(cb) {
+		async.parallel([
+			function(cb) {
+				require('./lib/startup').start('./test-storage', './config-1.yaml', cb)
+			},
+			function(cb) {
+				require('./lib/startup').start('./test-storage2', './config-2.yaml', cb)
+			},
+		], cb)
+	})
+
+	before(function auth(cb) {
+		async.map([server, server2], function(server, cb) {
+			server.auth('test', 'test', function(res, body) {
+				assert.equal(res.statusCode, 201)
+				assert.notEqual(body.ok.indexOf('"test"'), -1)
+				cb()
+			})
+		}, cb)
+	})
+
+	it('authenticate', function(){/* test for before() */})
+
+	require('./basic')()
+	require('./gh29')()
+	require('./tags')()
+	require('./mirror')()
+	require('./race')()
+})
+
diff --git a/test/lib/package.js b/test/functional/lib/package.js
similarity index 100%
rename from test/lib/package.js
rename to test/functional/lib/package.js
diff --git a/test/lib/server.js b/test/functional/lib/server.js
similarity index 61%
rename from test/lib/server.js
rename to test/functional/lib/server.js
index f7c92a603..8ca2c9de3 100644
--- a/test/lib/server.js
+++ b/test/functional/lib/server.js
@@ -1,94 +1,94 @@
-var request = require('request');
-var assert = require('assert');
+var request = require('request')
+  , assert = require('assert')
 
 function Server(url) {
-	if (!(this instanceof Server)) return new Server(url);
-	this.url = url.replace(/\/$/, '');
-	this.userAgent = 'node/v0.10.8 linux x64';
-	this.authstr = 'Basic '+(new Buffer('test:test')).toString('base64');
+	if (!(this instanceof Server)) return new Server(url)
+	this.url = url.replace(/\/$/, '')
+	this.userAgent = 'node/v0.10.8 linux x64'
+	this.authstr = 'Basic '+(new Buffer('test:test')).toString('base64')
 }
 
 function prep(cb) {
 	return function(err, res, body) {
-		if (err) throw err;
-		cb(res, body);
-	};
+		if (err) throw err
+		cb(res, body)
+	}
 }
 
 Server.prototype.request = function(options, cb) {
 	assert(options.uri)
-	var headers = options.headers || {};
-	headers.accept = headers.accept || 'application/json';
-	headers['user-agent'] = headers['user-agent'] || this.userAgent;
-	headers.authorization = headers.authorization || this.authstr;
+	var headers = options.headers || {}
+	headers.accept = headers.accept || 'application/json'
+	headers['user-agent'] = headers['user-agent'] || this.userAgent
+	headers.authorization = headers.authorization || this.authstr
 	return request({
 		url: this.url + options.uri,
 		method: options.method || 'GET',
 		headers: headers,
 		json: options.json || true,
-	}, cb);
+	}, cb)
 }
 
 Server.prototype.auth = function(user, pass, cb) {
-	this.authstr = 'Basic '+(new Buffer(user+':'+pass)).toString('base64');
+	this.authstr = 'Basic '+(new Buffer(user+':'+pass)).toString('base64')
 	this.request({
-		uri: '/-/user/org.couchdb.user:'+escape(user)+'/-rev/undefined',
+		uri: '/-/user/org.couchdb.user:'+encodeURIComponent(user)+'/-rev/undefined',
 		method: 'PUT',
 		json: {
 			content: "doesn't matter, 'cause sinopia uses info from Authorization header anywayz",
 		}
-	}, prep(cb));
+	}, prep(cb))
 }
 
 Server.prototype.get_package = function(name, cb) {
 	this.request({
 		uri: '/'+name,
 		method: 'GET',
-	}, prep(cb));
+	}, prep(cb))
 }
 
 Server.prototype.put_package = function(name, data, cb) {
-	if (typeof(data) === 'object' && !Buffer.isBuffer(data)) data = JSON.stringify(data);
+	if (typeof(data) === 'object' && !Buffer.isBuffer(data)) data = JSON.stringify(data)
 	this.request({
-		uri: '/'+escape(name),
+		uri: '/'+encodeURIComponent(name),
 		method: 'PUT',
 		headers: {
 			'content-type': 'application/json'
 		},
-	}, prep(cb)).end(data);
+	}, prep(cb)).end(data)
 }
 
 Server.prototype.put_version = function(name, version, data, cb) {
-	if (typeof(data) === 'object' && !Buffer.isBuffer(data)) data = JSON.stringify(data);
+	if (typeof(data) === 'object' && !Buffer.isBuffer(data)) data = JSON.stringify(data)
 	this.request({
-		uri: '/'+escape(name)+'/'+escape(version)+'/-tag/latest',
+		uri: '/'+encodeURIComponent(name)+'/'+encodeURIComponent(version)+'/-tag/latest',
 		method: 'PUT',
 		headers: {
 			'content-type': 'application/json'
 		},
-	}, prep(cb)).end(data);
+	}, prep(cb)).end(data)
 }
 
 Server.prototype.get_tarball = function(name, filename, cb) {
 	this.request({
-		uri: '/'+escape(name)+'/-/'+escape(filename),
+		uri: '/'+encodeURIComponent(name)+'/-/'+encodeURIComponent(filename),
 		method: 'GET',
-	}, prep(cb));
+	}, prep(cb))
 }
 
 Server.prototype.put_tarball = function(name, filename, data, cb) {
 	this.request({
-		uri: '/'+escape(name)+'/-/'+escape(filename)+'/whatever',
+		uri: '/'+encodeURIComponent(name)+'/-/'+encodeURIComponent(filename)+'/whatever',
 		method: 'PUT',
 		headers: {
 			'content-type': 'application/octet-stream'
 		},
-	}, prep(cb)).end(data);
+	}, prep(cb)).end(data)
 }
 
 Server.prototype.put_tarball_incomplete = function(name, filename, data, size, cb) {
 	var req = this.request({
-		uri: '/'+escape(name)+'/-/'+escape(filename)+'/whatever',
+		uri: '/'+encodeURIComponent(name)+'/-/'+encodeURIComponent(filename)+'/whatever',
 		method: 'PUT',
 		headers: {
 			'content-type': 'application/octet-stream',
@@ -96,14 +96,14 @@ Server.prototype.put_tarball_incomplete = function(name, filename, data, size, c
 		},
 		timeout: 1000,
 	}, function(err) {
-		assert(err);
-		cb();
-	});
-	req.write(data);
+		assert(err)
+		cb()
+	})
+	req.write(data)
 	setTimeout(function() {
-		req.req.abort();
-	}, 20);
+		req.req.abort()
+	}, 20)
 }
 
-module.exports = Server;
+module.exports = Server
 
diff --git a/test/functional/lib/startup.js b/test/functional/lib/startup.js
new file mode 100644
index 000000000..da5b0eed9
--- /dev/null
+++ b/test/functional/lib/startup.js
@@ -0,0 +1,33 @@
+var rimraf = require('rimraf')
+  , fork = require('child_process').fork
+  , assert = require('assert')
+  , express = require('express')
+  , readfile = require('fs').readFileSync
+  , Server = require('./server')
+
+var forks = process.forks = []
+process.server = new Server('http://localhost:55551/')
+process.server2 = new Server('http://localhost:55552/')
+process.express = express()
+process.express.listen(55550)
+
+module.exports.start = function start(dir, conf, cb) {
+	rimraf(__dirname + '/../' + dir, function() {
+		var f = fork(__dirname + '/../../../bin/sinopia'
+		          , ['-c', __dirname + '/../' + conf]
+		          , {silent: true}
+		)
+		forks.push(f)
+		f.on('message', function(msg) {
+			if ('sinopia_started' in msg) {
+				cb()
+			}
+		})
+	})
+}
+
+process.on('exit', function() {
+	if (forks[0]) forks[0].kill()
+	if (forks[1]) forks[1].kill()
+})
+
diff --git a/test/functional/mirror.js b/test/functional/mirror.js
new file mode 100644
index 000000000..623cfe634
--- /dev/null
+++ b/test/functional/mirror.js
@@ -0,0 +1,92 @@
+var assert = require('assert')
+  , ex = module.exports
+  , server = process.server
+  , server2 = process.server2
+
+function readfile(x) {
+	return require('fs').readFileSync(__dirname + '/' + x)
+}
+
+module.exports = function() {
+	it('testing anti-loop', function(cb) {
+		server2.get_package('testloop', function(res, body) {
+			assert.equal(res.statusCode, 404)
+			assert(~body.error.indexOf('no such package'))
+			cb()
+		})
+	})
+
+	;['fwd', /*'loop'*/].forEach(function(pkg) {
+		var prefix = pkg + ': '
+		pkg = 'test' + pkg
+
+		describe(pkg, function() {
+			before(function(cb) {
+				server.put_package(pkg, require('./lib/package')(pkg), function(res, body) {
+					assert.equal(res.statusCode, 201)
+					assert(~body.ok.indexOf('created new package'))
+					cb()
+				})
+			})
+
+			it(prefix+'creating new package', function(){})
+
+			describe(pkg, function() {
+				before(function(cb) {
+					server.put_version(pkg, '0.1.1', require('./lib/package')(pkg), function(res, body) {
+						assert.equal(res.statusCode, 201)
+						assert(~body.ok.indexOf('published'))
+						cb()
+					})
+				})
+
+				it(prefix+'uploading new package version', function(){})
+
+				it(prefix+'downloading package via server2', function(cb) {
+					server2.get_package(pkg, function(res, body) {
+						assert.equal(res.statusCode, 200)
+						assert.equal(body.name, pkg)
+						assert.equal(body.versions['0.1.1'].name, pkg)
+						assert.equal(body.versions['0.1.1'].dist.tarball, 'http://localhost:55552/'+pkg+'/-/blahblah')
+						cb()
+					})
+				})
+
+				it(prefix+'uploading incomplete tarball', function(cb) {
+					server.put_tarball_incomplete(pkg, pkg+'.bad', readfile('fixtures/binary'), 3000, function(res, body) {
+						cb()
+					})
+				})
+
+				describe('tarball', function() {
+					before(function(cb) {
+						server.put_tarball(pkg, pkg+'.file', readfile('fixtures/binary'), function(res, body) {
+							assert.equal(res.statusCode, 201)
+							assert(body.ok)
+							cb()
+						})
+					})
+
+					it(prefix+'uploading new tarball', function(){})
+				
+					it(prefix+'downloading tarball from server1', function(cb) {
+						server.get_tarball(pkg, pkg+'.file', function(res, body) {
+							assert.equal(res.statusCode, 200)
+							assert.deepEqual(body, readfile('fixtures/binary').toString('utf8'))
+							cb()
+						})
+					})
+
+					it(prefix+'downloading tarball from server2', function(cb) {
+						server2.get_tarball(pkg, pkg+'.file', function(res, body) {
+							assert.equal(res.statusCode, 200)
+							assert.deepEqual(body, readfile('fixtures/binary').toString('utf8'))
+							cb()
+						})
+					})
+				})
+			})
+		})
+	})
+}
+
diff --git a/test/functional/race.js b/test/functional/race.js
new file mode 100644
index 000000000..8946c8280
--- /dev/null
+++ b/test/functional/race.js
@@ -0,0 +1,93 @@
+var assert = require('assert')
+  , readfile = require('fs').readFileSync
+  , ex = module.exports
+  , server = process.server
+  , server2 = process.server2
+  , async = require('async')
+  , _oksum = 0
+
+module.exports = function() {
+	describe('race', function() {
+		before(function(cb) {
+			server.put_package('race', require('./lib/package')('race'), function(res, body) {
+				assert.equal(res.statusCode, 201)
+				assert(~body.ok.indexOf('created new package'))
+				cb()
+			})
+		})
+
+		it('creating new package', function(){})
+
+		it('uploading 10 same versions', function(cb) {
+			var fns = []
+			for (var i=0; i<10; i++) {
+				fns.push(function(cb_) {
+					var data = require('./lib/package')('race')
+					data.rand = Math.random()
+					server.put_version('race', '0.0.1', data, function(res, body) {
+						cb_(null, res, body)
+					})
+				})
+			}
+
+			async.parallel(fns, function(err, res) {
+				var okcount = 0
+				  , failcount = 0
+
+				res.forEach(function(arr) {
+					var resp = arr[0]
+					  , body = arr[1]
+
+					if (resp.statusCode === 201 && ~body.ok.indexOf('published')) okcount++
+					if (resp.statusCode === 409 && ~body.error.indexOf('already present')) failcount++
+					if (resp.statusCode === 503 && ~body.error.indexOf('unavailable')) failcount++
+				})
+				assert.equal(okcount + failcount, 10)
+				assert.equal(okcount, 1)
+				_oksum += okcount
+
+				cb()
+			})
+		})
+
+		it('uploading 10 diff versions', function(cb) {
+			var fns = []
+			for (var i=0; i<10; i++) {
+				;(function(i) {
+					fns.push(function(cb_) {
+						server.put_version('race', '0.1.'+String(i), require('./lib/package')('race'), function(res, body) {
+							cb_(null, res, body)
+						})
+					})
+				})(i)
+			}
+
+			async.parallel(fns, function(err, res) {
+				var okcount = 0
+				  , failcount = 0
+
+				res.forEach(function(arr) {
+					var resp = arr[0]
+					  , body = arr[1]
+					if (resp.statusCode === 201 && ~body.ok.indexOf('published')) okcount++
+					if (resp.statusCode === 409 && ~body.error.indexOf('already present')) failcount++
+					if (resp.statusCode === 503 && ~body.error.indexOf('unavailable')) failcount++
+				})
+				assert.equal(okcount + failcount, 10)
+				_oksum += okcount
+
+				cb()
+			})
+		})
+
+		// XXX: this should be after anything else, but we can't really ensure that with mocha
+		it('downloading package', function(cb) {
+			server.get_package('race', function(res, body) {
+				assert.equal(res.statusCode, 200)
+				assert.equal(Object.keys(body.versions).length, _oksum)
+				cb()
+			})
+		})
+	})
+}
+
diff --git a/test/functional/security.js b/test/functional/security.js
new file mode 100644
index 000000000..7d05e4e58
--- /dev/null
+++ b/test/functional/security.js
@@ -0,0 +1,81 @@
+var assert = require('assert')
+  , ex = module.exports
+  , server = process.server
+  , server2 = process.server2
+
+module.exports = function() {
+	describe('Security', function() {
+		server.get_package('package.json', function(res, body) {
+			assert.equal(res.statusCode, 403)
+			assert(~body.error.indexOf('invalid package'))
+			cb()
+		})
+
+		server.get_package('__proto__', function(res, body) {
+			assert.equal(res.statusCode, 403)
+			assert(~body.error.indexOf('invalid package'))
+			cb()
+		})
+
+		it('__proto__, connect stuff', function(cb) {
+			server.request({uri:'/testpkg?__proto__=1'}, function(err, res, body) {
+				// test for NOT outputting stack trace
+				assert(!body || typeof(body) === 'object' || body.indexOf('node_modules') === -1)
+
+				// test for NOT crashing
+				server.request({uri:'/testpkg'}, function(err, res, body) {
+					assert.equal(res.statusCode, 200)
+					cb()
+				})
+			})
+		})
+
+		it('do not return package.json as an attachment', function(cb) {
+			server.request({uri:'/testpkg/-/package.json'}, function(err, res, body) {
+				assert.equal(res.statusCode, 403)
+				assert(body.error.match(/invalid filename/))
+				cb()
+			})
+		})
+
+		it('silly things - reading #1', function(cb) {
+			server.request({uri:'/testpkg/-/../../../../../../../../etc/passwd'}, function(err, res, body) {
+				assert.equal(res.statusCode, 404)
+				cb()
+			})
+		})
+
+		it('silly things - reading #2', function(cb) {
+			server.request({uri:'/testpkg/-/%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd'}, function(err, res, body) {
+				assert.equal(res.statusCode, 403)
+				assert(body.error.match(/invalid filename/))
+				cb()
+			})
+		})
+
+		it('silly things - writing #1', function(cb) {
+			server.put_tarball('testpkg', 'package.json', '{}', function(res, body) {
+				assert.equal(res.statusCode, 403)
+				assert(body.error.match(/invalid filename/))
+				cb()
+			})
+		})
+
+		it('silly things - writing #3', function(cb) {
+			server.put_tarball('testpkg', 'node_modules', '{}', function(res, body) {
+				assert.equal(res.statusCode, 403)
+				assert(body.error.match(/invalid filename/))
+				cb()
+			})
+		})
+
+		it('silly things - writing #4', function(cb) {
+			server.put_tarball('testpkg', '../testpkg.tgz', '{}', function(res, body) {
+				assert.equal(res.statusCode, 403)
+				assert(body.error.match(/invalid filename/))
+				cb()
+			})
+		})
+	})
+}
+
diff --git a/test/functional/tags.js b/test/functional/tags.js
new file mode 100644
index 000000000..e46360791
--- /dev/null
+++ b/test/functional/tags.js
@@ -0,0 +1,40 @@
+var assert = require('assert')
+  , ex = module.exports
+  , server = process.server
+  , express = process.express
+
+function readfile(x) {
+	return require('fs').readFileSync(__dirname + '/' + x)
+}
+
+module.exports = function() {
+	it('tags - testing for 404', function(cb) {
+		server.get_package('testexp_tags', function(res, body) {
+			// shouldn't exist yet
+			assert.equal(res.statusCode, 404)
+			assert(~body.error.indexOf('no such package'))
+			cb()
+		})
+	})
+
+	describe('tags', function() {
+		before(function(cb) {
+			express.get('/testexp_tags', function(req, res) {
+				res.send(JSON.parse(readfile('fixtures/tags.json')))
+			})
+			cb()
+		})
+
+		it('fetching package again', function(cb) {
+			server.get_package('testexp_tags', function(res, body) {
+				// shouldn't exist yet
+				assert.equal(res.statusCode, 200)
+				assert.equal(typeof(body.versions['1.1']), 'object')
+				assert.equal(body['dist-tags'].something, '0.1.1alpha')
+				assert.equal(body['dist-tags'].latest, '0.1.3alpha')
+				assert.equal(body['dist-tags'].bad, null)
+				cb()
+			})
+		})
+	})
+}
diff --git a/test/gh29.js b/test/gh29.js
deleted file mode 100644
index a433564a1..000000000
--- a/test/gh29.js
+++ /dev/null
@@ -1,57 +0,0 @@
-var assert = require('assert')
-  , readfile = require('fs').readFileSync
-  , crypto = require('crypto')
-  , ex = module.exports
-  , server = process.server
-  , server2 = process.server2
-
-ex['downloading non-existent tarball #1 / srv2'] = function(cb) {
-	server2.get_tarball('testpkg-gh29', 'blahblah', function(res, body) {
-		assert.equal(res.statusCode, 404)
-		assert(~body.error.indexOf('no such package'))
-		cb()
-	})
-}
-
-ex['creating new package / srv1'] = function(cb) {
-	server.put_package('testpkg-gh29', require('./lib/package')('testpkg-gh29'), function(res, body) {
-		assert.equal(res.statusCode, 201)
-		assert(~body.ok.indexOf('created new package'))
-		cb()
-	})
-}
-
-ex['downloading non-existent tarball #2 / srv2'] = function(cb) {
-	server2.get_tarball('testpkg-gh29', 'blahblah', function(res, body) {
-		assert.equal(res.statusCode, 404)
-		assert(~body.error.indexOf('no such file'))
-		cb()
-	})
-}
-
-ex['uploading new tarball / srv1'] = function(cb) {
-	server.put_tarball('testpkg-gh29', 'blahblah', readfile('fixtures/binary'), function(res, body) {
-		assert.equal(res.statusCode, 201)
-		assert(body.ok)
-		cb()
-	})
-}
-
-ex['uploading new package version / srv1'] = function(cb) {
-	var pkg = require('./lib/package')('testpkg-gh29')
-	pkg.dist.shasum = crypto.createHash('sha1').update(readfile('fixtures/binary')).digest('hex')
-	server.put_version('testpkg-gh29', '0.0.1', pkg, function(res, body) {
-		assert.equal(res.statusCode, 201)
-		assert(~body.ok.indexOf('published'))
-		cb()
-	})
-}
-
-ex['downloading newly created tarball / srv2'] = function(cb) {
-	server2.get_tarball('testpkg-gh29', 'blahblah', function(res, body) {
-		assert.equal(res.statusCode, 200)
-		assert.deepEqual(body, readfile('fixtures/binary').toString('utf8'))
-		cb()
-	})
-}
-
diff --git a/test/mirror.js b/test/mirror.js
deleted file mode 100644
index e70d4a7ba..000000000
--- a/test/mirror.js
+++ /dev/null
@@ -1,75 +0,0 @@
-var assert = require('assert')
-  , readfile = require('fs').readFileSync
-  , ex = module.exports
-  , server = process.server
-  , server2 = process.server2
-
-ex['testing anti-loop'] = function(cb) {
-	server2.get_package('testloop', function(res, body) {
-		assert.equal(res.statusCode, 404)
-		assert(~body.error.indexOf('no such package'))
-		cb()
-	})
-}
-
-;['fwd', /*'loop'*/].forEach(function(pkg) {
-	var prefix = pkg + ': '
-	pkg = 'test' + pkg
-
-	ex[prefix+'creating new package'] = function(cb) {
-		server.put_package(pkg, require('./lib/package')(pkg), function(res, body) {
-			assert.equal(res.statusCode, 201)
-			assert(~body.ok.indexOf('created new package'))
-			cb()
-		})
-	}
-
-	ex[prefix+'uploading new package version'] = function(cb) {
-		server.put_version(pkg, '0.1.1', require('./lib/package')(pkg), function(res, body) {
-			assert.equal(res.statusCode, 201)
-			assert(~body.ok.indexOf('published'))
-			cb()
-		})
-	}
-
-	ex[prefix+'downloading package via server2'] = function(cb) {
-		server2.get_package(pkg, function(res, body) {
-			assert.equal(res.statusCode, 200)
-			assert.equal(body.name, pkg)
-			assert.equal(body.versions['0.1.1'].name, pkg)
-			assert.equal(body.versions['0.1.1'].dist.tarball, 'http://localhost:55552/'+pkg+'/-/blahblah')
-			cb()
-		})
-	}
-
-	ex[prefix+'uploading incomplete tarball'] = function(cb) {
-		server.put_tarball_incomplete(pkg, pkg+'.bad', readfile('fixtures/binary'), 3000, function(res, body) {
-			cb()
-		})
-	}
-
-	ex[prefix+'uploading new tarball'] = function(cb) {
-		server.put_tarball(pkg, pkg+'.file', readfile('fixtures/binary'), function(res, body) {
-			assert.equal(res.statusCode, 201)
-			assert(body.ok)
-			cb()
-		})
-	}
-
-	ex[prefix+'downloading tarball from server1'] = function(cb) {
-		server.get_tarball(pkg, pkg+'.file', function(res, body) {
-			assert.equal(res.statusCode, 200)
-			assert.deepEqual(body, readfile('fixtures/binary').toString('utf8'))
-			cb()
-		})
-	}
-
-	ex[prefix+'downloading tarball from server2'] = function(cb) {
-		server2.get_tarball(pkg, pkg+'.file', function(res, body) {
-			assert.equal(res.statusCode, 200)
-			assert.deepEqual(body, readfile('fixtures/binary').toString('utf8'))
-			cb()
-		})
-	}
-})
-
diff --git a/test/race.js b/test/race.js
deleted file mode 100644
index 16665c5e9..000000000
--- a/test/race.js
+++ /dev/null
@@ -1,86 +0,0 @@
-var assert = require('assert')
-  , readfile = require('fs').readFileSync
-  , ex = module.exports
-  , server = process.server
-  , server2 = process.server2
-  , async = require('async')
-  , _oksum = 0
-
-ex['creating new package'] = function(cb) {
-	server.put_package('race', require('./lib/package')('race'), function(res, body) {
-		assert.equal(res.statusCode, 201)
-		assert(~body.ok.indexOf('created new package'))
-		cb()
-	})
-}
-
-ex['uploading 10 same versions'] = function(cb) {
-	var fns = []
-	for (var i=0; i<10; i++) {
-		fns.push(function(cb_) {
-			var data = require('./lib/package')('race')
-			data.rand = Math.random()
-			server.put_version('race', '0.0.1', data, function(res, body) {
-				cb_(null, res, body)
-			})
-		})
-	}
-
-	async.parallel(fns, function(err, res) {
-		var okcount = 0
-		  , failcount = 0
-
-		res.forEach(function(arr) {
-			var resp = arr[0]
-			  , body = arr[1]
-
-			if (resp.statusCode === 201 && ~body.ok.indexOf('published')) okcount++
-			if (resp.statusCode === 409 && ~body.error.indexOf('already present')) failcount++
-			if (resp.statusCode === 503 && ~body.error.indexOf('unavailable')) failcount++
-		})
-		assert.equal(okcount + failcount, 10)
-		assert.equal(okcount, 1)
-		_oksum += okcount
-
-		cb()
-	})
-}
-
-ex['uploading 10 diff versions'] = function(cb) {
-	var fns = []
-	for (var i=0; i<10; i++) {
-		;(function(i) {
-			fns.push(function(cb_) {
-				server.put_version('race', '0.1.'+String(i), require('./lib/package')('race'), function(res, body) {
-					cb_(null, res, body)
-				})
-			})
-		})(i)
-	}
-
-	async.parallel(fns, function(err, res) {
-		var okcount = 0
-		  , failcount = 0
-
-		res.forEach(function(arr) {
-			var resp = arr[0]
-			  , body = arr[1]
-			if (resp.statusCode === 201 && ~body.ok.indexOf('published')) okcount++
-			if (resp.statusCode === 409 && ~body.error.indexOf('already present')) failcount++
-			if (resp.statusCode === 503 && ~body.error.indexOf('unavailable')) failcount++
-		})
-		assert.equal(okcount + failcount, 10)
-		_oksum += okcount
-
-		cb()
-	})
-}
-
-ex['downloading package'] = function(cb) {
-	server.get_package('race', function(res, body) {
-		assert.equal(res.statusCode, 200)
-		assert.equal(Object.keys(body.versions).length, _oksum)
-		cb()
-	})
-}
-
diff --git a/test/start.sh b/test/start.sh
index cbfd4f9de..8f0da7d29 100755
--- a/test/start.sh
+++ b/test/start.sh
@@ -4,7 +4,7 @@ CWD=$(pwd)
 PATH='../node_modules/.bin':$PATH
 TESTDIR=$(dirname $0)
 cd $TESTDIR
-mocha -R list --ui exports ./tests.js ./unit
+mocha ./functional ./unit
 TESTRES=$?
 cd $CWD
 exit $TESTRES
diff --git a/test/startup.js b/test/startup.js
deleted file mode 100644
index 9b1b18acf..000000000
--- a/test/startup.js
+++ /dev/null
@@ -1,42 +0,0 @@
-var rimraf = require('rimraf')
-  , fork = require('child_process').fork
-  , assert = require('assert')
-  , readfile = require('fs').readFileSync
-  , ex = module.exports
-  , server = process.server
-  , server2 = process.server2
-  , forks = process.forks
-
-ex['starting servers'] = function(cb) {
-	var count = 0
-	function start(dir, conf) {
-		count++
-		rimraf(dir, function() {
-			var f = fork('../bin/sinopia'
-			          , ['-c', conf]
-			          , {silent: true}
-			)
-			forks.push(f)
-			f.on('message', function(msg) {
-				if ('sinopia_started' in msg) {
-					if (!--count) cb()
-				}
-			})
-		})
-	}
-
-	start('./test-storage', './config-1.yaml', cb)
-	start('./test-storage2', './config-2.yaml', cb)
-}
-
-ex['authentication to servers'] = function(cb) {
-	var count = 0
-	;[server, server2].forEach(function(server) {
-		count++
-		server.auth('test', 'test', function(res, body) {
-			assert.equal(res.statusCode, 201)
-			assert.notEqual(body.ok.indexOf('"test"'), -1)
-			if (!--count) cb()
-		})
-	})
-}
diff --git a/test/tags.js b/test/tags.js
deleted file mode 100644
index d8c3d0348..000000000
--- a/test/tags.js
+++ /dev/null
@@ -1,34 +0,0 @@
-var assert = require('assert')
-  , ex = module.exports
-  , server = process.server
-  , readfile = require('fs').readFileSync
-  , express = process.express
-
-ex['testing for 404'] = function(cb) {
-	server.get_package('testexp_tags', function(res, body) {
-		// shouldn't exist yet
-		assert.equal(res.statusCode, 404)
-		assert(~body.error.indexOf('no such package'))
-		cb()
-	})
-}
-
-ex['setting up server with bad tags'] = function(cb) {
-	express.get('/testexp_tags', function(req, res) {
-		res.send(JSON.parse(readfile('fixtures/tags.json')))
-	})
-	cb()
-}
-
-ex['fetching package again'] = function(cb) {
-	server.get_package('testexp_tags', function(res, body) {
-		// shouldn't exist yet
-		assert.equal(res.statusCode, 200)
-		assert.equal(typeof(body.versions['1.1']), 'object')
-		assert.equal(body['dist-tags'].something, '0.1.1alpha')
-		assert.equal(body['dist-tags'].latest, '0.1.3alpha')
-		assert.equal(body['dist-tags'].bad, null)
-		cb()
-	})
-}
-
diff --git a/test/tests.js b/test/tests.js
deleted file mode 100644
index 4710c66ba..000000000
--- a/test/tests.js
+++ /dev/null
@@ -1,27 +0,0 @@
-var fs = require('fs')
-  , async = require('async')
-  , assert = require('assert')
-  , Server = require('./lib/server')
-  , readfile = require('fs').readFileSync
-  , express = require('express')
-  , ex = module.exports
-
-var forks = process.forks = []
-process.server = new Server('http://localhost:55551/')
-process.server2 = new Server('http://localhost:55552/')
-process.express = express()
-
-process.express.listen(55550)
-
-ex['Startup:'] = require('./startup')
-ex['Basic:'] = require('./basic')
-ex['Mirror:'] = require('./mirror')
-ex['Race:'] = require('./race')
-ex['Tags:'] = require('./tags')
-ex['GH29:'] = require('./gh29')
-
-process.on('exit', function() {
-	if (forks[0]) forks[0].kill()
-	if (forks[1]) forks[1].kill()
-})
-
diff --git a/test/unit/no_proxy.js b/test/unit/no_proxy.js
index 1bcbbb301..3bd636d32 100644
--- a/test/unit/no_proxy.js
+++ b/test/unit/no_proxy.js
@@ -8,18 +8,18 @@ function setup(host, config, mainconfig) {
 	return new Storage(config, mainconfig)
 }
 
-exports['Use proxy'] = {
-	'should work fine without proxy': function() {
+describe('Use proxy', function() {
+	it('should work fine without proxy', function() {
 		var x = setup('http://x/x', {}, {})
 		assert.equal(x.proxy, null)
-	},
+	})
 
-	'local config should take priority': function() {
+	it('local config should take priority', function() {
 		var x = setup('http://x/x', {http_proxy: '123'}, {http_proxy: '456'})
 		assert.equal(x.proxy, '123')
-	},
+	})
 
-	'no_proxy is invalid': function() {
+	it('no_proxy is invalid', function() {
 		var x = setup('http://x/x', {http_proxy: '123', no_proxy: false}, {})
 		assert.equal(x.proxy, '123')
 		var x = setup('http://x/x', {http_proxy: '123', no_proxy: null}, {})
@@ -28,19 +28,19 @@ exports['Use proxy'] = {
 		assert.equal(x.proxy, '123')
 		var x = setup('http://x/x', {http_proxy: '123', no_proxy: ''}, {})
 		assert.equal(x.proxy, '123')
-	},
+	})
 
-	'no_proxy - simple/include': function() {
+	it('no_proxy - simple/include', function() {
 		var x = setup('http://localhost', {http_proxy: '123'}, {no_proxy: 'localhost'})
 		assert.equal(x.proxy, undefined)
-	},
+	})
 
-	'no_proxy - simple/not': function() {
+	it('no_proxy - simple/not', function() {
 		var x = setup('http://localhost', {http_proxy: '123'}, {no_proxy: 'blah'})
 		assert.equal(x.proxy, '123')
-	},
+	})
 
-	'no_proxy - various, single string': function() {
+	it('no_proxy - various, single string', function() {
 		var x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: 'blah'})
 		assert.equal(x.proxy, '123')
 		var x = setup('http://blah.blah', {}, {http_proxy: '123', no_proxy: 'blah'})
@@ -53,9 +53,9 @@ exports['Use proxy'] = {
 		assert.equal(x.proxy, null)
 		var x = setup('http://blahh', {http_proxy: '123', no_proxy: 'blah'},  {})
 		assert.equal(x.proxy, '123')
-	},
+	})
 
-	'no_proxy - various, array': function() {
+	it('no_proxy - various, array', function() {
 		var x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'})
 		assert.equal(x.proxy, '123')
 		var x = setup('http://blah.blah', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'})
@@ -68,21 +68,21 @@ exports['Use proxy'] = {
 		assert.equal(x.proxy, '123')
 		var x = setup('http://blah.blah', {http_proxy: '123'}, {no_proxy: ['foo','bar','blah']})
 		assert.equal(x.proxy, null)
-	},
+	})
 
-	'no_proxy - hostport': function() {
+	it('no_proxy - hostport', function() {
 		var x = setup('http://localhost:80', {http_proxy: '123'}, {no_proxy: 'localhost'})
 		assert.equal(x.proxy, null)
 		var x = setup('http://localhost:8080', {http_proxy: '123'}, {no_proxy: 'localhost'})
 		assert.equal(x.proxy, null)
-	},
+	})
 
-	'no_proxy - secure': function() {
+	it('no_proxy - secure', function() {
 		var x = setup('https://something', {http_proxy: '123'}, {})
 		assert.equal(x.proxy, null)
 		var x = setup('https://something', {https_proxy: '123'}, {})
 		assert.equal(x.proxy, '123')
 		var x = setup('https://something', {http_proxy: '456', https_proxy: '123'}, {})
 		assert.equal(x.proxy, '123')
-	},
-}
+	})
+})
diff --git a/test/unit/st_merge.js b/test/unit/st_merge.js
index 35b78e178..a52a8ccc7 100644
--- a/test/unit/st_merge.js
+++ b/test/unit/st_merge.js
@@ -4,8 +4,8 @@ var assert = require('assert')
 
 //require('../lib/logger').setup()
 
-exports['Merge'] = {
-	'simple': function() {
+describe('Merge', function() {
+	it('simple', function() {
 		var x = {
 			versions: {a:1,b:1,c:1},
 			'dist-tags': {},
@@ -15,9 +15,9 @@ exports['Merge'] = {
 			versions: {a:1,b:1,c:1,q:2},
 			'dist-tags': {},
 		})
-	},
+	})
 
-	'dist-tags - compat': function() {
+	it('dist-tags - compat', function() {
 		var x = {
 			versions: {},
 			'dist-tags': {q:'1.1.1',w:['2.2.2']},
@@ -27,9 +27,9 @@ exports['Merge'] = {
 			versions: {},
 			'dist-tags': {q:['1.1.1','2.2.2'],w:['2.2.2','3.3.3'],t:['4.4.4']},
 		})
-	},
+	})
 
-	'dist-tags - sort': function() {
+	it('dist-tags - sort', function() {
 		var x = {
 			versions: {},
 			'dist-tags': {w:['2.2.2','1.1.1','12.2.2','2.2.2-rc2']},
@@ -39,15 +39,15 @@ exports['Merge'] = {
 			versions: {},
 			'dist-tags': {w:["1.1.1","2.2.2-rc2","2.2.2","3.3.3","12.2.2"]},
 		})
-	},
+	})
 
-	'semver_sort': function() {
+	it('semver_sort', function() {
 		assert.deepEqual(semver_sort(['1.2.3','1.2','1.2.3a','1.2.3c','1.2.3-b']),
 		[ '1.2.3a',
 		  '1.2.3-b',
 		  '1.2.3c',
 		  '1.2.3' ]
 		)
-	},
-}
+	})
+})
 
diff --git a/test/unit/transaction.js b/test/unit/transaction.js
index 79ea1a58f..a44b3151f 100644
--- a/test/unit/transaction.js
+++ b/test/unit/transaction.js
@@ -36,39 +36,53 @@ function test(uplinks, cb) {
 	)
 }
 
-// everything is fine
-test([true, true, true, true, true], function(err, calls) {
-	assert.deepEqual(err, undefined)
-	assert.deepEqual(calls, [ 'l', 'r0', 'r1', 'r2', 'r3' ])
-})
-
-// local throws errors - don't call remotes
-test([false, true, true, true, true], function(err, calls) {
-	assert.deepEqual(err, 'l')
-	assert.deepEqual(calls, ['l'])
-})
-
-// remote fails, call all rollbacks
-test([true, true, true, false, true], function(err, calls) {
-	assert.deepEqual(err, 'r2')
-	assert.deepEqual(calls, [ 'l', 'r0', 'r1', 'r2', 'r3', 'rb0', 'rb1', 'rb3', 'lb' ])
-})
-
-// no remotes
-test([true], function(err, calls) {
-	assert.deepEqual(err, undefined)
-	assert.deepEqual(calls, [ 'l' ])
-})
-
-// all remotes fail
-test([true, false, false, false, false], function(err, calls) {
-	assert.deepEqual(err, 'r0')
-	assert.deepEqual(calls, [ 'l', 'r0', 'r1', 'r2', 'r3', 'lb' ])
-})
-
-// mix
-test([true, true, false, true, false], function(err, calls) {
-	assert.deepEqual(err, 'r1')
-	assert.deepEqual(calls, [ 'l', 'r0', 'r1', 'r2', 'r3', 'rb0', 'rb2', 'lb' ])
+describe('Transaction', function() {
+	it('everything is fine', function(cb) {
+		test([true, true, true, true, true], function(err, calls) {
+			assert.deepEqual(err, undefined)
+			assert.deepEqual(calls, [ 'l', 'r0', 'r1', 'r2', 'r3' ])
+			cb()
+		})
+	})
+
+	it("local throws errors - don't call remotes", function(cb) {
+		test([false, true, true, true, true], function(err, calls) {
+			assert.deepEqual(err, 'l')
+			assert.deepEqual(calls, ['l'])
+			cb()
+		})
+	})
+
+	it('remote fails, call all rollbacks', function(cb) {
+		test([true, true, true, false, true], function(err, calls) {
+			assert.deepEqual(err, 'r2')
+			assert.deepEqual(calls, [ 'l', 'r0', 'r1', 'r2', 'r3', 'rb0', 'rb1', 'rb3', 'lb' ])
+			cb()
+		})
+	})
+
+	it('no remotes', function(cb) {
+		test([true], function(err, calls) {
+			assert.deepEqual(err, undefined)
+			assert.deepEqual(calls, [ 'l' ])
+			cb()
+		})
+	})
+
+	it('all remotes fail', function(cb) {
+		test([true, false, false, false, false], function(err, calls) {
+			assert.deepEqual(err, 'r0')
+			assert.deepEqual(calls, [ 'l', 'r0', 'r1', 'r2', 'r3', 'lb' ])
+			cb()
+		})
+	})
+
+	it('mix', function(cb) {
+		test([true, true, false, true, false], function(err, calls) {
+			assert.deepEqual(err, 'r1')
+			assert.deepEqual(calls, [ 'l', 'r0', 'r1', 'r2', 'r3', 'rb0', 'rb2', 'lb' ])
+			cb()
+		})
+	})
 })
 
diff --git a/test/unit/utils.js b/test/unit/utils.js
index fcd7bd010..e69d8db3e 100644
--- a/test/unit/utils.js
+++ b/test/unit/utils.js
@@ -1,42 +1,42 @@
 var assert = require('assert')
   , validate = require('../../lib/utils').validate_name
 
-exports['Validate'] = {
-	'good ones': function() {
+describe('Validate', function() {
+	it('good ones', function() {
 		assert(validate('sinopia'))
 		assert(validate('some.weird.package-zzz'))
-	},
+	})
 
-	'uppercase': function() {
+	it('uppercase', function() {
 		assert(validate('EVE'))
 		assert(validate('JSONStream'))
-	},
+	})
 
-	'no package.json': function() {
+	it('no package.json', function() {
 		assert(!validate('package.json'))
-	},
+	})
 
-	'no path seps': function() {
+	it('no path seps', function() {
 		assert(!validate('some/thing'))
 		assert(!validate('some\\thing'))
-	},
+	})
 	
-	'no hidden': function() {
+	it('no hidden', function() {
 		assert(!validate('.bin'))
-	},
+	})
 	
-	'no reserved': function() {
+	it('no reserved', function() {
 		assert(!validate('favicon.ico'))
 		assert(!validate('node_modules'))
 		assert(!validate('__proto__'))
-	},
+	})
 
-	'other': function() {
+	it('other', function() {
 		assert(!validate('pkg@'))
 		assert(!validate('pk g'))
 		assert(!validate('pk\tg'))
 		assert(!validate('pk%20g'))
 		assert(!validate('pk+g'))
 		assert(!validate('pk:g'))
-	},
-}
+	})
+})