initial
This commit is contained in:
235
test/test.memory.provider.ts
Normal file
235
test/test.memory.provider.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* Tests for Memory provider
|
||||
*/
|
||||
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import { SmartFs, SmartFsProviderMemory } from '../ts/index.js';
|
||||
|
||||
// Create test instance
|
||||
const memoryProvider = new SmartFsProviderMemory();
|
||||
const fs = new SmartFs(memoryProvider);
|
||||
|
||||
tap.test('should create SmartFS instance with Memory provider', async () => {
|
||||
expect(fs).toBeInstanceOf(SmartFs);
|
||||
expect(fs.getProviderName()).toEqual('memory');
|
||||
});
|
||||
|
||||
tap.test('should write and read a file', async () => {
|
||||
await fs.file('/test.txt').write('Hello, World!');
|
||||
const content = await fs.file('/test.txt').encoding('utf8').read();
|
||||
expect(content).toEqual('Hello, World!');
|
||||
});
|
||||
|
||||
tap.test('should write and read a file with encoding', async () => {
|
||||
await fs.file('/test2.txt').encoding('utf8').write('Test content');
|
||||
const content = await fs.file('/test2.txt').encoding('utf8').read();
|
||||
expect(content).toEqual('Test content');
|
||||
});
|
||||
|
||||
tap.test('should check if file exists', async () => {
|
||||
const exists = await fs.file('/test.txt').exists();
|
||||
expect(exists).toEqual(true);
|
||||
|
||||
const notExists = await fs.file('/nonexistent.txt').exists();
|
||||
expect(notExists).toEqual(false);
|
||||
});
|
||||
|
||||
tap.test('should get file stats', async () => {
|
||||
await fs.file('/stats-test.txt').write('stats test');
|
||||
const stats = await fs.file('/stats-test.txt').stat();
|
||||
|
||||
expect(stats).toHaveProperty('size');
|
||||
expect(stats).toHaveProperty('mtime');
|
||||
expect(stats).toHaveProperty('birthtime');
|
||||
expect(stats.isFile).toEqual(true);
|
||||
expect(stats.isDirectory).toEqual(false);
|
||||
});
|
||||
|
||||
tap.test('should append to a file', async () => {
|
||||
await fs.file('/append-test.txt').write('Hello');
|
||||
await fs.file('/append-test.txt').append(' World!');
|
||||
const content = await fs.file('/append-test.txt').encoding('utf8').read();
|
||||
expect(content).toEqual('Hello World!');
|
||||
});
|
||||
|
||||
tap.test('should delete a file', async () => {
|
||||
await fs.file('/delete-test.txt').write('to be deleted');
|
||||
await fs.file('/delete-test.txt').delete();
|
||||
const exists = await fs.file('/delete-test.txt').exists();
|
||||
expect(exists).toEqual(false);
|
||||
});
|
||||
|
||||
tap.test('should copy a file', async () => {
|
||||
await fs.file('/copy-source.txt').write('copy me');
|
||||
await fs.file('/copy-source.txt').copy('/copy-dest.txt');
|
||||
|
||||
const sourceContent = await fs.file('/copy-source.txt').encoding('utf8').read();
|
||||
const destContent = await fs.file('/copy-dest.txt').encoding('utf8').read();
|
||||
|
||||
expect(sourceContent).toEqual('copy me');
|
||||
expect(destContent).toEqual('copy me');
|
||||
});
|
||||
|
||||
tap.test('should move a file', async () => {
|
||||
await fs.file('/move-source.txt').write('move me');
|
||||
await fs.file('/move-source.txt').move('/move-dest.txt');
|
||||
|
||||
const sourceExists = await fs.file('/move-source.txt').exists();
|
||||
const destContent = await fs.file('/move-dest.txt').encoding('utf8').read();
|
||||
|
||||
expect(sourceExists).toEqual(false);
|
||||
expect(destContent).toEqual('move me');
|
||||
});
|
||||
|
||||
tap.test('should create a directory', async () => {
|
||||
await fs.directory('/test-dir').create();
|
||||
const exists = await fs.directory('/test-dir').exists();
|
||||
expect(exists).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('should create nested directories recursively', async () => {
|
||||
await fs.directory('/nested/deep/path').recursive().create();
|
||||
const exists = await fs.directory('/nested/deep/path').exists();
|
||||
expect(exists).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('should list directory contents', async () => {
|
||||
await fs.directory('/list-test').create();
|
||||
await fs.file('/list-test/file1.txt').write('file1');
|
||||
await fs.file('/list-test/file2.txt').write('file2');
|
||||
await fs.directory('/list-test/subdir').create();
|
||||
|
||||
const entries = await fs.directory('/list-test').list();
|
||||
|
||||
expect(entries).toHaveLength(3);
|
||||
const names = entries.map((e) => e.name).sort();
|
||||
expect(names).toEqual(['file1.txt', 'file2.txt', 'subdir']);
|
||||
});
|
||||
|
||||
tap.test('should list directory contents recursively', async () => {
|
||||
await fs.directory('/recursive-test').create();
|
||||
await fs.file('/recursive-test/file1.txt').write('file1');
|
||||
await fs.directory('/recursive-test/subdir').create();
|
||||
await fs.file('/recursive-test/subdir/file2.txt').write('file2');
|
||||
|
||||
const entries = await fs.directory('/recursive-test').recursive().list();
|
||||
|
||||
expect(entries.length).toBeGreaterThanOrEqual(3);
|
||||
});
|
||||
|
||||
tap.test('should filter directory listings', async () => {
|
||||
await fs.directory('/filter-test').create();
|
||||
await fs.file('/filter-test/file1.ts').write('ts file');
|
||||
await fs.file('/filter-test/file2.js').write('js file');
|
||||
await fs.file('/filter-test/file3.ts').write('ts file');
|
||||
|
||||
const entries = await fs.directory('/filter-test').filter('*.ts').list();
|
||||
|
||||
expect(entries).toHaveLength(2);
|
||||
expect(entries.every((e) => e.name.endsWith('.ts'))).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('should delete a directory recursively', async () => {
|
||||
await fs.directory('/delete-dir-test').create();
|
||||
await fs.file('/delete-dir-test/file.txt').write('file');
|
||||
await fs.directory('/delete-dir-test/subdir').create();
|
||||
|
||||
await fs.directory('/delete-dir-test').recursive().delete();
|
||||
|
||||
const exists = await fs.directory('/delete-dir-test').exists();
|
||||
expect(exists).toEqual(false);
|
||||
});
|
||||
|
||||
tap.test('should handle file streams', async () => {
|
||||
const testData = 'Stream test data with some content';
|
||||
await fs.file('/stream-test.txt').write(testData);
|
||||
|
||||
const readStream = await fs.file('/stream-test.txt').readStream();
|
||||
const chunks: Uint8Array[] = [];
|
||||
|
||||
const reader = readStream.getReader();
|
||||
let done = false;
|
||||
|
||||
while (!done) {
|
||||
const result = await reader.read();
|
||||
done = result.done;
|
||||
if (result.value) {
|
||||
chunks.push(result.value);
|
||||
}
|
||||
}
|
||||
|
||||
const buffer = Buffer.concat(chunks.map((c) => Buffer.from(c)));
|
||||
const content = buffer.toString('utf8');
|
||||
|
||||
expect(content).toEqual(testData);
|
||||
});
|
||||
|
||||
tap.test('should write file streams', async () => {
|
||||
const testData = 'Writing via stream';
|
||||
const buffer = Buffer.from(testData);
|
||||
|
||||
const writeStream = await fs.file('/write-stream-test.txt').writeStream();
|
||||
const writer = writeStream.getWriter();
|
||||
|
||||
await writer.write(new Uint8Array(buffer));
|
||||
await writer.close();
|
||||
|
||||
const content = await fs.file('/write-stream-test.txt').encoding('utf8').read();
|
||||
expect(content).toEqual(testData);
|
||||
});
|
||||
|
||||
tap.test('should execute transactions', async () => {
|
||||
await fs
|
||||
.transaction()
|
||||
.file('/tx-file1.txt')
|
||||
.write('transaction file 1')
|
||||
.file('/tx-file2.txt')
|
||||
.write('transaction file 2')
|
||||
.file('/tx-file3.txt')
|
||||
.write('transaction file 3')
|
||||
.commit();
|
||||
|
||||
const content1 = await fs.file('/tx-file1.txt').encoding('utf8').read();
|
||||
const content2 = await fs.file('/tx-file2.txt').encoding('utf8').read();
|
||||
const content3 = await fs.file('/tx-file3.txt').encoding('utf8').read();
|
||||
|
||||
expect(content1).toEqual('transaction file 1');
|
||||
expect(content2).toEqual('transaction file 2');
|
||||
expect(content3).toEqual('transaction file 3');
|
||||
});
|
||||
|
||||
tap.test('should rollback transactions on error', async () => {
|
||||
// This test verifies that transaction operations are prepared with backups
|
||||
// Since memory provider doesn't naturally fail, we just verify the mechanism exists
|
||||
await fs.file('/tx-rollback-test.txt').write('original');
|
||||
|
||||
const tx = fs.transaction();
|
||||
tx.file('/tx-rollback-test.txt').write('modified');
|
||||
|
||||
// Verify operations are tracked
|
||||
const operations = tx.getOperations();
|
||||
expect(operations.length).toEqual(1);
|
||||
expect(operations[0].type).toEqual('write');
|
||||
|
||||
// Commit normally (rollback test would require mocking provider failure)
|
||||
await tx.commit();
|
||||
expect(tx.isCommitted()).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('should handle file watching', async () => {
|
||||
await fs.file('/watch-test.txt').write('initial');
|
||||
|
||||
// Create watcher (verifies the API works)
|
||||
const watcher = await fs
|
||||
.watch('/watch-test.txt')
|
||||
.onChange((event) => {
|
||||
// Event handler registered
|
||||
})
|
||||
.start();
|
||||
|
||||
// Verify watcher can be stopped
|
||||
await watcher.stop();
|
||||
expect(true).toEqual(true); // Test passes if we get here
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
264
test/test.node.provider.ts
Normal file
264
test/test.node.provider.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* Tests for Node.js provider
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs/promises';
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import { SmartFs, SmartFsProviderNode } from '../ts/index.js';
|
||||
|
||||
// Create temp directory for tests
|
||||
const tempDir = path.join(process.cwd(), '.nogit', 'test-temp');
|
||||
|
||||
// Create test instance
|
||||
const nodeProvider = new SmartFsProviderNode();
|
||||
const smartFs = new SmartFs(nodeProvider);
|
||||
|
||||
tap.preTask('setup temp directory', async () => {
|
||||
await fs.mkdir(tempDir, { recursive: true });
|
||||
});
|
||||
|
||||
tap.test('should create SmartFS instance with Node provider', async () => {
|
||||
expect(smartFs).toBeInstanceOf(SmartFs);
|
||||
expect(smartFs.getProviderName()).toEqual('node');
|
||||
});
|
||||
|
||||
tap.test('should write and read a file', async () => {
|
||||
const filePath = path.join(tempDir, 'test.txt');
|
||||
await smartFs.file(filePath).write('Hello, World!');
|
||||
const content = await smartFs.file(filePath).encoding('utf8').read();
|
||||
expect(content).toEqual('Hello, World!');
|
||||
});
|
||||
|
||||
tap.test('should write atomically', async () => {
|
||||
const filePath = path.join(tempDir, 'atomic.txt');
|
||||
await smartFs.file(filePath).atomic().write('Atomic write test');
|
||||
const content = await smartFs.file(filePath).encoding('utf8').read();
|
||||
expect(content).toEqual('Atomic write test');
|
||||
});
|
||||
|
||||
tap.test('should check if file exists', async () => {
|
||||
const filePath = path.join(tempDir, 'exists-test.txt');
|
||||
await smartFs.file(filePath).write('exists');
|
||||
|
||||
const exists = await smartFs.file(filePath).exists();
|
||||
expect(exists).toEqual(true);
|
||||
|
||||
const notExists = await smartFs.file(path.join(tempDir, 'nonexistent.txt')).exists();
|
||||
expect(notExists).toEqual(false);
|
||||
});
|
||||
|
||||
tap.test('should get file stats', async () => {
|
||||
const filePath = path.join(tempDir, 'stats-test.txt');
|
||||
await smartFs.file(filePath).write('stats test');
|
||||
const stats = await smartFs.file(filePath).stat();
|
||||
|
||||
expect(stats).toHaveProperty('size');
|
||||
expect(stats).toHaveProperty('mtime');
|
||||
expect(stats).toHaveProperty('birthtime');
|
||||
expect(stats.isFile).toEqual(true);
|
||||
expect(stats.isDirectory).toEqual(false);
|
||||
expect(stats.size).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
tap.test('should append to a file', async () => {
|
||||
const filePath = path.join(tempDir, 'append-test.txt');
|
||||
await smartFs.file(filePath).write('Hello');
|
||||
await smartFs.file(filePath).append(' World!');
|
||||
const content = await smartFs.file(filePath).encoding('utf8').read();
|
||||
expect(content).toEqual('Hello World!');
|
||||
});
|
||||
|
||||
tap.test('should delete a file', async () => {
|
||||
const filePath = path.join(tempDir, 'delete-test.txt');
|
||||
await smartFs.file(filePath).write('to be deleted');
|
||||
await smartFs.file(filePath).delete();
|
||||
const exists = await smartFs.file(filePath).exists();
|
||||
expect(exists).toEqual(false);
|
||||
});
|
||||
|
||||
tap.test('should copy a file', async () => {
|
||||
const sourcePath = path.join(tempDir, 'copy-source.txt');
|
||||
const destPath = path.join(tempDir, 'copy-dest.txt');
|
||||
|
||||
await smartFs.file(sourcePath).write('copy me');
|
||||
await smartFs.file(sourcePath).copy(destPath);
|
||||
|
||||
const sourceContent = await smartFs.file(sourcePath).encoding('utf8').read();
|
||||
const destContent = await smartFs.file(destPath).encoding('utf8').read();
|
||||
|
||||
expect(sourceContent).toEqual('copy me');
|
||||
expect(destContent).toEqual('copy me');
|
||||
});
|
||||
|
||||
tap.test('should move a file', async () => {
|
||||
const sourcePath = path.join(tempDir, 'move-source.txt');
|
||||
const destPath = path.join(tempDir, 'move-dest.txt');
|
||||
|
||||
await smartFs.file(sourcePath).write('move me');
|
||||
await smartFs.file(sourcePath).move(destPath);
|
||||
|
||||
const sourceExists = await smartFs.file(sourcePath).exists();
|
||||
const destContent = await smartFs.file(destPath).encoding('utf8').read();
|
||||
|
||||
expect(sourceExists).toEqual(false);
|
||||
expect(destContent).toEqual('move me');
|
||||
});
|
||||
|
||||
tap.test('should create a directory', async () => {
|
||||
const dirPath = path.join(tempDir, 'test-dir');
|
||||
await smartFs.directory(dirPath).create();
|
||||
const exists = await smartFs.directory(dirPath).exists();
|
||||
expect(exists).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('should create nested directories recursively', async () => {
|
||||
const dirPath = path.join(tempDir, 'nested', 'deep', 'path');
|
||||
await smartFs.directory(dirPath).recursive().create();
|
||||
const exists = await smartFs.directory(dirPath).exists();
|
||||
expect(exists).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('should list directory contents', async () => {
|
||||
const dirPath = path.join(tempDir, 'list-test');
|
||||
await smartFs.directory(dirPath).create();
|
||||
await smartFs.file(path.join(dirPath, 'file1.txt')).write('file1');
|
||||
await smartFs.file(path.join(dirPath, 'file2.txt')).write('file2');
|
||||
await smartFs.directory(path.join(dirPath, 'subdir')).create();
|
||||
|
||||
const entries = await smartFs.directory(dirPath).list();
|
||||
|
||||
expect(entries).toHaveLength(3);
|
||||
const names = entries.map((e) => e.name).sort();
|
||||
expect(names).toEqual(['file1.txt', 'file2.txt', 'subdir']);
|
||||
});
|
||||
|
||||
tap.test('should list directory contents recursively', async () => {
|
||||
const dirPath = path.join(tempDir, 'recursive-test');
|
||||
await smartFs.directory(dirPath).create();
|
||||
await smartFs.file(path.join(dirPath, 'file1.txt')).write('file1');
|
||||
await smartFs.directory(path.join(dirPath, 'subdir')).create();
|
||||
await smartFs.file(path.join(dirPath, 'subdir', 'file2.txt')).write('file2');
|
||||
|
||||
const entries = await smartFs.directory(dirPath).recursive().list();
|
||||
|
||||
expect(entries.length).toBeGreaterThanOrEqual(3);
|
||||
});
|
||||
|
||||
tap.test('should filter directory listings with RegExp', async () => {
|
||||
const dirPath = path.join(tempDir, 'filter-test');
|
||||
await smartFs.directory(dirPath).create();
|
||||
await smartFs.file(path.join(dirPath, 'file1.ts')).write('ts file');
|
||||
await smartFs.file(path.join(dirPath, 'file2.js')).write('js file');
|
||||
await smartFs.file(path.join(dirPath, 'file3.ts')).write('ts file');
|
||||
|
||||
const entries = await smartFs.directory(dirPath).filter(/\.ts$/).list();
|
||||
|
||||
expect(entries).toHaveLength(2);
|
||||
expect(entries.every((e) => e.name.endsWith('.ts'))).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('should delete a directory recursively', async () => {
|
||||
const dirPath = path.join(tempDir, 'delete-dir-test');
|
||||
await smartFs.directory(dirPath).create();
|
||||
await smartFs.file(path.join(dirPath, 'file.txt')).write('file');
|
||||
await smartFs.directory(path.join(dirPath, 'subdir')).create();
|
||||
|
||||
await smartFs.directory(dirPath).recursive().delete();
|
||||
|
||||
const exists = await smartFs.directory(dirPath).exists();
|
||||
expect(exists).toEqual(false);
|
||||
});
|
||||
|
||||
tap.test('should handle file streams', async () => {
|
||||
const filePath = path.join(tempDir, 'stream-test.txt');
|
||||
const testData = 'Stream test data with some content';
|
||||
await smartFs.file(filePath).write(testData);
|
||||
|
||||
const readStream = await smartFs.file(filePath).readStream();
|
||||
const chunks: Uint8Array[] = [];
|
||||
|
||||
const reader = readStream.getReader();
|
||||
let done = false;
|
||||
|
||||
while (!done) {
|
||||
const result = await reader.read();
|
||||
done = result.done;
|
||||
if (result.value) {
|
||||
chunks.push(result.value);
|
||||
}
|
||||
}
|
||||
|
||||
const buffer = Buffer.concat(chunks.map((c) => Buffer.from(c)));
|
||||
const content = buffer.toString('utf8');
|
||||
|
||||
expect(content).toEqual(testData);
|
||||
});
|
||||
|
||||
tap.test('should write file streams', async () => {
|
||||
const filePath = path.join(tempDir, 'write-stream-test.txt');
|
||||
const testData = 'Writing via stream';
|
||||
const buffer = Buffer.from(testData);
|
||||
|
||||
const writeStream = await smartFs.file(filePath).writeStream();
|
||||
const writer = writeStream.getWriter();
|
||||
|
||||
await writer.write(new Uint8Array(buffer));
|
||||
await writer.close();
|
||||
|
||||
const content = await smartFs.file(filePath).encoding('utf8').read();
|
||||
expect(content).toEqual(testData);
|
||||
});
|
||||
|
||||
tap.test('should execute transactions', async () => {
|
||||
const tx = smartFs.transaction();
|
||||
|
||||
const file1Path = path.join(tempDir, 'tx-file1.txt');
|
||||
const file2Path = path.join(tempDir, 'tx-file2.txt');
|
||||
const file3Path = path.join(tempDir, 'tx-file3.txt');
|
||||
|
||||
await tx
|
||||
.file(file1Path)
|
||||
.write('transaction file 1')
|
||||
.file(file2Path)
|
||||
.write('transaction file 2')
|
||||
.file(file3Path)
|
||||
.write('transaction file 3')
|
||||
.commit();
|
||||
|
||||
const content1 = await smartFs.file(file1Path).encoding('utf8').read();
|
||||
const content2 = await smartFs.file(file2Path).encoding('utf8').read();
|
||||
const content3 = await smartFs.file(file3Path).encoding('utf8').read();
|
||||
|
||||
expect(content1).toEqual('transaction file 1');
|
||||
expect(content2).toEqual('transaction file 2');
|
||||
expect(content3).toEqual('transaction file 3');
|
||||
});
|
||||
|
||||
tap.test('should handle file watching', async () => {
|
||||
const filePath = path.join(tempDir, 'watch-test.txt');
|
||||
await smartFs.file(filePath).write('initial');
|
||||
|
||||
return new Promise<void>(async (resolve) => {
|
||||
const watcher = await smartFs
|
||||
.watch(filePath)
|
||||
.onChange(async (event) => {
|
||||
expect(event.type).toEqual('change');
|
||||
await watcher.stop();
|
||||
resolve();
|
||||
})
|
||||
.start();
|
||||
|
||||
// Wait a bit for watcher to be ready
|
||||
setTimeout(async () => {
|
||||
await smartFs.file(filePath).write('changed');
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('cleanup temp directory', async () => {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
expect(true).toEqual(true);
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
7
test/test.ts
Normal file
7
test/test.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Main test entry point
|
||||
* Imports all test files
|
||||
*/
|
||||
|
||||
import './test.memory.provider.js';
|
||||
import './test.node.provider.js';
|
||||
Reference in New Issue
Block a user