Client refactor, with pooled client (minor lint cleanup as well)

This commit is contained in:
Mark Cavage 2012-04-26 20:23:43 -07:00
parent 5be9dedbf6
commit 59ea20ffa2
10 changed files with 670 additions and 290 deletions

60
examples/pooled_client.js Normal file
View File

@ -0,0 +1,60 @@
var Logger = require('bunyan');
var ldap = require('../lib/index');
///
// Run the "inmemory.js" server in the same directory
///
function ifError(err) {
if (err) {
console.error(err.stack);
process.exit(1);
}
}
var LOG = new Logger({
name: 'ldapjs',
stream: process.stderr,
level: (process.env.LOG_LEVEL || 'info'),
serializers: Logger.stdSerializers
});
var MAX_CONNS = process.env.LDAP_MAX_CONNS || 10;
var client = ldap.createClient({
url: 'ldap://localhost:1389',
maxConnections: MAX_CONNS,
log: LOG
});
client.bind('cn=root', 'secret', function (err) {
ifError(err);
client.add('o=smartdc', {o: 'smartdc'}, function (err) {
ifError(err);
var finished = 0;
for (var i = 0; i < MAX_CONNS; i++) {
client.search('o=smartdc', function (err, res) {
ifError(err);
res.on('end', function () {
if (++finished === (MAX_CONNS - 1)) {
console.error('Go kill the LDAP server and restart it')
setTimeout(function () {
console.log('readding suffix');
client.add('o=smartdc', {o: 'smartdc'}, function (err) {
ifError(err);
client.unbind(function () {
console.log('All done');
process.exit(0);
});
});
}, 15000);
}
});
});
}
});
});

View File

