diff --git a/package.json b/package.json index 864d558..c23dd4e 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,9 @@ "mocha": "latest", "chai": "latest", "debug": "~0.7.2", - "ini": "~1.1.0" + "ini": "~1.1.0", + "proxyquire": "~0.5.1", + "sinon": "~1.7.3" }, "optionalDependencies": {}, "engines": { diff --git a/server.js b/server.js index b48f75b..1dd1550 100644 --- a/server.js +++ b/server.js @@ -159,10 +159,10 @@ server.get('/:name', function (req, res) { var meta = data.packageMeta(packageName); if (!meta) return notFound(res); - var versions = data.whichVersions(packageName).sort(); + var versions = getPackageVersions(packageName); var versionsData = {}; var times = {}; - versions.forEach(function(v) { + versions.all.forEach(function(v) { versionsData[v] = meta.versions[v].data; times[v] = meta.versions[v].time; }); @@ -173,7 +173,7 @@ server.get('/:name', function (req, res) { name: meta.name, description: meta.description, 'dist-tags': { - latest: versions[versions.length-1] + latest: versions.latest }, versions: versionsData, maintainers: [], @@ -210,11 +210,10 @@ function listAction(req, res) { res.json(200, result); function getPackageInfo(packageName) { - var versions = data.whichVersions(packageName).sort(); + var versions = getPackageVersions(packageName); var meta = data.packageMeta(packageName); - var lastVersion = versions[versions.length-1]; var versionsData = {}; - versions.forEach(function(v) { + versions.all.forEach(function(v) { versionsData[v] = 'latest'; }); @@ -223,19 +222,27 @@ function listAction(req, res) { name: meta.name, description: meta.description, 'dist-tags': { - latest: lastVersion + latest: versions.latest }, versions: versionsData, maintainers: [], author: meta.author, repository: meta.repository, time: { - modified: meta.versions[lastVersion].time + modified: meta.versions[versions.latest].time } }; } } +function getPackageVersions(packageName) { + var versions = data.whichVersions(packageName).sort(semver.compare); + return { + all: versions, + latest: semver.maxSatisfying(versions, '*') + }; +} + server.put('/:name/-/:filename/-rev/:rev', function (req, res) { var filename = req.params.filename; var rand = Math.floor(Math.random()*4294967296).toString(36); diff --git a/test/server.test.js b/test/server.test.js new file mode 100644 index 0000000..e6b55ad --- /dev/null +++ b/test/server.test.js @@ -0,0 +1,105 @@ +'use strict'; + +var proxyquire = require('proxyquire'), + expect = require('chai').expect, + sinon = require('sinon'), + semver = require('semver'); + +var VERBS = ['get', 'post', 'put', 'del']; + +describe('reggie npm server (unit)', function () { + var reggieServer, + req = {}, + res = {}, + routes = {}, + restify = {}, + data = function Data() {}; + + var versions = [ + '1.0.0', + '1.0.1', + '1.0.2-2', + '1.0.2-10' + ]; + data.prototype.packageMeta = function () { + return { + versions: versions.reduce(function (verObj, currVer) { + verObj[currVer] = {data: {}, time: {}}; + return verObj; + }, {}) + }; + }; + data.prototype.whichVersions = function () { + return versions; + }; + + beforeEach(function () { + //Store route functions in a hash + VERBS.forEach(function (verb) { routes[verb] = {}; }); + + //mock restify + restify.createServer = function() { + var serverMock = { + 'use': function () {}, + 'listen': function () {} + }; + VERBS.forEach(function (verb) { + serverMock[verb] = function (route, cb) { + routes[verb][route] = cb; + }; + }); + return serverMock; + }; + + data.prototype.init = function () {}; + + reggieServer = proxyquire('../server', { + 'restify': restify, + './lib/data': data + }); + + req.params = { name: 'test' }; + res.json = sinon.spy(); + }); + + describe('/:name', function () { + beforeEach(function () { + routes.get['/:name'](req, res); + }); + + it('Should choose latest by semver order', function () { + expect(res.json.firstCall.args[1]['dist-tags'].latest).to.equal('1.0.2-10'); + }); + + it('Should "sort" by semver order', function () { + // Yes, this is depending on an implementation detail of Object.keys + // Yes, that violates the API contract of Object and is bad + // Yes, this is how the npm client actually works + var resultVersions; + resultVersions = Object.keys(res.json.firstCall.args[1].versions); + expect(resultVersions).to.deep.equal(versions.sort(semver.compare)); + }); + }); + + describe('/-/all', function () { + beforeEach(function () { + data.prototype.getAllPackageNames = function () { + return ['test']; + }; + routes.get['/-/all'](req, res); + }); + + it('Should choose "latest" by semver order', function () { + expect(res.json.firstCall.args[1].test['dist-tags'].latest).to.equal('1.0.2-10'); + }); + + it('Should "sort" by semver order', function () { + // Yes, this is depending on an implementation detail of Object.keys + // Yes, that violates the API contract of Object and is bad + // Yes, this is how the npm client actually works + var resultVersions; + resultVersions = Object.keys(res.json.firstCall.args[1].test.versions); + expect(resultVersions).to.deep.equal(versions.sort(semver.compare)); + }); + }); +}); \ No newline at end of file