0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2025-04-01 02:42:23 -05:00

Merge branch 'master' into new_footer

This commit is contained in:
Meeeeow 2018-02-03 18:06:11 +08:00 committed by GitHub
commit 982b974e63
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 1078 additions and 407 deletions

View file

@ -39,7 +39,7 @@ The issue tracker is a channel were mostly users/developers post.
#### I want to report a bug
We considere a bug a feature that is not working as is described in the documentation. Before reporte a bug follow the next steps:
We considere a bug a feature that is not working as is described in the documentation. Before reporting a bug follow the next steps:
1. Use the GitHub issue search — check if the issue has already been reported.
@ -49,7 +49,7 @@ Verdaccio still does not support all npm commands due either in the initial desi
## Request Features
A new feature is always welcome, thus, analyse whether you ir idea fits in the scope of the project and elaborate your request providing enough context, for instance:
A new feature is always welcome, thus, analyse whether your idea fits in the scope of the project and elaborate your request providing enough context, for instance:
* A wide description the advantages of your request.
* It's compatible with `npm` and `yarn`?

View file

@ -1,14 +1,6 @@
# path to a directory with all packages
storage: ./storage
# a list of users
#
# This is deprecated, use auth plugins instead (see htpasswd below).
# users:
# admin:
# crypto.createHash('sha1').update(pass).digest('hex')
# password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
web:
# WebUI is enabled as default, if you want disable it, just uncomment this line
#enable: false

View file

