index.test.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. 'use strict'
  2. const tap = require('tap')
  3. const {
  4. BerReader,
  5. BerWriter
  6. } = require('@ldapjs/asn1')
  7. const { core: { LBER_SET } } = require('@ldapjs/protocol')
  8. const warning = require('./lib/deprecations')
  9. const Attribute = require('./')
  10. // Silence the standard warning logs. We will test the messages explicitly.
  11. process.removeAllListeners('warning')
  12. tap.test('constructor', t => {
  13. t.test('new no args', async t => {
  14. t.ok(new Attribute())
  15. // TODO: verify attributes
  16. })
  17. t.test('new with args', async t => {
  18. let attr = new Attribute({
  19. type: 'cn',
  20. values: ['foo', 'bar']
  21. })
  22. t.ok(attr)
  23. attr.addValue('baz')
  24. t.equal(attr.type, 'cn')
  25. const values = attr.values
  26. t.equal(values.length, 3)
  27. t.equal(values[0], 'foo')
  28. t.equal(values[1], 'bar')
  29. t.equal(values[2], 'baz')
  30. t.throws(function () {
  31. const typeThatIsNotAString = 1
  32. attr = new Attribute({
  33. type: typeThatIsNotAString
  34. })
  35. })
  36. })
  37. t.test('supports binary attributes', async t => {
  38. const attr = new Attribute({
  39. type: 'foo;binary',
  40. values: ['bar']
  41. })
  42. t.strictSame(attr.pojo, {
  43. type: 'foo;binary',
  44. values: ['bao=']
  45. })
  46. })
  47. t.test('warns for vals', t => {
  48. process.on('warning', handler)
  49. t.teardown(async () => {
  50. process.removeListener('warning', handler)
  51. warning.emitted.set('LDAP_MESSAGE_DEP_001', false)
  52. })
  53. const attr = new Attribute({
  54. type: 'foo',
  55. vals: ['bar']
  56. })
  57. t.ok(attr)
  58. function handler (error) {
  59. t.equal(
  60. error.message,
  61. 'options.vals is deprecated. Use options.values instead.'
  62. )
  63. t.end()
  64. }
  65. })
  66. t.end()
  67. })
  68. tap.test('.values', t => {
  69. t.test('adds an array of strings', async t => {
  70. const attr = new Attribute({ type: 'foo' })
  71. attr.values = ['bar', 'baz']
  72. t.strictSame(attr.pojo, {
  73. type: 'foo',
  74. values: ['bar', 'baz']
  75. })
  76. })
  77. t.test('adds a single string', async t => {
  78. const attr = new Attribute({ type: 'foo' })
  79. attr.values = 'bar'
  80. t.strictSame(attr.pojo, {
  81. type: 'foo',
  82. values: ['bar']
  83. })
  84. })
  85. t.end()
  86. })
  87. tap.test('.vals', t => {
  88. t.beforeEach(async t => {
  89. process.on('warning', handler)
  90. t.context.handler = handler
  91. function handler (error) {
  92. t.equal(
  93. error.message,
  94. 'Instance property .vals is deprecated. Use property .values instead.'
  95. )
  96. t.end()
  97. }
  98. })
  99. t.afterEach(async (t) => {
  100. process.removeListener('warning', t.context.handler)
  101. warning.emitted.set('LDAP_ATTRIBUTE_DEP_003', false)
  102. })
  103. t.test('adds an array of strings', async t => {
  104. const attr = new Attribute({ type: 'foo' })
  105. attr.vals = ['bar', 'baz']
  106. t.strictSame(attr.pojo, {
  107. type: 'foo',
  108. values: ['bar', 'baz']
  109. })
  110. })
  111. t.test('adds a single string', async t => {
  112. const attr = new Attribute({ type: 'foo' })
  113. attr.vals = 'bar'
  114. t.strictSame(attr.pojo, {
  115. type: 'foo',
  116. values: ['bar']
  117. })
  118. })
  119. t.end()
  120. })
  121. tap.test('.buffers', t => {
  122. t.test('returns underlying buffers', async t => {
  123. const attr = new Attribute({
  124. type: 'foo',
  125. values: ['bar', 'baz']
  126. })
  127. const buffers = attr.buffers
  128. t.equal(buffers.length, 2)
  129. let expected = Buffer.from('bar', 'utf8')
  130. t.equal(expected.compare(buffers[0]), 0)
  131. expected = Buffer.from('baz', 'utf8')
  132. t.equal(expected.compare(buffers[1]), 0)
  133. })
  134. t.end()
  135. })
  136. tap.test('.type', t => {
  137. t.test('gets and sets', async t => {
  138. const attr = new Attribute(({
  139. type: 'foo',
  140. values: ['bar']
  141. }))
  142. t.equal(attr.type, 'foo')
  143. attr.type = 'bar'
  144. t.equal(attr.type, 'bar')
  145. })
  146. t.end()
  147. })
  148. tap.test('toBer', async t => {
  149. t.test('renders type with values', async t => {
  150. const attr = new Attribute({
  151. type: 'cn',
  152. values: ['foo', 'bar']
  153. })
  154. const reader = attr.toBer()
  155. t.ok(reader.readSequence())
  156. t.equal(reader.readString(), 'cn')
  157. t.equal(reader.readSequence(LBER_SET), LBER_SET)
  158. t.equal(reader.readString(), 'foo')
  159. t.equal(reader.readString(), 'bar')
  160. })
  161. t.test('renders type without values', async t => {
  162. const attr = new Attribute({ type: 'cn' })
  163. const reader = attr.toBer()
  164. t.ok(reader.readSequence())
  165. t.equal(reader.readString(), 'cn')
  166. t.equal(reader.readSequence(LBER_SET), LBER_SET)
  167. t.equal(reader.remain, 0)
  168. })
  169. })
  170. tap.test('parse', t => {
  171. t.beforeEach(async t => {
  172. process.on('warning', handler)
  173. t.teardown(async () => {
  174. process.removeListener('warning', handler)
  175. warning.emitted.set('LDAP_MESSAGE_DEP_002', false)
  176. })
  177. function handler (error) {
  178. t.equal(
  179. error.message,
  180. 'Instance method .parse is deprecated. Use static .fromBer instead.'
  181. )
  182. t.end()
  183. }
  184. })
  185. t.test('parse', async t => {
  186. const ber = new BerWriter()
  187. ber.startSequence()
  188. ber.writeString('cn')
  189. ber.startSequence(0x31)
  190. ber.writeStringArray(['foo', 'bar'])
  191. ber.endSequence()
  192. ber.endSequence()
  193. const attr = new Attribute()
  194. attr.parse(new BerReader(ber.buffer))
  195. t.equal(attr.type, 'cn')
  196. t.equal(attr.vals.length, 2)
  197. t.equal(attr.vals[0], 'foo')
  198. t.equal(attr.vals[1], 'bar')
  199. })
  200. t.test('parse - without 0x31', async t => {
  201. const ber = new BerWriter()
  202. ber.startSequence()
  203. ber.writeString('sn')
  204. ber.endSequence()
  205. const attr = new Attribute()
  206. attr.parse(new BerReader(ber.buffer))
  207. t.equal(attr.type, 'sn')
  208. t.equal(attr.vals.length, 0)
  209. })
  210. t.end()
  211. })
  212. tap.test('pojo / toJSON', t => {
  213. t.test('returns an object', async t => {
  214. const expected = {
  215. type: 'foo',
  216. values: ['bar']
  217. }
  218. const attr = new Attribute(expected)
  219. t.strictSame(attr.pojo, expected)
  220. t.strictSame(JSON.stringify(attr), JSON.stringify(expected))
  221. })
  222. t.end()
  223. })
  224. tap.test('#fromBer', t => {
  225. const attributeWithValuesBytes = [
  226. 0x30, 0x1c, // start first attribute sequence, 28 bytes
  227. 0x04, 0x0b, // string, 11 bytes
  228. 0x6f, 0x62, 0x6a, 0x65, // "objectClass"
  229. 0x63, 0x74, 0x43, 0x6c,
  230. 0x61, 0x73, 0x73,
  231. 0x31, 0x0d, // start value sequence, 13 bytes
  232. 0x04, 0x03, 0x74, 0x6f, 0x70, // string: "top"
  233. 0x04, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e // string: "domain"
  234. ]
  235. t.test('parses an attribute with values', async t => {
  236. const ber = new BerReader(Buffer.from(attributeWithValuesBytes))
  237. const attr = Attribute.fromBer(ber)
  238. t.equal(attr.type, 'objectClass')
  239. t.equal(attr.vals[0], 'top')
  240. t.equal(attr.vals[1], 'domain')
  241. })
  242. t.test('parses an attribute without values', async t => {
  243. const ber = new BerWriter()
  244. ber.startSequence()
  245. ber.writeString('sn')
  246. ber.endSequence()
  247. const attr = Attribute.fromBer(new BerReader(ber.buffer))
  248. t.equal(attr.type, 'sn')
  249. t.strictSame(attr.vals, [])
  250. })
  251. t.end()
  252. })
  253. tap.test('#fromObject', t => {
  254. t.test('handles basic object', async t => {
  255. const attrs = Attribute.fromObject({
  256. foo: ['foo'],
  257. bar: 'bar',
  258. 'baz;binary': Buffer.from([0x00])
  259. })
  260. for (const attr of attrs) {
  261. t.equal(Object.prototype.toString.call(attr), '[object LdapAttribute]')
  262. }
  263. })
  264. t.end()
  265. })
  266. tap.test('#isAttribute', t => {
  267. t.test('rejects non-object', async t => {
  268. t.equal(Attribute.isAttribute(42), false)
  269. })
  270. t.test('accepts Attribute instances', async t => {
  271. const input = new Attribute({
  272. type: 'cn',
  273. values: ['foo']
  274. })
  275. t.equal(Attribute.isAttribute(input), true)
  276. })
  277. t.test('accepts attribute-like objects', async t => {
  278. const input = {
  279. type: 'cn',
  280. values: [
  281. 'foo',
  282. Buffer.from('bar')
  283. ]
  284. }
  285. t.equal(Attribute.isAttribute(input), true)
  286. })
  287. t.test('rejects non-attribute-like objects', async t => {
  288. let input = {
  289. foo: 'foo',
  290. values: 'bar'
  291. }
  292. t.equal(Attribute.isAttribute(input), false)
  293. input = {
  294. type: 'cn',
  295. values: [42]
  296. }
  297. t.equal(Attribute.isAttribute(input), false)
  298. })
  299. t.end()
  300. })
  301. tap.test('compare', async t => {
  302. const comp = Attribute.compare
  303. let a = new Attribute({
  304. type: 'foo',
  305. values: ['bar']
  306. })
  307. const b = new Attribute({
  308. type: 'foo',
  309. values: ['bar']
  310. })
  311. const notAnAttribute = 'this is not an attribute'
  312. t.throws(
  313. () => comp(a, notAnAttribute),
  314. Error('can only compare Attribute instances')
  315. )
  316. t.throws(
  317. () => comp(notAnAttribute, b),
  318. Error('can only compare Attribute instances')
  319. )
  320. t.equal(comp(a, b), 0)
  321. // Different types
  322. a = new Attribute({ type: 'boo' })
  323. t.equal(comp(a, b), -1)
  324. t.equal(comp(b, a), 1)
  325. // Different value counts
  326. a = new Attribute({
  327. type: 'foo',
  328. values: ['bar', 'bar']
  329. })
  330. t.equal(comp(a, b), 1)
  331. t.equal(comp(b, a), -1)
  332. // Different value contents (same count)
  333. a = new Attribute({
  334. type: 'foo',
  335. values: ['baz']
  336. })
  337. t.equal(comp(a, b), 1)
  338. t.equal(comp(b, a), -1)
  339. })