123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- 'use strict'
- const tap = require('tap')
- const { BerReader } = require('@ldapjs/asn1')
- const Attribute = require('@ldapjs/attribute')
- const Change = require('./index')
- tap.test('constructor', t => {
- t.test('throws for bad operation', async t => {
- t.throws(
- () => new Change({ operation: 'bad' }),
- Error('invalid operation type: bad')
- )
- })
- t.test('throws for bad modification', async t => {
- t.throws(
- () => new Change({ modification: 'bad' }),
- Error('modification must be an Attribute')
- )
- })
- t.test('creates an instance', async t => {
- const change = new Change({
- modification: new Attribute()
- })
- t.equal(change.operation, 'add')
- t.type(change.modification, Attribute)
- t.equal(Object.prototype.toString.call(change), '[object LdapChange]')
- })
- t.end()
- })
- tap.test('modification', t => {
- t.test('gets', async t => {
- const attr = new Attribute()
- const change = new Change({ modification: attr })
- t.equal(change.modification, attr)
- })
- t.test('sets', async t => {
- const attr1 = new Attribute()
- const attr2 = new Attribute()
- const change = new Change({ modification: attr1 })
- t.equal(change.modification, attr1)
- change.modification = attr2
- t.equal(change.modification, attr2)
- t.not(attr1, attr2)
- })
- t.test('throws if value is not attribute-like', async t => {
- const change = new Change({ modification: new Attribute() })
- t.throws(
- () => { change.modification = { foo: 'foo' } },
- Error('modification must be an Attribute')
- )
- })
- t.test('converts attribute-like to Attribute', async t => {
- const change = new Change({
- modification: {
- type: 'dn=foo,dc=example,dc=com',
- values: []
- }
- })
- t.equal(
- Object.prototype.toString.call(change.modification),
- '[object LdapAttribute]'
- )
- })
- t.end()
- })
- tap.test('.operation', t => {
- const attr = new Attribute()
- const change = new Change({ modification: attr })
- t.test('throws for unrecognized operation', async t => {
- t.throws(
- () => { change.operation = 'bad' },
- Error('invalid operation type: bad')
- )
- t.throws(
- () => { change.operation = 0xff },
- Error('invalid operation type: 0xff')
- )
- })
- t.test('sets and gets', async t => {
- change.operation = 0
- t.equal(change.operation, 'add')
- change.operation = 'add'
- t.equal(change.operation, 'add')
- change.operation = 1
- t.equal(change.operation, 'delete')
- change.operation = 'delete'
- t.equal(change.operation, 'delete')
- change.operation = 2
- t.equal(change.operation, 'replace')
- change.operation = 'replace'
- t.equal(change.operation, 'replace')
- change.operation = 'Replace'
- t.equal(change.operation, 'replace')
- })
- t.end()
- })
- tap.test('.pojo', t => {
- t.test('returns a plain object', async t => {
- const change = new Change({
- modification: new Attribute()
- })
- const expected = {
- operation: 'add',
- modification: {
- type: '',
- values: []
- }
- }
- t.strictSame(change.pojo, expected)
- t.strictSame(change.toJSON(), expected)
- })
- t.end()
- })
- tap.test('toBer', t => {
- t.test('serializes to ber', async t => {
- const expected = Buffer.from([
- 0x30, 0x15, // sequence, 21 bytes
- 0x0a, 0x01, 0x00, // enumerated value 0
- 0x30, 0x10, // sequence, 16 bytes
- 0x04, 0x02, // string, 2 bytes
- 0x63, 0x6e, // 'cn'
- 0x31, 0x0a, // sequence of strings, 10 bytes
- 0x04, 0x03, // string, 3 bytes
- 0x66, 0x6f, 0x6f, // 'foo'
- 0x04, 0x03, // string 3 bytes
- 0x62, 0x61, 0x72
- ])
- const change = new Change({
- modification: {
- type: 'cn',
- values: ['foo', 'bar']
- }
- })
- const ber = change.toBer()
- t.equal(expected.compare(ber.buffer), 0)
- })
- t.end()
- })
- tap.test('#apply', t => {
- t.test('throws if change is not a Change', async t => {
- t.throws(
- () => Change.apply({}, {}),
- Error('change must be an instance of Change')
- )
- })
- t.test('applies to a target with no type', async t => {
- const attr = new Attribute({
- type: 'cn',
- values: ['new']
- })
- const change = new Change({ modification: attr })
- const target = {}
- Change.apply(change, target)
- t.strictSame(target, {
- cn: ['new']
- })
- })
- t.test('applies to a target with a scalar type', async t => {
- const attr = new Attribute({
- type: 'cn',
- values: ['new']
- })
- const change = new Change({ modification: attr })
- const target = { cn: 'old' }
- Change.apply(change, target)
- t.strictSame(target, {
- cn: ['old', 'new']
- })
- })
- t.test('applies to a target with an array type', async t => {
- const attr = new Attribute({
- type: 'cn',
- values: ['new']
- })
- const change = new Change({ modification: attr })
- const target = { cn: ['old'] }
- Change.apply(change, target)
- t.strictSame(target, {
- cn: ['old', 'new']
- })
- })
- t.test('add operation adds only new values', async t => {
- const attr = new Attribute({
- type: 'cn',
- values: ['new', 'foo']
- })
- const change = new Change({ modification: attr })
- const target = { cn: ['old', 'new'] }
- Change.apply(change, target)
- t.strictSame(target, {
- cn: ['old', 'new', 'foo']
- })
- })
- t.test('delete operation removes property', async t => {
- const attr = new Attribute({
- type: 'cn',
- values: ['new']
- })
- const change = new Change({
- operation: 'delete',
- modification: attr
- })
- const target = { cn: ['new'] }
- Change.apply(change, target)
- t.strictSame(target, {})
- })
- t.test('delete operation removes values', async t => {
- const attr = new Attribute({
- type: 'cn',
- values: ['remove_me']
- })
- const change = new Change({
- operation: 'delete',
- modification: attr
- })
- const target = { cn: ['remove_me', 'keep_me'] }
- Change.apply(change, target)
- t.strictSame(target, {
- cn: ['keep_me']
- })
- })
- t.test('replace removes empty set', async t => {
- const attr = new Attribute({
- type: 'cn',
- values: []
- })
- const change = new Change({
- operation: 'replace',
- modification: attr
- })
- const target = { cn: ['old'] }
- Change.apply(change, target)
- t.strictSame(target, {})
- })
- t.test('replace removes values', async t => {
- const attr = new Attribute({
- type: 'cn',
- values: ['new_set']
- })
- const change = new Change({
- operation: 'replace',
- modification: attr
- })
- const target = { cn: ['old_set'] }
- Change.apply(change, target)
- t.strictSame(target, {
- cn: ['new_set']
- })
- })
- t.test('scalar option works for new single values', async t => {
- const attr = new Attribute({
- type: 'cn',
- values: ['new']
- })
- const change = new Change({ modification: attr })
- const target = {}
- Change.apply(change, target, true)
- t.strictSame(target, {
- cn: 'new'
- })
- })
- t.test('scalar option is ignored for multiple values', async t => {
- const attr = new Attribute({
- type: 'cn',
- values: ['new']
- })
- const change = new Change({ modification: attr })
- const target = {
- cn: ['old']
- }
- Change.apply(change, target, true)
- t.strictSame(target, {
- cn: ['old', 'new']
- })
- })
- t.end()
- })
- tap.test('#isChange', t => {
- t.test('true for instance', async t => {
- const change = new Change({ modification: new Attribute() })
- t.equal(Change.isChange(change), true)
- })
- t.test('false for non-object', async t => {
- t.equal(Change.isChange([]), false)
- })
- t.test('true for shape match', async t => {
- const change = {
- operation: 'add',
- modification: {
- type: '',
- values: []
- }
- }
- t.equal(Change.isChange(change), true)
- change.operation = 0
- change.modification = new Attribute()
- t.equal(Change.isChange(change), true)
- })
- t.test('false for shape mis-match', async t => {
- const change = {
- operation: 'add',
- mod: {
- type: '',
- values: []
- }
- }
- t.equal(Change.isChange(change), false)
- })
- t.end()
- })
- tap.test('#compare', t => {
- t.test('throws if params are not changes', async t => {
- const change = new Change({ modification: new Attribute() })
- const expected = Error('can only compare Change instances')
- t.throws(
- () => Change.compare({}, change),
- expected
- )
- t.throws(
- () => Change.compare(change, {}),
- expected
- )
- })
- t.test('orders add first', async t => {
- const change1 = new Change({ modification: new Attribute() })
- const change2 = new Change({
- operation: 'delete',
- modification: new Attribute()
- })
- t.equal(Change.compare(change1, change2), -1)
- change2.operation = 'replace'
- t.equal(Change.compare(change1, change2), -1)
- })
- t.test('orders delete above add', async t => {
- const change1 = new Change({ modification: new Attribute() })
- const change2 = new Change({
- operation: 'delete',
- modification: new Attribute()
- })
- t.equal(Change.compare(change2, change1), 1)
- })
- t.test('orders by attribute for same operation', async t => {
- const change1 = new Change({ modification: new Attribute() })
- const change2 = new Change({ modification: new Attribute() })
- t.equal(Change.compare(change1, change2), 0)
- })
- t.end()
- })
- tap.test('#fromBer', t => {
- t.test('creates instance', async t => {
- const bytes = [
- 0x30, 0x15, // sequence, 21 bytes
- 0x0a, 0x01, 0x00, // enumerated value 0
- 0x30, 0x10, // sequence, 16 bytes
- 0x04, 0x02, // string, 2 bytes
- 0x63, 0x6e, // 'cn'
- 0x31, 0x0a, // sequence of strings, 10 bytes
- 0x04, 0x03, // string, 3 bytes
- 0x66, 0x6f, 0x6f, // 'foo'
- 0x04, 0x03, // string 3 bytes
- 0x62, 0x61, 0x72
- ]
- const reader = new BerReader(Buffer.from(bytes))
- const change = Change.fromBer(reader)
- t.strictSame(change.pojo, {
- operation: 'add',
- modification: {
- type: 'cn',
- values: ['foo', 'bar']
- }
- })
- })
- t.end()
- })
|