@ -1,8 +1,8 @@
// flow-typed signature: bdff15032a92c1b6daf0ab0067861cb1
// flow-typed version: b43dff3e0e/jest_v19.x.x/flow_>=v0.16.x
// flow-typed signature: 6e1fc0a644aa956f79029fec0709e597
// flow-typed version: 07ebad4796/jest_v22.x.x/flow_>=v0.39.x
type JestMockFn = {
(...args: Array<any>): any,
type JestMockFn<TArguments: $ReadOnlyArray<*>, TReturn> = {
(...args: TArguments): TReturn,
/**
* An object for introspecting mock calls
*/
@ -12,37 +12,49 @@ type JestMockFn = {
* function. Each call is represented by an array of arguments that were
* passed during the call.
*/
calls: Array<Array<any>>,
calls: Array<TArguments>,
/**
* An array that contains all the object instances that have been
* instantiated from this mock function.
*/
instances: mixed,
instances: Array<TReturn>
},
/**
* Resets all information stored in the mockFn.mock.calls and
* mockFn.mock.instances arrays. Often this is useful when you want to clean
* up a mock's usage data between two assertions.
*/
mockClear(): Function,
mockClear(): void,
/**
* Resets all information stored in the mock. This is useful when you want to
* completely restore a mock back to its initial state.
*/
mockReset(): Function,
mockReset(): void,
/**
* Removes the mock and restores the initial implementation. This is useful
* when you want to mock functions in certain test cases and restore the
* original implementation in others. Beware that mockFn.mockRestore only
* works when mock was created with jest.spyOn. Thus you have to take care of
* restoration yourself when manually assigning jest.fn().
*/
mockRestore(): void,
/**
* Accepts a function that should be used as the implementation of the mock.
* The mock itself will still record all calls that go into and instances
* that come from itself -- the only difference is that the implementation
* will also be executed when the mock is called.
*/
mockImplementation(fn: Function): JestMockFn,
mockImplementation(
fn: (...args: TArguments) => TReturn
): JestMockFn<TArguments, TReturn>,
/**
* Accepts a function that will be used as an implementation of the mock for
* one call to the mocked function. Can be chained so that multiple function
* calls produce different results.
*/
mockImplementationOnce(fn: Function): JestMockFn,
mockImplementationOnce(
fn: (...args: TArguments) => TReturn
): JestMockFn<TArguments, TReturn>,
/**
* Just a simple sugar function for returning `this`
*/
@ -50,19 +62,19 @@ type JestMockFn = {
/**
* Deprecated: use jest.fn(() => value) instead
*/
mockReturnValue(value: any): JestMockFn,
mockReturnValue(value: TReturn): JestMockFn<TArguments, TReturn>,
/**
* Sugar for only returning a value once inside your mock
*/
mockReturnValueOnce(value: any): JestMockFn,
}
mockReturnValueOnce(value: TReturn): JestMockFn<TArguments, TReturn>
};
type JestAsymmetricEqualityType = {
/**
* A custom Jasmine equality tester
*/
asymmetricMatch(value: mixed): boolean,
}
asymmetricMatch(value: mixed): boolean
};
type JestCallsType = {
allArgs(): mixed,
@ -71,25 +83,61 @@ type JestCallsType = {
count(): number,
first(): mixed,
mostRecent(): mixed,
reset(): void,
}
reset(): void
};
type JestClockType = {
install(): void,
mockDate(date: Date): void,
tick(milliseconds?:number): void,
uninstall(): void,
}
tick(milliseconds?: number): void,
uninstall(): void
};
type JestMatcherResult = {
message?: string | ()=>string,
pass: boolean,
}
message?: string | (() => string),
pass: boolean
};
type JestMatcher = (actual: any, expected: any) => JestMatcherResult;
type JestPromiseType = {
/**
* Use rejects to unwrap the reason of a rejected promise so any other
* matcher can be chained. If the promise is fulfilled the assertion fails.
*/
rejects: JestExpectType,
/**
* Use resolves to unwrap the value of a fulfilled promise so any other
* matcher can be chained. If the promise is rejected the assertion fails.
*/
resolves: JestExpectType
};
/**
* Plugin: jest-enzyme
*/
type EnzymeMatchersType = {
toBeChecked(): void,
toBeDisabled(): void,
toBeEmpty(): void,
toBePresent(): void,
toContainReact(element: React$Element<any>): void,
toHaveClassName(className: string): void,
toHaveHTML(html: string): void,
toHaveProp(propKey: string, propValue?: any): void,
toHaveRef(refName: string): void,
toHaveState(stateKey: string, stateValue?: any): void,
toHaveStyle(styleKey: string, styleValue?: any): void,
toHaveTagName(tagName: string): void,
toHaveText(text: string): void,
toIncludeText(text: string): void,
toHaveValue(value: any): void,
toMatchElement(element: React$Element<any>): void,
toMatchSelector(selector: string): void
};
type JestExpectType = {
not: JestExpectType,
not: JestExpectType & EnzymeMatchersType,
/**
* If you have a mock function, you can use .lastCalledWith to test what
* arguments it was last called with.
@ -190,8 +238,8 @@ type JestExpectType = {
*/
toHaveBeenCalledWith(...args: Array<any>): void,
/**
* If you have a mock function, you can use .toHaveBeenLastCalledWith to test what
* arguments it was last called with.
* Use .toHaveBeenLastCalledWith to ensure that a mock function was last called
* with specific arguments.
*/
toHaveBeenLastCalledWith(...args: Array<any>): void,
/**
@ -204,33 +252,33 @@ type JestExpectType = {
*/
toHaveProperty(propPath: string, value?: any): void,
/**
* Use .toMatch to check that a string matches a regular expression.
* Use .toMatch to check that a string matches a regular expression or string.
*/
toMatch(regexp: RegExp): void,
toMatch(regexpOrString: RegExp | string): void,
/**
* Use .toMatchObject to check that a javascript object matches a subset of the properties of an object.
*/
toMatchObject(object: Object): void,
toMatchObject(object: Object | Array<Object>): void,
/**
* This ensures that a React component matches the most recent snapshot.
*/
toMatchSnapshot(name?: string): void,
/**
* Use .toThrow to test that a function throws when it is called.
* If you want to test that a specific error gets thrown, you can provide an
* argument to toThrow. The argument can be a string for the error message,
* a class for the error, or a regex that should match the error.
*
* Alias: .toThrowError
*/
toThrow(message?: string | Error): void,
/**
* Use .toThrowError to test that a function throws a specific error when it
* is called. The argument can be a string for the error message, a class for
* the error, or a regex that should match the error.
*/
toThrowError(message?: string | Error | RegExp): void,
toThrow(message?: string | Error | Class<Error> | RegExp): void,
toThrowError(message?: string | Error | Class<Error> | RegExp): void,
/**
* Use .toThrowErrorMatchingSnapshot to test that a function throws a error
* matching the most recent snapshot when it is called.
*/
toThrowErrorMatchingSnapshot(): void,
}
toThrowErrorMatchingSnapshot(): void
};
type JestObjectType = {
/**
@ -262,6 +310,10 @@ type JestObjectType = {
* mocked function.
*/
resetAllMocks(): JestObjectType,
/**
* Restores all mocks back to their original value.
*/
restoreAllMocks(): JestObjectType,
/**
* Removes any pending timers from the timer system.
*/
@ -280,7 +332,9 @@ type JestObjectType = {
* Returns a new, unused mock function. Optionally takes a mock
* implementation.
*/
fn(implementation?: Function): JestMockFn,
fn<TArguments: $ReadOnlyArray<*>, TReturn>(
implementation?: (...args: TArguments) => TReturn
): JestMockFn<TArguments, TReturn>,
/**
* Determines if the given function is a mocked function.
*/
@ -299,7 +353,21 @@ type JestObjectType = {
* The third argument can be used to create virtual mocks -- mocks of modules
* that don't exist anywhere in the system.
*/
mock(moduleName: string, moduleFactory?: any, options?: Object): JestObjectType,
mock(
moduleName: string,
moduleFactory?: any,
options?: Object
): JestObjectType,
/**
* Returns the actual module instead of a mock, bypassing all checks on
* whether the module should receive a mock implementation or not.
*/
requireActual(moduleName: string): any,
/**
* Returns a mock module instead of the actual module, bypassing all checks
* on whether the module should be required normally or not.
*/
requireMock(moduleName: string): any,
/**
* Resets the module registry - the cache of all required modules. This is
* useful to isolate modules where local state might conflict between tests.
@ -356,23 +424,56 @@ type JestObjectType = {
* Creates a mock function similar to jest.fn but also tracks calls to
* object[methodName].
*/
spyOn(object: Object, methodName: string): JestMockFn,
}
spyOn(object: Object, methodName: string): JestMockFn<any, any>,
/**
* Set the default timeout interval for tests and before/after hooks in milliseconds.
* Note: The default timeout interval is 5 seconds if this method is not called.
*/
setTimeout(timeout: number): JestObjectType
};
type JestSpyType = {
calls: JestCallsType,
}
calls: JestCallsType
};
/** Runs this function after every test inside this context */
declare function afterEach(fn: Function): void;
declare function afterEach(
fn: (done: () => void) => ?Promise<mixed>,
timeout?: number
): void;
/** Runs this function before every test inside this context */
declare function beforeEach(fn: Function): void;
declare function beforeEach(
fn: (done: () => void) => ?Promise<mixed>,
timeout?: number
): void;
/** Runs this function after all tests have finished inside this context */
declare function afterAll(fn: Function): void;
declare function afterAll(
fn: (done: () => void) => ?Promise<mixed>,
timeout?: number
): void;
/** Runs this function before any tests have started inside this context */
declare function beforeAll(fn: Function): void;
declare function beforeAll(
fn: (done: () => void) => ?Promise<mixed>,
timeout?: number
): void;
/** A context for grouping tests together */
declare function describe(name: string, fn: Function): void;
declare var describe: {
/**
* Creates a block that groups together several related tests in one "test suite"
*/
(name: string, fn: () => void): void,
/**
* Only run this describe block
*/
only(name: string, fn: () => void): void,
/**
* Skip running this describe block
*/
skip(name: string, fn: () => void): void
};
/** An individual test unit */
declare var it: {
@ -381,31 +482,55 @@ declare var it: {
*
* @param {string} Name of Test
* @param {Function} Test
* @param {number} Timeout for the test, in milliseconds.
*/
(name: string, fn?: Function): ?Promise<void>,
(
name: string,
fn?: (done: () => void) => ?Promise<mixed>,
timeout?: number
): void,
/**
* Only run this test
*
* @param {string} Name of Test
* @param {Function} Test
* @param {number} Timeout for the test, in milliseconds.
*/
only(name: string, fn?: Function): ?Promise<void>,
only(
name: string,
fn?: (done: () => void) => ?Promise<mixed>,
timeout?: number
): void,
/**
* Skip running this test
*
* @param {string} Name of Test
* @param {Function} Test
* @param {number} Timeout for the test, in milliseconds.
*/
skip(name: string, fn?: Function): ?Promise<void>,
skip(
name: string,
fn?: (done: () => void) => ?Promise<mixed>,
timeout?: number
): void,
/**
* Run the test concurrently
*
* @param {string} Name of Test
* @param {Function} Test
* @param {number} Timeout for the test, in milliseconds.
*/
concurrent(name: string, fn?: Function): ?Promise<void>,
concurrent(
name: string,
fn?: (done: () => void) => ?Promise<mixed>,
timeout?: number
): void
};
declare function fit(name: string, fn: Function): ?Promise<void>;
declare function fit(
name: string,
fn: (done: () => void) => ?Promise<mixed>,
timeout?: number
): void;
/** An individual test unit */
declare var test: typeof it;
/** A disabled group of tests */
@ -420,19 +545,20 @@ declare var xtest: typeof it;
/** The expect function is used every time you want to test a value */
declare var expect: {
/** The object that you want to make assertions against */
(value: any): JestExpectType,
(value: any): JestExpectType & JestPromiseType & EnzymeMatchersType,
/** Add additional Jasmine matchers to Jest's roster */
extend(matchers: {[name:string]: JestMatcher}): void,
extend(matchers: { [name: string]: JestMatcher }): void,
/** Add a module that formats application-specific data structures. */
addSnapshotSerializer(serializer: (input: Object) => string): void,
assertions(expectedAssertions: number): void,
hasAssertions(): void,
any(value: mixed): JestAsymmetricEqualityType,
anything(): void,
arrayContaining(value: Array<mixed>): void,
objectContaining(value: Object): void,
/** Matches any received string that contains the exact expected string. */
stringContaining(value: string): void,
stringMatching(value: string | RegExp): void,
stringMatching(value: string | RegExp): void
};
// TODO handle return type
@ -440,10 +566,10 @@ declare var expect: {
declare function spyOn(value: mixed, method: string): Object;
/** Holds all functions related to manipulating test runner */
declare var jest: JestObjectType
declare var jest: JestObjectType;
/**
* The global Jamine object, this is generally not exposed as the public API,
* The global Jasmine object, this is generally not exposed as the public API,
* using features inside here could break in later versions of Jest.
*/
declare var jasmine: {
@ -453,7 +579,10 @@ declare var jasmine: {
arrayContaining(value: Array<mixed>): void,
clock(): JestClockType,
createSpy(name: string): JestSpyType,
createSpyObj(baseName: string, methodNames: Array<string>): {[methodName: string]: JestSpyType},
createSpyObj(
baseName: string,
methodNames: Array<string>
): { [methodName: string]: JestSpyType },
objectContaining(value: Object): void,
stringMatching(value: string): void,
}
stringMatching(value: string): void
};

View file

@ -1,6 +1,6 @@
{
"name": "verdaccio",
"version": "3.0.0-alpha.10",
"version": "3.0.0-alpha.11",
"description": "Private npm repository server",
"author": {
"name": "Alex Kocharin",
@ -121,7 +121,9 @@
"stylelint-config-recommended-scss": "3.0.0",
"stylelint-scss": "2.2.0",
"stylelint-webpack-plugin": "0.10.1",
"supertest": "^3.0.0",
"url-loader": "0.6.2",
"verdaccio-auth-memory": "^0.0.3",
"webpack": "3.10.0",
"webpack-dev-server": "2.11.1",
"webpack-merge": "4.1.1"

20
src/api/debug/index.js Normal file
View file

@ -0,0 +1,20 @@
import _ from 'lodash';
export default (app, selfPath) => {
// Hook for tests only
app.get('/-/_debug', function(req, res, next) {
const doGarbabeCollector = _.isNil(global.gc) === false;
if (doGarbabeCollector) {
global.gc();
}
next({
pid: process.pid,
main: process.mainModule.filename,
conf: selfPath,
mem: process.memoryUsage(),
gc: doGarbabeCollector,
});
});
};

View file

@ -5,7 +5,6 @@ const mime = require('mime');
const _ = require('lodash');
const media = Middleware.media;
const expect_json = Middleware.expect_json;
module.exports = function(route, auth, storage) {
const can = Middleware.allow(auth);
@ -63,8 +62,7 @@ module.exports = function(route, auth, storage) {
});
});
route.post('/-/package/:package/dist-tags', can('publish'), media(mime.getType('json')), expect_json,
function(req, res, next) {
route.post('/-/package/:package/dist-tags', can('publish'), function(req, res, next) {
storage.merge_tags(req.params.package, req.body, function(err) {
if (err) {
return next(err);

View file

@ -1,5 +1,3 @@
'use strict';
const _ = require('lodash');
const createError = require('http-errors');
@ -30,7 +28,7 @@ module.exports = function(route, auth, storage, config) {
if (_.isNil(info['dist-tags'][queryVersion]) === false) {
queryVersion = info['dist-tags'][queryVersion];
t = Utils.get_version(info, queryVersion);
if (_.isNil(t)) {
if (_.isNil(t) === false) {
return next(t);
}
}
@ -48,6 +46,7 @@ module.exports = function(route, auth, storage, config) {
route.get('/:package/-/:filename', can('access'), function(req, res) {
const stream = storage.get_tarball(req.params.package, req.params.filename);
stream.on('content-length', function(content) {
res.header('Content-Length', content);
});

View file

@ -30,7 +30,7 @@ module.exports = function(route, auth) {
// With npm registering is the same as logging in,
// and npm accepts only an 409 error.
// So, changing status code here.
return next( createError[409](err.message) );
return next( createError[err.status || 409](err.message) );
}
return next(err);
}

View file

@ -1,5 +1,3 @@
'use strict';
module.exports = function(route) {
route.get('/whoami', function(req, res, next) {
if (req.headers.referer === 'whoami') {

View file

@ -1,11 +1,9 @@
'use strict';
const express = require('express');
const bodyParser = require('body-parser');
const Middleware = require('../web/middleware');
const match = Middleware.match;
const validate_name = Middleware.validate_name;
const validate_pkg = Middleware.validate_package;
const validateName = Middleware.validate_name;
const validatePkg = Middleware.validate_package;
const encodeScopePackage = Middleware.encodeScopePackage;
const whoami = require('./api/whoami');
@ -23,12 +21,12 @@ module.exports = function(config, auth, storage) {
// validate all of these params as a package name
// this might be too harsh, so ask if it causes trouble
app.param('package', validate_pkg);
app.param('filename', validate_name);
app.param('tag', validate_name);
app.param('version', validate_name);
app.param('revision', validate_name);
app.param('token', validate_name);
app.param('package', validatePkg);
app.param('filename', validateName);
app.param('tag', validateName);
app.param('version', validateName);
app.param('revision', validateName);
app.param('token', validateName);
// these can't be safely put into express url for some reason
// TODO: For some reason? what reason?

View file

@ -5,6 +5,7 @@ import _ from 'lodash';
import cors from 'cors';
import Storage from '../lib/storage';
import {loadPlugin} from '../lib/plugin-loader';
import hookDebug from './debug';
const Auth = require('../lib/auth');
const Logger = require('../lib/logger');
@ -24,34 +25,9 @@ module.exports = function(configHash) {
app.set('env', process.env.NODE_ENV || 'production');
app.use(cors());
// Middleware
const error_reporting_middleware = function(req, res, next) {
res.report_error = res.report_error || function(err) {
if (err.status && err.status >= 400 && err.status < 600) {
if (_.isNil(res.headersSent) === false) {
res.status(err.status);
next({error: err.message || 'unknown error'});
}
} else {
Logger.logger.error( {err: err}
, 'unexpected error: @{!err.message}\n@{err.stack}');
if (!res.status || !res.send) {
Logger.logger.error('this is an error in express.js, please report this');
res.destroy();
} else if (!res.headersSent) {
res.status(500);
next({error: 'internal server error'});
} else {
// socket should be already closed
}
}
};
next();
};
// Router setup
app.use(Middleware.log);
app.use(error_reporting_middleware);
app.use(Middleware.errorReportingMiddleware);
app.use(function(req, res, next) {
res.setHeader('X-Powered-By', config.user_agent);
next();
@ -66,20 +42,9 @@ module.exports = function(configHash) {
// Hook for tests only
if (config._debug) {
app.get('/-/_debug', function(req, res, next) {
const do_gc = _.isNil(global.gc) === false;
if (do_gc) {
global.gc();
}
next({
pid: process.pid,
main: process.mainModule.filename,
conf: config.self_path,
mem: process.memoryUsage(),
gc: do_gc,
});
});
hookDebug(app, config.self_path);
}
// register middleware plugins
const plugin_params = {
config: config,
@ -118,7 +83,7 @@ module.exports = function(configHash) {
if (_.isFunction(res.report_error) === false) {
// in case of very early error this middleware may not be loaded before error is generated
// fixing that
error_reporting_middleware(req, res, _.noop);
Middleware.errorReportingMiddleware(req, res, _.noop);
}
res.report_error(err);
} else {

View file

@ -1,7 +1,5 @@
/* eslint prefer-rest-params: "off" */
'use strict';
const crypto = require('crypto');
const _ = require('lodash');
const createError = require('http-errors');
@ -251,3 +249,29 @@ module.exports.log = function(req, res, next) {
};
next();
};
// Middleware
module.exports.errorReportingMiddleware = function(req, res, next) {
res.report_error = res.report_error || function(err) {
if (err.status && err.status >= 400 && err.status < 600) {
if (_.isNil(res.headersSent) === false) {
res.status(err.status);
next({error: err.message || 'unknown error'});
}
} else {
Logger.logger.error( {err: err}
, 'unexpected error: @{!err.message}\n@{err.stack}');
if (!res.status || !res.send) {
Logger.logger.error('this is an error in express.js, please report this');
res.destroy();
} else if (!res.headersSent) {
res.status(500);
next({error: 'internal server error'});
} else {
// socket should be already closed
}
}
};
next();
};

View file

@ -5,7 +5,6 @@ import {loadPlugin} from '../lib/plugin-loader';
const Crypto = require('crypto');
const Error = require('http-errors');
const Logger = require('./logger');
const pkgJson = require('../../package.json');
const jwt = require('jsonwebtoken');
/**
* Handles the authentification, load auth plugins.
@ -37,30 +36,6 @@ class Auth {
return p.authenticate || p.allow_access || p.allow_publish;
});
this.plugins.unshift({
verdaccio_version: pkgJson.version,
authenticate: function(user, password, cb) {
if (config.users != null
&& config.users[user] != null
&& (Crypto.createHash('sha1').update(password).digest('hex')
=== config.users[user].password)
) {
return cb(null, [user]);
}
return cb();
},
adduser: function(user, password, cb) {
if (config.users && config.users[user]) {
return cb(Error[403]('this user already exists'));
}
return cb();
},
});
const allow_action = function(action) {
return function(user, pkg, cb) {
let ok = pkg[action].reduce(function(prev, curr) {
@ -138,9 +113,14 @@ class Auth {
if (typeof(p[n]) !== 'function') {
next();
} else {
// p.add_user() execution
p[n](user, password, function(err, ok) {
if (err) return cb(err);
if (ok) return self.authenticate(user, password, cb);
if (err) {
return cb(err);
}
if (ok) {
return self.authenticate(user, password, cb);
}
next();
});
}
@ -166,8 +146,15 @@ class Auth {
}
p.allow_access(user, pkg, function(err, ok) {
if (err) return callback(err);
if (ok) return callback(null, ok);
if (err) {
return callback(err);
}
if (ok) {
return callback(null, ok);
}
next(); // cb(null, false) causes next plugin to roll
});
})();
@ -228,7 +215,9 @@ class Auth {
req.remote_user = buildAnonymousUser();
let authorization = req.headers.authorization;
if (authorization == null) return next();
if (authorization == null) {
return next();
}
let parts = authorization.split(' ');
@ -421,7 +410,7 @@ function buildAnonymousUser() {
return {
name: undefined,
// groups without '$' are going to be deprecated eventually
groups: ['$all', '$anonymous', '@all', '@anonymous', 'all', 'undefined', 'anonymous'],
groups: ['$all', '$anonymous', '@all', '@anonymous'],
real_groups: [],
};
}

View file

@ -1,10 +1,8 @@
import fs from 'fs';
import path from 'path';
export default function(server) {
describe('npm adduser', () => {
const user = String(Math.random());
const pass = String(Math.random());
beforeAll(function() {
return server.auth(user, pass)
.status(201)
@ -25,22 +23,4 @@ export default function(server) {
.body_error(/maximum amount of users reached/);
});
});
describe('should adduser created with htpasswd', () => {
const user = 'preexisting';
const pass = 'preexisting';
beforeAll(function() {
return fs.appendFileSync(
path.join(__dirname, '../store/test-storage', '.htpasswd'),
'preexisting:$apr1$4YSboUa9$yVKjE7.PxIOuK3M4D7VjX.'
);
});
test('should log in', () => {
return server.auth(user, pass)
.status(201)
.body_ok(/you are authenticated as/);
});
});
}

View file

@ -16,6 +16,12 @@ function createHash() {
export default function(server, server2) {
describe('basic test endpoints', () => {
beforeAll(function() {
return server.auth('test', 'test')
.status(201)
.body_ok(/'test'/);
});
require('./whoIam')(server);
require('./ping')(server);
@ -114,31 +120,41 @@ export default function(server, server2) {
/* test for before() */
});
test('downloading newly created package', () => {
return server.getPackage('testpkg')
.status(200)
.then(function (body) {
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'
describe('should download a package', () => {
beforeAll(function() {
return server.auth('test', 'test')
.status(201)
.body_ok(/'test'/);
});
test('should download a newly created package from server1', () => {
return server.getPackage('testpkg')
.status(200)
.then(function (body) {
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'
});
});
});
});
test('should downloading a package from server2', () => {
return server2.getPackage('testpkg')
.status(200)
.then(function (body) {
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'
});
});
});
});
test('downloading package via server2', () => {
return server2.getPackage('testpkg')
.status(200)
.then(function (body) {
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'
});
});
});
});
});
});

View file

@ -1,12 +1,8 @@
'use strict';
const assert = require('assert');
module.exports = function(server) {
test('who am I?', () => {
return server.whoami().then(function (username) {
assert.equal(username, 'test');
expect(username).toMatch('test');
});
});

View file

@ -1,25 +0,0 @@
'use strict';
function Plugin(config) {
let self = Object.create(Plugin.prototype);
self._config = config;
return self;
}
// plugin is expected to be compatible with...
Plugin.prototype.verdaccio_version = '1.1.0';
Plugin.prototype.authenticate = function(user, password, cb) {
if (user !== this._config.accept_user) {
// delegate to next plugin
return cb(null, false);
}
if (password !== this._config.with_password) {
const err = Error('i don\'t like your password');
err.status = 403;
return cb(err);
}
return cb(null, [user]);
};
module.exports = Plugin;

View file

@ -1,30 +0,0 @@
'use strict';
module.exports = Plugin;
function Plugin(config) {
let self = Object.create(Plugin.prototype);
self._config = config;
return self;
}
// plugin is expected to be compatible with...
Plugin.prototype.verdaccio_version = '1.1.0';
Plugin.prototype.allow_access = function(user, pkg, cb) {
if (!pkg.handled_by_auth_plugin) {
// delegate to next plugin
return cb(null, false);
}
if (user.name !== this._config.allow_user) {
let err = Error('i don\'t know anything about you');
err.status = 403;
return cb(err);
}
if (pkg.name !== this._config.to_access) {
let err = Error('you\'re not allowed here');
err.status = 403;
return cb(err);
}
return cb(null, true);
};

View file

@ -25,7 +25,7 @@ export default function (server, server2) {
test('downloading non-existent tarball #2 / srv2', () => {
return server2.getTarball('testpkg-gh29', 'blahblah')
.status(404)
.body_error(/no such file/);
.body_error(/no such file available/);
});
describe('tarball', () => {
@ -51,10 +51,10 @@ export default function (server, server2) {
test('downloading newly created tarball / srv2', () => {
return server2.getTarball('testpkg-gh29', 'blahblah')
.status(200)
.then(function(body) {
assert.deepEqual(body, readfile('fixtures/binary'));
});
.status(200)
.then(function(body) {
assert.deepEqual(body, readfile('fixtures/binary'));
});
});
});
});

View file

@ -34,21 +34,22 @@ import upLinkCache from './uplink.cache.spec';
import upLinkAuth from './uplink.auth.spec';
describe('functional test verdaccio', function() {
jest.setTimeout(10000);
const EXPRESS_PORT = 55550;
const SILENCE_LOG = !process.env.VERDACCIO_DEBUG;
const processRunning = [];
const config1 = new VerdaccioConfig(
'./store/test-storage',
'./store/config-1.yaml',
'http://localhost:55551/');
'http://localhost:55551/', 55551);
const config2 = new VerdaccioConfig(
'./store/test-storage2',
'./store/config-2.yaml',
'http://localhost:55552/');
'http://localhost:55552/', 55552);
const config3 = new VerdaccioConfig(
'./store/test-storage3',
'./store/config-3.yaml',
'http://localhost:55553/');
'http://localhost:55553/', 55553);
const server1: IServerBridge = new Server(config1.domainPath);
const server2: IServerBridge = new Server(config2.domainPath);
const server3: IServerBridge = new Server(config3.domainPath);
@ -68,10 +69,12 @@ describe('functional test verdaccio', function() {
express.start(EXPRESS_PORT).then((app) =>{
done();
}, (err) => {
done(err);
console.error(err);
done();
});
}).catch((error) => {
done(error);
}).catch((err) => {
console.error(err);
done();
});
});

View file

@ -32,8 +32,9 @@ export default class VerdaccioProcess implements IServerProcess {
this.childFork = fork(verdaccioRegisterWrap,
['-c', configPath],
{
silent: this.silence
}
silent: this.silence,
execArgv: [`--inspect=${this.config.port + 5}`]
},
);
this.childFork.on('message', (msg) => {
@ -49,16 +50,16 @@ export default class VerdaccioProcess implements IServerProcess {
}
});
this.childFork.on('error', function(err) {
reject(err);
this.childFork.on('error', (err) => {
reject([err, this]);
});
this.childFork.on('disconnect', function(err) {
reject(err);
this.childFork.on('disconnect', (err) => {
reject([err, this]);
});
this.childFork.on('exit', function(err) {
reject(err);
this.childFork.on('exit', (err) => {
reject([err, this]);
});
});
@ -70,4 +71,4 @@ export default class VerdaccioProcess implements IServerProcess {
return this.childFork.kill('SIGINT');
}
}
}

View file

@ -4,6 +4,7 @@ export interface IVerdaccioConfig {
storagePath: string;
configPath: string;
domainPath: string;
port: number;
}
export interface IRequestPromise {
@ -49,4 +50,4 @@ export interface IServerBridge {
whoami(): Promise<any>;
ping(): Promise<any>;
debug(): IRequestPromise;
}
}

View file

@ -6,10 +6,12 @@ export class VerdaccioConfig implements IVerdaccioConfig {
storagePath: string;
configPath: string;
domainPath: string;
port: number;
constructor(storagePath: string, configPath: string, domainPath: string) {
constructor(storagePath: string, configPath: string, domainPath: string, port: number) {
this.storagePath = storagePath;
this.configPath = configPath;
this.domainPath = domainPath;
this.port = port;
}
}

View file

@ -4,15 +4,6 @@ export default function(server) {
const buildToken = (auth) => {
return `Basic ${(new Buffer(auth).toString('base64'))}`;
};
let oldAuth;
beforeAll(function() {
oldAuth = server.authstr;
});
afterAll(function() {
server.authstr = oldAuth;
});
/**
* Check whether the user is allowed to fetch packages
@ -72,7 +63,7 @@ export default function(server) {
checkPublish(undefined, testAccessOnly, false);
checkPublish(badCredentials, testAccessOnly, false);
// // all are allowed to publish
// all are allowed to publish
checkAccess(validCredentials, testPublishOnly, false);
checkAccess(undefined, testPublishOnly, false);
checkAccess(badCredentials, testPublishOnly, false);

View file

@ -1,9 +1,9 @@
import assert from 'assert';
export default function(server2){
const requestAuthFail = (user, pass, message) => {
const requestAuthFail = (user, pass, message, statusCode) => {
return server2.auth(user, pass)
.status(409)
.status(statusCode)
.body_error(message)
.then(function() {
return server2.whoami();
@ -12,9 +12,9 @@ export default function(server2){
assert.equal(username, null);
});
};
const requestAuthOk = (user, pass, regex) => {
const requestAuthOk = (user, pass, regex, statusCode) => {
return server2.auth(user, pass)
.status(201)
.status(statusCode)
.body_ok(regex)
.then(function() {
return server2.whoami();
@ -26,39 +26,22 @@ export default function(server2){
};
describe('test default authentication', () => {
let authstr;
beforeAll(function() {
authstr = server2.authstr;
});
test('should not authenticate with wrong password', () => {
return requestAuthFail('authtest', 'wrongpass', 'this user already exists');
});
test('should be wrong password handled by plugin', () => {
return requestAuthFail('authtest2', 'wrongpass', 'registration is disabled');
return requestAuthFail('authtest', 'wrongpass1', 'i don\'t like your password', 401);
});
test('should right password handled by plugin', () => {
return requestAuthOk('authtest2', 'blahblah', /'authtest2'/);
return requestAuthOk('authtest2', 'blahblah', /'authtest2'/, 201);
});
afterAll(function() {
server2.authstr = authstr;
});
});
describe('test access authorization', () => {
let authstr;
beforeAll(function() {
authstr = server2.authstr;
});
describe('access with user authtest', () => {
describe('access with user authtest', () => {
beforeAll(function() {
return server2.auth('authtest', 'test')
return server2.auth('authtest', 'blahblah')
.status(201)
.body_ok(/'authtest'/);
});
@ -69,10 +52,10 @@ export default function(server2){
.body_error('no such package available');
});
test('access test-auth-deny', () => {
return server2.getPackage('test-auth-deny')
test('access test-deny', () => {
return server2.getPackage('test-deny')
.status(403)
.body_error('you\'re not allowed here');
.body_error('not allowed to access package');
});
test('access test-auth-regular', () => {
@ -92,13 +75,13 @@ export default function(server2){
test('access test-auth-allow', () => {
return server2.getPackage('test-auth-allow')
.status(403)
.body_error('i don\'t know anything about you');
.body_error('not allowed to access package');
});
test('access test-auth-deny', () => {
return server2.getPackage('test-auth-deny')
.status(403)
.body_error('i don\'t know anything about you');
.body_error('not allowed to access package');
});
test('access test-auth-regular', () => {
@ -108,8 +91,5 @@ export default function(server2){
});
});
afterAll(function() {
server2.authstr = authstr;
});
});
}

View file

@ -18,8 +18,8 @@ export default function (server, server2) {
test('add pkg', () => {});
describe('should check readme file', () => {
const matchReadme = (server) => {
return server.request({
const matchReadme = (serverRef) => {
return serverRef.request({
uri: '/-/verdaccio/package/readme/readme-test'
}).status(200).then(function(body) {
assert.equal(body, '<p>this is a readme</p>\n');

View file

@ -34,7 +34,7 @@ export default function (server, express) {
});
ddd.forEach(function (type) {
test('should not store tarballs / ' + type, callback => {
test.skip('should not store tarballs / ' + type, callback => {
let called;
express.get('/testexp-incomplete/-/' + type + '.tar.gz', function (_, response) {
if (called) {

View file

@ -1,14 +1,18 @@
storage: ./test-storage
users:
test:
password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
users_file: ./test-storage/.htpasswd
max_users: 1
max_users: 2
web:
enable: true
title: verdaccio-server-1
auth:
auth-memory:
users:
test:
name: test
password: test
uplinks:
express:
@ -26,36 +30,70 @@ logs:
packages:
'@test/*':
allow_access: all
allow_publish: all
allow_access: $all
allow_publish: $all
proxy: server2
'testfwd*':
allow_access: all
allow_publish: all
allow_access: $all
allow_publish: $all
proxy_access: server2
proxy_publish: server2
'testloop':
allow_access: all
allow_publish: all
allow_access: $all
allow_publish: $all
proxy_access: server2
proxy_publish: server2
'testexp*':
allow_access: all
allow_publish: all
'testexp':
allow_access: $anonymous
# used by tags.spec.js
'testexp_tag*':
allow_access: $all
allow_publish: $all
proxy_access: express
# used by gzip.spec.js
'testexp_gzi*':
allow_access: $all
allow_publish: $all
proxy_access: express
# used by gh29.js
'testpkg-gh29':
allow_access: $all
allow_publish: $all
proxy_access: express
# used by preserve_tags_spec.js
'testpkg-preserve':
allow_access: $all
allow_publish: $all
proxy_access: express
# used by racycrash.js
'testexp-racycrash':
allow_access: $all
allow_publish: $all
proxy_access: express
# used by incomplete.js
'testexp-incomplete':
allow_access: $all
allow_publish: $all
proxy_access: express
'test-nullstorage*':
allow_access: all
allow_publish: all
allow_access: $all
allow_publish: $all
proxy_access: server2
storage: false
'baduplink':
allow_access: all
allow_publish: all
allow_access: $all
allow_publish: $all
proxy_access: baduplink
'test-access-only':
@ -79,12 +117,8 @@ packages:
storage: false
'*':
allow_access: test undefined
allow_publish: test undefined
# this should not matter
testpkg:
allow_access: none
allow_access: test $anonymous
allow_publish: test $anonymous
listen: 55551

View file

@ -1,11 +1,5 @@
storage: ./test-storage2
users:
test:
password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
authtest:
password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
uplinks:
server1:
url: http://localhost:55551/
@ -13,62 +7,84 @@ uplinks:
web:
enable: true
title: verdaccio-server-2
middlewares:
../fixtures/plugins/middlewares:
message: this is a custom route
auth:
../fixtures/plugins/authenticate:
accept_user: authtest2
with_password: blahblah
max_users: 3
../fixtures/plugins/authorize:
allow_user: authtest
to_access: test-auth-allow
auth:
auth-memory:
users:
test:
name: test
password: test
authtest2:
name: authtest2
password: blahblah
authtest:
name: authtest
password: blahblah
logs:
- {type: stdout, format: pretty, level: trace}
packages:
'@test/*':
allow_access: all
allow_publish: all
allow_access: $all
allow_publish: $all
proxy: server1
'testfwd':
allow_access: all
allow_publish: all
allow_access: $all
allow_publish: $all
'testloop':
allow_access: all
allow_publish: all
allow_access: $all
allow_publish: $all
proxy_access: server1
proxy_publish: server1
'testpkg*':
allow_access: test anonymous
allow_publish: test anonymous
# used by gh29.js
'testpkg-gh29':
allow_access: test $anonymous
allow_publish: test $anonymous
proxy_access: server1
# used by preserve_tags_spec.js
'testpkg-preserve':
allow_access: test $anonymous
allow_publish: test $anonymous
proxy_access: server1
'testpkg':
allow_access: test $anonymous
allow_publish: test $anonymous
proxy_access: server1
'readme-*':
allow_access: test anonymous
allow_publish: test anonymous
allow_access: test $anonymous
allow_publish: test $anonymous
proxy_access: server1
'test-nullstorage*':
allow_access: all
allow_publish: all
allow_access: $all
allow_publish: $all
'test-auth-regular':
allow_access: $authenticated
'test-auth-*':
handled_by_auth_plugin: true
allow_access: authtest
'test-deny':
allow_access: authtest2
'*':
allow_access: test anonymous
allow_publish: test anonymous
allow_access: test $anonymous
allow_publish: test $anonymous
listen: 55552

View file

@ -1,11 +1,8 @@
storage: ./test-storage3
users:
test:
password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
web:
enable: true
title: verdaccio-server-3
uplinks:
server1:
@ -14,6 +11,13 @@ uplinks:
url: http://localhost:55552/
cache: false
auth:
auth-memory:
users:
test:
name: test
password: test
logs:
- {type: stdout, format: pretty, level: trace}

486
test/unit/api.spec.js Normal file
View file

@ -0,0 +1,486 @@
import request from 'supertest';
import _ from 'lodash';
import path from 'path';
import rimraf from 'rimraf';
import configDefault from './partials/config';
import publishMetadata from './partials/publish-api';
import Config from '../../src/lib/config';
import Storage from '../../src/lib/storage';
import Auth from '../../src/lib/auth';
import indexAPI from '../../src/api/index';
require('../../src/lib/logger').setup([]);
describe('endpoint unit test', () => {
let config;
let storage;
let auth;
let app;
beforeAll(function(done) {
const store = path.join(__dirname, './partials/store/test-storage');
rimraf(store, () => {
const configForTest = _.clone(configDefault);
configForTest.auth = {
htpasswd: {
file: './test-storage/htpasswd-test'
}
};
configForTest.self_path = store;
config = new Config(configForTest);
storage = new Storage(config);
auth = new Auth(config);
app = indexAPI(config, auth, storage);
done();
});
});
describe('should test ping api', () => {
test('should test endpoint /-/ping', (done) => {
request(app)
.get('/-/ping')
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
if (err) {
return done(err);
}
done();
});
});
});
describe('should test whoami api', () => {
test('should test /-/whoami endpoint', (done) => {
request(app)
.get('/-/whoami')
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
if (err) {
return done(err);
}
done();
});
});
test('should test /whoami endpoint', (done) => {
request(app)
.get('/-/whoami')
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
if (err) {
return done(err);
}
done();
});
});
});
describe('should test user api', () => {
const credentials = { name: 'Jota', password: 'secretPass' };
test('should test add a new user', (done) => {
request(app)
.put('/-/user/org.couchdb.user:jota')
.send(credentials)
.expect('Content-Type', /json/)
.expect(201)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.ok).toBeDefined();
expect(res.body.ok).toMatch(`user '${credentials.name}' created`);
done();
});
});
test('should test fails add a new user with missing name', (done) => {
const credentialsShort = _.clone(credentials);
delete credentialsShort.name;
request(app)
.put('/-/user/org.couchdb.user:jota')
.send(credentialsShort)
.expect('Content-Type', /json/)
.expect(409)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(/username should not contain non-uri-safe characters/);
done();
});
});
test('should test fails add a new user with missing password', (done) => {
const credentialsShort = _.clone(credentials);
delete credentialsShort.password;
request(app)
.put('/-/user/org.couchdb.user:jota')
.send(credentialsShort)
.expect('Content-Type', /json/)
.expect(403)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
//FIXME: message is not 100% accurate
expect(res.body.error).toMatch(/this user already exists/);
done();
});
});
test('should test fails add a new user', (done) => {
request(app)
.put('/-/user/org.couchdb.user:jota')
.send(credentials)
.expect('Content-Type', /json/)
.expect(403)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
//FIXME: message is not 100% accurate
expect(res.body.error).toMatch(/this user already exists/);
done();
});
});
test('should test fails add a new user with wrong password', (done) => {
const credentialsShort = _.clone(credentials);
credentialsShort.password = 'failPassword';
request(app)
.put('/-/user/org.couchdb.user:jota')
.send(credentialsShort)
.expect('Content-Type', /json/)
//TODO: this should return 401 and will fail when issue
// https://github.com/verdaccio/verdaccio-htpasswd/issues/5
// is being fixed
.expect(403)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(/this user already exists/);
done();
});
});
});
describe('should test package api', () => {
test('should fetch jquery package from remote uplink', (done) => {
request(app)
.get('/jquery')
.set('content-type', 'application/json; charset=utf-8')
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body).toBeDefined();
expect(res.body.name).toMatch(/jquery/);
done();
});
});
test('should fetch jquery specific version package from remote uplink', (done) => {
request(app)
.get('/jquery/1.5.1')
.set('content-type', 'application/json; charset=utf-8')
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body).toBeDefined();
expect(res.body.name).toMatch(/jquery/);
done();
});
});
test('should fetch jquery specific tag package from remote uplink', (done) => {
request(app)
.get('/jquery/latest')
.set('content-type', 'application/json; charset=utf-8')
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body).toBeDefined();
expect(res.body.name).toMatch(/jquery/);
done();
});
});
test('should fails on fetch jquery specific tag package from remote uplink', (done) => {
request(app)
.get('/jquery/never-will-exist-this-tag')
.set('content-type', 'application/json; charset=utf-8')
.expect('Content-Type', /json/)
.expect(404)
.end(function(err, res) {
if (err) {
return done(err);
}
done();
});
});
test('should not found a unexisting remote package under scope', (done) => {
request(app)
.get('/@verdaccio/not-found')
.set('content-type', 'application/json; charset=utf-8')
.expect('Content-Type', /json/)
.expect(404)
.end(function(err, res) {
if (err) {
return done(err);
}
done();
});
});
test('should forbid access to remote package', (done) => {
request(app)
.get('/forbidden-place')
.set('content-type', 'application/json; charset=utf-8')
.expect('Content-Type', /json/)
.expect(403)
.end(function(err, res) {
if (err) {
return done(err);
}
done();
});
});
test('should fetch a tarball from remote uplink', (done) => {
request(app)
.get('/jquery/-/jquery-1.5.1.tgz')
.expect('Content-Type', /application\/octet-stream/)
.expect(200)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body).toBeDefined();
done();
});
});
test('should fails fetch a tarball from remote uplink', (done) => {
request(app)
.get('/jquery/-/jquery-0.0.1.tgz')
.expect('Content-Type', /application\/octet-stream/)
.expect(404)
.end(function(err, res) {
if (err) {
return done(err);
}
done();
});
});
});
describe('should test dist-tag api', () => {
const jqueryVersion = '2.1.2';
const jqueryUpdatedVersion = {
'beta': '3.0.0',
'jota': '1.6.3'
};
test('should set a new tag on jquery', (done) => {
request(app)
.put('/jquery/verdaccio-tag')
.send(JSON.stringify(jqueryVersion))
.set('accept', 'gzip')
.set('accept-encoding', 'application/json')
.set('content-type', 'application/json')
.expect(201)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.ok).toBeDefined();
expect(res.body.ok).toMatch(/package tagged/);
done();
});
});
test('should fetch all tag for jquery', (done) => {
request(app)
.get('/-/package/jquery/dist-tags')
.set('accept-encoding', 'application/json')
.set('content-type', 'application/json')
.expect(200)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body).toBeDefined();
expect(res.body['verdaccio-tag']).toMatch(jqueryVersion);
done();
});
});
test('should update a new tag on jquery', (done) => {
request(app)
.post('/-/package/jquery/dist-tags')
.send(JSON.stringify(jqueryUpdatedVersion))
.set('content-type', 'application/json')
.expect(201)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.ok).toBeDefined();
expect(res.body.ok).toMatch(/tags updated/);
done();
});
});
test('should fetch all tags for jquery and ccheck previous update', (done) => {
request(app)
.get('/-/package/jquery/dist-tags')
.set('accept-encoding', 'application/json')
.set('content-type', 'application/json')
.expect(200)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body).toBeDefined();
expect(res.body['beta']).toMatch(jqueryUpdatedVersion['beta']);
done();
});
});
test('should set a remove a tag on jquery', (done) => {
request(app)
.del('/-/package/jquery/dist-tags/verdaccio-tag')
.set('accept-encoding', 'application/json')
.set('content-type', 'application/json')
//.expect('Content-Type', /json/)
.expect(201)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.ok).toBeDefined();
expect(res.body.ok).toMatch(/tag removed/);
done();
});
});
});
describe('should test search api', () => {
test('should perform a search', (done) => {
const now = Date.now()
const cacheTime = now - 6000000;
request(app)
.get('/-/all/since?stale=update_after&startkey=' + cacheTime)
// .set('accept-encoding', 'application/json')
// .set('content-type', 'application/json')
//.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
if (err) {
return done(err);
}
//TODO: we have to catch the stream check whether it returns something
// we should not spend much time on this api since is deprecated somehow.
done();
});
});
});
describe('should test publish api', () => {
test('should publish a new package', (done) => {
request(app)
.put('/@scope%2fpk1-test')
.set('content-type', 'application/json')
.send(JSON.stringify(publishMetadata))
.expect(201)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.ok).toBeDefined();
expect(res.body.success).toBeDefined();
expect(res.body.success).toBeTruthy();
expect(res.body.ok).toMatch(/created new package/);
done();
});
});
test('should unpublish a new package', (done) => {
//FUTURE: for some reason it does not remove the scope folder
request(app)
.del('/@scope%2fpk1-test/-rev/4-6abcdb4efd41a576')
.set('content-type', 'application/json')
.expect(201)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.ok).toBeDefined();
expect(res.body.ok).toMatch(/package removed/);
done();
});
});
});
});

View file

@ -1,5 +1,3 @@
'use strict';
const config = {
storage: __dirname + '/store/test-storage',
uplinks: {
@ -8,8 +6,25 @@ const config = {
}
},
packages: {
'@*/*': {
allow_access: '$all',
allow_publish: '$all',
proxy: 'npmjs'
},
'forbidden-place': {
allow_access: 'nobody',
allow_publish: 'nobody'
},
'jquery': {
allow_access: '$all',
allow_publish: '$all',
proxy: 'npmjs'
},
'*': {
allow_access: '$all',
allow_publish: '$all'
},
},
logs: [

View file

@ -0,0 +1,53 @@
const json = {
"_id": "@scope\/pk1-test",
"name": "@scope\/pk1-test",
"description": "",
"dist-tags": {
"latest": "1.0.6"
},
"versions": {
"1.0.6": {
"name": "@scope\/pk1-test",
"version": "1.0.6",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
],
"author": {
"name": "Juan Picado",
"email": "juan@jotadeveloper.com"
},
"license": "ISC",
"dependencies": {
"verdaccio": "^2.7.2"
},
"readme": "# test",
"readmeFilename": "README.md",
"_id": "@scope\/pk1-test@1.0.6",
"_npmVersion": "5.5.1",
"_nodeVersion": "8.7.0",
"_npmUser": {
},
"dist": {
"integrity": "sha512-6gHiERpiDgtb3hjqpQH5\/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==",
"shasum": "2c03764f651a9f016ca0b7620421457b619151b9",
"tarball": "http:\/\/localhost:5555\/@scope\/pk1-test\/-\/@scope\/pk1-test-1.0.6.tgz"
}
}
},
"readme": "# test",
"_attachments": {
"@scope\/pk1-test-1.0.6.tgz": {
"content_type": "application\/octet-stream",
"data": "H4sIAAAAAAAAE+2W32vbMBDH85y\/QnjQp9qxLEeBMsbGlocNBmN7bFdQ5WuqxJaEpGQdo\/\/79KPeQsnIw5KUDX\/9IOvurLuz\/DHSjK\/YAiY6jcXSKjk6sMqypHWNdtmD6hlBI0wqQmo8nVbVqMR4OsNoVB66kF1aW8eML+Vv10m9oF\/jP6IfY4QyyTrILlD2eqkcm+gVzpdrJrPz4NuAsULJ4MZFWdBkbcByI7R79CRjx0ScCdnAvf+SkjUFWu8IubzBgXUhDPidQlfZ3BhlLpBUKDiQ1cDFrYDmKkNnZwjuhUM4808+xNVW8P2bMk1Y7vJrtLC1u1MmLPjBF40+Cc4ahV6GDmI\/DWygVRpMwVX3KtXUCg7Sxp7ff3nbt6TBFy65gK1iffsN41yoEHtdFbOiisWMH8bPvXUH0SP3k+KG3UBr+DFy7OGfEJr4x5iWVeS\/pLQe+D+FIv\/agIWI6GX66kFuIhT+1gDjrp\/4d7WAvAwEJPh0u14IufWkM0zaW2W6nLfM2lybgJ4LTJ0\/jWiAK8OcMjt8MW3OlfQppcuhhQ6k+2OgkK2Q8DssFPi\/IHpU9fz3\/+xj5NjDf8QFE39VmE4JDfzPCBn4P4X6\/f88f\/Pu47zomiPk2Lv\/dOv8h+P\/34\/D\/p9CL+Kp67mrGDRo0KBBp9ZPsETQegASAAA=",
"length": 512
}
}
}
module.exports = json;

View file

@ -1973,7 +1973,7 @@ compare-func@^1.3.1:
array-ify "^1.0.0"
dot-prop "^3.0.0"
component-emitter@^1.2.1:
component-emitter@^1.2.0, component-emitter@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
@ -2188,6 +2188,10 @@ cookie@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
cookiejar@^2.1.0:
version "2.1.1"
resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz#41ad57b1b555951ec171412a81942b1e8200d34a"
cookies@^0.7.0:
version "0.7.1"
resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.7.1.tgz#7c8a615f5481c61ab9f16c833731bcb8f663b99b"
@ -3521,6 +3525,14 @@ forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
form-data@^2.3.1, form-data@~2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf"
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.5"
mime-types "^2.1.12"
form-data@~2.1.1:
version "2.1.4"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1"
@ -3529,13 +3541,9 @@ form-data@~2.1.1:
combined-stream "^1.0.5"
mime-types "^2.1.12"
form-data@~2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf"
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.5"
mime-types "^2.1.12"
formidable@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9"
forwarded@~0.1.2:
version "0.1.2"
@ -5571,7 +5579,7 @@ merge@^1.1.3:
version "1.2.0"
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da"
methods@~1.1.2:
methods@^1.1.1, methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@ -6846,7 +6854,7 @@ q@^1.1.2, q@^1.4.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
qs@6.5.1, qs@~6.5.1:
qs@6.5.1, qs@^6.5.1, qs@~6.5.1:
version "6.5.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
@ -7089,7 +7097,7 @@ readable-stream@1.0:
isarray "0.0.1"
string_decoder "~0.10.x"
readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3:
readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
dependencies:
@ -8157,6 +8165,28 @@ sugarss@^1.0.0:
dependencies:
postcss "^6.0.14"
superagent@^3.0.0:
version "3.8.2"
resolved "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz#e4a11b9d047f7d3efeb3bbe536d9ec0021d16403"
dependencies:
component-emitter "^1.2.0"
cookiejar "^2.1.0"
debug "^3.1.0"
extend "^3.0.0"
form-data "^2.3.1"
formidable "^1.1.1"
methods "^1.1.1"
mime "^1.4.1"
qs "^6.5.1"
readable-stream "^2.0.5"
supertest@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/supertest/-/supertest-3.0.0.tgz#8d4bb68fd1830ee07033b1c5a5a9a4021c965296"
dependencies:
methods "~1.1.2"
superagent "^3.0.0"
supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
@ -8658,6 +8688,10 @@ vendors@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22"
verdaccio-auth-memory@^0.0.3:
version "0.0.3"
resolved "https://registry.npmjs.org/verdaccio-auth-memory/-/verdaccio-auth-memory-0.0.3.tgz#6d175a8959c04a46f1441fa09370ede3517acf5b"
verror@1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"