feat(rust-provider): add cross-runtime Rust provider tests and docs; simplify bridge event handling and bump tstest
This commit is contained in:
271
test/test.rust.provider.node+bun+deno.ts
Normal file
271
test/test.rust.provider.node+bun+deno.ts
Normal file
@@ -0,0 +1,271 @@
|
||||
// tstest:deno:allowAll
|
||||
/**
|
||||
* Tests for Rust provider
|
||||
*/
|
||||
|
||||
import * as path from 'node:path';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import { SmartFs, SmartFsProviderRust } from '../ts/index.js';
|
||||
|
||||
// Create temp directory for tests
|
||||
const tempDir = path.join(process.cwd(), '.nogit', 'test-temp-rust');
|
||||
|
||||
// Create test instance
|
||||
const rustProvider = new SmartFsProviderRust();
|
||||
const smartFs = new SmartFs(rustProvider);
|
||||
|
||||
tap.preTask('setup temp directory', async () => {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
await fs.mkdir(tempDir, { recursive: true });
|
||||
});
|
||||
|
||||
tap.test('should create SmartFS instance with Rust provider', async () => {
|
||||
expect(smartFs).toBeInstanceOf(SmartFs);
|
||||
expect(smartFs.getProviderName()).toEqual('rust');
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
tap.test('should delete a file', async () => {
|
||||
const filePath = path.join(tempDir, 'delete-test.txt');
|
||||
await smartFs.file(filePath).write('to delete');
|
||||
await smartFs.file(filePath).delete();
|
||||
|
||||
const exists = await smartFs.file(filePath).exists();
|
||||
expect(exists).toEqual(false);
|
||||
});
|
||||
|
||||
tap.test('should copy a file', async () => {
|
||||
const srcPath = path.join(tempDir, 'copy-src.txt');
|
||||
const destPath = path.join(tempDir, 'copy-dest.txt');
|
||||
await smartFs.file(srcPath).write('copy me');
|
||||
await smartFs.file(srcPath).copy(destPath);
|
||||
|
||||
const content = await smartFs.file(destPath).encoding('utf8').read();
|
||||
expect(content).toEqual('copy me');
|
||||
});
|
||||
|
||||
tap.test('should move a file', async () => {
|
||||
const srcPath = path.join(tempDir, 'move-src.txt');
|
||||
const destPath = path.join(tempDir, 'move-dest.txt');
|
||||
await smartFs.file(srcPath).write('move me');
|
||||
await smartFs.file(srcPath).move(destPath);
|
||||
|
||||
const exists = await smartFs.file(srcPath).exists();
|
||||
expect(exists).toEqual(false);
|
||||
|
||||
const content = await smartFs.file(destPath).encoding('utf8').read();
|
||||
expect(content).toEqual('move me');
|
||||
});
|
||||
|
||||
tap.test('should create and list a directory', async () => {
|
||||
const dirPath = path.join(tempDir, 'list-test');
|
||||
await smartFs.directory(dirPath).create();
|
||||
|
||||
await smartFs.file(path.join(dirPath, 'a.txt')).write('a');
|
||||
await smartFs.file(path.join(dirPath, 'b.txt')).write('b');
|
||||
|
||||
const entries = await smartFs.directory(dirPath).list();
|
||||
expect(entries.length).toEqual(2);
|
||||
const names = entries.map(e => e.name).sort();
|
||||
expect(names).toEqual(['a.txt', 'b.txt']);
|
||||
});
|
||||
|
||||
tap.test('should check directory exists', async () => {
|
||||
const dirPath = path.join(tempDir, 'exists-dir');
|
||||
await smartFs.directory(dirPath).create();
|
||||
|
||||
const exists = await smartFs.directory(dirPath).exists();
|
||||
expect(exists).toEqual(true);
|
||||
|
||||
const notExists = await smartFs.directory(path.join(tempDir, 'nonexistent-dir')).exists();
|
||||
expect(notExists).toEqual(false);
|
||||
});
|
||||
|
||||
tap.test('should delete directory recursively', async () => {
|
||||
const dirPath = path.join(tempDir, 'delete-dir');
|
||||
await smartFs.directory(dirPath).create();
|
||||
await smartFs.file(path.join(dirPath, 'file.txt')).write('data');
|
||||
|
||||
await smartFs.directory(dirPath).delete();
|
||||
|
||||
const exists = await smartFs.directory(dirPath).exists();
|
||||
expect(exists).toEqual(false);
|
||||
});
|
||||
|
||||
tap.test('should list directory recursively', async () => {
|
||||
const dirPath = path.join(tempDir, 'recursive-test');
|
||||
await smartFs.directory(dirPath).create();
|
||||
await smartFs.directory(path.join(dirPath, 'sub')).create();
|
||||
await smartFs.file(path.join(dirPath, 'root.txt')).write('root');
|
||||
await smartFs.file(path.join(dirPath, 'sub', 'child.txt')).write('child');
|
||||
|
||||
const entries = await smartFs.directory(dirPath).recursive().list();
|
||||
const names = entries.map(e => e.name).sort();
|
||||
expect(names).toContain('root.txt');
|
||||
expect(names).toContain('child.txt');
|
||||
expect(names).toContain('sub');
|
||||
});
|
||||
|
||||
// ── Append file ──────────────────────────────────────────────────────────────
|
||||
|
||||
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!');
|
||||
});
|
||||
|
||||
// ── Binary Buffer round-trip ─────────────────────────────────────────────────
|
||||
|
||||
tap.test('should write and read binary data (Buffer)', async () => {
|
||||
const filePath = path.join(tempDir, 'binary-test.bin');
|
||||
const binaryData = Buffer.from([0x00, 0x01, 0x02, 0xFF, 0xFE, 0xFD, 0x80, 0x7F]);
|
||||
|
||||
await rustProvider.writeFile(filePath, binaryData);
|
||||
const result = await rustProvider.readFile(filePath, { encoding: 'buffer' });
|
||||
|
||||
expect(Buffer.isBuffer(result)).toEqual(true);
|
||||
expect(Buffer.compare(result as Buffer, binaryData)).toEqual(0);
|
||||
});
|
||||
|
||||
// ── Streaming ────────────────────────────────────────────────────────────────
|
||||
|
||||
tap.test('should read a file via stream', async () => {
|
||||
const filePath = path.join(tempDir, 'stream-read.txt');
|
||||
const testData = 'Stream test data with enough content to verify streaming works correctly';
|
||||
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 content = Buffer.concat(chunks.map(c => Buffer.from(c))).toString('utf8');
|
||||
expect(content).toEqual(testData);
|
||||
});
|
||||
|
||||
tap.test('should write a file via stream', async () => {
|
||||
const filePath = path.join(tempDir, 'stream-write.txt');
|
||||
const testData = 'Writing via stream';
|
||||
|
||||
const writeStream = await smartFs.file(filePath).writeStream();
|
||||
const writer = writeStream.getWriter();
|
||||
await writer.write(new Uint8Array(Buffer.from(testData)));
|
||||
await writer.close();
|
||||
|
||||
const content = await smartFs.file(filePath).encoding('utf8').read();
|
||||
expect(content).toEqual(testData);
|
||||
});
|
||||
|
||||
// ── Watch ────────────────────────────────────────────────────────────────────
|
||||
|
||||
tap.test('should watch for file changes', async () => {
|
||||
const dirPath = path.join(tempDir, 'watch-test');
|
||||
await smartFs.directory(dirPath).create();
|
||||
const filePath = path.join(dirPath, 'watched.txt');
|
||||
|
||||
const received: any[] = [];
|
||||
const watcher = await smartFs
|
||||
.watch(dirPath)
|
||||
.onAll((event) => {
|
||||
received.push(event);
|
||||
})
|
||||
.start();
|
||||
|
||||
// Give watcher time to start
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
|
||||
await smartFs.file(filePath).write('changed');
|
||||
|
||||
// Wait for event to propagate
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
await watcher.stop();
|
||||
|
||||
expect(received.length).toBeGreaterThan(0);
|
||||
expect(received[0]).toHaveProperty('type');
|
||||
expect(received[0]).toHaveProperty('path');
|
||||
});
|
||||
|
||||
// ── Transactions ─────────────────────────────────────────────────────────────
|
||||
|
||||
tap.test('should execute a transaction', async () => {
|
||||
const file1 = path.join(tempDir, 'tx-file1.txt');
|
||||
const file2 = path.join(tempDir, 'tx-file2.txt');
|
||||
|
||||
await smartFs.transaction()
|
||||
.file(file1).write('tx content 1')
|
||||
.file(file2).write('tx content 2')
|
||||
.commit();
|
||||
|
||||
const c1 = await smartFs.file(file1).encoding('utf8').read();
|
||||
const c2 = await smartFs.file(file2).encoding('utf8').read();
|
||||
expect(c1).toEqual('tx content 1');
|
||||
expect(c2).toEqual('tx content 2');
|
||||
});
|
||||
|
||||
// ── Directory filter ─────────────────────────────────────────────────────────
|
||||
|
||||
tap.test('should filter directory listings with regex', async () => {
|
||||
const dirPath = path.join(tempDir, 'filter-test');
|
||||
await smartFs.directory(dirPath).create();
|
||||
await smartFs.file(path.join(dirPath, 'file1.ts')).write('ts');
|
||||
await smartFs.file(path.join(dirPath, 'file2.js')).write('js');
|
||||
await smartFs.file(path.join(dirPath, 'file3.ts')).write('ts');
|
||||
|
||||
const entries = await smartFs.directory(dirPath).filter(/\.ts$/).list();
|
||||
expect(entries.length).toEqual(2);
|
||||
const allTs = entries.every(e => e.name.endsWith('.ts'));
|
||||
expect(allTs).toEqual(true);
|
||||
});
|
||||
|
||||
// ── Shutdown ─────────────────────────────────────────────────────────────────
|
||||
|
||||
tap.test('should shutdown the Rust provider', async () => {
|
||||
await rustProvider.shutdown();
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
Reference in New Issue
Block a user