index.test.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. 'use strict'
  2. const tap = require('tap')
  3. const { BerReader } = require('@ldapjs/asn1')
  4. const Attribute = require('@ldapjs/attribute')
  5. const Change = require('./index')
  6. tap.test('constructor', t => {
  7. t.test('throws for bad operation', async t => {
  8. t.throws(
  9. () => new Change({ operation: 'bad' }),
  10. Error('invalid operation type: bad')
  11. )
  12. })
  13. t.test('throws for bad modification', async t => {
  14. t.throws(
  15. () => new Change({ modification: 'bad' }),
  16. Error('modification must be an Attribute')
  17. )
  18. })
  19. t.test('creates an instance', async t => {
  20. const change = new Change({
  21. modification: new Attribute()
  22. })
  23. t.equal(change.operation, 'add')
  24. t.type(change.modification, Attribute)
  25. t.equal(Object.prototype.toString.call(change), '[object LdapChange]')
  26. })
  27. t.end()
  28. })
  29. tap.test('modification', t => {
  30. t.test('gets', async t => {
  31. const attr = new Attribute()
  32. const change = new Change({ modification: attr })
  33. t.equal(change.modification, attr)
  34. })
  35. t.test('sets', async t => {
  36. const attr1 = new Attribute()
  37. const attr2 = new Attribute()
  38. const change = new Change({ modification: attr1 })
  39. t.equal(change.modification, attr1)
  40. change.modification = attr2
  41. t.equal(change.modification, attr2)
  42. t.not(attr1, attr2)
  43. })
  44. t.test('throws if value is not attribute-like', async t => {
  45. const change = new Change({ modification: new Attribute() })
  46. t.throws(
  47. () => { change.modification = { foo: 'foo' } },
  48. Error('modification must be an Attribute')
  49. )
  50. })
  51. t.test('converts attribute-like to Attribute', async t => {
  52. const change = new Change({
  53. modification: {
  54. type: 'dn=foo,dc=example,dc=com',
  55. values: []
  56. }
  57. })
  58. t.equal(
  59. Object.prototype.toString.call(change.modification),
  60. '[object LdapAttribute]'
  61. )
  62. })
  63. t.end()
  64. })
  65. tap.test('.operation', t => {
  66. const attr = new Attribute()
  67. const change = new Change({ modification: attr })
  68. t.test('throws for unrecognized operation', async t => {
  69. t.throws(
  70. () => { change.operation = 'bad' },
  71. Error('invalid operation type: bad')
  72. )
  73. t.throws(
  74. () => { change.operation = 0xff },
  75. Error('invalid operation type: 0xff')
  76. )
  77. })
  78. t.test('sets and gets', async t => {
  79. change.operation = 0
  80. t.equal(change.operation, 'add')
  81. change.operation = 'add'
  82. t.equal(change.operation, 'add')
  83. change.operation = 1
  84. t.equal(change.operation, 'delete')
  85. change.operation = 'delete'
  86. t.equal(change.operation, 'delete')
  87. change.operation = 2
  88. t.equal(change.operation, 'replace')
  89. change.operation = 'replace'
  90. t.equal(change.operation, 'replace')
  91. change.operation = 'Replace'
  92. t.equal(change.operation, 'replace')
  93. })
  94. t.end()
  95. })
  96. tap.test('.pojo', t => {
  97. t.test('returns a plain object', async t => {
  98. const change = new Change({
  99. modification: new Attribute()
  100. })
  101. const expected = {
  102. operation: 'add',
  103. modification: {
  104. type: '',
  105. values: []
  106. }
  107. }
  108. t.strictSame(change.pojo, expected)
  109. t.strictSame(change.toJSON(), expected)
  110. })
  111. t.end()
  112. })
  113. tap.test('toBer', t => {
  114. t.test('serializes to ber', async t => {
  115. const expected = Buffer.from([
  116. 0x30, 0x15, // sequence, 21 bytes
  117. 0x0a, 0x01, 0x00, // enumerated value 0
  118. 0x30, 0x10, // sequence, 16 bytes
  119. 0x04, 0x02, // string, 2 bytes
  120. 0x63, 0x6e, // 'cn'
  121. 0x31, 0x0a, // sequence of strings, 10 bytes
  122. 0x04, 0x03, // string, 3 bytes
  123. 0x66, 0x6f, 0x6f, // 'foo'
  124. 0x04, 0x03, // string 3 bytes
  125. 0x62, 0x61, 0x72
  126. ])
  127. const change = new Change({
  128. modification: {
  129. type: 'cn',
  130. values: ['foo', 'bar']
  131. }
  132. })
  133. const ber = change.toBer()
  134. t.equal(expected.compare(ber.buffer), 0)
  135. })
  136. t.end()
  137. })
  138. tap.test('#apply', t => {
  139. t.test('throws if change is not a Change', async t => {
  140. t.throws(
  141. () => Change.apply({}, {}),
  142. Error('change must be an instance of Change')
  143. )
  144. })
  145. t.test('applies to a target with no type', async t => {
  146. const attr = new Attribute({
  147. type: 'cn',
  148. values: ['new']
  149. })
  150. const change = new Change({ modification: attr })
  151. const target = {}
  152. Change.apply(change, target)
  153. t.strictSame(target, {
  154. cn: ['new']
  155. })
  156. })
  157. t.test('applies to a target with a scalar type', async t => {
  158. const attr = new Attribute({
  159. type: 'cn',
  160. values: ['new']
  161. })
  162. const change = new Change({ modification: attr })
  163. const target = { cn: 'old' }
  164. Change.apply(change, target)
  165. t.strictSame(target, {
  166. cn: ['old', 'new']
  167. })
  168. })
  169. t.test('applies to a target with an array type', async t => {
  170. const attr = new Attribute({
  171. type: 'cn',
  172. values: ['new']
  173. })
  174. const change = new Change({ modification: attr })
  175. const target = { cn: ['old'] }
  176. Change.apply(change, target)
  177. t.strictSame(target, {
  178. cn: ['old', 'new']
  179. })
  180. })
  181. t.test('add operation adds only new values', async t => {
  182. const attr = new Attribute({
  183. type: 'cn',
  184. values: ['new', 'foo']
  185. })
  186. const change = new Change({ modification: attr })
  187. const target = { cn: ['old', 'new'] }
  188. Change.apply(change, target)
  189. t.strictSame(target, {
  190. cn: ['old', 'new', 'foo']
  191. })
  192. })
  193. t.test('delete operation removes property', async t => {
  194. const attr = new Attribute({
  195. type: 'cn',
  196. values: ['new']
  197. })
  198. const change = new Change({
  199. operation: 'delete',
  200. modification: attr
  201. })
  202. const target = { cn: ['new'] }
  203. Change.apply(change, target)
  204. t.strictSame(target, {})
  205. })
  206. t.test('delete operation removes values', async t => {
  207. const attr = new Attribute({
  208. type: 'cn',
  209. values: ['remove_me']
  210. })
  211. const change = new Change({
  212. operation: 'delete',
  213. modification: attr
  214. })
  215. const target = { cn: ['remove_me', 'keep_me'] }
  216. Change.apply(change, target)
  217. t.strictSame(target, {
  218. cn: ['keep_me']
  219. })
  220. })
  221. t.test('replace removes empty set', async t => {
  222. const attr = new Attribute({
  223. type: 'cn',
  224. values: []
  225. })
  226. const change = new Change({
  227. operation: 'replace',
  228. modification: attr
  229. })
  230. const target = { cn: ['old'] }
  231. Change.apply(change, target)
  232. t.strictSame(target, {})
  233. })
  234. t.test('replace removes values', async t => {
  235. const attr = new Attribute({
  236. type: 'cn',
  237. values: ['new_set']
  238. })
  239. const change = new Change({
  240. operation: 'replace',
  241. modification: attr
  242. })
  243. const target = { cn: ['old_set'] }
  244. Change.apply(change, target)
  245. t.strictSame(target, {
  246. cn: ['new_set']
  247. })
  248. })
  249. t.test('scalar option works for new single values', async t => {
  250. const attr = new Attribute({
  251. type: 'cn',
  252. values: ['new']
  253. })
  254. const change = new Change({ modification: attr })
  255. const target = {}
  256. Change.apply(change, target, true)
  257. t.strictSame(target, {
  258. cn: 'new'
  259. })
  260. })
  261. t.test('scalar option is ignored for multiple values', async t => {
  262. const attr = new Attribute({
  263. type: 'cn',
  264. values: ['new']
  265. })
  266. const change = new Change({ modification: attr })
  267. const target = {
  268. cn: ['old']
  269. }
  270. Change.apply(change, target, true)
  271. t.strictSame(target, {
  272. cn: ['old', 'new']
  273. })
  274. })
  275. t.end()
  276. })
  277. tap.test('#isChange', t => {
  278. t.test('true for instance', async t => {
  279. const change = new Change({ modification: new Attribute() })
  280. t.equal(Change.isChange(change), true)
  281. })
  282. t.test('false for non-object', async t => {
  283. t.equal(Change.isChange([]), false)
  284. })
  285. t.test('true for shape match', async t => {
  286. const change = {
  287. operation: 'add',
  288. modification: {
  289. type: '',
  290. values: []
  291. }
  292. }
  293. t.equal(Change.isChange(change), true)
  294. change.operation = 0
  295. change.modification = new Attribute()
  296. t.equal(Change.isChange(change), true)
  297. })
  298. t.test('false for shape mis-match', async t => {
  299. const change = {
  300. operation: 'add',
  301. mod: {
  302. type: '',
  303. values: []
  304. }
  305. }
  306. t.equal(Change.isChange(change), false)
  307. })
  308. t.end()
  309. })
  310. tap.test('#compare', t => {
  311. t.test('throws if params are not changes', async t => {
  312. const change = new Change({ modification: new Attribute() })
  313. const expected = Error('can only compare Change instances')
  314. t.throws(
  315. () => Change.compare({}, change),
  316. expected
  317. )
  318. t.throws(
  319. () => Change.compare(change, {}),
  320. expected
  321. )
  322. })
  323. t.test('orders add first', async t => {
  324. const change1 = new Change({ modification: new Attribute() })
  325. const change2 = new Change({
  326. operation: 'delete',
  327. modification: new Attribute()
  328. })
  329. t.equal(Change.compare(change1, change2), -1)
  330. change2.operation = 'replace'
  331. t.equal(Change.compare(change1, change2), -1)
  332. })
  333. t.test('orders delete above add', async t => {
  334. const change1 = new Change({ modification: new Attribute() })
  335. const change2 = new Change({
  336. operation: 'delete',
  337. modification: new Attribute()
  338. })
  339. t.equal(Change.compare(change2, change1), 1)
  340. })
  341. t.test('orders by attribute for same operation', async t => {
  342. const change1 = new Change({ modification: new Attribute() })
  343. const change2 = new Change({ modification: new Attribute() })
  344. t.equal(Change.compare(change1, change2), 0)
  345. })
  346. t.end()
  347. })
  348. tap.test('#fromBer', t => {
  349. t.test('creates instance', async t => {
  350. const bytes = [
  351. 0x30, 0x15, // sequence, 21 bytes
  352. 0x0a, 0x01, 0x00, // enumerated value 0
  353. 0x30, 0x10, // sequence, 16 bytes
  354. 0x04, 0x02, // string, 2 bytes
  355. 0x63, 0x6e, // 'cn'
  356. 0x31, 0x0a, // sequence of strings, 10 bytes
  357. 0x04, 0x03, // string, 3 bytes
  358. 0x66, 0x6f, 0x6f, // 'foo'
  359. 0x04, 0x03, // string 3 bytes
  360. 0x62, 0x61, 0x72
  361. ]
  362. const reader = new BerReader(Buffer.from(bytes))
  363. const change = Change.fromBer(reader)
  364. t.strictSame(change.pojo, {
  365. operation: 'add',
  366. modification: {
  367. type: 'cn',
  368. values: ['foo', 'bar']
  369. }
  370. })
  371. })
  372. t.end()
  373. })