add module controller test cases; fix next module not exists logic bug.

This commit is contained in:
fengmk2
2013-12-05 21:33:44 +08:00
parent 295122e8e3
commit 3cef86678f
12 changed files with 348 additions and 94 deletions

View File

@@ -1,5 +1,4 @@
language: node_js language: node_js
node_js: node_js:
- '0.10' - '0.10'
- '0.8'
script: make test-coveralls script: make test-coveralls

View File

@@ -28,4 +28,3 @@ test-coveralls: test
test-all: test test-cov test-all: test test-cov
.PHONY: test .PHONY: test

View File

@@ -1,7 +1,7 @@
cnpmjs.org cnpmjs.org
======= =======
[![Build Status](https://secure.travis-ci.org/fengmk2/cnpmjs.org.png)](http://travis-ci.org/fengmk2/cnpmjs.org) [![Coverage Status](https://coveralls.io/repos/fengmk2/cnpmjs.org/badge.png)](https://coveralls.io/r/fengmk2/cnpmjs.org) [![Build Status](https://drone.io/github.com/fengmk2/cnpmjs.org/status.png)](https://drone.io/github.com/fengmk2/cnpmjs.org/latest) [![Build Status](https://secure.travis-ci.org/fengmk2/cnpmjs.org.png)](http://travis-ci.org/fengmk2/cnpmjs.org) [![Coverage Status](https://coveralls.io/repos/fengmk2/cnpmjs.org/badge.png)](https://coveralls.io/r/fengmk2/cnpmjs.org)
[![NPM](https://nodei.co/npm/cnpmjs.org.png?downloads=true&stars=true)](https://nodei.co/npm/cnpmjs.org/) [![NPM](https://nodei.co/npm/cnpmjs.org.png?downloads=true&stars=true)](https://nodei.co/npm/cnpmjs.org/)

View File

@@ -19,6 +19,7 @@ var debug = require('debug')('cnpmjs.org:controllers:registry:module');
var path = require('path'); var path = require('path');
var fs = require('fs'); var fs = require('fs');
var crypto = require('crypto'); var crypto = require('crypto');
var eventproxy = require('eventproxy');
var config = require('../../config'); var config = require('../../config');
var Module = require('../../proxy/module'); var Module = require('../../proxy/module');
@@ -28,40 +29,43 @@ exports.show = function (req, res, next) {
if (err || rows.length === 0) { if (err || rows.length === 0) {
return next(err); return next(err);
} }
var latest; var nextMod = rows[0];
for (var i = 0; i < rows.length; i++) { var latest = rows[1];
var row = rows[i]; var startIndex = 1;
if (row.version === 'latest') { if (nextMod.version !== 'next') {
latest = row; // next create fail
break; latest = nextMod;
} startIndex = 0;
nextMod = null;
} }
if (!latest) { if (!latest) {
return next(); latest = nextMod;
} }
var distTags = {}; var distTags = {};
var versions = {}; var versions = {};
var times = {}; var times = {};
var attachments = {}; var attachments = {};
for (var i = 0; i < rows.length; i++) { for (var i = startIndex; i < rows.length; i++) {
var row = rows[i]; var row = rows[i];
var pkg = row.package; var pkg = row.package;
if (!pkg.version || pkg.version === 'latest') {
continue;
}
versions[pkg.version] = pkg; versions[pkg.version] = pkg;
times[pkg.version] = row.gmt_modified; times[pkg.version] = row.gmt_modified;
} }
if (latest.package.version || latest.package.version !== 'init') { if (latest.package.version && latest.package.version !== 'next') {
distTags['latest'] = latest.package.version; distTags.latest = latest.package.version;
}
var rev = '';
if (nextMod) {
rev = String(nextMod.id);
} }
var info = { var info = {
_id: latest.name, _id: latest.name,
_rev: String(latest.id), _rev: rev,
name: latest.name, name: latest.name,
description: latest.package.description, description: latest.package.description,
versions: versions, versions: versions,
@@ -88,6 +92,9 @@ exports.upload = function (req, res, next) {
var name = req.params.name; var name = req.params.name;
var id = Number(req.params.rev); var id = Number(req.params.rev);
var filename = req.params.filename; var filename = req.params.filename;
var version = filename.substring(filename.indexOf('-') + 1);
version = version.replace(/\.tgz$/, '');
// save version on pkg upload
debug('%s: upload %s, file size: %d', username, req.url, length); debug('%s: upload %s, file size: %d', username, req.url, length);
Module.getById(id, function (err, mod) { Module.getById(id, function (err, mod) {
@@ -98,12 +105,20 @@ exports.upload = function (req, res, next) {
return item.name === username; return item.name === username;
}); });
if (match.length === 0 || mod.name !== name) { if (match.length === 0 || mod.name !== name) {
return res.json(401, { return res.json(403, {
error: 'noperms', error: 'no_perms',
reason: 'Current user can not publish this module' reason: 'Current user can not publish this module'
}); });
} }
if (mod.version !== 'next') {
// rev wrong
return res.json(403, {
error: 'rev_wrong',
reason: 'rev not match next module'
});
}
var filepath = path.join(config.uploadDir, filename); var filepath = path.join(config.uploadDir, filename);
var ws = fs.createWriteStream(filepath); var ws = fs.createWriteStream(filepath);
var shasum = crypto.createHash('sha1'); var shasum = crypto.createHash('sha1');
@@ -115,8 +130,8 @@ exports.upload = function (req, res, next) {
}); });
ws.on('finish', function () { ws.on('finish', function () {
if (dataSize !== length) { if (dataSize !== length) {
return res.json(401, { return res.json(403, {
error: 'wrongsize', error: 'size_wrong',
reason: 'Header size ' + length + ' not match download size ' + dataSize, reason: 'Header size ' + length + ' not match download size ' + dataSize,
}); });
} }
@@ -127,12 +142,14 @@ exports.upload = function (req, res, next) {
size: length size: length
}; };
mod.package.dist = dist; mod.package.dist = dist;
debug('%s module: save file to %s, size: %d, sha1: %s, dist: %j', id, filepath, length, shasum, dist); mod.package.version = version;
debug('%s module: save file to %s, size: %d, sha1: %s, dist: %j, version: %s',
id, filepath, length, shasum, dist, version);
Module.update(mod, function (err, result) { Module.update(mod, function (err, result) {
if (err) { if (err) {
return next(err); return next(err);
} }
res.json(201, {ok: true, rev: String(result.id), date: result.gmt_modified}); res.json(201, {ok: true, rev: String(result.id)});
}); });
}); });
}); });
@@ -142,14 +159,14 @@ exports.updateLatest = function (req, res, next) {
var username = req.session.name; var username = req.session.name;
var name = req.params.name; var name = req.params.name;
var version = req.params.version; var version = req.params.version;
Module.get(name, 'latest', function (err, mod) { Module.get(name, 'next', function (err, nextMod) {
if (err) { if (err) {
return next(err); return next(err);
} }
if (!mod) { if (!nextMod) {
return next(); return next();
} }
var match = mod.package.maintainers.filter(function (item) { var match = nextMod.package.maintainers.filter(function (item) {
return item.name === username; return item.name === username;
}); });
if (match.length === 0) { if (match.length === 0) {
@@ -159,32 +176,39 @@ exports.updateLatest = function (req, res, next) {
}); });
} }
var body = req.body; // check version if not match pkg upload
if (nextMod.package.version !== version) {
return res.json(403, {
error: 'version_wrong',
reason: 'version not match'
});
}
mod.version = version; var body = req.body;
mod.author = username; nextMod.version = version;
body.dist = mod.package.dist; nextMod.author = username;
body.maintainers = mod.package.maintainers; body.dist = nextMod.package.dist;
body.maintainers = nextMod.package.maintainers;
if (!body.author) { if (!body.author) {
body.author = { body.author = {
name: username, name: username,
email: req.session.email, email: req.session.email,
}; };
} }
mod.package = body; nextMod.package = body;
debug('update %s:%s %j', mod.package.name, mod.package.version, mod.package.dist); debug('update %s:%s %j', nextMod.package.name, nextMod.package.version, nextMod.package.dist);
// change latest to version // change latest to version
Module.update(mod, function (err) { Module.update(nextMod, function (err) {
if (err) { if (err) {
return next(err); return next(err);
} }
// add a new latest version // add a new latest version
mod.version = 'latest'; nextMod.version = 'next';
Module.add(mod, function (err, result) { Module.add(nextMod, function (err, result) {
if (err) { if (err) {
return next(err); return next(err);
} }
res.json(201, {ok: true, rev: String(result.id), date: result.gmt_modified}); res.json(201, {ok: true, rev: String(result.id)});
}); });
}); });
}); });
@@ -199,59 +223,65 @@ exports.add = function (req, res, next) {
return item.name === username; return item.name === username;
}); });
if (match.length === 0) { if (match.length === 0) {
return res.json(401, { return res.json(403, {
error: 'noperms', error: 'no_perms',
reason: 'Current user can not publish this module' reason: 'Current user can not publish this module'
}); });
} }
Module.get(name, 'latest', function (err, mod) { var ep = eventproxy.create();
if (err) { ep.fail(next);
return next(err);
Module.getLatest(name, ep.doneLater('latest'));
Module.get(name, 'next', ep.done(function (nextMod) {
if (nextMod) {
nextMod.exists = true;
return ep.emit('next', nextMod);
}
// ensure next module exits
// because updateLatest will create next module fail
nextMod = {
name: name,
version: 'next',
author: username,
package: {
name: name,
version: 'next',
description: pkg.description,
readme: pkg.readme,
maintainers: pkg.maintainers,
},
};
Module.add(nextMod, ep.done(function (result) {
nextMod.id = result.id;
ep.emit('next', nextMod);
}));
}));
ep.all('latest', 'next', function (latestMod, nextMod) {
var maintainers = latestMod ? latestMod.package.maintainers : nextMod.package.maintainers;
var match = maintainers.filter(function (item) {
return item.name === username;
});
if (match.length === 0) {
return res.json(403, {
error: 'no_perms',
reason: 'Current user can not publish this module'
});
} }
if (mod) { if (latestMod || nextMod.exists) {
match = mod.package.maintainers.filter(function (item) {
return item.name === username;
});
if (match.length === 0) {
return res.json(401, {
error: 'noperms',
reason: 'Current user can not publish this module'
});
}
return res.json(409, { return res.json(409, {
error: 'conflict', error: 'conflict',
reason: 'Document update conflict.' reason: 'Document update conflict.'
}); });
} }
mod = { res.json(201, {
name: name, ok: true,
version: 'latest', id: name,
author: username, rev: String(nextMod.id),
package: {
name: name,
version: 'init',
description: pkg.description,
readme: pkg.readme,
maintainers: pkg.maintainers,
author: {
name: username,
email: req.session.email,
}
},
};
Module.add(mod, function (err, result) {
if (err) {
return next(err);
}
res.json(201, {
ok: true,
id: name,
rev: String(result.id),
});
}); });
}); });
}; };

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -20,19 +20,19 @@
"debug": "0.7.4", "debug": "0.7.4",
"utility": "0.1.8", "utility": "0.1.8",
"ready": "0.1.1", "ready": "0.1.1",
"connect": "~2.11.0", "connect": "2.11.2",
"connect-rt": "~0.0.2", "connect-rt": "0.0.2",
"connect-redis": "~1.4.6", "connect-redis": "1.4.6",
"urlrouter": "~0.5.3", "urlrouter": "0.5.3",
"graceful": "~0.0.5", "graceful": "0.0.5",
"moment": "~2.4.0", "moment": "2.4.0",
"logfilestream": "~0.1.0", "logfilestream": "0.1.0",
"ms": "~0.6.1", "ms": "0.6.1",
"mkdirp": "~0.3.5", "mkdirp": "0.3.5",
"mysql": "2.0.0-rc1", "mysql": "2.0.0-rc1",
"response-patch": "~0.1.1", "response-patch": "0.1.1",
"response-cookie": "~0.0.2", "response-cookie": "0.0.2",
"eventproxy": "~0.2.6" "eventproxy": "0.2.6"
}, },
"devDependencies": { "devDependencies": {
"supertest": "*", "supertest": "*",

View File

@@ -101,7 +101,7 @@ exports.get = function (name, version, callback) {
}); });
}; };
var SELECT_LATEST_MODULE_SQL = 'SELECT ' + MODULE_COLUMNS + ' FROM module WHERE name=? ORDER BY id DESC LIMIT 1;'; var SELECT_LATEST_MODULE_SQL = 'SELECT ' + MODULE_COLUMNS + ' FROM module WHERE name=? AND version <> "next" ORDER BY id DESC LIMIT 1;';
exports.getLatest = function (name, callback) { exports.getLatest = function (name, callback) {
mysql.queryOne(SELECT_LATEST_MODULE_SQL, [name], function (err, row) { mysql.queryOne(SELECT_LATEST_MODULE_SQL, [name], function (err, row) {
@@ -138,3 +138,9 @@ exports.listByName = function (name, callback) {
callback(err, rows); callback(err, rows);
}); });
}; };
var DELETE_MODULE_BY_NAME_SQL = 'DELETE FROM module WHERE name=?;';
exports.removeByName = function (name, callback) {
mysql.query(DELETE_MODULE_BY_NAME_SQL, [name], callback);
};

View File

@@ -27,12 +27,13 @@ function routes(app) {
// module // module
app.get('/:name', mod.show); app.get('/:name', mod.show);
// try to add module
app.put('/:name', login, mod.add); app.put('/:name', login, mod.add);
// put tarball // put tarball
// https://registry.npmjs.org/cnpmjs.org/-/cnpmjs.org-0.0.0.tgz/-rev/1-c85bc65e8d2470cc4d82b8f40da65b8e // https://registry.npmjs.org/cnpmjs.org/-/cnpmjs.org-0.0.0.tgz/-rev/1-c85bc65e8d2470cc4d82b8f40da65b8e
app.put('/:name/-/:filename/-rev/:rev', login, mod.upload); app.put('/:name/-/:filename/-rev/:rev', login, mod.upload);
// tag // put package.json to module
app.put('/:name/:version/-tag/latest', login, mod.updateLatest); app.put('/:name/:version/-tag/latest', login, mod.updateLatest);
// try to create a new user // try to create a new user

View File

@@ -50,6 +50,10 @@ app.use(auth());
app.use(urlrouter(routes)); app.use(urlrouter(routes));
app.use(function (req, res, next) {
res.json(404, {error: 'not_found', reason: 'document not found'});
});
/** /**
* Error handler * Error handler
*/ */

View File

@@ -14,9 +14,14 @@
* Module dependencies. * Module dependencies.
*/ */
var fs = require('fs');
var path = require('path');
var should = require('should'); var should = require('should');
var request = require('supertest'); var request = require('supertest');
var app = require('../../../servers/registry'); var app = require('../../../servers/registry');
var Module = require('../../../proxy/module');
var fixtures = path.join(path.dirname(path.dirname(__dirname)), 'fixtures');
describe('controllers/registry/module.test.js', function () { describe('controllers/registry/module.test.js', function () {
before(function (done) { before(function (done) {
@@ -45,4 +50,213 @@ describe('controllers/registry/module.test.js', function () {
}); });
}); });
}); });
describe('PUT /:name', function () {
var pkg = {
name: 'testputmodule',
description: 'test put module',
readme: 'readme text',
maintainers: [{
name: 'cnpmjstest10',
email: 'cnpmjstest10@cnpmjs.org'
}],
};
var baseauth = 'Basic ' + new Buffer('cnpmjstest10:cnpmjstest10').toString('base64');
var baseauthOther = 'Basic ' + new Buffer('cnpmjstest101:cnpmjstest101').toString('base64');
var lastRev;
before(function (done) {
// clean up testputmodule
Module.removeByName('testputmodule', done);
});
it('should try to add not exists module return 201', function (done) {
request(app)
.put('/' + pkg.name)
.set('authorization', baseauth)
.send(pkg)
.expect(201, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('ok', 'id', 'rev');
res.body.ok.should.equal(true);
res.body.id.should.equal(pkg.name);
res.body.rev.should.be.a.String;
done();
});
});
it('should try to add return 409 when only next module exists', function (done) {
request(app)
.put('/' + pkg.name)
.set('authorization', baseauth)
.send(pkg)
.expect(409, function (err, res) {
should.not.exist(err);
res.body.should.eql({
error: 'conflict',
reason: 'Document update conflict.'
});
done();
});
});
it('should try to add return 403 when not module user and only next module exists', function (done) {
request(app)
.put('/' + pkg.name)
.set('authorization', baseauthOther)
.send(pkg)
.expect(403, function (err, res) {
should.not.exist(err);
res.body.should.eql({
error: 'no_perms',
reason: 'Current user can not publish this module'
});
done();
});
});
it('should get versions empty when only next module exists', function (done) {
request(app)
.get('/' + pkg.name)
.expect(200, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('_id', '_rev', 'name', 'description', 'versions', 'dist-tags',
'readme', 'maintainers', 'time', '_attachments');
res.body.versions.should.eql({});
res.body.time.should.eql({});
res.body['dist-tags'].should.eql({});
lastRev = res.body._rev;
console.log('lastRev: %s', lastRev);
done();
});
});
it('should upload tarball success: /:name/-/:filename/-rev/:rev', function (done) {
var body = fs.readFileSync(path.join(fixtures, 'testputmodule-0.1.9.tgz'));
request(app)
.put('/' + pkg.name + '/-/' + pkg.name + '-0.1.9.tgz/-rev/' + lastRev)
.set('authorization', baseauth)
.set('content-type', 'application/octet-stream')
.set('content-length', '' + body.length)
.send(body)
.expect(201, function (err, res) {
should.not.exist(err);
res.body.should.eql({
ok: true,
rev: lastRev,
});
done();
});
});
it('should upload tarball success again: /:name/-/:filename/-rev/:rev', function (done) {
var body = fs.readFileSync(path.join(fixtures, 'testputmodule-0.1.9.tgz'));
request(app)
.put('/' + pkg.name + '/-/' + pkg.name + '-0.1.9.tgz/-rev/' + lastRev)
.set('authorization', baseauth)
.set('content-type', 'application/octet-stream')
.set('content-length', '' + body.length)
.send(body)
.expect(201, function (err, res) {
should.not.exist(err);
res.body.should.eql({
ok: true,
rev: lastRev,
});
done();
});
});
// it('should upload tarball fail 403 when header size not match body size', function (done) {
// var body = fs.readFileSync(path.join(fixtures, 'testputmodule-0.1.9.tgz'));
// request(app)
// .put('/' + pkg.name + '/-/' + pkg.name + '-0.1.9.tgz/-rev/' + lastRev)
// .set('authorization', baseauth)
// .set('content-type', 'application/octet-stream')
// .set('content-length', '' + (body.length + 1))
// .send(body)
// .expect(404, function (err, res) {
// should.not.exist(err);
// res.body.should.eql({
// error: 'size_wrong',
// reason: 'document not found'
// });
// done();
// });
// });
it('should upload tarball fail 403 when rev not match current module', function (done) {
var body = fs.readFileSync(path.join(fixtures, 'testputmodule-0.1.9.tgz'));
request(app)
.put('/' + pkg.name + '/-/' + pkg.name + '-0.1.9.tgz/-rev/25')
.set('authorization', baseauth)
.set('content-type', 'application/octet-stream')
.set('content-length', '' + body.length)
.send(body)
.expect(403, function (err, res) {
should.not.exist(err);
res.body.should.eql({
error: 'no_perms',
reason: 'Current user can not publish this module'
});
done();
});
});
it('should upload tarball fail 404 when rev wrong', function (done) {
var body = fs.readFileSync(path.join(fixtures, 'testputmodule-0.1.9.tgz'));
request(app)
.put('/' + pkg.name + '/-/' + pkg.name + '-0.1.9.tgz/-rev/' + lastRev + '1')
.set('authorization', baseauth)
.set('content-type', 'application/octet-stream')
.set('content-length', '' + body.length)
.send(body)
.expect(404, function (err, res) {
should.not.exist(err);
res.body.should.eql({
error: 'not_found',
reason: 'document not found'
});
done();
});
});
it('should update package.json info success: /:name/:version/-tag/latest', function (done) {
var pkg = require(path.join(fixtures, 'testputmodule.json')).versions['0.1.8'];
pkg.name = 'testputmodule';
pkg.version = '0.1.9';
request(app)
.put('/' + pkg.name + '/' + pkg.version + '/-tag/latest')
.set('authorization', baseauth)
.send(pkg)
.expect(201, function (err, res) {
should.not.exist(err);
res.body.should.eql({
ok: true,
rev: Number(lastRev) + 1
});
done();
});
});
it('should update package.json info again fail 403: /:name/:version/-tag/latest', function (done) {
var pkg = require(path.join(fixtures, 'testputmodule.json')).versions['0.1.8'];
pkg.name = 'testputmodule';
pkg.version = '0.1.10';
request(app)
.put('/' + pkg.name + '/' + pkg.version + '/-tag/latest')
.set('authorization', baseauth)
.send(pkg)
.expect(403, function (err, res) {
should.not.exist(err);
res.body.should.eql({
error: 'version_wrong',
reason: 'version not match'
});
done();
});
});
});
}); });

BIN
test/fixtures/testputmodule-0.1.9.tgz vendored Normal file

Binary file not shown.

1
test/fixtures/testputmodule.json vendored Normal file

File diff suppressed because one or more lines are too long