@ -6,15 +6,15 @@ var net = require('net');
var tls = require('tls');
var util = require('util');
var Attribute = require('./attribute');
var Change = require('./change');
var Control = require('./controls/index').Control;
var Protocol = require('./protocol');
var dn = require('./dn');
var errors = require('./errors');
var filters = require('./filters');
var messages = require('./messages');
var url = require('./url');
var Attribute = require('../attribute');
var Change = require('../change');
var Control = require('../controls/index').Control;
var Protocol = require('../protocol');
var dn = require('../dn');
var errors = require('../errors');
var filters = require('../filters');
var messages = require('../messages');
var url = require('../url');
@ -38,29 +38,27 @@ var SearchReference = messages.SearchReference;
var SearchResponse = messages.SearchResponse;
var Parser = messages.Parser;
var Filter = filters.Filter;
var PresenceFilter = filters.PresenceFilter;
var ConnectionError = errors.ConnectionError;
var CMP_EXPECT = [errors.LDAP_COMPARE_TRUE, errors.LDAP_COMPARE_FALSE];
var MAX_MSGID = Math.pow(2, 31) - 1;
// node 0.6 got rid of FDs, so make up a client id for logging
var CLIENT_ID = 0;
///--- Internal Helpers
function xor() {
var b = false;
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] && !b) {
b = true;
} else if (arguments[i] && b) {
return false;
}
}
return b;
}
function nextClientId() {
if (++CLIENT_ID === MAX_MSGID)
return 1;
return CLIENT_ID;
}
function validateControls(controls) {
if (Array.isArray(controls)) {
@ -78,15 +76,120 @@ function validateControls(controls) {
}
function ConnectionError(message) {
errors.LDAPError.call(this,
'ConnectionError',
0x80, // LDAP_OTHER,
message,
null,
ConnectionError);
function setupSocket(socket, opts) {
var log = opts.log;
socket.ldap = {
id: opts.url ? opts.url.href : opts.socketPath,
messageID: 0,
messages: {},
getNextMessageID: function getNextMessageID() {
if (++socket.ldap.messageID >= MAX_MSGID)
socket.ldap.messageID = 1;
return socket.ldap.messageID;
},
parser: new Parser({
log: log
})
};
// This won't be set on TLS. So. Very. Annoying.
if (typeof (socket.setKeepAlive) !== 'function') {
socket.setKeepAlive = function setKeepAlive(enable, delay) {
return socket.socket ? socket.socket.setKeepAlive(enable, delay) : false;
};
}
// On close we have to walk the outstanding messages and go invoke their
// callback with an error
socket.on('close', function onClose(had_err) {
if (log.trace())
log.trace('close event had_err=%s', had_err ? 'yes' : 'no');
opts.emit('close', had_err);
Object.keys(socket.ldap.messages).forEach(function (msgid) {
var err;
if (socket.unbindMessageID !== parseInt(msgid, 10)) {
err = new ConnectionError(socket.ldap.id + ' closed');
} else {
err = new UnbindResponse({
messageID: msgid
});
err.status = 'unbind';
}
if (typeof (socket.ldap.messages[msgid]) === 'function') {
var callback = socket.ldap.messages[msgid];
delete socket.ldap.messages[msgid];
return callback(err);
} else if (socket.ldap.messages[msgid]) {
if (err instanceof Error)
socket.ldap.messages[msgid].emit('error', err);
delete socket.ldap.messages[msgid];
}
delete socket.ldap.parser;
delete socket.ldap;
return false;
});
});
socket.on('data', function onData(data) {
if (log.trace())
log.trace('data event: %s', util.inspect(data));
socket.ldap.parser.write(data);
});
socket.on('end', function onEnd() {
if (log.trace())
log.trace('end event');
opts.emit('end');
socket.end();
});
socket.on('error', function onError(err) {
if (log.trace())
log.trace({err: err}, 'error event: %s', new Error().stack);
if (opts.listeners('error').length)
opts.emit('error', err);
socket.end();
});
socket.on('timeout', function onTimeout() {
if (log.trace())
log.trace('timeout event');
opts.emit('socketTimeout');
socket.end();
});
// The "router"
socket.ldap.parser.on('message', function onMessage(message) {
message.connection = socket;
var callback = socket.ldap.messages[message.messageID];
if (!callback) {
log.error({message: message.json}, 'unsolicited message');
return false;
}
return callback(message);
});
socket.ldap.parser.on('error', function onParseError(err) {
log.debug({err: err}, 'parser error event');
if (opts.listeners('error').length)
opts.emit('error', err);
socket.end();
});
}
util.inherits(ConnectionError, errors.LDAPError);
@ -105,38 +208,24 @@ util.inherits(ConnectionError, errors.LDAPError);
* @throws {TypeError} on bad input.
*/
function Client(options) {
if (!options || typeof (options) !== 'object')
throw new TypeError('options (object) required');
if (options.url && typeof (options.url) !== 'string')
throw new TypeError('options.url (string) required');
if (options.socketPath && typeof (options.socketPath) !== 'string')
throw new TypeError('options.socketPath must be a string');
if (typeof (options.log) !== 'object')
throw new TypeError('options.log must be an object');
if (!xor(options.url, options.socketPath))
throw new TypeError('options.url ^ options.socketPath (String) required');
assert.ok(options);
EventEmitter.call(this, options);
var parsedUrl;
var _url;
if (options.url)
parsedUrl = url.parse(options.url);
_url = url.parse(options.url);
this.connection = null;
this.connectTimeout = options.connectTimeout || false;
this.connectOptions = {
port: parsedUrl ? parsedUrl.port : options.socketPath,
host: parsedUrl ? parsedUrl.hostname : undefined,
socketPath: options.socketPath || undefined
};
this.log = options.log;
this.secure = parsedUrl ? parsedUrl.secure : false;
this.timeout = options.timeout || false;
this.url = parsedUrl || false;
this.connectTimeout = parseInt((options.connectTimeout || 0), 10);
this.host = _url ? _url.hostname : undefined;
this.log = options.log.child({clazz: 'Client'}, true);
this.port = _url ? _url.port : false;
this.secure = _url ? _url.secure : false;
this.socketPath = options.socketPath || false;
this.timeout = parseInt((options.timeout || 0), 10);
this.url = _url;
// We'll emit a connect event when this is done
this.connect();
this.socket = this._connect();
}
util.inherits(Client, EventEmitter);
module.exports = Client;
@ -605,7 +694,7 @@ Client.prototype.unbind = function unbind(callback) {
if (typeof (callback) !== 'function')
throw new TypeError('callback must be a function');
if (!this.connection)
if (!this.socket)
return callback();
var req = new UnbindRequest();
@ -613,155 +702,47 @@ Client.prototype.unbind = function unbind(callback) {
};
/**
* Connects this client, either at construct time, or after an unbind has
* been called. Under normal circumstances you don't need to call this method.
*
* @param {Function} (optional) callback invoked when `connect` is emitted.
*/
Client.prototype.connect = function connect(callback) {
var c = null;
///--- Private API
Client.prototype._connect = function _connect() {
var log = this.log;
var opts = this.connectOptions;
var proto = this.secure ? tls : net;
var self = this;
var socket = null;
var timer = false;
c = proto.connect(opts.port, opts.host, function () {
function onConnect() {
if (timer)
clearTimeout(timer);
assert.ok(c.ldap);
assert.ok(socket.ldap);
c.ldap.id += c.fd ? (':' + c.fd) : '';
socket.ldap.id = nextClientId() + '__' + socket.ldap.id;
self.log = self.log.child({ldap_id: socket.ldap.id}, true);
if (log.trace())
log.trace('%s connect event', c.ldap.id);
log.trace('connect event');
self.connection = c;
self.emit('connect', c);
self.socket = socket;
self.emit('connect', socket);
}
return (typeof (callback) === 'function' ? callback(null, c) : false);
});
socket = proto.createConnection((this.port || this.socketPath),
(this.host ? this.host : onConnect),
(this.host ? onConnect : undefined));
setupSocket(socket, this);
if (this.connectTimeout) {
timer = setTimeout(function () {
c.destroy();
timer = setTimeout(function onConnectTimeout() {
socket.destroy();
self.emit('connectTimeout', new ConnectionError('timeout'));
self.emit('connectTimeout');
}, this.connectTimeout);
}
if (typeof (c.setKeepAlive) !== 'function') {
c.setKeepAlive = function setKeepAlive(enable, delay) {
return c.socket ? c.socket.setKeepAlive(enable, delay) : false;
};
}
c.ldap = {
id: self.url ? self.url.href : opts.socketPath,
messageID: 0,
messages: {},
get nextMessageID() {
if (++c.ldap.messageID >= MAX_MSGID)
c.ldap.messageID = 1;
return c.ldap.messageID;
},
parser: new Parser({
log: self.log
})
};
c.on('end', function () {
if (log.trace())
log.trace('%s end event', c.ldap.id);
c.end();
});
// On close we have to walk the outstanding messages and go invoke their
// callback with an error
c.on('close', function (had_err) {
if (log.trace())
log.trace('%s close event had_err=%s', c.ldap.id, had_err ? 'yes' : 'no');
Object.keys(c.ldap.messages).forEach(function (msgid) {
var err;
if (c.unbindMessageID !== parseInt(msgid, 10)) {
err = new ConnectionError(c.ldap.id + ' closed');
} else {
err = new UnbindResponse({
messageID: msgid
});
err.status = 'unbind';
}
if (typeof (c.ldap.messages[msgid]) === 'function') {
var callback = c.ldap.messages[msgid];
delete c.ldap.messages[msgid];
return callback(err);
} else if (c.ldap.messages[msgid]) {
if (err instanceof Error)
c.ldap.messages[msgid].emit('error', err);
delete c.ldap.messages[msgid];
}
delete c.ldap.parser;
delete c.ldap;
return false;
});
});
c.on('error', function (err) {
if (log.trace())
log.trace({err: err}, '%s error event', c.ldap.id);
if (self.listeners('error').length)
self.emit('error', err);
c.end();
});
c.on('timeout', function () {
if (log.trace())
log.trace('%s timeout event=%s', c.ldap.id);
self.emit('timeout');
c.end();
});
c.on('data', function (data) {
if (log.trace())
log.trace('%s data event: %s', c.ldap.id, util.inspect(data));
c.ldap.parser.write(data);
});
// The "router"
c.ldap.parser.on('message', function (message) {
message.connection = c;
var callback = c.ldap.messages[message.messageID];
if (!callback) {
log.error({message: message.json}, '%s: unsolicited message', c.ldap.id);
return false;
}
return callback(message);
});
c.ldap.parser.on('error', function (err) {
log.debug({err: err}, '%s parser error event', c.ldap.id, err);
if (self.listeners('error').length)
self.emit('error', err);
c.end();
});
return c;
return socket;
};
@ -771,98 +752,95 @@ Client.prototype._send = function _send(message, expect, emitter, callback) {
assert.ok(typeof (emitter) !== undefined);
assert.ok(callback);
var conn = this.connection;
var conn = this.socket;
var log = this.log;
var self = this;
var timer = false;
if (!conn)
return callback(new ConnectionError('no socket'));
message.messageID = conn.ldap.nextMessageID;
conn.ldap.messages[message.messageID] = function messageCallback(res) {
function _done(event, obj) {
if (emitter)
return emitter.emit(event, obj);
if (event === 'error')
return callback(obj);
return callback(null, obj);
} // end function _done(event, obj)
function messageCallback(msg) {
if (timer)
clearTimeout(timer);
if (log.debug())
log.debug({msg: msg ? msg.json : null}, 'response received');
if (expect === 'abandon')
return callback(null);
return _done('end', null);
if (self.log.debug())
self.log.debug({res: res.json}, '%s: response received', conn.ldap.id);
var err = null;
if (res instanceof LDAPResult) {
if (msg instanceof SearchEntry || msg instanceof SearchReference) {
var event = msg.constructor.name;
event = event[0].toLowerCase() + event.slice(1);
return _done(event, msg);
} else {
delete conn.ldap.messages[message.messageID];
if (expect.indexOf(res.status) === -1) {
err = errors.getError(res);
if (emitter)
return emitter.emit('error', err);
if (msg instanceof LDAPResult) {
if (expect.indexOf(msg.status) === -1)
return _done('error', errors.getError(msg));
return callback(err);
return _done('end', msg);
} else if (msg instanceof Error) {
return _done('error', msg);
} else {
return _done('error', new errors.ProtocolError(msg.type));
}
if (emitter)
return emitter.emit('end', res);
return callback(null, res);
} else if (res instanceof SearchEntry || res instanceof SearchReference) {
assert.ok(emitter);
var event = res.constructor.name;
event = event[0].toLowerCase() + event.slice(1);
return emitter.emit(event, res);
} else if (res instanceof Error) {
if (emitter)
return emitter.emit('error', res);
return callback(res);
}
} // end function messageCallback(msg)
delete conn.ldap.messages[message.messageID];
err = new errors.ProtocolError(res.type);
function onRequestTimeout() {
self.emit('timeout', message);
if (conn.ldap.messages[message.messageID]) {
conn.ldap.messages[message.messageID](new LDAPResult({
status: 80, // LDAP_OTHER
errorMessage: 'request timeout (client interrupt)'
}));
}
} // end function onRequestTimeout()
if (emitter)
return emitter.emit('error', err);
function writeCallback() {
if (expect === 'abandon') {
return callback(null);
} else if (expect === 'unbind') {
conn.unbindMessageID = message.id;
conn.end();
} else if (emitter) {
return callback(null, emitter);
}
return false;
} // end writeCallback()
return callback(err);
};
// Start actually doing something...
message.messageID = conn.ldap.getNextMessageID();
conn.ldap.messages[message.messageID] = messageCallback;
// If there's a user specified timeout, pick that up
if (this.timeout) {
timer = setTimeout(function () {
self.emit('timeout', message);
if (conn.ldap.messages[message.messageID]) {
conn.ldap.messages[message.messageID](new LDAPResult({
status: 80, // LDAP_OTHER
errorMessage: 'request timeout (client interrupt)'
}));
}
}, this.timeout);
if (self.timeout) {
log.debug('Setting timeout to %d', self.timeout);
timer = setTimeout(onRequestTimeout, self.timeout);
}
if (log.debug())
log.debug('sending request %j', message.json);
try {
// Finally send some data
if (this.log.debug())
this.log.debug({msg: message.json}, '%s: sending request', conn.ldap.id);
return conn.write(message.toBer(), function writeCallback() {
if (expect === 'abandon') {
return callback(null);
} else if (expect === 'unbind') {
conn.unbindMessageID = message.id;
conn.end();
} else if (emitter) {
return callback(null, emitter);
}
return false;
});
return conn.write(message.toBer(), writeCallback);
} catch (e) {
if (timer)
clearTimeout(timer);
conn.destroy();
delete self.connection;
log.debug({err: e}, 'Error writing message to socket');
return callback(e);
}
};

64
lib/client/index.js Normal file
View File

@ -0,0 +1,64 @@
// Copyright 2012 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var Logger = require('bunyan');
var Pool = require('generic-pool').Pool;
var Client = require('./client');
var ClientPool = require('./pool');
///--- Globals
var DEF_LOG = new Logger({
name: 'ldapjs',
component: 'client',
stream: process.stderr,
serializers: Logger.stdSerializers
});
///--- Functions
function xor() {
var b = false;
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] && !b) {
b = true;
} else if (arguments[i] && b) {
return false;
}
}
return b;
}
///--- Exports
module.exports = {
createClient: function createClient(options) {
if (typeof (options) !== 'object')
throw new TypeError('options (object) required');
if (options.url && typeof (options.url) !== 'string')
throw new TypeError('options.url (string) required');
if (options.socketPath && typeof (options.socketPath) !== 'string')
throw new TypeError('options.socketPath must be a string');
if (!xor(options.url, options.socketPath))
throw new TypeError('options.url ^ options.socketPath (String) required');
if (!options.log)
options.log = DEF_LOG;
if (typeof (options.log) !== 'object')
throw new TypeError('options.log must be an object');
if (options.maxConnections > 1)
return new ClientPool(options);
return new Client(options);
}
};

263
lib/client/pool.js Normal file
View File

@ -0,0 +1,263 @@
// Copyright 2012 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var pooling = require('pooling');
var ConnectionError = require('../errors').ConnectionError;
var BindResponse = require('../messages').BindResponse;
var Client = require('./client');
///--- Globals
var STD_OPS = [
'add',
'del',
'modify',
'modifyDN'
];
var RETURN_VAL_OPS = [
'compare',
'exop'
];
///--- Internal Functions
function createPool(options) {
assert.ok(options);
return pooling.createPool({
checkInterval: options.checkInterval,
log: options.log,
name: 'ldapjs_' + (options.url || options.socketPath),
max: options.maxConnections,
maxIdleTime: options.maxIdleTime,
create: function createConnection(callback) {
var client = new Client(options);
client.once('error', function (err) {
return callback(err);
});
client.once('connect', function onConnect() {
client.removeAllListeners('error');
if (!options.bindDN || !options.bindCredentials)
return callback(null, client);
function bindCallback(err, res) {
if (err)
return callback(err, null);
return callback(null, client);
}
return client.bind(options.bindDN,
options.bindCredentials,
options.bindControls || [],
bindCallback);
});
},
check: function check(client, callback) {
// just do a root dse search
client.search('', '(objectclass=*)', function (err, res) {
if (err)
return callback(err);
res.on('error', function (e) {
return callback(e);
});
return res.on('end', function () {
return callback(null);
});
});
},
destroy: function destroy(client) {
client.unbind(function () {});
}
});
}
///--- API
function ClientPool(options) {
assert.ok(options);
EventEmitter.call(this, options);
this.log = options.log.child({clazz: 'ClientPool'}, true);
this.options = {
bindDN: options.bindDN,
bindCredentials: options.bindCredentials,
bindControls: options.bindControls || [],
checkInterval: options.checkInterval,
connectTimeout: (options.connectTimeout || 0),
maxIdleTime: options.maxIdleTime,
maxConnections: options.maxConnections,
log: options.log,
socketPath: options.socketPath,
timeout: (options.timeout || 0),
url: options.url
};
this.pool = createPool(options);
}
util.inherits(ClientPool, EventEmitter);
module.exports = ClientPool;
STD_OPS.forEach(function (op) {
ClientPool.prototype[op] = function clientProxy() {
var args = Array.prototype.slice.call(arguments);
var cb = args.pop();
if (typeof (cb) !== 'function')
throw new TypeError('callback (Function) required');
var self = this;
return this.pool.acquire(function onAcquire(err, client) {
if (err)
return cb(err);
args.push(function proxyCallback(err, res) {
self.pool.release(client);
return cb(err, res);
});
try {
return Client.prototype[op].apply(client, args);
} catch (e) {
self.pool.release(client);
return cb(e);
}
});
};
});
RETURN_VAL_OPS.forEach(function (op) {
ClientPool.prototype[op] = function clientProxy() {
var args = Array.prototype.slice.call(arguments);
var cb = args.pop();
if (typeof (cb) !== 'function')
throw new TypeError('callback (Function) required');
var self = this;
return this.pool.acquire(function onAcquire(poolErr, client) {
if (poolErr)
return cb(poolErr);
args.push(function proxyCallback(err, val, res) {
self.pool.release(client);
return cb(err, val, res);
});
try {
return Client.prototype[op].apply(client, args);
} catch (e) {
self.pool.release(client);
return cb(e);
}
});
};
});
ClientPool.prototype.search = function search(base, opts, controls, callback) {
if (typeof (controls) === 'function') {
callback = controls;
controls = [];
}
var self = this;
return this.pool.acquire(function onAcquire(err, client) {
if (err)
return callback(err);
// This is largely in existence for search requests
client.timeout = self.timeout || client.timeout;
return client.search(base, opts, controls, function (err, res) {
function cleanup() {
self.pool.release(client);
}
if (err) {
cleanup();
return callback(err, res);
}
res.on('error', cleanup);
res.on('end', cleanup);
return callback(null, res);
});
});
};
ClientPool.prototype.abandon = function abandon(msgid, controls, callback) {
if (typeof (controls) === 'function') {
callback = controls;
controls = [];
}
this.log.error({
messageID: msgid
}, 'Abandon is not supported with connection pooling. Ignoring.');
return callback(null);
};
ClientPool.prototype.bind = function bind(dn, creds, controls, callback) {
if (typeof (controls) === 'function') {
callback = controls;
controls = [];
}
var self = this;
self.options.bindDN = null;
self.options.bindCredentials = null;
self.options.bindControls = null;
return this.pool.shutdown(function () {
self.pool = createPool(self.options);
return self.pool.acquire(function onAcquire(err, client) {
if (err)
return callback(err);
return client.bind(dn, creds, controls, function (err, res) {
self.pool.release(client);
if (err)
return callback(err, res);
self.options.bindDN = dn;
self.options.bindCredentials = creds;
self.options.bindControls = controls;
return callback(null, res);
});
});
});
};
ClientPool.prototype.unbind = function unbind(callback) {
return this.pool.shutdown(callback);
};

View File

@ -50,7 +50,10 @@ PagedResultsControl.prototype.parse = function parse(buffer) {
this._value = {};
this._value.size = ber.readInt();
this._value.cookie = ber.readString(asn1.Ber.OctetString, true);
if(!this._value.cookie) this._value.cookie = new Buffer(0); //readString returns '' instead of a zero-length buffer
//readString returns '' instead of a zero-length buffer
if (!this._value.cookie)
this._value.cookie = new Buffer(0);
return true;
}
@ -67,10 +70,11 @@ PagedResultsControl.prototype._toBer = function (ber) {
var writer = new BerWriter();
writer.startSequence();
writer.writeInt(this.value.size);
if(this.value.cookie && this.value.cookie.length>0)
if (this.value.cookie && this.value.cookie.length > 0) {
writer.writeBuffer(this.value.cookie, asn1.Ber.OctetString);
else
} else {
writer.writeString(''); //writeBuffer rejects zero-length buffers
}
writer.endSequence();
ber.writeBuffer(writer.buffer, 0x04);

View File

@ -139,3 +139,16 @@ module.exports.getMessage = function (code) {
var errObj = ERRORS[code];
return (errObj && errObj.message ? errObj.message : '');
};
function ConnectionError(message) {
LDAPError.call(this,
'ConnectionError',
0x80, // LDAP_OTHER,
message,
null,
ConnectionError);
}
util.inherits(ConnectionError, LDAPError);
module.exports.ConnectionError = ConnectionError;

View File

@ -2,7 +2,7 @@
var Logger = require('bunyan');
var Client = require('./client');
var client = require('./client');
var Attribute = require('./attribute');
var Change = require('./change');
var Protocol = require('./protocol');
@ -42,20 +42,7 @@ if (!String.prototype.endsWith) {
module.exports = {
Client: Client,
createClient: function (options) {
if (typeof (options) !== 'object')
throw new TypeError('options (object) required');
if (!options.log) {
options.log = new Logger({
name: 'ldapjs',
component: 'client',
stream: process.stderr
});
}
return new Client(options);
},
createClient: client.createClient,
Server: Server,
createServer: function (options) {

View File

@ -25,13 +25,14 @@
},
"dependencies": {
"asn1": "0.1.11",
"buffertools": "1.0.7",
"bunyan": "0.6.3",
"dtrace-provider": "0.0.6",
"nopt": "1.0.10"
"buffertools": "1.0.9",
"bunyan": "0.6.8",
"dtrace-provider": "0.0.7",
"nopt": "1.0.10",
"pooling": "0.1.0"
},
"devDependencies": {
"tap": "0.2",
"tap": "0.2.4",
"node-uuid": "1.3.3"
},
"scripts": {

View File

@ -1,5 +1,7 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var Logger = require('bunyan');
var test = require('tap').test;
var uuid = require('node-uuid');
@ -129,7 +131,14 @@ test('setup', function (t) {
server.listen(SOCKET, function () {
client = ldap.createClient({
socketPath: SOCKET,
reconnect: false // turn this off for unit testing
maxConnections: process.env.LDAP_MAX_CONNS || 5,
log: new Logger({
name: 'ldapjs_unit_test',
stream: process.stderr,
level: (process.env.LOG_LEVEL || 'info'),
serializers: Logger.stdSerializers,
idleTimeoutMillis: 10
})
});
t.ok(client);
t.end();
@ -138,16 +147,6 @@ test('setup', function (t) {
});
test('simple bind success', function (t) {
client.bind(BIND_DN, BIND_PW, function (err, res) {
t.ifError(err);
t.ok(res);
t.equal(res.status, 0);
t.end();
});
});
test('simple bind failure', function (t) {
client.bind(BIND_DN, uuid(), function (err, res) {
t.ok(err);
@ -164,6 +163,16 @@ test('simple bind failure', function (t) {
});
test('simple bind success', function (t) {
client.bind(BIND_DN, BIND_PW, function (err, res) {
t.ifError(err);
t.ok(res);
t.equal(res.status, 0);
t.end();
});
});
test('add success', function (t) {
var attrs = [
new Attribute({
@ -579,6 +588,7 @@ test('search timeout (GH-51)', function (t) {
});
});
test('unbind (GH-30)', function (t) {
client.unbind(function (err) {
t.ifError(err);

View File

@ -9,7 +9,7 @@ var getControl;
var PagedResultsControl;
function bufferEqual(t, a, b) {
t.equal(a.toString('hex'), b.toString('hex'))
t.equal(a.toString('hex'), b.toString('hex'));
}
@ -38,14 +38,14 @@ test('new with args', function (t) {
criticality: true,
value: {
size: 1000,
cookie: new Buffer([1,2,3])
cookie: new Buffer([1, 2, 3])
}
});
t.ok(c);
t.equal(c.type, '1.2.840.113556.1.4.319');
t.ok(c.criticality);
t.equal(c.value.size, 1000);
bufferEqual(t,c.value.cookie, new Buffer([1,2,3]));
bufferEqual(t, c.value.cookie, new Buffer([1, 2, 3]));
var writer = new BerWriter();
@ -57,7 +57,7 @@ test('new with args', function (t) {
t.equal(psc.type, '1.2.840.113556.1.4.319');
t.ok(psc.criticality);
t.equal(psc.value.size, 1000);
bufferEqual(t,psc.value.cookie, new Buffer([1,2,3]));
bufferEqual(t, psc.value.cookie, new Buffer([1, 2, 3]));
t.end();
});
@ -80,7 +80,7 @@ test('tober', function (t) {
t.equal(c.type, '1.2.840.113556.1.4.319');
t.ok(c.criticality);
t.equal(c.value.size, 20);
bufferEqual(t,c.value.cookie, new Buffer(0));
bufferEqual(t, c.value.cookie, new Buffer(0));
t.end();
});