refactor: refactor functional test server launch (wip)

This commit is contained in:
Juan Picado @jotadeveloper 2017-11-19 16:08:04 +01:00 committed by juanpicado
parent 5cc8405d4f
commit adf7f3adb3
5 changed files with 325 additions and 131 deletions

View File

@ -1,21 +1,26 @@
'use strict'; 'use strict';
require('./lib/startup'); // require('./lib/startup');
//import _ from 'lodash';
import {VerdaccioServer, VerdaccioConfig} from './lib/verdaccio-server';
const _ = require('lodash');
const assert = require('assert'); const assert = require('assert');
const exec = require('child_process').exec;
describe('functional test verdaccio', function() { describe('functional test verdaccio', function() {
const server = process.server;
const server2 = process.server2; const config1 = new VerdaccioConfig('./store/test-storage', '/store/config-1.yaml', 'http://localhost:55551/');
const server3 = process.server3; const config2 = new VerdaccioConfig('./store/test-storage2', '/store/config-2.yaml', 'http://localhost:55552/');
const config3 = new VerdaccioConfig('./store/test-storage3', '/store/config-3.yaml', 'http://localhost:55553/');
const server1 = new VerdaccioServer(config1);
const server2 = new VerdaccioServer(config2);
const server3 = new VerdaccioServer(config3);
before(function(done) { before(function(done) {
Promise.all([ Promise.all([
require('./lib/startup').start('./store/test-storage', '/store/config-1.yaml'), server1.start(),
require('./lib/startup').start('./store/test-storage2', '/store/config-2.yaml'), server2.start(),
require('./lib/startup').start('./store/test-storage3', '/store/config-3.yaml'), server3.start(),
]).then(() => { ]).then(() => {
done(); done();
}).catch(function(error) { }).catch(function(error) {
@ -24,91 +29,39 @@ describe('functional test verdaccio', function() {
}); });
before(function() {
return Promise.all([server, server2, server3].map(function(server) {
return server.debug().status(200).then(function(body) {
server.pid = body.pid;
return new Promise(function(resolve, reject) {
exec('lsof -p ' + Number(server.pid), function(err, result) {
if (_.isNil(err) === false) {
reject(err);
}
assert.equal(err, null);
server.fdlist = result.replace(/ +/g, ' ');
resolve();
});
});
});
}));
});
before(function testBasicAuthentication() {
return Promise.all([server, server2, server3].map(function(server) {
// log in on server1
return server.auth('test', 'test')
.status(201)
.body_ok(/'test'/);
}));
});
it('authenticate', function() { it('authenticate', function() {
/* test for before() */ /* test for before() */
}); });
require('./package/access')(); require('./package/access')();
require('./basic')(); // require('./basic')();
require('./gh29')(); // require('./gh29')();
require('./tags/tags')(); // require('./tags/tags')();
require('./package/gzip.spec')(); // require('./package/gzip.spec')();
require('./sanity/incomplete')(); // require('./sanity/incomplete')();
require('./sanity/mirror')(); // require('./sanity/mirror')();
require('./tags/preserve_tags.spec')(); // require('./tags/preserve_tags.spec')();
require('./readme/readme.spec')(); // require('./readme/readme.spec')();
require('./sanity/nullstorage')(); // require('./sanity/nullstorage')();
require('./performance/race')(); // require('./performance/race')();
require('./sanity/racycrash')(); // require('./sanity/racycrash')();
require('./package/scoped.spec')(); // require('./package/scoped.spec')();
require('./sanity/security')(); // require('./sanity/security')();
require('./adduser/adduser')(); // require('./adduser/adduser')();
require('./adduser/logout')(); // require('./adduser/logout')();
require('./tags/addtag.spec')(); // require('./tags/addtag.spec')();
require('./plugins/auth.spec')(); // require('./plugins/auth.spec')();
require('./plugins/middleware.spec')(); // require('./notifications/notify')();
require('./notifications/notify')(); // // // requires packages published to server1/server2
// // requires packages published to server1/server2 // require('./uplink.cache.spec')();
require('./uplink.cache.spec')(); // require('./uplink.auth.spec')();
require('./uplink.auth.spec')();
after(function(done) { after(function(done) {
const check = (server) => { const check = (server) => {
return new Promise(function(resolve, reject) { return server.stop();
exec(`lsof -p ${parseInt(server.pid, 10)}`, function(err, result) {
if (err) {
reject();
} else {
result = result.split('\n').filter(function(query) {
if (query.match(/TCP .*->.* \(ESTABLISHED\)/)) {
return false;
}
if (query.match(/\/libcrypt-[^\/]+\.so/)) {
return false;
}
if (query.match(/\/node_modules\/crypt3\/build\/Release/)) {
return false;
}
return true;
}).join('\n').replace(/ +/g, ' ');
assert.equal(server.fdlist, result);
resolve();
}
});
});
}; };
Promise.all([check(server), check(server2), check(server3)]).then(function() { Promise.all([check(server1), check(server2), check(server3)]).then(function() {
done(); done();
}, (reason) => { }, (reason) => {
assert.equal(reason, null); assert.equal(reason, null);

View File

@ -1,15 +1,20 @@
const assert = require('assert'); // @flow
const request = require('request');
import assert from 'assert';
import request from 'request';
import _ from 'lodash';
import type {IRequestPromise} from './types';
const requestData = Symbol('smart_request_data'); const requestData = Symbol('smart_request_data');
const _ = require('lodash');
export class PromiseAssert extends Promise { export class PromiseAssert extends Promise<any> implements IRequestPromise{
constructor(options) { constructor(options: any) {
super(options); super(options);
} }
status(expected) { status(expected: number) {
// $FlowFixMe
const selfData = this[requestData]; const selfData = this[requestData];
return injectResponse(this, this.then(function(body) { return injectResponse(this, this.then(function(body) {
@ -23,8 +28,9 @@ export class PromiseAssert extends Promise {
})); }));
} }
body_ok(expected) { body_ok(expected: any) {
const self_data = this[requestData]; // $FlowFixMe
const selfData = this[requestData];
return injectResponse(this, this.then(function(body) { return injectResponse(this, this.then(function(body) {
try { try {
@ -35,16 +41,17 @@ export class PromiseAssert extends Promise {
} }
assert.equal(body.error, null); assert.equal(body.error, null);
} catch(err) { } catch(err) {
self_data.error.message = err.message; selfData.error.message = err.message;
throw self_data.error; throw selfData.error;
} }
return body; return body;
})); }));
} }
body_error(expected) { body_error(expected: any) {
const self_data = this[requestData]; // $FlowFixMe
const selfData = this[requestData];
return injectResponse(this, this.then(function(body) { return injectResponse(this, this.then(function(body) {
try { try {
@ -55,19 +62,21 @@ export class PromiseAssert extends Promise {
} }
assert.equal(body.ok, null); assert.equal(body.ok, null);
} catch(err) { } catch(err) {
self_data.error.message = err.message; selfData.error.message = err.message;
throw self_data.error; throw selfData.error;
} }
return body; return body;
})); }));
} }
request(callback) { request(callback: any) {
// $FlowFixMe
callback(this[requestData].request); callback(this[requestData].request);
return this; return this;
} }
response(cb) { response(cb: any) {
// $FlowFixMe
const selfData = this[requestData]; const selfData = this[requestData];
return injectResponse(this, this.then(function(body) { return injectResponse(this, this.then(function(body) {
@ -76,25 +85,30 @@ export class PromiseAssert extends Promise {
})); }));
} }
send(data) { send(data: any) {
// $FlowFixMe
this[requestData].request.end(data); this[requestData].request.end(data);
return this; return this;
} }
} }
function injectResponse(smartObject, promise) { function injectResponse(smartObject: any, promise: Promise<any>): Promise<any> {
// $FlowFixMe
promise[requestData] = smartObject[requestData]; promise[requestData] = smartObject[requestData];
return promise; return promise;
} }
function smartRequest(options) {
const smartObject = {}; function smartRequest(options: any): Promise<any> {
const smartObject: any = {};
smartObject[requestData] = {}; smartObject[requestData] = {};
smartObject[requestData].error = Error(); smartObject[requestData].error = Error();
Error.captureStackTrace(smartObject[requestData].error, smartRequest); Error.captureStackTrace(smartObject[requestData].error, smartRequest);
const result = new PromiseAssert(function(resolve, reject) {
const promiseResult: Promise<any> = new PromiseAssert(function(resolve, reject) {
// store request reference on symbol
smartObject[requestData].request = request(options, function(err, res, body) { smartObject[requestData].request = request(options, function(err, res, body) {
if (err) { if (err) {
return reject(err); return reject(err);
@ -106,9 +120,7 @@ function smartRequest(options) {
}); });
}); });
// console.log("--result->", result); return injectResponse(smartObject, promiseResult);
return injectResponse(smartObject, result);
} }
export default smartRequest; export default smartRequest;

View File

@ -1,28 +1,34 @@
// @flow
const _ = require('lodash'); import _ from 'lodash';
import assert from 'assert'; import assert from 'assert';
import request from './request'; import smartRequest from './request';
import type {IServerBridge} from './types';
const buildAuthHeader = (user, pass) => { const buildAuthHeader = (user, pass): string => {
return `Basic ${(new Buffer(`${user}:${pass}`)).toString('base64')}`; return `Basic ${(new Buffer(`${user}:${pass}`)).toString('base64')}`;
}; };
class Server { export default class Server implements IServerBridge{
url: string;
userAgent: string;
authstr: string;
constructor(url) { constructor(url: string) {
this.url = url.replace(/\/$/, ''); this.url = url.replace(/\/$/, '');
this.userAgent = 'node/v8.1.2 linux x64'; this.userAgent = 'node/v8.1.2 linux x64';
this.authstr = buildAuthHeader('test', 'test'); this.authstr = buildAuthHeader('test', 'test');
} }
request(options) { request(options: any): any {
assert(options.uri); assert(options.uri);
const headers = options.headers || {}; const headers: any = options.headers || {};
headers.accept = headers.accept || 'application/json'; headers.accept = headers.accept || 'application/json';
headers['user-agent'] = headers['user-agent'] || this.userAgent; headers['user-agent'] = headers['user-agent'] || this.userAgent;
headers.authorization = headers.authorization || this.authstr; headers.authorization = headers.authorization || this.authstr;
return request({ return smartRequest({
url: this.url + options.uri, url: this.url + options.uri,
method: options.method || 'GET', method: options.method || 'GET',
headers: headers, headers: headers,
@ -31,7 +37,7 @@ class Server {
}); });
} }
auth(name, password) { auth(name: string, password: string) {
this.authstr = buildAuthHeader(name, password); this.authstr = buildAuthHeader(name, password);
return this.request({ return this.request({
uri: `/-/user/org.couchdb.user:${encodeURIComponent(name)}/-rev/undefined`, uri: `/-/user/org.couchdb.user:${encodeURIComponent(name)}/-rev/undefined`,
@ -48,7 +54,7 @@ class Server {
}); });
} }
logout(token) { logout(token: string) {
return this.request({ return this.request({
uri: `/-/user/token/${encodeURIComponent(token)}`, uri: `/-/user/token/${encodeURIComponent(token)}`,
method: 'DELETE', method: 'DELETE',
@ -56,14 +62,14 @@ class Server {
} }
getPackage(name) { getPackage(name: string) {
return this.request({ return this.request({
uri: `/${encodeURIComponent(name)}`, uri: `/${encodeURIComponent(name)}`,
method: 'GET', method: 'GET',
}); });
} }
putPackage(name, data) { putPackage(name: string, data) {
if (_.isObject(data) && !Buffer.isBuffer(data)) { if (_.isObject(data) && !Buffer.isBuffer(data)) {
data = JSON.stringify(data); data = JSON.stringify(data);
} }
@ -76,10 +82,11 @@ class Server {
}).send(data); }).send(data);
} }
putVersion(name, version, data) { putVersion(name: string, version: string, data: any) {
if (_.isObject(data) && !Buffer.isBuffer(data)) { if (_.isObject(data) && !Buffer.isBuffer(data)) {
data = JSON.stringify(data); data = JSON.stringify(data);
} }
return this.request({ return this.request({
uri: `/${encodeURIComponent(name)}/${encodeURIComponent(version)}/-tag/latest`, uri: `/${encodeURIComponent(name)}/${encodeURIComponent(version)}/-tag/latest`,
method: 'PUT', method: 'PUT',
@ -89,7 +96,7 @@ class Server {
}).send(data); }).send(data);
} }
getTarball(name, filename) { getTarball(name: string, filename: string) {
return this.request({ return this.request({
uri: `/${encodeURIComponent(name)}/-/${encodeURIComponent(filename)}`, uri: `/${encodeURIComponent(name)}/-/${encodeURIComponent(filename)}`,
method: 'GET', method: 'GET',
@ -97,7 +104,7 @@ class Server {
}); });
} }
putTarball(name, filename, data) { putTarball(name: string, filename: string, data: any) {
return this.request({ return this.request({
uri: `/${encodeURIComponent(name)}/-/${encodeURIComponent(filename)}/whatever`, uri: `/${encodeURIComponent(name)}/-/${encodeURIComponent(filename)}/whatever`,
method: 'PUT', method: 'PUT',
@ -107,7 +114,7 @@ class Server {
}).send(data); }).send(data);
} }
removeTarball(name) { removeTarball(name: string) {
return this.request({ return this.request({
uri: `/${encodeURIComponent(name)}/-rev/whatever`, uri: `/${encodeURIComponent(name)}/-rev/whatever`,
method: 'DELETE', method: 'DELETE',
@ -117,7 +124,7 @@ class Server {
}); });
} }
removeSingleTarball(name, filename) { removeSingleTarball(name: string, filename: string) {
return this.request({ return this.request({
uri: `/${encodeURIComponent(name)}/-/${filename}/-rev/whatever`, uri: `/${encodeURIComponent(name)}/-/${filename}/-rev/whatever`,
method: 'DELETE', method: 'DELETE',
@ -128,7 +135,7 @@ class Server {
} }
addTag(name, tag, version) { addTag(name: string, tag: string, version: string) {
return this.request({ return this.request({
uri: `/${encodeURIComponent(name)}/${encodeURIComponent(tag)}`, uri: `/${encodeURIComponent(name)}/${encodeURIComponent(tag)}`,
method: 'PUT', method: 'PUT',
@ -138,7 +145,7 @@ class Server {
}).send(JSON.stringify(version)); }).send(JSON.stringify(version));
} }
putTarballIncomplete(name, filename, data, size, cb) { putTarballIncomplete(name: string, filename: string, data: any, size: number, cb: Function) {
let promise = this.request({ let promise = this.request({
uri: `/${encodeURIComponent(name)}/-/${encodeURIComponent(filename)}/whatever`, uri: `/${encodeURIComponent(name)}/-/${encodeURIComponent(filename)}/whatever`,
method: 'PUT', method: 'PUT',
@ -171,7 +178,7 @@ class Server {
}); });
} }
addPackage(name) { addPackage(name: string) {
return this.putPackage(name, require('../fixtures/package')(name)) return this.putPackage(name, require('../fixtures/package')(name))
.status(201) .status(201)
.body_ok('created new package'); .body_ok('created new package');
@ -204,6 +211,4 @@ class Server {
}, },
}) })
} }
} }
export default Server;

View File

@ -0,0 +1,52 @@
// @flow
export interface IVerdaccioConfig {
storagePath: string;
configPath: string;
domainPath: string;
}
export interface IRequestPromise {
status(reason: any): any;
body_ok(reason: any): any;
body_error(reason: any): any;
request(reason: any): any;
response(reason: any): any;
send(reason: any): any;
}
export interface IServerProcess {
bridge: IServerBridge;
init(): Promise<any>;
stop(): Promise<any>;
getBridge(): IServerBridge;
notify(callback: Function): void;
cleanStorage(): Promise<any>;
}
declare class verdaccio$PromiseAssert<IRequestPromise> extends Promise<any> {
constructor(options: any): IRequestPromise;
}
export interface IServerBridge {
url: string;
userAgent: string;
authstr: string;
request(options: any): typeof verdaccio$PromiseAssert;
auth(name: string, password: string): IRequestPromise;
logout(token: string): Promise<any>;
auth(name: string, password: string): IRequestPromise;
getPackage(name: string): Promise<any>;
putPackage(name: string, data: any): Promise<any>;
putVersion(name: string, version: string, data: any): Promise<any>;
getTarball(name: string, filename: string): Promise<any>;
putTarball(name: string, filename: string, data: any): Promise<any>;
removeTarball(name: string): Promise<any>;
removeSingleTarball(name: string, filename: string): Promise<any>;
addTag(name: string, tag: string, version: string): Promise<any>;
putTarballIncomplete(name: string, filename: string, data: any, size: number, cb: Function): Promise<any>;
addPackage(name: string): Promise<any>;
whoami(): Promise<any>;
ping(): Promise<any>;
debug(): IRequestPromise;
}

View File

@ -0,0 +1,172 @@
// @flow
import _ from 'lodash';
import express from 'express';
import rimRaf from 'rimraf';
import path from 'path';
import {fork} from 'child_process';
import bodyParser from 'body-parser';
import Server from './server';
import type {IVerdaccioConfig, IServerBridge, IServerProcess} from './types';
export class ExpressServer {
static start(): Promise<any> {
return new Promise(function(resolve, reject) {
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.listen(55550, function starExpressServer() {
resolve();
});
});
}
}
export class VerdaccioConfig implements IVerdaccioConfig {
storagePath: string;
configPath: string;
domainPath: string;
constructor(storagePath: string, configPath: string, domainPath: string) {
this.storagePath = storagePath;
this.configPath = configPath;
this.domainPath = domainPath;
}
}
class VerdaccioProcess implements IServerProcess {
bridge: IServerBridge;
config: IVerdaccioConfig;
childFork: any;
constructor(config: IVerdaccioConfig) {
this.config = config;
this.bridge = new Server(config.domainPath);
}
init(): Promise<any> {
return new Promise(function(resolve, reject) {
const verdaccioRegisterWrap = path.join(__dirname, '/../../helper/verdaccio-test');
const storageDir: string = path.join(__dirname, `/../${this.config.storagePath}`);
const configPath: string = path.join(__dirname, '../', this.config.configPath);
rimRaf(storageDir, function(err) {
if (_.isNil(err) === false) {
reject(err);
}
// const filteredArguments = process.execArgv = process.execArgv.filter(function(x) {
// // filter out --debug-brk and --inspect-brk since Node7
// return (x.indexOf('--debug-brk') === -1 && x.indexOf('--inspect-brk') === -1);
// });
this.childFork = fork(verdaccioRegisterWrap,
['-c', configPath],
{
// silent: !process.env.TRAVIS,
silent: false,
env: {
BABEL_ENV: 'registry'
}
}
);
// forks.push(childFork);
this.childFork.on('message', function(msg) {
if ('verdaccio_started' in msg) {
resolve(this.childFork);
}
});
this.childFork.on('error', function(err) {
reject(err);
});
this.childFork.on('disconnect', function(err) {
reject(err);
});
this.childFork.on('exit', function(err) {
reject(err);
});
//process.execArgv = filteredArguments;
});
});
}
getBridge(): IServerBridge {
return this.bridge;
}
stop(): Promise<any> {
return new Promise(function(resolve, reject) {
});
}
notify(callback: Function): void {
callback();
}
cleanStorage(): Promise<any> {
return new Promise(function(resolve, reject) {
rimRaf(this.config.storagePath, function(err) {
if(_.isNil(err) === false) {
reject(err);
}
});
});
}
}
export class VerdaccioServer {
serverProcess: IServerProcess;
pid: number;
constructor(config: IVerdaccioConfig) {
this.serverProcess = new VerdaccioProcess(config);
}
start(): Promise<any> {
return this.serverProcess.init().then(this.debugCheck);
}
debugCheck(): Promise<any>{
return new Promise((resolve, reject) => {
this.serverProcess.getBridge().debug().status(200).then((body) => {
if (_.isNil(body.pid)) {
reject();
}
this.pid = body.pid;
return this.authTestUser().catch(function(reason: any) {
reject(reason);
});
}).catch(function(reason: any) {
reject(reason);
});
});
}
authTestUser(): Promise<any> {
return this.serverProcess.getBridge().auth('test', 'test')
.status(201)
.body_ok(/'test'/);
}
stop() {
}
notify() {
}
}