Skip to content
This repository was archived by the owner on Dec 2, 2024. It is now read-only.

Commit 89c8034

Browse files
authored
Test illegal and stringified key types (#139)
* catch IndexedDB key and value errors on get, put, del and batch * test illegal and stringified key types * move isDataCloneError into structured-clone-test
1 parent 8db16c1 commit 89c8034

7 files changed

Lines changed: 184 additions & 52 deletions

File tree

index.js

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ var AbstractLevelDOWN = require('abstract-leveldown').AbstractLevelDOWN
88
var inherits = require('inherits')
99
var Iterator = require('./iterator')
1010
var mixedToBuffer = require('./util/mixed-to-buffer')
11-
var isDataCloneError = require('./util/is-data-clone-error')
1211
var setImmediate = require('./util/immediate')
1312
var support = require('./util/support')
1413

@@ -71,7 +70,17 @@ Level.prototype.await = function (request, callback) {
7170
}
7271

7372
Level.prototype._get = function (key, options, callback) {
74-
this.await(this.store('readonly').get(key), function (err, value) {
73+
var store = this.store('readonly')
74+
75+
try {
76+
var req = store.get(key)
77+
} catch (err) {
78+
return setImmediate(function () {
79+
callback(err)
80+
})
81+
}
82+
83+
this.await(req, function (err, value) {
7584
if (err) return callback(err)
7685

7786
if (value === undefined) {
@@ -88,21 +97,27 @@ Level.prototype._get = function (key, options, callback) {
8897
}
8998

9099
Level.prototype._del = function (key, options, callback) {
91-
this.await(this.store('readwrite').delete(key), callback)
100+
var store = this.store('readwrite')
101+
102+
try {
103+
var req = store.delete(key)
104+
} catch (err) {
105+
return setImmediate(function () {
106+
callback(err)
107+
})
108+
}
109+
110+
this.await(req, callback)
92111
}
93112

94113
Level.prototype._put = function (key, value, options, callback) {
95114
var store = this.store('readwrite')
96115

97116
try {
98-
// Will throw a DataCloneError if the environment
99-
// does not support serializing the key or value.
117+
// Will throw a DataError or DataCloneError if the environment
118+
// does not support serializing the key or value respectively.
100119
var req = store.put(value, key)
101120
} catch (err) {
102-
if (!isDataCloneError(err)) {
103-
throw err
104-
}
105-
106121
return setImmediate(function () {
107122
callback(err)
108123
})
@@ -148,9 +163,10 @@ Level.prototype._batch = function (operations, options, callback) {
148163
var store = this.store('readwrite')
149164
var transaction = store.transaction
150165
var index = 0
166+
var error
151167

152168
transaction.onabort = function () {
153-
callback(transaction.error || new Error('aborted by user'))
169+
callback(error || transaction.error || new Error('aborted by user'))
154170
}
155171

156172
transaction.oncomplete = function () {
@@ -161,7 +177,14 @@ Level.prototype._batch = function (operations, options, callback) {
161177
function loop () {
162178
var op = operations[index++]
163179
var key = op.key
164-
var req = op.type === 'del' ? store.delete(key) : store.put(op.value, key)
180+
181+
try {
182+
var req = op.type === 'del' ? store.delete(key) : store.put(op.value, key)
183+
} catch (err) {
184+
error = err
185+
transaction.abort()
186+
return
187+
}
165188

166189
if (index < operations.length) {
167190
req.onsuccess = loop

test/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,5 @@ require('abstract-leveldown/abstract/iterator-range-test').all(leveljs, test, te
2727
require('./custom-test')(leveljs, test, testCommon)
2828
require('./structured-clone-test')(leveljs, test, testCommon)
2929
require('./key-type-test')(leveljs, test, testCommon)
30+
require('./key-type-illegal-test')(leveljs, test, testCommon)
31+
require('./key-type-stringified-test')(leveljs, test, testCommon)

test/key-type-illegal-test.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/* global indexedDB */
2+
3+
'use strict'
4+
5+
var support = require('../util/support')
6+
7+
// Key types not supported by IndexedDB Second Edition.
8+
var illegalTypes = [
9+
// Allow failure because IE11 treats this as a valid key.
10+
{ name: 'NaN Date', allowFailure: true, key: new Date(''), error: 'DataError' },
11+
{ name: 'Error', key: new Error(), error: 'DataError' },
12+
{ name: 'Function', key: function () {}, error: 'DataError' },
13+
{ name: 'DOMNode', key: global.document, error: 'DataError' },
14+
{ name: 'Boolean(true)', key: new Boolean(true), error: 'DataError' }, // eslint-disable-line
15+
{ name: 'Boolean(false)', key: new Boolean(false), error: 'DataError' }, // eslint-disable-line
16+
]
17+
18+
// These are only tested if the environment supports array keys.
19+
// Cyclical arrays are not tested because our #_serializeKey goes into a loop.
20+
var illegalArrays = [
21+
// This type gets rejected by abstract-leveldown. As for why the error says
22+
// "empty String" rather than "empty Array", see Level/abstract-leveldown#230.
23+
{ name: 'empty Array', key: [], message: 'key cannot be an empty String' },
24+
25+
// These contain a valid element to ensure we don't hit an empty key assertion.
26+
{ name: 'Array w/ null', key: ['a', null], error: 'DataError' },
27+
{ name: 'Array w/ undefined', key: ['a', undefined], error: 'DataError' },
28+
29+
{ name: 'sparse Array', key: new Array(10), error: 'DataError' }
30+
]
31+
32+
module.exports = function (leveljs, test, testCommon) {
33+
test('setUp', testCommon.setUp)
34+
35+
if (support.test(['1'])(indexedDB)) {
36+
illegalTypes = illegalTypes.concat(illegalArrays)
37+
}
38+
39+
illegalTypes.forEach(function (item) {
40+
var skip = item.allowFailure ? 'pass' : 'fail'
41+
var db
42+
43+
test('open', function (t) {
44+
db = leveljs(testCommon.location())
45+
db.open(t.end.bind(t))
46+
})
47+
48+
test('put() illegal key type: ' + item.name, function (t) {
49+
db.put(item.key, 'value', verify.bind(null, t))
50+
})
51+
52+
test('del() illegal key type: ' + item.name, function (t) {
53+
db.del(item.key, verify.bind(null, t))
54+
})
55+
56+
test('get() illegal key type: ' + item.name, function (t) {
57+
db.get(item.key, function (err) {
58+
verify(t, /NotFound/.test(err) ? null : err)
59+
})
60+
})
61+
62+
test('batch() put illegal key type: ' + item.name, function (t) {
63+
db.batch([{ type: 'put', key: item.key, value: 'value' }], verify.bind(null, t))
64+
})
65+
66+
test('batch() del illegal key type: ' + item.name, function (t) {
67+
db.batch([{ type: 'del', key: item.key }], verify.bind(null, t))
68+
})
69+
70+
test('close', function (t) { db.close(t.end.bind(t)) })
71+
72+
function verify (t, err) {
73+
if (!err) {
74+
t[skip]('type is treated as valid in this environment')
75+
return t.end()
76+
}
77+
78+
if ('error' in item) t.is(err.name, item.error, 'is ' + item.error)
79+
if ('message' in item) t.is(err.message, item.message, item.message)
80+
81+
t.end()
82+
}
83+
})
84+
85+
test('tearDown', testCommon.tearDown)
86+
}

test/key-type-stringified-test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use strict'
2+
3+
// Key types that are not supported by IndexedDB Second Edition, but get
4+
// stringified for abstract-leveldown compatibility.
5+
var stringifiedTypes = [
6+
{ name: 'true', input: true, output: 'true' },
7+
{ name: 'false', input: false, output: 'false' },
8+
{ name: 'NaN', input: NaN, output: 'NaN' }
9+
]
10+
11+
module.exports = function (leveljs, test, testCommon) {
12+
test('setUp', testCommon.setUp)
13+
14+
stringifiedTypes.forEach(function (item) {
15+
test('stringified key type: ' + item.name, function (t) {
16+
var db = leveljs(testCommon.location())
17+
18+
db.open(function (err) {
19+
t.ifError(err, 'no open error')
20+
21+
db.put(item.input, 'value', function (err) {
22+
t.ifError(err, 'no put error')
23+
24+
var it = db.iterator({ keyAsBuffer: false, valueAsBuffer: false })
25+
26+
testCommon.collectEntries(it, function (err, entries) {
27+
t.ifError(err, 'no iterator error')
28+
t.same(entries, [{ key: item.output, value: 'value' }])
29+
30+
db.close(t.end.bind(t))
31+
})
32+
})
33+
})
34+
})
35+
})
36+
37+
test('tearDown', testCommon.tearDown)
38+
}

test/key-type-test.js

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,26 @@
55
var ta = require('./util/create-typed-array')
66
var support = require('../util/support')
77

8+
// All key types supported by IndexedDB Second Edition.
89
var types = [
9-
{ type: 'number', value: -20 },
10-
{ type: '+Infinity', value: Infinity },
11-
{ type: '-Infinity', value: -Infinity },
12-
{ type: 'string', value: 'test' },
13-
{ type: 'Date', ctor: true, value: new Date() },
14-
{ type: 'Array', ctor: true, allowFailure: true, value: [0, '1'] },
15-
{ type: 'ArrayBuffer', ctor: true, allowFailure: true, value: ta(Buffer).buffer },
16-
{ type: 'Int8Array', ctor: true, allowFailure: true, createValue: ta, view: true },
17-
{ type: 'Uint8Array', ctor: true, allowFailure: true, createValue: ta, view: true },
18-
{ type: 'Uint8ClampedArray', ctor: true, allowFailure: true, createValue: ta, view: true },
19-
{ type: 'Int16Array', ctor: true, allowFailure: true, createValue: ta, view: true },
20-
{ type: 'Uint16Array', ctor: true, allowFailure: true, createValue: ta, view: true },
21-
{ type: 'Int32Array', ctor: true, allowFailure: true, createValue: ta, view: true },
22-
{ type: 'Uint32Array', ctor: true, allowFailure: true, createValue: ta, view: true },
23-
{ type: 'Float32Array', ctor: true, allowFailure: true, createValue: ta, view: true },
24-
{ type: 'Float64Array', ctor: true, allowFailure: true, createValue: ta, view: true }
10+
{ type: 'number', key: -20 },
11+
{ type: '+Infinity', key: Infinity },
12+
{ type: '-Infinity', key: -Infinity },
13+
{ type: 'string', key: 'test' },
14+
{ type: 'Date', ctor: true, key: new Date() },
15+
{ type: 'Array', ctor: true, allowFailure: true, key: [0, '1'] },
16+
{ type: 'ArrayBuffer', ctor: true, allowFailure: true, key: ta(Buffer).buffer },
17+
{ type: 'Int8Array', ctor: true, allowFailure: true, createKey: ta, view: true },
18+
{ type: 'Uint8Array', ctor: true, allowFailure: true, createKey: ta, view: true },
19+
{ type: 'Uint8ClampedArray', ctor: true, allowFailure: true, createKey: ta, view: true },
20+
{ type: 'Int16Array', ctor: true, allowFailure: true, createKey: ta, view: true },
21+
{ type: 'Uint16Array', ctor: true, allowFailure: true, createKey: ta, view: true },
22+
{ type: 'Int32Array', ctor: true, allowFailure: true, createKey: ta, view: true },
23+
{ type: 'Uint32Array', ctor: true, allowFailure: true, createKey: ta, view: true },
24+
{ type: 'Float32Array', ctor: true, allowFailure: true, createKey: ta, view: true },
25+
{ type: 'Float64Array', ctor: true, allowFailure: true, createKey: ta, view: true }
2526
]
2627

27-
// TODO: test types that are not supported by IndexedDB Second Edition
28-
// - Date NaN (should be rejected by IndexedDB)
29-
// - empty array (should be rejected by abstract-leveldown)
30-
// - array containing null (should be rejected by IndexedDB)
31-
// - cyclical array (not sure)
32-
// - Array(10) (not sure)
33-
// var illegalTypes = []
34-
35-
// TODO: test types that are not supported by IndexedDB Second Edition, but get
36-
// stringified for abstract-leveldown compatibility.
37-
// - NaN
38-
// - boolean
39-
// var stringifiedTypes = []
40-
4128
module.exports = function (leveljs, test, testCommon) {
4229
var db
4330

@@ -53,16 +40,16 @@ module.exports = function (leveljs, test, testCommon) {
5340
test('key type: ' + testName, function (t) {
5441
var Constructor = item.ctor ? global[item.type] : null
5542
var skip = item.allowFailure ? 'pass' : 'fail'
56-
var input = item.value
43+
var input = item.key
5744

5845
if (item.ctor && !Constructor) {
5946
t[skip]('constructor is undefined in this environment')
6047
return t.end()
6148
}
6249

63-
if (item.createValue) {
50+
if (item.createKey) {
6451
try {
65-
input = item.createValue(Constructor)
52+
input = item.createKey(Constructor)
6653
} catch (err) {
6754
t[skip]('constructor is not spec-compliant in this environment')
6855
return t.end()

test/structured-clone-test.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
'use strict'
22

3-
var isDataCloneError = require('../util/is-data-clone-error')
43
var ta = require('./util/create-typed-array')
54

5+
function isDataCloneError (err) {
6+
return err.name === 'DataCloneError' || err.code === 25
7+
}
8+
69
// level-js supports all types of the structured clone algorithm
710
// except for null and undefined (unless nested in another type).
811
var types = [

util/is-data-clone-error.js

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)