dn.test.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. 'use strict'
  2. const tap = require('tap')
  3. const warning = require('./deprecations')
  4. const RDN = require('./rdn')
  5. const DN = require('./dn')
  6. // Silence the standard warning logs. We will test the messages explicitly.
  7. process.removeAllListeners('warning')
  8. tap.test('constructor', t => {
  9. t.test('throws for non-array', async t => {
  10. t.throws(
  11. () => new DN({ rdns: 42 }),
  12. Error('rdns must be an array')
  13. )
  14. })
  15. t.test('throws for non-rdn in array', async t => {
  16. const rdns = [
  17. new RDN(),
  18. { 'non-string-value': 42 },
  19. new RDN()
  20. ]
  21. t.throws(
  22. () => new DN({ rdns })
  23. )
  24. })
  25. t.test('handles mixed array', async t => {
  26. const rdns = [
  27. { cn: 'foo' },
  28. new RDN({ dc: 'example' }),
  29. new RDN({ dc: 'com' })
  30. ]
  31. const dn = new DN({ rdns })
  32. t.equal(dn.length, 3)
  33. t.equal(dn.toString(), 'cn=foo,dc=example,dc=com')
  34. })
  35. t.end()
  36. })
  37. tap.test('childOf', t => {
  38. t.test('false if we are shallower', async t => {
  39. const dn = new DN({
  40. rdns: [
  41. new RDN({ ou: 'people' }),
  42. new RDN({ dc: 'example' }),
  43. new RDN({ dc: 'com' })
  44. ]
  45. })
  46. const target = new DN({
  47. rdns: [
  48. new RDN({ cn: 'foo' }),
  49. new RDN({ ou: 'people' }),
  50. new RDN({ dc: 'example' }),
  51. new RDN({ dc: 'com' })
  52. ]
  53. })
  54. t.equal(dn.childOf(target), false)
  55. })
  56. t.test('false if differing path', async t => {
  57. const dn = new DN({
  58. rdns: [
  59. new RDN({ ou: 'people' }),
  60. new RDN({ dc: 'example' }),
  61. new RDN({ dc: 'com' })
  62. ]
  63. })
  64. const target = new DN({
  65. rdns: [
  66. new RDN({ dc: 'ldapjs' }),
  67. new RDN({ dc: 'com' })
  68. ]
  69. })
  70. t.equal(dn.childOf(target), false)
  71. })
  72. t.test('true if we are a child', async t => {
  73. const dn = new DN({
  74. rdns: [
  75. new RDN({ ou: 'people' }),
  76. new RDN({ dc: 'example' }),
  77. new RDN({ dc: 'com' })
  78. ]
  79. })
  80. const target = new DN({
  81. rdns: [
  82. new RDN({ dc: 'example' }),
  83. new RDN({ dc: 'com' })
  84. ]
  85. })
  86. t.equal(dn.childOf(target), true)
  87. })
  88. t.test('handles string input', async t => {
  89. const dn = new DN({
  90. rdns: [
  91. new RDN({ ou: 'people' }),
  92. new RDN({ dc: 'example' }),
  93. new RDN({ dc: 'com' })
  94. ]
  95. })
  96. const target = 'dc=example,dc=com'
  97. t.equal(dn.childOf(target), true)
  98. })
  99. t.end()
  100. })
  101. tap.test('clone', t => {
  102. t.test('returns a copy', async t => {
  103. const rdns = [new RDN({ cn: 'foo' })]
  104. const src = new DN({ rdns })
  105. const clone = src.clone()
  106. t.equal(src.length, clone.length)
  107. t.equal(src.toString(), clone.toString())
  108. })
  109. t.end()
  110. })
  111. tap.test('equals', t => {
  112. t.test('false for non-equal length', async t => {
  113. const dn = new DN({
  114. rdns: [
  115. new RDN({ ou: 'people' }),
  116. new RDN({ dc: 'example' }),
  117. new RDN({ dc: 'com' })
  118. ]
  119. })
  120. const target = new DN({
  121. rdns: [
  122. new RDN({ dc: 'example' }),
  123. new RDN({ dc: 'com' })
  124. ]
  125. })
  126. t.equal(dn.equals(target), false)
  127. })
  128. t.test('false for non-equal paths', async t => {
  129. const dn = new DN({
  130. rdns: [
  131. new RDN({ ou: 'people' }),
  132. new RDN({ dc: 'example' }),
  133. new RDN({ dc: 'com' })
  134. ]
  135. })
  136. const target = new DN({
  137. rdns: [
  138. new RDN({ ou: 'computers' }),
  139. new RDN({ dc: 'example' }),
  140. new RDN({ dc: 'com' })
  141. ]
  142. })
  143. t.equal(dn.equals(target), false)
  144. })
  145. t.test('true for equal paths', async t => {
  146. const dn = new DN({
  147. rdns: [
  148. new RDN({ ou: 'people' }),
  149. new RDN({ dc: 'example' }),
  150. new RDN({ dc: 'com' })
  151. ]
  152. })
  153. const target = new DN({
  154. rdns: [
  155. new RDN({ ou: 'people' }),
  156. new RDN({ dc: 'example' }),
  157. new RDN({ dc: 'com' })
  158. ]
  159. })
  160. t.equal(dn.equals(target), true)
  161. })
  162. t.test('handles string input', async t => {
  163. const dn = new DN({
  164. rdns: [
  165. new RDN({ ou: 'people' }),
  166. new RDN({ dc: 'example' }),
  167. new RDN({ dc: 'com' })
  168. ]
  169. })
  170. const target = 'ou=people,dc=example,dc=com'
  171. t.equal(dn.equals(target), true)
  172. })
  173. t.end()
  174. })
  175. tap.test('format', t => {
  176. t.test('emits warning', t => {
  177. process.on('warning', handler)
  178. t.teardown(async () => {
  179. process.removeListener('warning', handler)
  180. warning.emitted.set('LDAP_DN_DEP_002', false)
  181. })
  182. const rdns = [{ cn: 'foo' }]
  183. const dnString = (new DN({ rdns })).format()
  184. t.equal(dnString, 'cn=foo')
  185. function handler (error) {
  186. t.equal(error.message, '.format() is deprecated. Use .toString() instead')
  187. t.end()
  188. }
  189. })
  190. t.end()
  191. })
  192. tap.test('isEmpty', t => {
  193. t.test('returns correct result', async t => {
  194. let dn = new DN()
  195. t.equal(dn.isEmpty(), true)
  196. dn = new DN({
  197. rdns: [new RDN({ cn: 'foo' })]
  198. })
  199. t.equal(dn.isEmpty(), false)
  200. })
  201. t.end()
  202. })
  203. tap.test('parent', t => {
  204. t.test('undefined for an empty DN', async t => {
  205. const dn = new DN()
  206. const parent = dn.parent()
  207. t.equal(parent, undefined)
  208. })
  209. t.test('returns correct DN', async t => {
  210. const dn = new DN({
  211. rdns: [
  212. new RDN({ cn: 'jdoe', givenName: 'John' }),
  213. new RDN({ ou: 'people' }),
  214. new RDN({ dc: 'example' }),
  215. new RDN({ dc: 'com' })
  216. ]
  217. })
  218. const parent = dn.parent()
  219. t.equal(parent.toString(), 'ou=people,dc=example,dc=com')
  220. })
  221. t.end()
  222. })
  223. tap.test('parentOf', t => {
  224. t.test('false if we are deeper', async t => {
  225. const target = new DN({
  226. rdns: [
  227. new RDN({ ou: 'people' }),
  228. new RDN({ dc: 'example' }),
  229. new RDN({ dc: 'com' })
  230. ]
  231. })
  232. const dn = new DN({
  233. rdns: [
  234. new RDN({ cn: 'foo' }),
  235. new RDN({ ou: 'people' }),
  236. new RDN({ dc: 'example' }),
  237. new RDN({ dc: 'com' })
  238. ]
  239. })
  240. t.equal(dn.parentOf(target), false)
  241. })
  242. t.test('false if differing path', async t => {
  243. const target = new DN({
  244. rdns: [
  245. new RDN({ ou: 'people' }),
  246. new RDN({ dc: 'example' }),
  247. new RDN({ dc: 'com' })
  248. ]
  249. })
  250. const dn = new DN({
  251. rdns: [
  252. new RDN({ dc: 'ldapjs' }),
  253. new RDN({ dc: 'com' })
  254. ]
  255. })
  256. t.equal(dn.parentOf(target), false)
  257. })
  258. t.test('true if we are a parent', async t => {
  259. const target = new DN({
  260. rdns: [
  261. new RDN({ ou: 'people' }),
  262. new RDN({ dc: 'example' }),
  263. new RDN({ dc: 'com' })
  264. ]
  265. })
  266. const dn = new DN({
  267. rdns: [
  268. new RDN({ dc: 'example' }),
  269. new RDN({ dc: 'com' })
  270. ]
  271. })
  272. t.equal(dn.parentOf(target), true)
  273. })
  274. t.test('handles string input', async t => {
  275. const dn = new DN({
  276. rdns: [
  277. new RDN({ dc: 'example' }),
  278. new RDN({ dc: 'com' })
  279. ]
  280. })
  281. const target = 'ou=people,dc=example,dc=com'
  282. t.equal(dn.parentOf(target), true)
  283. })
  284. t.end()
  285. })
  286. tap.test('pop', t => {
  287. t.test('returns the last element and shortens the list', async t => {
  288. const dn = new DN({
  289. rdns: [
  290. new RDN({ cn: 'foo' }),
  291. new RDN({ dc: 'example' }),
  292. new RDN({ dc: 'com' })
  293. ]
  294. })
  295. t.equal(dn.toString(), 'cn=foo,dc=example,dc=com')
  296. const rdn = dn.pop()
  297. t.equal(rdn.toString(), 'dc=com')
  298. t.equal(dn.toString(), 'cn=foo,dc=example')
  299. })
  300. t.end()
  301. })
  302. tap.test('push', t => {
  303. t.test('throws for bad input', async t => {
  304. const dn = new DN()
  305. t.throws(
  306. () => dn.push({ cn: 'foo' }),
  307. Error('rdn must be a RDN instance')
  308. )
  309. })
  310. t.test('adds to the front of the list', async t => {
  311. const dn = new DN({
  312. rdns: [
  313. new RDN({ cn: 'foo' }),
  314. new RDN({ dc: 'example' })
  315. ]
  316. })
  317. t.equal(dn.toString(), 'cn=foo,dc=example')
  318. const newLength = dn.push(new RDN({ dc: 'com' }))
  319. t.equal(newLength, 3)
  320. t.equal(dn.toString(), 'cn=foo,dc=example,dc=com')
  321. })
  322. t.end()
  323. })
  324. tap.test('rdnAt', t => {
  325. t.test('returns correct RDN', async t => {
  326. const dn = new DN({
  327. rdns: [
  328. new RDN({ cn: 'jdoe', givenName: 'John' }),
  329. new RDN({ ou: 'people' }),
  330. new RDN({ dc: 'example' }),
  331. new RDN({ dc: 'com' })
  332. ]
  333. })
  334. const rdn = dn.rdnAt(1)
  335. t.equal(rdn.toString(), 'ou=people')
  336. })
  337. t.end()
  338. })
  339. tap.test('reverse', t => {
  340. t.test('reverses the list', async t => {
  341. const dn = new DN({
  342. rdns: [
  343. new RDN({ dc: 'com' }),
  344. new RDN({ dc: 'example' }),
  345. new RDN({ cn: 'foo' })
  346. ]
  347. })
  348. t.equal(dn.toString(), 'dc=com,dc=example,cn=foo')
  349. const result = dn.reverse()
  350. t.equal(dn, result)
  351. t.equal(dn.toString(), 'cn=foo,dc=example,dc=com')
  352. })
  353. t.end()
  354. })
  355. tap.test('setFormat', t => {
  356. t.test('emits warning', t => {
  357. process.on('warning', handler)
  358. t.teardown(async () => {
  359. process.removeListener('warning', handler)
  360. warning.emitted.set('LDAP_DN_DEP_004', false)
  361. })
  362. const rdns = [{ cn: 'foo' }]
  363. new DN({ rdns }).setFormat()
  364. function handler (error) {
  365. t.equal(error.message, '.setFormat() is deprecated. Options will be ignored')
  366. t.end()
  367. }
  368. })
  369. t.end()
  370. })
  371. tap.test('shift', t => {
  372. t.test('returns the first element and shortens the list', async t => {
  373. const dn = new DN({
  374. rdns: [
  375. new RDN({ cn: 'foo' }),
  376. new RDN({ dc: 'example' }),
  377. new RDN({ dc: 'com' })
  378. ]
  379. })
  380. t.equal(dn.toString(), 'cn=foo,dc=example,dc=com')
  381. const rdn = dn.shift()
  382. t.equal(rdn.toString(), 'cn=foo')
  383. t.equal(dn.toString(), 'dc=example,dc=com')
  384. })
  385. t.end()
  386. })
  387. tap.test('toString', t => {
  388. t.test('renders correctly', async t => {
  389. const dn = new DN({
  390. rdns: [
  391. new RDN({ cn: 'jdoe', givenName: 'John' }),
  392. new RDN({ ou: 'people' }),
  393. new RDN({ dc: 'example' }),
  394. new RDN({ dc: 'com' })
  395. ]
  396. })
  397. t.equal(dn.toString(), 'cn=jdoe+givenName=John,ou=people,dc=example,dc=com')
  398. })
  399. t.test('empty string for empty DN', async t => {
  400. const dn = new DN()
  401. t.equal(dn.toString(), '')
  402. })
  403. t.end()
  404. })
  405. tap.test('unshift', t => {
  406. t.test('throws for bad input', async t => {
  407. const dn = new DN()
  408. t.throws(
  409. () => dn.unshift({ cn: 'foo' }),
  410. Error('rdn must be a RDN instance')
  411. )
  412. })
  413. t.test('adds to the front of the list', async t => {
  414. const dn = new DN({
  415. rdns: [
  416. new RDN({ dc: 'example' }),
  417. new RDN({ dc: 'com' })
  418. ]
  419. })
  420. t.equal(dn.toString(), 'dc=example,dc=com')
  421. const newLength = dn.unshift(new RDN({ cn: 'foo' }))
  422. t.equal(newLength, 3)
  423. t.equal(dn.toString(), 'cn=foo,dc=example,dc=com')
  424. })
  425. t.end()
  426. })
  427. tap.test('#isDn', t => {
  428. t.test('true for instance', async t => {
  429. const dn = new DN()
  430. t.equal(DN.isDn(dn), true)
  431. })
  432. t.test('false for non-object', async t => {
  433. t.equal(DN.isDn(42), false)
  434. })
  435. t.test('false for non-array rdns', async t => {
  436. const input = { rdns: 42 }
  437. t.equal(DN.isDn(input), false)
  438. })
  439. t.test('false for bad rdn', async t => {
  440. const input = { rdns: [{ bad: 'rdn', answer: 42 }] }
  441. t.equal(DN.isDn(input), false)
  442. })
  443. t.test('true for dn-like', async t => {
  444. const input = { rdns: [{ name: 'cn', value: 'foo' }] }
  445. t.equal(DN.isDn(input), true)
  446. })
  447. t.end()
  448. })
  449. tap.test('#fromString', t => {
  450. t.test('parses a basic string into an instance', async t => {
  451. const input = 'cn=foo+sn=bar,dc=example,dc=com'
  452. const dn = DN.fromString(input)
  453. t.equal(DN.isDn(dn), true)
  454. t.equal(dn.length, 3)
  455. t.equal(dn.rdnAt(0).toString(), 'cn=foo+sn=bar')
  456. })
  457. t.end()
  458. })