Repository navigation
Expand file tree
/
Copy pathtest.coffee
More file actions
468 lines (383 loc) · 17 KB
/
Copy pathtest.coffee
File metadata and controls
468 lines (383 loc) · 17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# This used to be the whole set of tests - now some of the ancillary parts of
# livedb have been pulled out. These tests should probably be split out into
# multiple files.
livedb = require '../lib'
assert = require 'assert'
textType = require('ot-text').type
{createClient, setup, teardown, stripTs} = require './util'
# Snapshots we get back from livedb will have a timestamp with a
# m:{ctime:, mtime:} with the current time. We'll check the time is sometime
# between when the module is loaded and 10 seconds later. This is a bit
# brittle. It also copies functionality in ot.coffee.
checkAndStripMetadata = do ->
before = Date.now()
after = before + 10 * 1000
(snapshot) ->
assert.ok snapshot.m
assert.ok before <= snapshot.m.ctime < after if snapshot.m.ctime
assert.ok before <= snapshot.m.mtime < after
delete snapshot.m.ctime
delete snapshot.m.mtime
snapshot
describe 'livedb', ->
beforeEach setup
beforeEach ->
@cName = '_test'
@cName2 = '_test2'
@cName3 = '_test3'
afterEach teardown
describe 'submit', ->
it 'creates a doc', (done) ->
@collection.submit @docName, {v:0, create:{type:'text'}}, (err) ->
throw new Error err if err
done()
it 'allows create ops with a null version', (done) ->
@collection.submit @docName, {v:null, create:{type:'text'}}, (err) ->
throw new Error err if err
done()
it 'errors if you dont specify a type', (done) ->
@collection.submit @docName, {v:0, create:{}}, (err) ->
assert.ok err
done()
it 'can create a document with metadata', (done) ->
@collection.submit @docName, {v:0, create:{type:'text', m:{language:'en'}}}, (err, v) =>
throw new Error err if err
@collection.fetch @docName, (err, {v, m}) =>
throw new Error err if err
assert.equal m.language, 'en'
done()
it 'removes metadata when documents are recreated', (done) ->
@collection.submit @docName, {create:{type:'text', m:{language:'en'}}}, (err, v) =>
throw new Error err if err
@collection.submit @docName, {del:true}, (err, v) =>
throw new Error err if err
@collection.submit @docName, {create:{type:'text'}}, (err, v) =>
throw new Error err if err
@collection.fetch @docName, (err, {v, m}) =>
throw new Error err if err
assert.equal m.language, null
done()
it 'errors when oplog is missing all operations', (done) -> @create =>
@collection.submit @docName, {v:1, op:['test']}, (err, v) =>
throw new Error err if err
getOps = @client.driver.getOps
@client.driver.getOps = (cName, docName, from, to, fn) ->
fn null, []
@collection.submit @docName, {v:1, op:['test']}, (err, v) =>
assert.equal err, 'Missing operations'
@client.driver.getOps = getOps
done()
it 'errors when oplog is missing some operations', (done) -> @create =>
@collection.submit @docName, {v:1, op:['test']}, (err, v) =>
throw new Error err if err
@collection.submit @docName, {v:2, op:['test 2']}, (err, v) =>
throw new Error err if err
getOps = @client.driver.getOps
@client.driver.getOps = (cName, docName, from, to, fn) =>
getOps.call @client.driver, cName, docName, from, to, (err, ops) ->
throw new Error err if err
fn null, ops.slice 1
@collection.submit @docName, {v:1, op:['test']}, (err, v) =>
assert.equal err, 'Missing operations'
@client.driver.getOps = getOps
done()
it 'can modify a document', (done) -> @create =>
@collection.submit @docName, v:1, op:['hi'], (err, v) =>
throw new Error err if err
@collection.fetch @docName, (err, {v, data}) =>
throw new Error err if err
assert.deepEqual data, 'hi'
done()
it 'transforms operations', (done) -> @create =>
@collection.submit @docName, v:1, op:['a'], src:'abc', seq:123, (err, v, ops) =>
throw new Error err if err
assert.deepEqual ops, []
@collection.submit @docName, v:1, op:['b'], (err, v, ops) =>
throw new Error err if err
assert.deepEqual stripTs(ops), [{v:1, op:['a'], src:'abc', seq:123, m:{}}]
done()
it 'allows ops with a null version', (done) -> @create =>
@collection.submit @docName, v:null, op:['hi'], (err, v) =>
throw new Error err if err
@collection.fetch @docName, (err, {v, data}) =>
throw new Error err if err
assert.deepEqual data, 'hi'
done()
it 'removes a doc', (done) -> @create =>
@collection.submit @docName, v:1, del:true, (err, v) =>
throw new Error err if err
@collection.fetch @docName, (err, data) =>
throw new Error err if err
assert.equal data.data, null
assert.equal data.type, null
done()
it 'removes a doc and allows creation of a new one', (done) ->
@collection.submit @docName, {create: {type: 'text', data: 'world'}}, (err) =>
throw new Error err if err
@collection.submit @docName, v:1, del:true, (err, v) =>
throw new Error err if err
@collection.fetch @docName, (err, data) =>
throw new Error err if err
assert.equal data.data, null
assert.equal data.type, null
@collection.submit @docName, {create: {type: 'text', data: 'hello'}}, (err) =>
throw new Error err if err
@collection.fetch @docName, (err, data) =>
throw new Error err if err
assert.equal data.data, 'hello'
assert.equal data.type, 'http://sharejs.org/types/textv1'
done()
it 'passes an error back to fetch if fetching returns a document with no version'
it 'will execute concurrent operations', (done) -> @create =>
count = 0
callback = (err, v) =>
assert.equal err, null
count++
done() if count is 2
@collection.submit @docName, v:1, src:'abc', seq:1, op:['client 1'], callback
@collection.submit @docName, v:1, src:'def', seq:1, op:['client 2'], callback
it 'sends operations to the persistant oplog', (done) -> @create =>
@db.getVersion @cName, @docName, (err, v) =>
throw Error err if err
assert.strictEqual v, 1
@db.getOps @cName, @docName, 0, null, (err, ops) ->
throw Error err if err
assert.strictEqual ops.length, 1
done()
it 'sends operations to any extra db backends', (done) ->
@testWrapper.submit = (cName, docName, opData, options, snapshot, callback) =>
assert.equal cName, @cName
assert.equal docName, @docName
assert.deepEqual stripTs(opData), {v:0, create:{type:textType.uri, data:''}, m:{}, src:''}
checkAndStripMetadata snapshot
assert.deepEqual snapshot, {v:1, data:'', type:textType.uri, m:{}}
done()
@create()
describe 'pre validate', ->
it 'runs a supplied pre validate function on the data', (done) ->
validationRun = no
preValidate = (opData, snapshot) ->
assert.deepEqual snapshot, {v:0}
validationRun = yes
return
@collection.submit @docName, {v:0, create:{type:'text'}, preValidate}, (err) ->
assert.ok validationRun
done()
it 'does not submit if pre validation fails', (done) -> @create =>
preValidate = (opData, snapshot) ->
assert.deepEqual opData.op, ['hi']
return 'no you!'
@collection.submit @docName, {v:1, op:['hi'], preValidate}, (err) =>
assert.equal err, 'no you!'
@collection.fetch @docName, (err, {v, data}) =>
throw new Error err if err
assert.deepEqual data, ''
done()
it 'calls prevalidate on each component in turn, and applies them incrementally'
describe 'validate', ->
it 'runs a supplied validation function on the data', (done) ->
validationRun = no
validate = (opData, snapshot, callback) ->
checkAndStripMetadata snapshot
assert.deepEqual snapshot, {v:1, data:'', type:textType.uri, m:{}}
validationRun = yes
return
@collection.submit @docName, {v:0, create:{type:'text'}, validate}, (err) ->
assert.ok validationRun
done()
it 'does not submit if validation fails', (done) -> @create =>
validate = (opData, snapshot, callback) ->
assert.deepEqual opData.op, ['hi']
return 'no you!'
@collection.submit @docName, {v:1, op:['hi'], validate}, (err) =>
assert.equal err, 'no you!'
@collection.fetch @docName, (err, {v, data}) =>
throw new Error err if err
assert.deepEqual data, ''
done()
it 'calls validate on each component in turn, and applies them incrementally'
describe 'dirty data', ->
beforeEach ->
@checkConsume = (list, expected, options, callback) =>
# Stolen from driver tests.
[options, callback] = [{}, options] if typeof options is 'function'
called = false
consume = (data, callback) ->
assert !called
called = true
assert.deepEqual data, expected
callback()
@client.consumeDirtyData list, options, consume, (err) ->
throw Error err if err
assert.equal called, expected isnt null
callback()
it 'calls getDirtyDataPre and getDirtyData', (done) -> @create =>
op = {v:1, op:['hi']}
@client.getDirtyDataPre = (c, d, op_, snapshot) =>
assert.equal c, @cName
assert.equal d, @docName
assert.deepEqual op_, op
# Editing the snapshot here is a little naughty.
checkAndStripMetadata snapshot
assert.deepEqual snapshot, {v:1, data:'', type:textType.uri, m:{}}
return {a:5}
@client.getDirtyData = (c, d, op_, snapshot) =>
assert.equal c, @cName
assert.equal d, @docName
assert.deepEqual op_, op
# Editing the snapshot here is a little naughty.
checkAndStripMetadata snapshot
assert.deepEqual snapshot, {v:2, data:'hi', type:textType.uri, m:{}}
return {b:6}
@collection.submit @docName, op, (err) =>
throw Error err if err
@checkConsume 'a', [5], =>
@checkConsume 'b', [6], done
describe 'fetch', ->
it 'can fetch created documents', (done) -> @create 'hi', =>
@collection.fetch @docName, (err, {v, data}) ->
throw new Error err if err
assert.deepEqual data, 'hi'
assert.strictEqual v, 1
done()
describe 'bulk fetch', ->
it 'can fetch created documents', (done) -> @create 'hi', =>
request = {}
request[@cName] = [@docName]
@client.bulkFetch request, (err, data) =>
throw new Error err if err
expected = {} # Urgh javascript :(
expected[@cName] = {}
expected[@cName][@docName] = {data:'hi', v:1, type:textType.uri, m:{}}
for cName, docs of data
for docName, snapshot of docs
checkAndStripMetadata snapshot
assert.deepEqual data, expected
done()
it 'can bulk fetch a projected document and actual document at the same time'
# creating anyway here just 'cos.
it 'doesnt return anything for missing documents', (done) -> @create 'hi', =>
request = {}
request[@cName] = ['doesNotExist']
@client.bulkFetch request, (err, data) =>
throw new Error err if err
expected = {}
expected[@cName] = {doesNotExist:{v:0}}
assert.deepEqual data, expected
done()
it 'works with multiple collections', (done) -> @create 'hi', =>
# This test fetches a bunch of documents that don't exist, but whatever.
request =
aaaaa: []
bbbbb: ['a', 'b', 'c']
request[@cName] = [@docName]
# Adding this afterwards to make sure @cName doesn't come last in native iteration order
request.zzzzz = ['d', 'e', 'f']
@client.bulkFetch request, (err, data) =>
throw new Error err if err
expected =
aaaaa: {}
bbbbb: {a:{v:0}, b:{v:0}, c:{v:0}}
zzzzz: {d:{v:0}, e:{v:0}, f:{v:0}}
expected[@cName] = {}
expected[@cName][@docName] = {data:'hi', v:1, type:textType.uri, m:{}}
checkAndStripMetadata data[@cName][@docName]
assert.deepEqual data, expected
done()
describe 'getOps', ->
it 'returns an empty list for nonexistant documents', (done) ->
@collection.getOps @docName, 0, -1, (err, ops) ->
throw new Error err if err
assert.deepEqual ops, []
done()
it 'returns ops that have been submitted to a document', (done) -> @create =>
@collection.submit @docName, v:1, op:['hi'], (err, v) =>
@collection.getOps @docName, 0, 1, (err, ops) =>
throw new Error err if err
assert.deepEqual stripTs(ops), [create:{type:textType.uri, data:''}, v:0, m:{}, src:'']
@collection.getOps @docName, 1, 2, (err, ops) ->
throw new Error err if err
assert.deepEqual stripTs(ops), [op:['hi'], v:1, m:{}, src:'']
done()
it 'puts a decent timestamp in ops', (done) ->
# TS should be between start and end.
start = Date.now()
@create =>
end = Date.now()
@collection.getOps @docName, 0, (err, ops) ->
throw Error(err) if err
assert.equal ops.length, 1
assert ops[0].m.ts >= start
assert ops[0].m.ts <= end
done()
it 'puts a decent timestamp in ops which already have a m:{} field', (done) ->
# TS should be between start and end.
start = Date.now()
@collection.submit @docName, {v:0, create:{type:'text'}, m:{}}, (err) =>
throw Error(err) if err
@collection.submit @docName, {v:1, op:['hi there'], m:{ts:123}}, (err) =>
throw Error(err) if err
end = Date.now()
@collection.getOps @docName, 0, (err, ops) ->
throw Error(err) if err
assert.equal ops.length, 2
for op in ops
assert op.m.ts >= start
assert op.m.ts <= end
done()
it 'returns all ops if to is not defined', (done) -> @create =>
@collection.getOps @docName, 0, (err, ops) =>
throw new Error err if err
assert.deepEqual stripTs(ops), [create:{type:textType.uri, data:''}, v:0, m:{}, src:'']
@collection.submit @docName, v:1, op:['hi'], (err, v) =>
@collection.getOps @docName, 0, (err, ops) ->
throw new Error err if err
assert.deepEqual stripTs(ops), [{create:{type:textType.uri, data:''}, v:0, m:{}, src:''}, {op:['hi'], v:1, m:{}, src:''}]
done()
it 'errors if ops are missing from the snapshotdb and oplogs', (done) -> @create =>
@collection.submit @docName, {v:1, op:['test']}, (err, v) =>
throw new Error err if err
@collection.submit @docName, {v:1, op:['test2']}, (err, v) =>
throw new Error err if err
getOps = @client.driver.getOps
@client.driver.getOps = (cName, docName, from, to, fn) =>
getOps.call @client.driver, cName, docName, from, to, (err, ops) ->
throw new Error err if err
fn null, ops.slice 1
@collection.getOps @docName, 0, (err, ops) =>
assert.equal err, 'Missing operations'
@client.driver.getOps = getOps
done()
it 'works with separate clients', (done) -> @create =>
return done() unless @driver.distributed
numClients = 10 # You can go way higher, but it gets slow.
# We have to share the database here because these tests are written
# against the memory API, which doesn't share data between instances.
clients = (createClient @db for [0...numClients])
for c, i in clients
c.client.submit @cName, @docName, v:1, op:["client #{i} "], (err) ->
throw new Error err if err
@collection.subscribe @docName, 1, (err, stream) =>
throw new Error err if err
# We should get numClients ops on the stream, in order.
seq = 1
stream.on 'readable', tryRead = =>
data = stream.read()
return unless data
#console.log 'read', data
delete data.op
assert.deepEqual stripTs(data), {v:seq, m:{}} #, op:{op:'ins', p:['x', -1]}}
if seq is numClients
#console.log 'destroy stream'
stream.destroy()
for c, i in clients
c.redis.quit()
c.db.close()
done()
# Uncomment to see the actually submitted data
#@collection.fetch @docName, (err, {v, data}) =>
# console.log data
else
seq++
tryRead()
it 'Fails to apply an operation to a document that was deleted and recreated'