1688 lines
43 KiB
JavaScript
1688 lines
43 KiB
JavaScript
'use strict'
|
|
|
|
const util = require('util')
|
|
const assert = require('assert')
|
|
const tap = require('tap')
|
|
const vasync = require('vasync')
|
|
const getPort = require('get-port')
|
|
const { getSock, uuid } = require('./utils')
|
|
const ldap = require('../lib')
|
|
const { Attribute, Change } = ldap
|
|
|
|
const SUFFIX = 'dc=test'
|
|
const LDAP_CONNECT_TIMEOUT = process.env.LDAP_CONNECT_TIMEOUT || 0
|
|
const BIND_DN = 'cn=root'
|
|
const BIND_PW = 'secret'
|
|
|
|
tap.beforeEach((t) => {
|
|
return new Promise(resolve => {
|
|
t.context.socketPath = getSock()
|
|
t.context.server = ldap.createServer()
|
|
|
|
const server = t.context.server
|
|
server.bind(BIND_DN, function (req, res, next) {
|
|
if (req.credentials !== BIND_PW) { return next(new ldap.InvalidCredentialsError('Invalid password')) }
|
|
|
|
res.end()
|
|
return next()
|
|
})
|
|
|
|
server.add(SUFFIX, function (req, res, next) {
|
|
res.end()
|
|
return next()
|
|
})
|
|
|
|
server.compare(SUFFIX, function (req, res, next) {
|
|
res.end(req.value === 'test')
|
|
return next()
|
|
})
|
|
|
|
server.del(SUFFIX, function (req, res, next) {
|
|
res.end()
|
|
return next()
|
|
})
|
|
|
|
// LDAP whoami
|
|
server.exop('1.3.6.1.4.1.4203.1.11.3', function (req, res, next) {
|
|
res.value = 'u:xxyyz@EXAMPLE.NET'
|
|
res.end()
|
|
return next()
|
|
})
|
|
|
|
server.modify(SUFFIX, function (req, res, next) {
|
|
res.end()
|
|
return next()
|
|
})
|
|
|
|
server.modifyDN(SUFFIX, function (req, res, next) {
|
|
res.end()
|
|
return next()
|
|
})
|
|
|
|
server.modifyDN('cn=issue-480', function (req, res, next) {
|
|
assert(req.newRdn.toString().length > 132)
|
|
res.end()
|
|
return next()
|
|
})
|
|
|
|
server.search('dc=slow', function (req, res, next) {
|
|
res.send({
|
|
dn: 'dc=slow',
|
|
attributes: {
|
|
you: 'wish',
|
|
this: 'was',
|
|
faster: '.'
|
|
}
|
|
})
|
|
setTimeout(function () {
|
|
res.end()
|
|
next()
|
|
}, 250)
|
|
})
|
|
|
|
server.search('dc=timeout', function () {
|
|
// Cause the client to timeout by not sending a response.
|
|
})
|
|
|
|
server.search(SUFFIX, function (req, res, next) {
|
|
if (req.dn.equals('cn=ref,' + SUFFIX)) {
|
|
res.send(res.createSearchReference('ldap://localhost'))
|
|
} else if (req.dn.equals('cn=bin,' + SUFFIX)) {
|
|
res.send(res.createSearchEntry({
|
|
objectName: req.dn,
|
|
attributes: {
|
|
'foo;binary': 'wr0gKyDCvCA9IMK+',
|
|
gb18030: Buffer.from([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA]),
|
|
objectclass: 'binary'
|
|
}
|
|
}))
|
|
} else {
|
|
const e = res.createSearchEntry({
|
|
objectName: req.dn,
|
|
attributes: {
|
|
cn: ['unit', 'test'],
|
|
SN: 'testy'
|
|
}
|
|
})
|
|
res.send(e)
|
|
res.send(e)
|
|
}
|
|
|
|
res.end()
|
|
return next()
|
|
})
|
|
|
|
server.search('cn=sizelimit', function (req, res, next) {
|
|
const sizeLimit = 200
|
|
for (let i = 0; i < 1000; i++) {
|
|
if (req.sizeLimit > 0 && i >= req.sizeLimit) {
|
|
break
|
|
} else if (i > sizeLimit) {
|
|
res.end(ldap.LDAP_SIZE_LIMIT_EXCEEDED)
|
|
return next()
|
|
}
|
|
res.send({
|
|
dn: util.format('o=%d, cn=sizelimit', i),
|
|
attributes: {
|
|
o: [i],
|
|
objectclass: ['pagedResult']
|
|
}
|
|
})
|
|
}
|
|
res.end()
|
|
return next()
|
|
})
|
|
|
|
server.search('cn=paged', function (req, res, next) {
|
|
const min = 0
|
|
const max = 1000
|
|
|
|
function sendResults (start, end) {
|
|
start = (start < min) ? min : start
|
|
end = (end > max || end < min) ? max : end
|
|
let i
|
|
for (i = start; i < end; i++) {
|
|
res.send({
|
|
dn: util.format('o=%d, cn=paged', i),
|
|
attributes: {
|
|
o: [i],
|
|
objectclass: ['pagedResult']
|
|
}
|
|
})
|
|
}
|
|
return i
|
|
}
|
|
|
|
let cookie = null
|
|
let pageSize = 0
|
|
req.controls.forEach(function (control) {
|
|
if (control.type === ldap.PagedResultsControl.OID) {
|
|
pageSize = control.value.size
|
|
cookie = control.value.cookie
|
|
}
|
|
})
|
|
|
|
if (cookie && Buffer.isBuffer(cookie)) {
|
|
// Do simple paging
|
|
let first = min
|
|
if (cookie.length !== 0) {
|
|
first = parseInt(cookie.toString(), 10)
|
|
}
|
|
const last = sendResults(first, first + pageSize)
|
|
|
|
let resultCookie
|
|
if (last < max) {
|
|
resultCookie = Buffer.from(last.toString())
|
|
} else {
|
|
resultCookie = Buffer.from('')
|
|
}
|
|
res.controls.push(new ldap.PagedResultsControl({
|
|
value: {
|
|
size: pageSize, // correctness not required here
|
|
cookie: resultCookie
|
|
}
|
|
}))
|
|
res.end()
|
|
next()
|
|
} else {
|
|
// don't allow non-paged searches for this test endpoint
|
|
next(new ldap.UnwillingToPerformError())
|
|
}
|
|
})
|
|
server.search('cn=sssvlv', function (req, res, next) {
|
|
const min = 0
|
|
const max = 100
|
|
const results = []
|
|
let o = 'aa'
|
|
for (let i = min; i < max; i++) {
|
|
results.push({
|
|
dn: util.format('o=%s, cn=sssvlv', o),
|
|
attributes: {
|
|
o: [o],
|
|
objectclass: ['sssvlvResult']
|
|
}
|
|
})
|
|
o = ((parseInt(o, 36) + 1).toString(36)).replace(/0/g, 'a')
|
|
}
|
|
function sendResults (start, end, sortBy, sortDesc) {
|
|
start = (start < min) ? min : start
|
|
end = (end > max || end < min) ? max : end
|
|
const sorted = results.sort((a, b) => {
|
|
if (a.attributes[sortBy][0] < b.attributes[sortBy][0]) {
|
|
return sortDesc ? 1 : -1
|
|
} else if (a.attributes[sortBy][0] > b.attributes[sortBy][0]) {
|
|
return sortDesc ? -1 : 1
|
|
}
|
|
return 0
|
|
})
|
|
for (let i = start; i < end; i++) {
|
|
res.send(sorted[i])
|
|
}
|
|
}
|
|
let sortBy = null
|
|
let sortDesc = null
|
|
let afterCount = null
|
|
let targetOffset = null
|
|
req.controls.forEach(function (control) {
|
|
if (control.type === ldap.ServerSideSortingRequestControl.OID) {
|
|
sortBy = control.value[0].attributeType
|
|
sortDesc = control.value[0].reverseOrder
|
|
}
|
|
if (control.type === ldap.VirtualListViewRequestControl.OID) {
|
|
afterCount = control.value.afterCount
|
|
targetOffset = control.value.targetOffset
|
|
}
|
|
})
|
|
if (sortBy) {
|
|
if (afterCount && targetOffset) {
|
|
sendResults(targetOffset - 1, (targetOffset + afterCount), sortBy, sortDesc)
|
|
} else {
|
|
sendResults(min, max, sortBy, sortDesc)
|
|
}
|
|
res.end()
|
|
next()
|
|
} else {
|
|
next(new ldap.UnwillingToPerformError())
|
|
}
|
|
})
|
|
|
|
server.search('cn=pagederr', function (req, res, next) {
|
|
let cookie = null
|
|
req.controls.forEach(function (control) {
|
|
if (control.type === ldap.PagedResultsControl.OID) {
|
|
cookie = control.value.cookie
|
|
}
|
|
})
|
|
if (cookie && Buffer.isBuffer(cookie) && cookie.length === 0) {
|
|
// send first "page"
|
|
res.send({
|
|
dn: util.format('o=result, cn=pagederr'),
|
|
attributes: {
|
|
o: 'result',
|
|
objectclass: ['pagedResult']
|
|
}
|
|
})
|
|
res.controls.push(new ldap.PagedResultsControl({
|
|
value: {
|
|
size: 2,
|
|
cookie: Buffer.from('a')
|
|
}
|
|
}))
|
|
res.end()
|
|
return next()
|
|
} else {
|
|
// send error instead of second page
|
|
res.end(ldap.LDAP_SIZE_LIMIT_EXCEEDED)
|
|
return next()
|
|
}
|
|
})
|
|
|
|
server.search('dc=empty', function (req, res, next) {
|
|
res.send({
|
|
dn: 'dc=empty',
|
|
attributes: {
|
|
member: [],
|
|
'member;range=0-1': ['cn=user1, dc=empty', 'cn=user2, dc=empty']
|
|
}
|
|
})
|
|
res.end()
|
|
return next()
|
|
})
|
|
|
|
server.search('cn=busy', function (req, res, next) {
|
|
next(new ldap.BusyError('too much to do'))
|
|
})
|
|
|
|
server.search('', function (req, res, next) {
|
|
if (req.dn.toString() === '') {
|
|
res.send({
|
|
dn: '',
|
|
attributes: {
|
|
objectclass: ['RootDSE', 'top']
|
|
}
|
|
})
|
|
res.end()
|
|
} else {
|
|
// Turn away any other requests (since '' is the fallthrough route)
|
|
res.errorMessage = 'No tree found for: ' + req.dn.toString()
|
|
res.end(ldap.LDAP_NO_SUCH_OBJECT)
|
|
}
|
|
return next()
|
|
})
|
|
|
|
server.unbind(function (req, res, next) {
|
|
res.end()
|
|
return next()
|
|
})
|
|
|
|
server.listen(t.context.socketPath, function () {
|
|
const client = ldap.createClient({
|
|
connectTimeout: parseInt(LDAP_CONNECT_TIMEOUT, 10),
|
|
socketPath: t.context.socketPath
|
|
})
|
|
t.context.client = client
|
|
client.on('connect', () => resolve())
|
|
})
|
|
})
|
|
})
|
|
|
|
tap.afterEach((t) => {
|
|
return new Promise(resolve => {
|
|
t.context.client.unbind((err) => {
|
|
t.error(err)
|
|
t.context.server.close(() => resolve())
|
|
})
|
|
})
|
|
})
|
|
|
|
tap.test('createClient', t => {
|
|
t.test('requires an options object', async t => {
|
|
const match = /options.+required/
|
|
t.throws(() => ldap.createClient(), match)
|
|
t.throws(() => ldap.createClient([]), match)
|
|
t.throws(() => ldap.createClient(''), match)
|
|
t.throws(() => ldap.createClient(42), match)
|
|
})
|
|
|
|
t.test('url must be a string or array', async t => {
|
|
const match = /options\.url \(string\|array\) required/
|
|
t.throws(() => ldap.createClient({ url: {} }), match)
|
|
t.throws(() => ldap.createClient({ url: 42 }), match)
|
|
})
|
|
|
|
t.test('socketPath must be a string', async t => {
|
|
const match = /options\.socketPath must be a string/
|
|
t.throws(() => ldap.createClient({ socketPath: {} }), match)
|
|
t.throws(() => ldap.createClient({ socketPath: [] }), match)
|
|
t.throws(() => ldap.createClient({ socketPath: 42 }), match)
|
|
})
|
|
|
|
t.test('cannot supply both url and socketPath', async t => {
|
|
t.throws(
|
|
() => ldap.createClient({ url: 'foo', socketPath: 'bar' }),
|
|
/options\.url \^ options\.socketPath \(String\) required/
|
|
)
|
|
})
|
|
|
|
t.test('must supply at least url or socketPath', async t => {
|
|
t.throws(
|
|
() => ldap.createClient({}),
|
|
/options\.url \^ options\.socketPath \(String\) required/
|
|
)
|
|
})
|
|
|
|
t.test('exception from bad createClient parameter (issue #418)', t => {
|
|
try {
|
|
// This port number is totally invalid. It will cause the URL parser
|
|
// to throw an exception that should be caught.
|
|
ldap.createClient({ url: 'ldap://127.0.0.1:13891389' })
|
|
} catch (error) {
|
|
t.ok(error)
|
|
t.end()
|
|
}
|
|
})
|
|
|
|
t.test('url array is correctly assigned', async t => {
|
|
getPort().then(function (unusedPortNumber) {
|
|
const client = ldap.createClient({
|
|
url: [
|
|
`ldap://127.0.0.1:${unusedPortNumber}`,
|
|
`ldap://127.0.0.2:${unusedPortNumber}`
|
|
],
|
|
connectTimeout: 1
|
|
})
|
|
client.on('connectTimeout', () => {})
|
|
client.on('connectError', () => {})
|
|
client.on('connectRefused', () => {})
|
|
|
|
t.equal(client.urls.length, 2)
|
|
})
|
|
})
|
|
|
|
// TODO: this test is really flaky. It would be better if we could validate
|
|
// the options _withouth_ having to connect to a server.
|
|
// t.test('attaches a child function to logger', async t => {
|
|
// /* eslint-disable-next-line */
|
|
// let client
|
|
// const logger = Object.create(require('abstract-logging'))
|
|
// const socketPath = getSock()
|
|
// const server = ldap.createServer()
|
|
// server.listen(socketPath, () => {})
|
|
// t.teardown(() => {
|
|
// client.unbind(() => server.close())
|
|
// })
|
|
|
|
// client = ldap.createClient({ socketPath, log: logger })
|
|
// t.ok(logger.child)
|
|
// t.ok(typeof client.log.child === 'function')
|
|
// })
|
|
|
|
t.end()
|
|
})
|
|
|
|
tap.test('simple bind failure', function (t) {
|
|
t.context.client.bind(BIND_DN, uuid(), function (err, res) {
|
|
t.ok(err)
|
|
t.notOk(res)
|
|
|
|
t.ok(err instanceof ldap.InvalidCredentialsError)
|
|
t.ok(err instanceof Error)
|
|
t.ok(err.dn)
|
|
t.ok(err.message)
|
|
t.ok(err.stack)
|
|
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('simple bind success', function (t) {
|
|
t.context.client.bind(BIND_DN, BIND_PW, function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
t.equal(res.status, 0)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('simple anonymous bind (empty credentials)', function (t) {
|
|
t.context.client.bind('', '', function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
t.equal(res.status, 0)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('auto-bind bad credentials', function (t) {
|
|
const clt = ldap.createClient({
|
|
socketPath: t.context.socketPath,
|
|
bindDN: BIND_DN,
|
|
bindCredentials: 'totallybogus'
|
|
})
|
|
clt.once('error', function (err) {
|
|
t.equal(err.code, ldap.LDAP_INVALID_CREDENTIALS)
|
|
t.ok(clt._socket.destroyed, 'expect socket to be destroyed')
|
|
clt.destroy()
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('auto-bind success', function (t) {
|
|
const clt = ldap.createClient({
|
|
socketPath: t.context.socketPath,
|
|
bindDN: BIND_DN,
|
|
bindCredentials: BIND_PW
|
|
})
|
|
clt.once('connect', function () {
|
|
t.ok(clt)
|
|
clt.destroy()
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('add success', function (t) {
|
|
const attrs = [
|
|
new Attribute({
|
|
type: 'cn',
|
|
vals: ['test']
|
|
})
|
|
]
|
|
t.context.client.add('cn=add, ' + SUFFIX, attrs, function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
t.equal(res.status, 0)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('add success with object', function (t) {
|
|
const entry = {
|
|
cn: ['unit', 'add'],
|
|
sn: 'test'
|
|
}
|
|
t.context.client.add('cn=add, ' + SUFFIX, entry, function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
t.equal(res.status, 0)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('compare success', function (t) {
|
|
t.context.client.compare('cn=compare, ' + SUFFIX, 'cn', 'test', function (err, matched, res) {
|
|
t.error(err)
|
|
t.ok(matched)
|
|
t.ok(res)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('compare false', function (t) {
|
|
t.context.client.compare('cn=compare, ' + SUFFIX, 'cn', 'foo', function (err, matched, res) {
|
|
t.error(err)
|
|
t.notOk(matched)
|
|
t.ok(res)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('compare bad suffix', function (t) {
|
|
t.context.client.compare('cn=' + uuid(), 'cn', 'foo', function (err, matched, res) {
|
|
t.ok(err)
|
|
t.ok(err instanceof ldap.NoSuchObjectError)
|
|
t.notOk(matched)
|
|
t.notOk(res)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('delete success', function (t) {
|
|
t.context.client.del('cn=delete, ' + SUFFIX, function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('delete with control (GH-212)', function (t) {
|
|
const control = new ldap.Control({
|
|
type: '1.2.3.4',
|
|
criticality: false
|
|
})
|
|
t.context.client.del('cn=delete, ' + SUFFIX, control, function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('exop success', function (t) {
|
|
t.context.client.exop('1.3.6.1.4.1.4203.1.11.3', function (err, value, res) {
|
|
t.error(err)
|
|
t.ok(value)
|
|
t.ok(res)
|
|
t.equal(value, 'u:xxyyz@EXAMPLE.NET')
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('exop invalid', function (t) {
|
|
t.context.client.exop('1.2.3.4', function (err, res) {
|
|
t.ok(err)
|
|
t.ok(err instanceof ldap.ProtocolError)
|
|
t.notOk(res)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('bogus exop (GH-17)', function (t) {
|
|
t.context.client.exop('cn=root', function (err) {
|
|
t.ok(err)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('modify success', function (t) {
|
|
const change = new Change({
|
|
type: 'Replace',
|
|
modification: new Attribute({
|
|
type: 'cn',
|
|
vals: ['test']
|
|
})
|
|
})
|
|
t.context.client.modify('cn=modify, ' + SUFFIX, change, function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
t.equal(res.status, 0)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('modify change plain object success', function (t) {
|
|
const change = new Change({
|
|
type: 'Replace',
|
|
modification: {
|
|
cn: 'test'
|
|
}
|
|
})
|
|
t.context.client.modify('cn=modify, ' + SUFFIX, change, function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
t.equal(res.status, 0)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
// https://github.com/ldapjs/node-ldapjs/pull/435
|
|
tap.test('can delete attributes', function (t) {
|
|
const change = new Change({
|
|
type: 'Delete',
|
|
modification: { cn: null }
|
|
})
|
|
t.context.client.modify('cn=modify,' + SUFFIX, change, function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
t.equal(res.status, 0)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('modify array success', function (t) {
|
|
const changes = [
|
|
new Change({
|
|
operation: 'Replace',
|
|
modification: new Attribute({
|
|
type: 'cn',
|
|
vals: ['test']
|
|
})
|
|
}),
|
|
new Change({
|
|
operation: 'Delete',
|
|
modification: new Attribute({
|
|
type: 'sn'
|
|
})
|
|
})
|
|
]
|
|
t.context.client.modify('cn=modify, ' + SUFFIX, changes, function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
t.equal(res.status, 0)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('modify change plain object success (GH-31)', function (t) {
|
|
const change = {
|
|
type: 'replace',
|
|
modification: {
|
|
cn: 'test',
|
|
sn: 'bar'
|
|
}
|
|
}
|
|
t.context.client.modify('cn=modify, ' + SUFFIX, change, function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
t.equal(res.status, 0)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('modify DN new RDN only', function (t) {
|
|
t.context.client.modifyDN('cn=old, ' + SUFFIX, 'cn=new', function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
t.equal(res.status, 0)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('modify DN new superior', function (t) {
|
|
t.context.client.modifyDN('cn=old, ' + SUFFIX, 'cn=new, dc=foo', function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
t.equal(res.status, 0)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('modify DN excessive length (GH-480)', function (t) {
|
|
t.context.client.modifyDN('cn=issue-480', 'cn=a292979f2c86d513d48bbb9786b564b3c5228146e5ba46f404724e322544a7304a2b1049168803a5485e2d57a544c6a0d860af91330acb77e5907a9e601ad1227e80e0dc50abe963b47a004f2c90f570450d0e920d15436fdc771e3bdac0487a9735473ed3a79361d1778d7e53a7fb0e5f01f97a75ef05837d1d5496fc86968ff47fcb64', function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
t.equal(res.status, 0)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('search basic', function (t) {
|
|
t.context.client.search('cn=test, ' + SUFFIX, '(objectclass=*)', function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
let gotEntry = 0
|
|
res.on('searchEntry', function (entry) {
|
|
t.ok(entry)
|
|
t.ok(entry instanceof ldap.SearchEntry)
|
|
t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
|
|
t.ok(entry.attributes)
|
|
t.ok(entry.attributes.length)
|
|
t.equal(entry.attributes[0].type, 'cn')
|
|
t.equal(entry.attributes[1].type, 'SN')
|
|
t.ok(entry.object)
|
|
gotEntry++
|
|
})
|
|
res.on('error', function (err) {
|
|
t.fail(err)
|
|
})
|
|
res.on('end', function (res) {
|
|
t.ok(res)
|
|
t.ok(res instanceof ldap.SearchResponse)
|
|
t.equal(res.status, 0)
|
|
t.equal(gotEntry, 2)
|
|
t.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
tap.test('GH-602 search basic with delayed event listener binding', function (t) {
|
|
t.context.client.search('cn=test, ' + SUFFIX, '(objectclass=*)', function (err, res) {
|
|
t.error(err)
|
|
setTimeout(() => {
|
|
let gotEntry = 0
|
|
res.on('searchEntry', function () {
|
|
gotEntry++
|
|
})
|
|
res.on('error', function (err) {
|
|
t.fail(err)
|
|
})
|
|
res.on('end', function () {
|
|
t.equal(gotEntry, 2)
|
|
t.end()
|
|
})
|
|
}, 100)
|
|
})
|
|
})
|
|
|
|
tap.test('search sizeLimit', function (t) {
|
|
t.test('over limit', function (t2) {
|
|
t.context.client.search('cn=sizelimit', {}, function (err, res) {
|
|
t2.error(err)
|
|
res.on('error', function (error) {
|
|
t2.equal(error.name, 'SizeLimitExceededError')
|
|
t2.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
t.test('under limit', function (t2) {
|
|
const limit = 100
|
|
t.context.client.search('cn=sizelimit', { sizeLimit: limit }, function (err, res) {
|
|
t2.error(err)
|
|
let count = 0
|
|
res.on('searchEntry', function () {
|
|
count++
|
|
})
|
|
res.on('end', function () {
|
|
t2.pass()
|
|
t2.equal(count, limit)
|
|
t2.end()
|
|
})
|
|
res.on('error', t2.error.bind(t))
|
|
})
|
|
})
|
|
|
|
t.end()
|
|
})
|
|
|
|
tap.test('search paged', { timeout: 10000 }, function (t) {
|
|
t.test('paged - no pauses', function (t2) {
|
|
let countEntries = 0
|
|
let countPages = 0
|
|
let currentSearchRequest = null
|
|
t.context.client.search('cn=paged', { paged: { pageSize: 100 } }, function (err, res) {
|
|
t2.error(err)
|
|
res.on('searchEntry', entryListener)
|
|
res.on('searchRequest', (searchRequest) => {
|
|
t2.ok(searchRequest instanceof ldap.SearchRequest)
|
|
if (currentSearchRequest === null) {
|
|
t2.equal(countPages, 0)
|
|
}
|
|
currentSearchRequest = searchRequest
|
|
})
|
|
res.on('page', pageListener)
|
|
res.on('error', (err) => t2.error(err))
|
|
res.on('end', function (result) {
|
|
t2.equal(countEntries, 1000)
|
|
t2.equal(countPages, 10)
|
|
t2.equal(result.messageID, currentSearchRequest.messageID)
|
|
t2.end()
|
|
})
|
|
|
|
t2.teardown(() => {
|
|
res.removeListener('searchEntry', entryListener)
|
|
res.removeListener('page', pageListener)
|
|
})
|
|
|
|
function entryListener () {
|
|
countEntries += 1
|
|
}
|
|
|
|
function pageListener (result) {
|
|
countPages += 1
|
|
if (countPages < 10) {
|
|
t2.equal(result.messageID, currentSearchRequest.messageID)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
t.test('paged - pauses', function (t2) {
|
|
let countPages = 0
|
|
t.context.client.search('cn=paged', {
|
|
paged: {
|
|
pageSize: 100,
|
|
pagePause: true
|
|
}
|
|
}, function (err, res) {
|
|
t2.error(err)
|
|
res.on('page', pageListener)
|
|
res.on('error', (err) => t2.error(err))
|
|
res.on('end', function () {
|
|
t2.equal(countPages, 9)
|
|
t2.end()
|
|
})
|
|
|
|
function pageListener (result, cb) {
|
|
countPages++
|
|
// cancel after 9 to verify callback usage
|
|
if (countPages === 9) {
|
|
// another page should never be encountered
|
|
res.removeListener('page', pageListener)
|
|
.on('page', t2.fail.bind(null, 'unexpected page'))
|
|
return cb(new Error())
|
|
}
|
|
return cb()
|
|
}
|
|
})
|
|
})
|
|
|
|
t.test('paged - no support (err handled)', function (t2) {
|
|
t.context.client.search(SUFFIX, {
|
|
paged: { pageSize: 100 }
|
|
}, function (err, res) {
|
|
t2.error(err)
|
|
res.on('pageError', t2.ok.bind(t2))
|
|
res.on('end', function () {
|
|
t2.pass()
|
|
t2.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
t.test('paged - no support (err not handled)', function (t2) {
|
|
t.context.client.search(SUFFIX, {
|
|
paged: { pageSize: 100 }
|
|
}, function (err, res) {
|
|
t2.error(err)
|
|
res.on('end', t2.fail.bind(t2))
|
|
res.on('error', function (error) {
|
|
t2.ok(error)
|
|
t2.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
t.test('paged - redundant control', function (t2) {
|
|
try {
|
|
t.context.client.search(SUFFIX, {
|
|
paged: { pageSize: 100 }
|
|
}, new ldap.PagedResultsControl(),
|
|
function (err) {
|
|
t.error(err)
|
|
t2.fail()
|
|
})
|
|
} catch (e) {
|
|
t2.ok(e)
|
|
t2.end()
|
|
}
|
|
})
|
|
|
|
t.test('paged - handle later error', function (t2) {
|
|
let countEntries = 0
|
|
let countPages = 0
|
|
t.context.client.search('cn=pagederr', {
|
|
paged: { pageSize: 1 }
|
|
}, function (err, res) {
|
|
t2.error(err)
|
|
res.on('searchEntry', function () {
|
|
t2.ok(++countEntries)
|
|
})
|
|
res.on('page', function () {
|
|
t2.ok(++countPages)
|
|
})
|
|
res.on('error', function (error) {
|
|
t2.ok(error)
|
|
t2.equal(countEntries, 1)
|
|
t2.equal(countPages, 1)
|
|
t2.end()
|
|
})
|
|
res.on('end', function () {
|
|
t2.fail('should not be reached')
|
|
})
|
|
})
|
|
})
|
|
|
|
tap.test('paged - search with delayed event listener binding', function (t) {
|
|
t.context.client.search('cn=paged', { filter: '(objectclass=*)', paged: true }, function (err, res) {
|
|
t.error(err)
|
|
setTimeout(() => {
|
|
let gotEntry = 0
|
|
res.on('searchEntry', function () {
|
|
gotEntry++
|
|
})
|
|
res.on('error', function (err) {
|
|
t.fail(err)
|
|
})
|
|
res.on('end', function () {
|
|
t.equal(gotEntry, 1000)
|
|
t.end()
|
|
})
|
|
}, 100)
|
|
})
|
|
})
|
|
|
|
t.end()
|
|
})
|
|
|
|
tap.test('search - sssvlv', { timeout: 10000 }, function (t) {
|
|
t.test('ssv - asc', function (t2) {
|
|
let preventry = null
|
|
const sssrcontrol = new ldap.ServerSideSortingRequestControl(
|
|
{
|
|
value: {
|
|
attributeType: 'o',
|
|
orderingRule: 'caseIgnoreOrderingMatch',
|
|
reverseOrder: false
|
|
}
|
|
}
|
|
)
|
|
t.context.client.search('cn=sssvlv', {}, sssrcontrol, function (err, res) {
|
|
t2.error(err)
|
|
res.on('searchEntry', function (entry) {
|
|
t2.ok(entry)
|
|
t2.ok(entry instanceof ldap.SearchEntry)
|
|
t2.ok(entry.attributes)
|
|
t2.ok(entry.attributes.length)
|
|
if (preventry != null) {
|
|
t2.ok(entry.attributes[0]._vals[0] >= preventry.attributes[0]._vals[0])
|
|
}
|
|
preventry = entry
|
|
})
|
|
res.on('error', (err) => t2.error(err))
|
|
res.on('end', function () {
|
|
t2.end()
|
|
})
|
|
})
|
|
})
|
|
t.test('ssv - desc', function (t2) {
|
|
let preventry = null
|
|
const sssrcontrol = new ldap.ServerSideSortingRequestControl(
|
|
{
|
|
value: {
|
|
attributeType: 'o',
|
|
orderingRule: 'caseIgnoreOrderingMatch',
|
|
reverseOrder: true
|
|
}
|
|
}
|
|
)
|
|
t.context.client.search('cn=sssvlv', {}, sssrcontrol, function (err, res) {
|
|
t2.error(err)
|
|
res.on('searchEntry', function (entry) {
|
|
t2.ok(entry)
|
|
t2.ok(entry instanceof ldap.SearchEntry)
|
|
t2.ok(entry.attributes)
|
|
t2.ok(entry.attributes.length)
|
|
if (preventry != null) {
|
|
t2.ok(entry.attributes[0]._vals[0] <= preventry.attributes[0]._vals[0])
|
|
}
|
|
preventry = entry
|
|
})
|
|
res.on('error', (err) => t2.error(err))
|
|
res.on('end', function () {
|
|
t2.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
t.test('vlv - first page', function (t2) {
|
|
const sssrcontrol = new ldap.ServerSideSortingRequestControl(
|
|
{
|
|
value: {
|
|
attributeType: 'o',
|
|
orderingRule: 'caseIgnoreOrderingMatch',
|
|
reverseOrder: false
|
|
}
|
|
}
|
|
)
|
|
const vlvrcontrol = new ldap.VirtualListViewRequestControl(
|
|
{
|
|
value: {
|
|
beforeCount: 0,
|
|
afterCount: 9,
|
|
targetOffset: 1,
|
|
contentCount: 0
|
|
}
|
|
}
|
|
)
|
|
let count = 0
|
|
let preventry = null
|
|
t.context.client.search('cn=sssvlv', {}, [sssrcontrol, vlvrcontrol], function (err, res) {
|
|
t2.error(err)
|
|
res.on('searchEntry', function (entry) {
|
|
t2.ok(entry)
|
|
t2.ok(entry instanceof ldap.SearchEntry)
|
|
t2.ok(entry.attributes)
|
|
t2.ok(entry.attributes.length)
|
|
if (preventry != null) {
|
|
t2.ok(entry.attributes[0]._vals[0] >= preventry.attributes[0]._vals[0])
|
|
}
|
|
preventry = entry
|
|
count++
|
|
})
|
|
res.on('error', (err) => t2.error(err))
|
|
res.on('end', function () {
|
|
t2.equal(count, 10)
|
|
t2.end()
|
|
})
|
|
})
|
|
})
|
|
t.test('vlv - last page', function (t2) {
|
|
const sssrcontrol = new ldap.ServerSideSortingRequestControl(
|
|
{
|
|
value: {
|
|
attributeType: 'o',
|
|
orderingRule: 'caseIgnoreOrderingMatch',
|
|
reverseOrder: false
|
|
}
|
|
}
|
|
)
|
|
const vlvrcontrol = new ldap.VirtualListViewRequestControl(
|
|
{
|
|
value: {
|
|
beforeCount: 0,
|
|
afterCount: 9,
|
|
targetOffset: 91,
|
|
contentCount: 0
|
|
}
|
|
}
|
|
)
|
|
let count = 0
|
|
let preventry = null
|
|
t.context.client.search('cn=sssvlv', {}, [sssrcontrol, vlvrcontrol], function (err, res) {
|
|
t2.error(err)
|
|
res.on('searchEntry', function (entry) {
|
|
t2.ok(entry)
|
|
t2.ok(entry instanceof ldap.SearchEntry)
|
|
t2.ok(entry.attributes)
|
|
t2.ok(entry.attributes.length)
|
|
if (preventry != null) {
|
|
t2.ok(entry.attributes[0]._vals[0] >= preventry.attributes[0]._vals[0])
|
|
}
|
|
preventry = entry
|
|
count++
|
|
})
|
|
res.on('error', (err) => t2.error(err))
|
|
res.on('end', function () {
|
|
t2.equal(count, 10)
|
|
t2.end()
|
|
})
|
|
})
|
|
})
|
|
t.end()
|
|
})
|
|
|
|
tap.test('search referral', function (t) {
|
|
t.context.client.search('cn=ref, ' + SUFFIX, '(objectclass=*)', function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
let gotEntry = 0
|
|
let gotReferral = false
|
|
res.on('searchEntry', function () {
|
|
gotEntry++
|
|
})
|
|
res.on('searchReference', function (referral) {
|
|
gotReferral = true
|
|
t.ok(referral)
|
|
t.ok(referral instanceof ldap.SearchReference)
|
|
t.ok(referral.uris)
|
|
t.ok(referral.uris.length)
|
|
})
|
|
res.on('error', function (err) {
|
|
t.fail(err)
|
|
})
|
|
res.on('end', function (res) {
|
|
t.ok(res)
|
|
t.ok(res instanceof ldap.SearchResponse)
|
|
t.equal(res.status, 0)
|
|
t.equal(gotEntry, 0)
|
|
t.ok(gotReferral)
|
|
t.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
tap.test('search rootDSE', function (t) {
|
|
t.context.client.search('', '(objectclass=*)', function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
res.on('searchEntry', function (entry) {
|
|
t.ok(entry)
|
|
t.equal(entry.dn.toString(), '')
|
|
t.ok(entry.attributes)
|
|
t.ok(entry.object)
|
|
})
|
|
res.on('error', function (err) {
|
|
t.fail(err)
|
|
})
|
|
res.on('end', function (res) {
|
|
t.ok(res)
|
|
t.ok(res instanceof ldap.SearchResponse)
|
|
t.equal(res.status, 0)
|
|
t.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
tap.test('search empty attribute', function (t) {
|
|
t.context.client.search('dc=empty', '(objectclass=*)', function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
let gotEntry = 0
|
|
res.on('searchEntry', function (entry) {
|
|
const obj = entry.toObject()
|
|
t.equal('dc=empty', obj.dn)
|
|
t.ok(obj.member)
|
|
t.equal(obj.member.length, 0)
|
|
t.ok(obj['member;range=0-1'])
|
|
t.ok(obj['member;range=0-1'].length)
|
|
gotEntry++
|
|
})
|
|
res.on('error', function (err) {
|
|
t.fail(err)
|
|
})
|
|
res.on('end', function (res) {
|
|
t.ok(res)
|
|
t.ok(res instanceof ldap.SearchResponse)
|
|
t.equal(res.status, 0)
|
|
t.equal(gotEntry, 1)
|
|
t.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
tap.test('GH-21 binary attributes', function (t) {
|
|
t.context.client.search('cn=bin, ' + SUFFIX, '(objectclass=*)', function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
let gotEntry = 0
|
|
const expect = Buffer.from('\u00bd + \u00bc = \u00be', 'utf8')
|
|
const expect2 = Buffer.from([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA])
|
|
res.on('searchEntry', function (entry) {
|
|
t.ok(entry)
|
|
t.ok(entry instanceof ldap.SearchEntry)
|
|
t.equal(entry.dn.toString(), 'cn=bin,' + SUFFIX)
|
|
t.ok(entry.attributes)
|
|
t.ok(entry.attributes.length)
|
|
t.equal(entry.attributes[0].type, 'foo;binary')
|
|
t.equal(entry.attributes[0].vals[0], expect.toString('base64'))
|
|
t.equal(entry.attributes[0].buffers[0].toString('base64'),
|
|
expect.toString('base64'))
|
|
|
|
t.ok(entry.attributes[1].type, 'gb18030')
|
|
t.equal(entry.attributes[1].buffers.length, 1)
|
|
t.equal(expect2.length, entry.attributes[1].buffers[0].length)
|
|
for (let i = 0; i < expect2.length; i++) { t.equal(expect2[i], entry.attributes[1].buffers[0][i]) }
|
|
|
|
t.ok(entry.object)
|
|
gotEntry++
|
|
})
|
|
res.on('error', function (err) {
|
|
t.fail(err)
|
|
})
|
|
res.on('end', function (res) {
|
|
t.ok(res)
|
|
t.ok(res instanceof ldap.SearchResponse)
|
|
t.equal(res.status, 0)
|
|
t.equal(gotEntry, 1)
|
|
t.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
tap.test('GH-23 case insensitive attribute filtering', function (t) {
|
|
const opts = {
|
|
filter: '(objectclass=*)',
|
|
attributes: ['Cn']
|
|
}
|
|
t.context.client.search('cn=test, ' + SUFFIX, opts, function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
let gotEntry = 0
|
|
res.on('searchEntry', function (entry) {
|
|
t.ok(entry)
|
|
t.ok(entry instanceof ldap.SearchEntry)
|
|
t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
|
|
t.ok(entry.attributes)
|
|
t.ok(entry.attributes.length)
|
|
t.equal(entry.attributes[0].type, 'cn')
|
|
t.ok(entry.object)
|
|
gotEntry++
|
|
})
|
|
res.on('error', function (err) {
|
|
t.fail(err)
|
|
})
|
|
res.on('end', function (res) {
|
|
t.ok(res)
|
|
t.ok(res instanceof ldap.SearchResponse)
|
|
t.equal(res.status, 0)
|
|
t.equal(gotEntry, 2)
|
|
t.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
tap.test('GH-24 attribute selection of *', function (t) {
|
|
const opts = {
|
|
filter: '(objectclass=*)',
|
|
attributes: ['*']
|
|
}
|
|
t.context.client.search('cn=test, ' + SUFFIX, opts, function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
let gotEntry = 0
|
|
res.on('searchEntry', function (entry) {
|
|
t.ok(entry)
|
|
t.ok(entry instanceof ldap.SearchEntry)
|
|
t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
|
|
t.ok(entry.attributes)
|
|
t.ok(entry.attributes.length)
|
|
t.equal(entry.attributes[0].type, 'cn')
|
|
t.equal(entry.attributes[1].type, 'SN')
|
|
t.ok(entry.object)
|
|
gotEntry++
|
|
})
|
|
res.on('error', function (err) {
|
|
t.fail(err)
|
|
})
|
|
res.on('end', function (res) {
|
|
t.ok(res)
|
|
t.ok(res instanceof ldap.SearchResponse)
|
|
t.equal(res.status, 0)
|
|
t.equal(gotEntry, 2)
|
|
t.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
tap.test('idle timeout', function (t) {
|
|
t.context.client.idleTimeout = 250
|
|
function premature () {
|
|
t.error(true)
|
|
}
|
|
t.context.client.on('idle', premature)
|
|
t.context.client.search('dc=slow', 'objectclass=*', function (err, res) {
|
|
t.error(err)
|
|
res.on('searchEntry', function (res) {
|
|
t.ok(res)
|
|
})
|
|
res.on('error', function (err) {
|
|
t.error(err)
|
|
})
|
|
res.on('end', function () {
|
|
const late = setTimeout(function () {
|
|
t.fail('too late')
|
|
}, 500)
|
|
// It's ok to go idle now
|
|
t.context.client.removeListener('idle', premature)
|
|
t.context.client.on('idle', function () {
|
|
clearTimeout(late)
|
|
t.context.client.removeAllListeners('idle')
|
|
t.context.client.idleTimeout = 0
|
|
t.end()
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
tap.test('setup action', function (t) {
|
|
const setupClient = ldap.createClient({
|
|
connectTimeout: parseInt(LDAP_CONNECT_TIMEOUT, 10),
|
|
socketPath: t.context.socketPath
|
|
})
|
|
setupClient.on('setup', function (clt, cb) {
|
|
clt.bind(BIND_DN, BIND_PW, function (err) {
|
|
t.error(err)
|
|
cb(err)
|
|
})
|
|
})
|
|
setupClient.search(SUFFIX, { scope: 'base' }, function (err, res) {
|
|
t.error(err)
|
|
t.ok(res)
|
|
res.on('end', function () {
|
|
setupClient.destroy()
|
|
t.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
tap.test('setup reconnect', function (t) {
|
|
const rClient = ldap.createClient({
|
|
connectTimeout: parseInt(LDAP_CONNECT_TIMEOUT, 10),
|
|
socketPath: t.context.socketPath,
|
|
reconnect: true
|
|
})
|
|
rClient.on('setup', function (clt, cb) {
|
|
clt.bind(BIND_DN, BIND_PW, function (err) {
|
|
t.error(err)
|
|
cb(err)
|
|
})
|
|
})
|
|
|
|
function doSearch (_, cb) {
|
|
rClient.search(SUFFIX, { scope: 'base' }, function (err, res) {
|
|
t.error(err)
|
|
res.on('end', function () {
|
|
cb()
|
|
})
|
|
})
|
|
}
|
|
|
|
vasync.pipeline({
|
|
funcs: [
|
|
doSearch,
|
|
function cleanDisconnect (_, cb) {
|
|
t.ok(rClient.connected)
|
|
rClient.once('close', function (err) {
|
|
t.error(err)
|
|
t.equal(rClient.connected, false)
|
|
cb()
|
|
})
|
|
rClient.unbind()
|
|
},
|
|
doSearch,
|
|
function simulateError (_, cb) {
|
|
const msg = 'fake socket error'
|
|
rClient.once('error', function (err) {
|
|
t.equal(err.message, msg)
|
|
t.ok(err)
|
|
})
|
|
rClient.once('close', function () {
|
|
// can't test had_err because the socket error is being faked
|
|
cb()
|
|
})
|
|
rClient._socket.emit('error', new Error(msg))
|
|
},
|
|
doSearch
|
|
]
|
|
}, function (err) {
|
|
t.error(err)
|
|
rClient.destroy()
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('setup abort', function (t) {
|
|
const setupClient = ldap.createClient({
|
|
connectTimeout: parseInt(LDAP_CONNECT_TIMEOUT, 10),
|
|
socketPath: t.context.socketPath,
|
|
reconnect: true
|
|
})
|
|
const message = "It's a trap!"
|
|
setupClient.on('setup', function (clt, cb) {
|
|
// simulate failure
|
|
t.ok(clt)
|
|
cb(new Error(message))
|
|
})
|
|
setupClient.on('setupError', function (err) {
|
|
t.ok(true)
|
|
t.equal(err.message, message)
|
|
setupClient.destroy()
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('abort reconnect', function (t) {
|
|
const abortClient = ldap.createClient({
|
|
connectTimeout: parseInt(LDAP_CONNECT_TIMEOUT, 10),
|
|
socketPath: 'an invalid path',
|
|
reconnect: true
|
|
})
|
|
let retryCount = 0
|
|
abortClient.on('connectError', function () {
|
|
++retryCount
|
|
})
|
|
abortClient.once('connectError', function () {
|
|
t.ok(true)
|
|
abortClient.once('destroy', function () {
|
|
t.ok(retryCount < 3)
|
|
t.end()
|
|
})
|
|
abortClient.destroy()
|
|
})
|
|
})
|
|
|
|
tap.test('reconnect max retries', function (t) {
|
|
const RETRIES = 5
|
|
const rClient = ldap.createClient({
|
|
connectTimeout: 100,
|
|
socketPath: 'an invalid path',
|
|
reconnect: {
|
|
failAfter: RETRIES,
|
|
// Keep the test duration low
|
|
initialDelay: 10,
|
|
maxDelay: 100
|
|
}
|
|
})
|
|
let count = 0
|
|
rClient.on('connectError', function () {
|
|
count++
|
|
})
|
|
rClient.on('error', function (err) {
|
|
t.ok(err)
|
|
t.equal(count, RETRIES)
|
|
rClient.destroy()
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('reconnect on server close', function (t) {
|
|
const clt = ldap.createClient({
|
|
socketPath: t.context.socketPath,
|
|
reconnect: true
|
|
})
|
|
clt.on('setup', function (sclt, cb) {
|
|
sclt.bind(BIND_DN, BIND_PW, function (err) {
|
|
t.error(err)
|
|
cb(err)
|
|
})
|
|
})
|
|
clt.once('connect', function () {
|
|
t.ok(clt._socket)
|
|
clt.once('connect', function () {
|
|
t.ok(true, 'successful reconnect')
|
|
clt.destroy()
|
|
t.end()
|
|
})
|
|
|
|
// Simulate server-side close
|
|
clt._socket.destroy()
|
|
})
|
|
})
|
|
|
|
tap.test('no auto-reconnect on unbind', function (t) {
|
|
const clt = ldap.createClient({
|
|
socketPath: t.context.socketPath,
|
|
reconnect: true
|
|
})
|
|
clt.on('setup', function (sclt, cb) {
|
|
sclt.bind(BIND_DN, BIND_PW, function (err) {
|
|
t.error(err)
|
|
cb(err)
|
|
})
|
|
})
|
|
clt.once('connect', function () {
|
|
clt.once('connect', function () {
|
|
t.error(new Error('client should not reconnect'))
|
|
})
|
|
clt.once('close', function () {
|
|
t.ok(true, 'initial close')
|
|
setImmediate(function () {
|
|
t.ok(!clt.connected, 'should not be connected')
|
|
t.ok(!clt.connecting, 'should not be connecting')
|
|
clt.destroy()
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
clt.unbind()
|
|
})
|
|
})
|
|
|
|
tap.test('abandon (GH-27)', function (t) {
|
|
// FIXME: test abandoning a real request
|
|
t.context.client.abandon(401876543, function (err) {
|
|
t.error(err)
|
|
t.end()
|
|
})
|
|
})
|
|
|
|
tap.test('search timeout (GH-51)', function (t) {
|
|
t.context.client.timeout = 250
|
|
t.context.client.search('dc=timeout', 'objectclass=*', function (err, res) {
|
|
t.error(err)
|
|
res.on('error', function () {
|
|
t.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
tap.test('resultError handling', function (t) {
|
|
const client = t.context.client
|
|
vasync.pipeline({ funcs: [errSearch, cleanSearch] }, function (err) {
|
|
t.error(err)
|
|
client.removeListener('resultError', error1)
|
|
client.removeListener('resultError', error2)
|
|
t.end()
|
|
})
|
|
|
|
function errSearch (_, cb) {
|
|
client.once('resultError', error1)
|
|
client.search('cn=busy', {}, function (err, res) {
|
|
t.error(err)
|
|
res.once('error', function (error) {
|
|
t.equal(error.name, 'BusyError')
|
|
cb()
|
|
})
|
|
})
|
|
}
|
|
|
|
function cleanSearch (_, cb) {
|
|
client.on('resultError', error2)
|
|
client.search(SUFFIX, {}, function (err, res) {
|
|
t.error(err)
|
|
res.once('end', function () {
|
|
t.pass()
|
|
cb()
|
|
})
|
|
})
|
|
}
|
|
|
|
function error1 (error) {
|
|
t.equal(error.name, 'BusyError')
|
|
}
|
|
|
|
function error2 () {
|
|
t.fail('should not get error')
|
|
}
|
|
})
|
|
|
|
tap.test('connection refused', function (t) {
|
|
getPort().then(function (unusedPortNumber) {
|
|
const client = ldap.createClient({
|
|
url: `ldap://0.0.0.0:${unusedPortNumber}`
|
|
})
|
|
|
|
client.on('connectRefused', () => {})
|
|
|
|
client.bind('cn=root', 'secret', function (err, res) {
|
|
t.ok(err)
|
|
t.type(err, Error)
|
|
t.equal(err.code, 'ECONNREFUSED')
|
|
t.notOk(res)
|
|
t.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
tap.test('connection timeout', function (t) {
|
|
getPort().then(function (unusedPortNumber) {
|
|
const client = ldap.createClient({
|
|
url: `ldap://example.org:${unusedPortNumber}`,
|
|
connectTimeout: 1,
|
|
timeout: 1
|
|
})
|
|
|
|
client.on('connectTimeout', () => {})
|
|
|
|
let done = false
|
|
|
|
setTimeout(function () {
|
|
if (!done) {
|
|
throw new Error('LDAPJS waited for the server for too long')
|
|
}
|
|
}, 2000)
|
|
|
|
client.bind('cn=root', 'secret', function (err, res) {
|
|
t.ok(err)
|
|
t.type(err, Error)
|
|
t.equal(err.message, 'connection timeout')
|
|
done = true
|
|
t.notOk(res)
|
|
t.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
tap.only('emitError', function (t) {
|
|
t.test('connectTimeout', function (t) {
|
|
getPort().then(function (unusedPortNumber) {
|
|
const client = ldap.createClient({
|
|
url: `ldap://example.org:${unusedPortNumber}`,
|
|
connectTimeout: 1,
|
|
timeout: 1
|
|
})
|
|
|
|
const timeout = setTimeout(function () {
|
|
throw new Error('LDAPJS waited for the server for too long')
|
|
}, 2000)
|
|
|
|
client.on('error', (err) => {
|
|
t.fail(err)
|
|
})
|
|
client.on('connectTimeout', (err) => {
|
|
t.ok(err)
|
|
t.type(err, Error)
|
|
t.equal(err.message, 'connection timeout')
|
|
clearTimeout(timeout)
|
|
t.end()
|
|
})
|
|
|
|
client.bind('cn=root', 'secret', () => {})
|
|
})
|
|
})
|
|
|
|
t.test('connectTimeout to error', function (t) {
|
|
getPort().then(function (unusedPortNumber) {
|
|
const client = ldap.createClient({
|
|
url: `ldap://example.org:${unusedPortNumber}`,
|
|
connectTimeout: 1,
|
|
timeout: 1
|
|
})
|
|
|
|
const timeout = setTimeout(function () {
|
|
throw new Error('LDAPJS waited for the server for too long')
|
|
}, 2000)
|
|
|
|
client.on('error', (err) => {
|
|
t.ok(err)
|
|
t.type(err, Error)
|
|
t.equal(err.message, 'connectTimeout: connection timeout')
|
|
clearTimeout(timeout)
|
|
t.end()
|
|
})
|
|
|
|
client.bind('cn=root', 'secret', () => {})
|
|
})
|
|
})
|
|
|
|
t.test('connectRefused', function (t) {
|
|
getPort().then(function (unusedPortNumber) {
|
|
const client = ldap.createClient({
|
|
url: `ldap://0.0.0.0:${unusedPortNumber}`
|
|
})
|
|
|
|
client.on('error', (err) => {
|
|
t.fail(err)
|
|
})
|
|
client.on('connectRefused', (err) => {
|
|
t.ok(err)
|
|
t.type(err, Error)
|
|
t.equal(err.message, `connect ECONNREFUSED 0.0.0.0:${unusedPortNumber}`)
|
|
t.equal(err.code, 'ECONNREFUSED')
|
|
t.end()
|
|
})
|
|
|
|
client.bind('cn=root', 'secret', () => {})
|
|
})
|
|
})
|
|
|
|
t.test('connectRefused to error', function (t) {
|
|
getPort().then(function (unusedPortNumber) {
|
|
const client = ldap.createClient({
|
|
url: `ldap://0.0.0.0:${unusedPortNumber}`
|
|
})
|
|
|
|
client.on('error', (err) => {
|
|
t.ok(err)
|
|
t.type(err, Error)
|
|
t.equal(err.message, `connectRefused: connect ECONNREFUSED 0.0.0.0:${unusedPortNumber}`)
|
|
t.equal(err.code, 'ECONNREFUSED')
|
|
t.end()
|
|
})
|
|
|
|
client.bind('cn=root', 'secret', () => {})
|
|
})
|
|
})
|
|
|
|
t.end()
|
|
})
|