UNPKG

23.5 kBPlain TextView Raw
1import {expect} from "chai";
2import {Correlation, FileSystem, fileSystemEventNames, FileSystemReadSync, Directory, DirectoryContent, pathSeparator} from '../src/universal';
3import {EventsMatcher} from '../test-kit/drivers/events-matcher';
4import {delayedPromise} from "../src/promise-utils";
5
6
7export const dirName = 'foo';
8export const fileName = 'bar.txt';
9export const content = 'content';
10export const ignoredDir = 'ignored';
11export const ignoredFile = `${dirName}${pathSeparator}ignored.txt`;
12
13export function assertFileSystemContract(fsProvider: () => Promise<FileSystem>, options: EventsMatcher.Options) {
14 describe(`filesystem contract`, () => {
15 let fs: FileSystem;
16 let matcher: EventsMatcher;
17 beforeEach(() => {
18 matcher = new EventsMatcher(options);
19 return fsProvider()
20 .then(newFs => {
21 fs = newFs;
22 matcher.track(fs.events, ...fileSystemEventNames);
23 });
24 });
25
26 it(`initially empty`, function () {
27 return expect(fs.loadDirectoryTree()).to.become({type: 'dir', name: '', fullPath: '', children: []});
28 });
29
30 it(`loading a non-existing file - fails`, function () {
31 return expect(fs.loadTextFile(fileName)).to.be.rejectedWith(Error);
32 });
33
34 it(`loading a directory as a file - fails`, function () {
35 return fs.ensureDirectory(dirName)
36 .then(() => {
37 return matcher.expect([{type: 'directoryCreated', fullPath: dirName}])
38 })
39 .then(() => expect(fs.loadTextFile(dirName)).to.be.rejectedWith(Error))
40 .then(() => matcher.expect([]));
41 });
42
43 it(`saving an illegal file name - fails`, function () {
44 return expect(fs.saveFile('', content)).to.be.rejectedWith(Error)
45 .then(() => matcher.expect([]));
46 });
47
48 it(`ensuring existence of directory`, function () {
49 const expectedStructure = {
50 type: 'dir', name: '', fullPath: '', children: [
51 {type: 'dir', name: dirName, fullPath: dirName, children: []}
52 ]
53 };
54 return fs.ensureDirectory(dirName)
55 .then(() => matcher.expect([{type: 'directoryCreated', fullPath: dirName}]))
56 .then(() => expect(fs.loadDirectoryTree()).to.become(expectedStructure))
57 .then(() => fs.ensureDirectory(dirName)) //2nd time does nothing
58 .then(() => expect(fs.loadDirectoryTree()).to.become(expectedStructure))
59 .then(() => matcher.expect([]))
60 });
61
62 it(`saving a file over a directory - fails`, function () {
63 return fs.ensureDirectory(dirName)
64 .then(() => matcher.expect([{type: 'directoryCreated', fullPath: dirName}]))
65 .then(() => expect(fs.saveFile(dirName, content)).to.be.rejectedWith(Error))
66 .then(() => expect(fs.loadDirectoryTree()).to.become({
67 type: 'dir', name: '', fullPath: '', children: [
68 {type: 'dir', name: dirName, fullPath: dirName, children: []}
69 ]
70 }))
71 .then(() => matcher.expect([]));
72 });
73
74 it(`saving a file over a file in its path - fails`, function () {
75 const fileNameAsDir = dirName;
76 return fs.saveFile(fileNameAsDir, content)
77 .then(() => matcher.expect([{type: 'fileCreated', fullPath: fileNameAsDir, newContent: content}]))
78 .then(() => expect(fs.saveFile(`${fileNameAsDir}/${fileName}`, '_${content}')).to.be.rejectedWith(Error))
79 .then(() => expect(fs.loadDirectoryTree()).to.become({
80 type: 'dir', name: '', fullPath: '', children: [
81 {type: 'file', name: fileNameAsDir, fullPath: fileNameAsDir}
82 ]
83 }))
84 .then(() => matcher.expect([]));
85 });
86
87 it(`saving a new file (and a new directory to hold it)`, function () {
88 return fs.saveFile(`${dirName}/${fileName}`, content)
89 .then(() => matcher.expect([{type: 'directoryCreated', fullPath: dirName}, {
90 type: 'fileCreated',
91 fullPath: `${dirName}/${fileName}`,
92 newContent: content
93 }]))
94 .then(() => expect(fs.loadDirectoryTree()).to.become({
95 type: 'dir', name: '', fullPath: '', children: [
96 {
97 type: 'dir', name: dirName, fullPath: dirName, children: [
98 {type: 'file', name: fileName, fullPath: `${dirName}/${fileName}`}]
99 }]
100 }))
101 .then(() => matcher.expect([]));
102 });
103
104 it(`saving a file with different content`, function () {
105 const newContent = `_${content}`;
106 return fs.saveFile(fileName, content)
107 .then(() => matcher.expect([{type: 'fileCreated', fullPath: fileName, newContent: content}]))
108 .then(() => expect(fs.loadTextFile(fileName)).to.become(content))
109 .then(() => fs.saveFile(fileName, newContent))
110 .then(() => matcher.expect([{type: 'fileChanged', fullPath: fileName, newContent: newContent}]))
111 .then(() => expect(fs.loadTextFile(fileName)).to.become(newContent))
112 .then(() => matcher.expect([]));
113 });
114
115 it(`saving a file with same content`, function () {
116 const expectedStructure = {
117 type: 'dir', name: '', fullPath: '', children: [
118 {name: fileName, fullPath: fileName, type: 'file'}
119 ]
120 };
121
122 return fs.saveFile(fileName, content)
123 .then(() => matcher.expect([{type: 'fileCreated', fullPath: fileName, newContent: content}]))
124 .then(() => expect(fs.loadTextFile(fileName)).to.become(content))
125 .then(() => fs.saveFile(fileName, content)) // may or may not trigger an event
126 .then(() => expect(fs.loadDirectoryTree()).to.become(expectedStructure))
127 .then(() => expect(fs.loadTextFile(fileName)).to.become(content));
128 });
129
130 it(`deleting root directory - fails`, function () {
131 return expect(fs.deleteDirectory('')).to.be.rejectedWith(Error)
132 .then(() => matcher.expect([]));
133 });
134
135 it(`deleting a directory`, function () {
136 return fs.ensureDirectory(`${dirName}/_${dirName}`)
137 .then(() => matcher.expect([
138 {type: 'directoryCreated', fullPath: dirName},
139 {type: 'directoryCreated', fullPath: `${dirName}/_${dirName}`}]))
140 .then(() => fs.deleteDirectory(`${dirName}/_${dirName}`))
141 .then(() => matcher.expect([{type: 'directoryDeleted', fullPath: `${dirName}/_${dirName}`}]))
142 .then(() => expect(fs.loadDirectoryTree()).to.eventually.have.property('children').eql([
143 {children: [], fullPath: dirName, name: dirName, type: 'dir'}]))
144 .then(() => matcher.expect([]));
145 });
146
147 it(`deleting non existing directory succeeds`, function () {
148 return fs.deleteDirectory(`${dirName}/_${dirName}`)
149 .then(() => expect(fs.loadDirectoryTree()).to.eventually.have.property('children').eql([]))
150 .then(() => matcher.expect([]));
151 });
152
153 it(`deleting directory which is actually a file - fails`, function () {
154 return fs.saveFile(fileName, content)
155 .then(() => matcher.expect([{type: 'fileCreated', fullPath: fileName, newContent: content}]))
156 .then(() => expect(fs.deleteDirectory(fileName)).to.be.rejectedWith(Error))
157 .then(() => matcher.expect([]));
158 });
159
160 it(`deleting non-empty directory without recursive flag - fails`, function () {
161 return fs.saveFile(`${dirName}/_${dirName}/${fileName}`, content)
162 .then(() => matcher.expect([
163 {type: 'directoryCreated', fullPath: dirName},
164 {type: 'directoryCreated', fullPath: `${dirName}/_${dirName}`},
165 {type: 'fileCreated', fullPath: `${dirName}/_${dirName}/${fileName}`, newContent: content}]))
166 .then(() => expect(fs.deleteDirectory(`${dirName}/_${dirName}`)).to.be.rejectedWith(Error))
167 .then(() => matcher.expect([]));
168 });
169
170 it(`deleting non-empty directory with recursive flag`, function () {
171 const filePath = `${dirName}/_${dirName}/${fileName}`;
172 return fs.saveFile(filePath, content)
173 .then(() => matcher.expect([
174 {type: 'directoryCreated', fullPath: dirName},
175 {type: 'directoryCreated', fullPath: `${dirName}/_${dirName}`},
176 {type: 'fileCreated', fullPath: filePath, newContent: content}]))
177 .then(() => fs.deleteDirectory(dirName, true))
178 .then(() => matcher.expect([
179 {type: 'directoryDeleted', fullPath: dirName},
180 {type: 'fileDeleted', fullPath: filePath}
181 ]))
182 .then(() => expect(fs.loadDirectoryTree()).to.eventually.have.property('children').eql([]))
183 .then(() => matcher.expect([]));
184 });
185
186 it(`deleting file which is actually a directory - fails`, function () {
187 const dirNameAsFileName = fileName;
188 return fs.ensureDirectory(dirNameAsFileName)
189 .then(() => matcher.expect([{type: 'directoryCreated', fullPath: dirNameAsFileName}]))
190 .then(() => expect(fs.deleteFile(dirNameAsFileName)).to.be.rejectedWith(Error))
191 .then(() => matcher.expect([]));
192 });
193
194 it(`deleting only one file`, function () {
195 return fs.saveFile(fileName, content)
196 .then(() => matcher.expect([{type: 'fileCreated', fullPath: fileName, newContent: content}]))
197 .then(() => fs.saveFile(`_${fileName}`, `_${content}`))
198 .then(() => matcher.expect([{
199 type: 'fileCreated',
200 fullPath: `_${fileName}`,
201 newContent: `_${content}`
202 }]))
203 .then(() => fs.deleteFile(`_${fileName}`))
204 .then(() => matcher.expect([{type: 'fileDeleted', fullPath: `_${fileName}`}]))
205 .then(() => expect(fs.loadDirectoryTree()).to.eventually.have.property('children').eql([{
206 fullPath: fileName,
207 name: fileName,
208 type: 'file'
209 }]))
210 .then(() => matcher.expect([]));
211 });
212
213 it(`deleting non existing file succeeds`, function () {
214 return fs.deleteFile(fileName)
215 .then(() => expect(fs.loadDirectoryTree()).to.eventually.have.property('children').eql([]))
216 .then(() => matcher.expect([]));
217 });
218
219 it(`deleting non existing file (deep path) succeeds`, function () {
220 return fs.deleteFile(`${dirName}/${fileName}`)
221 .then(() => expect(fs.loadDirectoryTree()).to.eventually.have.property('children').eql([]))
222 .then(() => matcher.expect([]));
223 });
224
225 it(`deleting ignored file succeeds`, function () {
226 return fs.deleteFile(ignoredFile)
227 .then(() => matcher.expect([]))
228 .then(() => expect(fs.loadDirectoryTree()).to.eventually.have.property('children').eql([]));
229 });
230
231 it(`deleting ignored directory succeeds`, function () {
232 return fs.deleteDirectory(ignoredDir)
233 .then(() => matcher.expect([]))
234 .then(() => expect(fs.loadDirectoryTree()).to.eventually.have.property('children').eql([]));
235 });
236
237 it(`saving ignored file - fails`, function () {
238 return expect(fs.saveFile(ignoredFile, 'foo')).to.be.rejectedWith(Error);
239 });
240
241 it(`saving ignored dir - fails`, function () {
242 return expect(fs.ensureDirectory(ignoredDir)).to.be.rejectedWith(Error);
243 });
244
245 it(`loading existed ignored file - fails`, function () {
246 return fs.ensureDirectory(dirName)
247 .then(() => expect(fs.loadTextFile(ignoredFile)).to.be.rejectedWith(Error));
248 });
249
250 it(`loadDirectoryTree`, function () {
251 const expected = {
252 fullPath: ``, name: '', type: 'dir', children: [
253 {
254 fullPath: `${dirName}`, name: dirName, type: 'dir', children: [
255 {
256 fullPath: `${dirName}/_${dirName}`, name: `_${dirName}`, type: 'dir', children: [
257 {fullPath: `${dirName}/_${dirName}/${fileName}`, name: fileName, type: 'file'}
258 ]
259 }]
260 }]
261 };
262
263 return fs.saveFile(`${dirName}/_${dirName}/${fileName}`, content)
264 .then(() => expect(fs.loadDirectoryTree()).to.eventually.eql(expected))
265 .then(() => expect(fs.loadDirectoryTree(dirName), `loadDirectoryTree('${dirName}')`).to.eventually.eql(expected.children[0]))
266 .then(() => expect(fs.loadDirectoryTree(`${dirName}/_${dirName}`), `loadDirectoryTree('${dirName}/_${dirName}')`).to.eventually.eql(expected.children[0].children[0]))
267
268 });
269
270 it(`loadDirectoryTree on an illegal sub-path`, function () {
271 return expect(fs.loadDirectoryTree(fileName)).to.be.rejectedWith(Error);
272 });
273
274 it(`loadDirectoryChildren`, function () {
275 return fs.saveFile(`${dirName}/_${dirName}/${fileName}`, content)
276 .then(() => fs.saveFile(`${fileName}`, content))
277 .then(() => expect(fs.loadDirectoryChildren('')).to.eventually.have.deep.members([
278 {fullPath: `${dirName}`, name: dirName, type: 'dir'},
279 {fullPath: fileName, name: fileName, type: 'file'}
280 ]))
281 .then(() => expect(fs.loadDirectoryChildren(dirName), `loadDirectoryChildren('${dirName}')`).to.eventually.have.deep.members(
282 [{fullPath: `${dirName}/_${dirName}`, name: `_${dirName}`, type: 'dir'}]))
283 .then(() => expect(fs.loadDirectoryChildren(`${dirName}/_${dirName}`), `loadDirectoryChildren('${dirName}/_${dirName}')`).to.eventually.have.deep.members(
284 [{fullPath: `${dirName}/_${dirName}/${fileName}`, name: fileName, type: 'file'}]));
285 });
286
287 it(`loadDirectoryChildren on an illegal sub-path`, function () {
288 return expect(fs.loadDirectoryChildren(fileName)).to.be.rejectedWith(Error);
289 });
290
291 describe(`action-event correlation`, function () {
292 it(`single event per action`, async function () {
293 this.timeout(30 * 1000);
294 let allCorelations: Set<Correlation> = new Set();
295 let correlation = await fs.saveFile(fileName, 'foo');
296 expect(correlation).to.be.a('string');
297 allCorelations.add(correlation);
298 expect(allCorelations.size).to.eql(1);
299 await matcher.expect([{type: 'fileCreated', fullPath: fileName, correlation}]);
300 await delayedPromise(100);
301 correlation = await fs.saveFile(fileName, 'bar');
302 expect(correlation).to.be.a('string');
303 allCorelations.add(correlation);
304 expect(allCorelations.size).to.eql(2);
305 await matcher.expect([{type: 'fileChanged', fullPath: fileName, correlation}]);
306 await delayedPromise(100);
307 correlation = await fs.deleteFile(fileName);
308 expect(correlation).to.be.a('string');
309 allCorelations.add(correlation);
310 expect(allCorelations.size).to.eql(3);
311 await matcher.expect([{type: 'fileDeleted', fullPath: fileName, correlation}]);
312 await delayedPromise(100);
313 correlation = await fs.ensureDirectory(dirName);
314 expect(correlation).to.be.a('string');
315 allCorelations.add(correlation);
316 expect(allCorelations.size).to.eql(4);
317 await matcher.expect([{type: 'directoryCreated', fullPath: dirName, correlation}]);
318 await delayedPromise(100);
319 correlation = await fs.deleteDirectory(dirName);
320 expect(correlation).to.be.a('string');
321 allCorelations.add(correlation);
322 expect(allCorelations.size).to.eql(5);
323 await matcher.expect([{type: 'directoryDeleted', fullPath: dirName, correlation}]);
324 });
325
326 it(`multiple events per action`, async function () {
327 this.timeout(10 * 1000);
328 let correlation = await fs.saveFile(`${dirName}/${fileName}`, content);
329 expect(correlation).to.be.a('string');
330 await matcher.expect([
331 {type: 'directoryCreated', fullPath: dirName, correlation},
332 {
333 type: 'fileCreated',
334 fullPath: `${dirName}/${fileName}`,
335 newContent: content,
336 correlation
337 }]);
338 correlation = await fs.deleteDirectory(dirName, true);
339 await matcher.expect([
340 {type: 'directoryDeleted', fullPath: dirName, correlation},
341 {
342 type: 'fileDeleted',
343 fullPath: `${dirName}/${fileName}`,
344 correlation
345 }]);
346 });
347 });
348 });
349}
350
351export function assertFileSystemSyncContract(fsProvider: () => Promise<FileSystemReadSync>, options: EventsMatcher.Options) {
352 let fs: FileSystemReadSync;
353 let matcher: EventsMatcher;
354 beforeEach(() => {
355 matcher = new EventsMatcher(options);
356 return fsProvider()
357 .then(newFs => {
358 fs = newFs;
359 matcher.track(fs.events, ...fileSystemEventNames);
360 });
361 });
362
363 describe(`filesystem sync contract`, () => {
364 let fs: FileSystemReadSync;
365 let matcher: EventsMatcher;
366 beforeEach(() => {
367 matcher = new EventsMatcher(options);
368 return fsProvider()
369 .then(newFs => {
370 fs = newFs;
371 matcher.track(fs.events, ...fileSystemEventNames);
372 });
373 });
374
375
376 it(`loading a non-existing file - fails`, function () {
377 return expect(() => fs.loadTextFileSync(fileName)).to.throw(Error);
378 });
379
380 it(`loading a directory as a file - fails`, function () {
381 return fs.ensureDirectory(dirName)
382 .then(() => {
383 return matcher.expect([{type: 'directoryCreated', fullPath: dirName}])
384 })
385 .then(() => expect(() => fs.loadTextFileSync(dirName)).to.throw(Error))
386 .then(() => matcher.expect([]));
387 });
388
389 it(`loading existed ignored file - fails`, function () {
390 return fs.ensureDirectory(dirName)
391 .then(() => expect(() => fs.loadTextFileSync(ignoredFile)).to.throw(Error));
392 });
393
394 it(`loadDirectoryTreeSync`, function () {
395 const expected = {
396 fullPath: ``, name: '', type: 'dir', children: [
397 {
398 fullPath: `${dirName}`, name: dirName, type: 'dir', children: [
399 {
400 fullPath: `${dirName}/_${dirName}`, name: `_${dirName}`, type: 'dir', children: [
401 {fullPath: `${dirName}/_${dirName}/${fileName}`, name: fileName, type: 'file'}
402 ]
403 }]
404 }]
405 };
406
407 return fs.saveFile(`${dirName}/_${dirName}/${fileName}`, content)
408 .then(() => {
409 expect(fs.loadDirectoryTreeSync()).to.eql(expected);
410 expect(fs.loadDirectoryTreeSync(dirName), `loadDirectoryTreeSync('${dirName}')`).to.eql(expected.children[0])
411 expect(fs.loadDirectoryTreeSync(`${dirName}/_${dirName}`), `loadDirectoryTreeSync('${dirName}/_${dirName}')`).to.eql(expected.children[0].children[0])
412 })
413
414 });
415
416 it(`loadDirectoryTreeSync on an illegal sub-path`, function () {
417 return expect(() => fs.loadDirectoryTreeSync(fileName)).to.throw(Error);
418 });
419
420 it(`loadDirectoryChildrenSync`, function () {
421 return fs.saveFile(`${dirName}/_${dirName}/${fileName}`, content)
422 .then(() => fs.saveFile(`${fileName}`, content))
423 .then(() => {
424 expect(fs.loadDirectoryChildrenSync('')).to.have.deep.members([
425 {fullPath: `${dirName}`, name: dirName, type: 'dir'},
426 {fullPath: fileName, name: fileName, type: 'file'}
427 ]);
428 expect(fs.loadDirectoryChildrenSync(dirName), `loadDirectoryChildrenSync('${dirName}')`).to.have.deep.members(
429 [{fullPath: `${dirName}/_${dirName}`, name: `_${dirName}`, type: 'dir'}]);
430 expect(fs.loadDirectoryChildrenSync(`${dirName}/_${dirName}`), `loadDirectoryChildrenSync('${dirName}/_${dirName}')`).to.have.deep.members(
431 [{fullPath: `${dirName}/_${dirName}/${fileName}`, name: fileName, type: 'file'}])
432 })
433 });
434
435 it(`loadDirectoryChildrenSync on an illegal sub-path`, function () {
436 return expect(() => fs.loadDirectoryChildrenSync(fileName)).to.throw(Error);
437 });
438
439 it(`loadDirectoryContentSync`, function () {
440 const expected = Directory.toContent({
441 fullPath: ``, name: '', type: 'dir', children: [
442 {
443 fullPath: `${dirName}`, name: dirName, type: 'dir', children: [
444 {
445 fullPath: `${dirName}/_${dirName}`, name: `_${dirName}`, type: 'dir', children: [
446 {fullPath: `${dirName}/_${dirName}/${fileName}`, name: fileName, type: 'file', content}
447 ]
448 }]
449 }]
450 });
451
452 return fs.saveFile(`${dirName}/_${dirName}/${fileName}`, content)
453 .then(() => {
454 expect(fs.loadDirectoryContentSync()).to.eql(expected);
455 expect(fs.loadDirectoryContentSync(dirName), `loadDirectoryContentSync('${dirName}')`).to.eql(expected[dirName]);
456 expect(fs.loadDirectoryContentSync(`${dirName}/_${dirName}`), `loadDirectoryContentSync('${dirName}/_${dirName}')`).to.eql((expected[dirName] as DirectoryContent)['_' + dirName]);
457 })
458
459 });
460
461 it(`loadDirectoryTreeSync on an illegal sub-path`, function () {
462 return expect(() => fs.loadDirectoryTreeSync(fileName)).to.throw(Error);
463 });
464 });
465}