2025-09-24 17:31:48 -07:00
|
|
|
/**
|
|
|
|
|
* Virtual Filesystem Tests
|
|
|
|
|
*
|
|
|
|
|
* REAL tests for production VFS implementation
|
|
|
|
|
* These tests demonstrate that VFS actually works
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
|
|
|
import { VirtualFileSystem } from '../../src/vfs/index.js'
|
|
|
|
|
import { Brainy } from '../../src/brainy.js'
|
|
|
|
|
import { VFSErrorCode } from '../../src/vfs/types.js'
|
|
|
|
|
|
|
|
|
|
describe('VirtualFileSystem - Production Tests', () => {
|
|
|
|
|
let vfs: VirtualFileSystem
|
|
|
|
|
let brain: Brainy
|
|
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
// Create a fresh Brainy instance with in-memory storage for each test
|
|
|
|
|
brain = new Brainy({
|
|
|
|
|
storage: { type: 'memory' },
|
|
|
|
|
silent: true // Reduce test output
|
|
|
|
|
})
|
|
|
|
|
await brain.init()
|
|
|
|
|
|
|
|
|
|
// Create VFS on top of Brainy
|
|
|
|
|
vfs = brain.vfs()
|
|
|
|
|
await vfs.init()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterEach(async () => {
|
|
|
|
|
if (vfs) {
|
|
|
|
|
await vfs.close()
|
|
|
|
|
}
|
|
|
|
|
if (brain) {
|
|
|
|
|
await brain.close()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Core File Operations', () => {
|
|
|
|
|
it('should write and read a file', async () => {
|
|
|
|
|
const content = 'Hello, VFS World!'
|
|
|
|
|
const path = '/test.txt'
|
|
|
|
|
|
|
|
|
|
// Write file
|
|
|
|
|
await vfs.writeFile(path, content)
|
|
|
|
|
|
|
|
|
|
// Read file
|
|
|
|
|
const result = await vfs.readFile(path)
|
|
|
|
|
expect(result.toString()).toBe(content)
|
|
|
|
|
|
|
|
|
|
// Verify file exists
|
|
|
|
|
const exists = await vfs.exists(path)
|
|
|
|
|
expect(exists).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle binary files', async () => {
|
|
|
|
|
const binaryData = Buffer.from([0x00, 0x01, 0x02, 0xFF])
|
|
|
|
|
const path = '/binary.dat'
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(path, binaryData)
|
|
|
|
|
const result = await vfs.readFile(path)
|
|
|
|
|
|
|
|
|
|
expect(Buffer.compare(result, binaryData)).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should update existing files', async () => {
|
|
|
|
|
const path = '/update-test.txt'
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(path, 'original')
|
|
|
|
|
await vfs.writeFile(path, 'updated')
|
|
|
|
|
|
|
|
|
|
const content = await vfs.readFile(path)
|
|
|
|
|
expect(content.toString()).toBe('updated')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should append to files', async () => {
|
|
|
|
|
const path = '/append-test.txt'
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(path, 'Hello')
|
|
|
|
|
await vfs.appendFile(path, ' World')
|
|
|
|
|
|
|
|
|
|
const content = await vfs.readFile(path)
|
|
|
|
|
expect(content.toString()).toBe('Hello World')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should delete files', async () => {
|
|
|
|
|
const path = '/delete-me.txt'
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(path, 'temporary')
|
|
|
|
|
expect(await vfs.exists(path)).toBe(true)
|
|
|
|
|
|
|
|
|
|
await vfs.unlink(path)
|
|
|
|
|
expect(await vfs.exists(path)).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle file metadata', async () => {
|
|
|
|
|
const path = '/metadata-test.json'
|
|
|
|
|
const content = JSON.stringify({ key: 'value' })
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(path, content)
|
|
|
|
|
|
|
|
|
|
const stats = await vfs.stat(path)
|
|
|
|
|
expect(stats.isFile()).toBe(true)
|
|
|
|
|
expect(stats.size).toBe(content.length)
|
|
|
|
|
expect(stats.path).toBe(path)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Directory Operations', () => {
|
|
|
|
|
it('should create directories', async () => {
|
|
|
|
|
const path = '/my-directory'
|
|
|
|
|
|
|
|
|
|
await vfs.mkdir(path)
|
|
|
|
|
|
|
|
|
|
const exists = await vfs.exists(path)
|
|
|
|
|
expect(exists).toBe(true)
|
|
|
|
|
|
|
|
|
|
const stats = await vfs.stat(path)
|
|
|
|
|
expect(stats.isDirectory()).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should create nested directories recursively', async () => {
|
|
|
|
|
const path = '/level1/level2/level3'
|
|
|
|
|
|
|
|
|
|
await vfs.mkdir(path, { recursive: true })
|
|
|
|
|
|
|
|
|
|
expect(await vfs.exists('/level1')).toBe(true)
|
|
|
|
|
expect(await vfs.exists('/level1/level2')).toBe(true)
|
|
|
|
|
expect(await vfs.exists(path)).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should list directory contents', async () => {
|
|
|
|
|
const dir = '/list-test'
|
|
|
|
|
await vfs.mkdir(dir)
|
|
|
|
|
|
|
|
|
|
// Create some files
|
|
|
|
|
await vfs.writeFile(`${dir}/file1.txt`, 'content1')
|
|
|
|
|
await vfs.writeFile(`${dir}/file2.txt`, 'content2')
|
|
|
|
|
await vfs.mkdir(`${dir}/subdir`)
|
|
|
|
|
|
|
|
|
|
// List directory
|
|
|
|
|
const contents = await vfs.readdir(dir) as string[]
|
|
|
|
|
|
|
|
|
|
expect(contents).toContain('file1.txt')
|
|
|
|
|
expect(contents).toContain('file2.txt')
|
|
|
|
|
expect(contents).toContain('subdir')
|
|
|
|
|
expect(contents).toHaveLength(3)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should list directory with file types', async () => {
|
|
|
|
|
const dir = '/typed-list'
|
|
|
|
|
await vfs.mkdir(dir)
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(`${dir}/doc.txt`, 'text')
|
|
|
|
|
await vfs.mkdir(`${dir}/folder`)
|
|
|
|
|
|
|
|
|
|
const entries = await vfs.readdir(dir, { withFileTypes: true })
|
|
|
|
|
|
|
|
|
|
expect(entries).toHaveLength(2)
|
|
|
|
|
const file = entries.find(e => e.name === 'doc.txt')
|
|
|
|
|
const folder = entries.find(e => e.name === 'folder')
|
|
|
|
|
|
|
|
|
|
expect(file?.type).toBe('file')
|
|
|
|
|
expect(folder?.type).toBe('directory')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should remove empty directories', async () => {
|
|
|
|
|
const path = '/empty-dir'
|
|
|
|
|
|
|
|
|
|
await vfs.mkdir(path)
|
|
|
|
|
expect(await vfs.exists(path)).toBe(true)
|
|
|
|
|
|
|
|
|
|
await vfs.rmdir(path)
|
|
|
|
|
expect(await vfs.exists(path)).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should recursively remove directories', async () => {
|
|
|
|
|
const dir = '/recursive-delete'
|
|
|
|
|
|
|
|
|
|
await vfs.mkdir(`${dir}/sub1/sub2`, { recursive: true })
|
|
|
|
|
await vfs.writeFile(`${dir}/file.txt`, 'content')
|
|
|
|
|
await vfs.writeFile(`${dir}/sub1/file2.txt`, 'content2')
|
|
|
|
|
|
|
|
|
|
await vfs.rmdir(dir, { recursive: true })
|
|
|
|
|
|
|
|
|
|
expect(await vfs.exists(dir)).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Path Resolution', () => {
|
|
|
|
|
it('should resolve absolute paths', async () => {
|
|
|
|
|
await vfs.mkdir('/absolute/path', { recursive: true })
|
|
|
|
|
await vfs.writeFile('/absolute/path/file.txt', 'content')
|
|
|
|
|
|
|
|
|
|
const resolved = await vfs.resolvePath('/absolute/path/file.txt')
|
|
|
|
|
expect(resolved).toBe('/absolute/path/file.txt')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should normalize paths with multiple slashes', async () => {
|
|
|
|
|
await vfs.mkdir('/normal', { recursive: true })
|
|
|
|
|
await vfs.writeFile('/normal/file.txt', 'content')
|
|
|
|
|
|
|
|
|
|
// Multiple slashes should work
|
|
|
|
|
const content = await vfs.readFile('//normal///file.txt')
|
|
|
|
|
expect(content.toString()).toBe('content')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle deep nesting', async () => {
|
|
|
|
|
const deepPath = '/a/b/c/d/e/f/g/h/i/j/k/file.txt'
|
|
|
|
|
const dirPath = '/a/b/c/d/e/f/g/h/i/j/k'
|
|
|
|
|
|
|
|
|
|
await vfs.mkdir(dirPath, { recursive: true })
|
|
|
|
|
await vfs.writeFile(deepPath, 'deep content')
|
|
|
|
|
|
|
|
|
|
const content = await vfs.readFile(deepPath)
|
|
|
|
|
expect(content.toString()).toBe('deep content')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Semantic Search (Triple Intelligence)', () => {
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
// Create test files with different content
|
|
|
|
|
await vfs.mkdir('/search-test', { recursive: true })
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile('/search-test/auth.js', `
|
|
|
|
|
function authenticate(username, password) {
|
|
|
|
|
// User authentication logic
|
|
|
|
|
return checkCredentials(username, password)
|
|
|
|
|
}
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile('/search-test/login.html', `
|
|
|
|
|
<form>
|
|
|
|
|
<input type="text" name="username" />
|
|
|
|
|
<input type="password" name="password" />
|
|
|
|
|
<button>Sign In</button>
|
|
|
|
|
</form>
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile('/search-test/readme.md', `
|
|
|
|
|
# Project Documentation
|
|
|
|
|
This project implements a secure authentication system.
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile('/search-test/config.json', `
|
|
|
|
|
{
|
|
|
|
|
"database": "postgres://localhost/myapp",
|
|
|
|
|
"port": 3000
|
|
|
|
|
}
|
|
|
|
|
`)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should search files by semantic meaning', async () => {
|
|
|
|
|
// Search for authentication-related files
|
|
|
|
|
const results = await vfs.search('user authentication security', {
|
|
|
|
|
path: '/search-test'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Should find auth.js and login.html as most relevant
|
|
|
|
|
expect(results.length).toBeGreaterThan(0)
|
|
|
|
|
|
|
|
|
|
const paths = results.map(r => r.path)
|
|
|
|
|
expect(paths).toContain('/search-test/auth.js')
|
|
|
|
|
expect(paths).toContain('/search-test/login.html')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should filter by metadata', async () => {
|
|
|
|
|
const results = await vfs.search('', {
|
|
|
|
|
where: {
|
|
|
|
|
path: { $startsWith: '/search-test/' },
|
|
|
|
|
mimeType: 'application/json'
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(results).toHaveLength(1)
|
|
|
|
|
expect(results[0].path).toBe('/search-test/config.json')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should find similar files', async () => {
|
|
|
|
|
// Find files similar to auth.js
|
|
|
|
|
const similar = await vfs.findSimilar('/search-test/auth.js', {
|
2025-10-01 13:50:21 -07:00
|
|
|
limit: 3
|
2025-09-24 17:31:48 -07:00
|
|
|
})
|
|
|
|
|
|
2025-10-01 13:50:21 -07:00
|
|
|
// login.html should be in the results (both about authentication)
|
|
|
|
|
// Note: The first result might be auth.js itself (most similar to itself)
|
2025-09-24 17:31:48 -07:00
|
|
|
expect(similar.length).toBeGreaterThan(0)
|
2025-10-01 13:50:21 -07:00
|
|
|
const paths = similar.map(r => r.path)
|
|
|
|
|
expect(paths).toContain('/search-test/login.html')
|
2025-09-24 17:31:48 -07:00
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Extended Attributes', () => {
|
|
|
|
|
it('should store and retrieve custom attributes', async () => {
|
|
|
|
|
const path = '/attributed-file.txt'
|
|
|
|
|
await vfs.writeFile(path, 'content')
|
|
|
|
|
|
|
|
|
|
// Get initial attributes
|
|
|
|
|
const attrs = await vfs.listxattr(path)
|
|
|
|
|
expect(attrs).toEqual([])
|
|
|
|
|
|
|
|
|
|
// Set custom attribute
|
|
|
|
|
await vfs.setxattr(path, 'project', 'alpha')
|
|
|
|
|
await vfs.setxattr(path, 'priority', 'high')
|
|
|
|
|
|
|
|
|
|
// Get specific attribute
|
|
|
|
|
const project = await vfs.getxattr(path, 'project')
|
|
|
|
|
expect(project).toBe('alpha')
|
|
|
|
|
|
|
|
|
|
// List all attributes
|
|
|
|
|
const allAttrs = await vfs.listxattr(path)
|
|
|
|
|
expect(allAttrs).toContain('project')
|
|
|
|
|
expect(allAttrs).toContain('priority')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle todos on files', async () => {
|
|
|
|
|
const path = '/todo-file.js'
|
|
|
|
|
await vfs.writeFile(path, 'function incomplete() {}')
|
|
|
|
|
|
|
|
|
|
// Get initial todos (should be empty)
|
|
|
|
|
const todos = await vfs.getTodos(path)
|
|
|
|
|
expect(todos).toBeUndefined()
|
|
|
|
|
|
|
|
|
|
// Add a todo
|
|
|
|
|
await vfs.addTodo(path, {
|
|
|
|
|
id: '1',
|
|
|
|
|
task: 'Complete implementation',
|
|
|
|
|
priority: 'high',
|
|
|
|
|
status: 'pending'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Verify todo was added
|
|
|
|
|
const updatedTodos = await vfs.getTodos(path)
|
|
|
|
|
expect(updatedTodos).toHaveLength(1)
|
|
|
|
|
expect(updatedTodos![0].task).toBe('Complete implementation')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Error Handling', () => {
|
|
|
|
|
it('should throw ENOENT for non-existent files', async () => {
|
|
|
|
|
await expect(vfs.readFile('/does-not-exist.txt'))
|
|
|
|
|
.rejects
|
|
|
|
|
.toThrow('No such file or directory')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should throw EISDIR when reading a directory', async () => {
|
|
|
|
|
await vfs.mkdir('/is-directory')
|
|
|
|
|
|
|
|
|
|
await expect(vfs.readFile('/is-directory'))
|
|
|
|
|
.rejects
|
|
|
|
|
.toThrow('Is a directory')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should throw ENOTDIR when listing a file', async () => {
|
|
|
|
|
await vfs.writeFile('/is-file.txt', 'content')
|
|
|
|
|
|
|
|
|
|
await expect(vfs.readdir('/is-file.txt'))
|
|
|
|
|
.rejects
|
|
|
|
|
.toThrow('Not a directory')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should throw ENOTEMPTY when removing non-empty directory', async () => {
|
|
|
|
|
await vfs.mkdir('/non-empty')
|
|
|
|
|
await vfs.writeFile('/non-empty/file.txt', 'content')
|
|
|
|
|
|
|
|
|
|
await expect(vfs.rmdir('/non-empty'))
|
|
|
|
|
.rejects
|
|
|
|
|
.toThrow('Directory not empty')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should throw EEXIST when creating existing directory', async () => {
|
|
|
|
|
await vfs.mkdir('/already-exists')
|
|
|
|
|
|
|
|
|
|
await expect(vfs.mkdir('/already-exists'))
|
|
|
|
|
.rejects
|
|
|
|
|
.toThrow('Directory exists')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Performance', () => {
|
|
|
|
|
it('should handle many files efficiently', async () => {
|
|
|
|
|
const dir = '/performance-test'
|
|
|
|
|
await vfs.mkdir(dir)
|
|
|
|
|
|
|
|
|
|
const startWrite = Date.now()
|
|
|
|
|
|
|
|
|
|
// Create 100 files
|
|
|
|
|
const promises = []
|
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
|
|
|
promises.push(
|
|
|
|
|
vfs.writeFile(`${dir}/file-${i}.txt`, `Content ${i}`)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
await Promise.all(promises)
|
|
|
|
|
|
|
|
|
|
const writeTime = Date.now() - startWrite
|
|
|
|
|
console.log(`Written 100 files in ${writeTime}ms`)
|
|
|
|
|
|
|
|
|
|
// List directory
|
|
|
|
|
const startList = Date.now()
|
|
|
|
|
const files = await vfs.readdir(dir)
|
|
|
|
|
const listTime = Date.now() - startList
|
|
|
|
|
|
|
|
|
|
expect(files).toHaveLength(100)
|
|
|
|
|
console.log(`Listed 100 files in ${listTime}ms`)
|
|
|
|
|
|
|
|
|
|
// Performance assertions
|
|
|
|
|
expect(writeTime).toBeLessThan(5000) // Should write 100 files in < 5s
|
|
|
|
|
expect(listTime).toBeLessThan(100) // Should list 100 files in < 100ms
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should cache paths for fast repeated access', async () => {
|
|
|
|
|
const path = '/cached/file.txt'
|
|
|
|
|
await vfs.mkdir('/cached')
|
|
|
|
|
await vfs.writeFile(path, 'cached content')
|
|
|
|
|
|
|
|
|
|
// First read (cold cache)
|
|
|
|
|
const start1 = Date.now()
|
|
|
|
|
await vfs.readFile(path)
|
|
|
|
|
const time1 = Date.now() - start1
|
|
|
|
|
|
|
|
|
|
// Second read (warm cache)
|
|
|
|
|
const start2 = Date.now()
|
|
|
|
|
await vfs.readFile(path)
|
|
|
|
|
const time2 = Date.now() - start2
|
|
|
|
|
|
|
|
|
|
// Cache should make second read faster
|
|
|
|
|
expect(time2).toBeLessThanOrEqual(time1)
|
|
|
|
|
console.log(`Cold read: ${time1}ms, Warm read: ${time2}ms`)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Real-World Scenarios', () => {
|
|
|
|
|
it('should handle a code project structure', async () => {
|
|
|
|
|
// Create a typical project structure
|
|
|
|
|
const project = '/my-project'
|
|
|
|
|
|
|
|
|
|
await vfs.mkdir(`${project}/src/components`, { recursive: true })
|
|
|
|
|
await vfs.mkdir(`${project}/src/utils`, { recursive: true })
|
|
|
|
|
await vfs.mkdir(`${project}/tests`, { recursive: true })
|
|
|
|
|
await vfs.mkdir(`${project}/docs`, { recursive: true })
|
|
|
|
|
|
|
|
|
|
// Add files
|
|
|
|
|
await vfs.writeFile(`${project}/package.json`, JSON.stringify({
|
|
|
|
|
name: 'my-project',
|
|
|
|
|
version: '1.0.0'
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(`${project}/README.md`, '# My Project')
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(`${project}/src/index.js`, `
|
|
|
|
|
import App from './components/App'
|
|
|
|
|
export default App
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(`${project}/src/components/App.js`, `
|
2025-10-01 13:50:21 -07:00
|
|
|
// React component
|
2025-09-24 17:31:48 -07:00
|
|
|
export default function App() {
|
|
|
|
|
return 'Hello World'
|
|
|
|
|
}
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
// Verify structure
|
|
|
|
|
const srcFiles = await vfs.readdir(`${project}/src`)
|
|
|
|
|
expect(srcFiles).toContain('components')
|
|
|
|
|
expect(srcFiles).toContain('utils')
|
|
|
|
|
expect(srcFiles).toContain('index.js')
|
|
|
|
|
|
|
|
|
|
// Search for components
|
|
|
|
|
const components = await vfs.search('react component', {
|
|
|
|
|
path: project
|
|
|
|
|
})
|
|
|
|
|
expect(components.length).toBeGreaterThan(0)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should support file relationships', async () => {
|
|
|
|
|
// Create related files
|
|
|
|
|
await vfs.writeFile('/code/user-model.js', 'class User {}')
|
|
|
|
|
await vfs.writeFile('/tests/user-model.test.js', 'test User')
|
|
|
|
|
await vfs.writeFile('/docs/user-api.md', '# User API')
|
|
|
|
|
|
|
|
|
|
// Add relationships
|
|
|
|
|
await vfs.addRelationship(
|
|
|
|
|
'/tests/user-model.test.js',
|
|
|
|
|
'/code/user-model.js',
|
|
|
|
|
'references' // Test file references the code it tests
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
await vfs.addRelationship(
|
|
|
|
|
'/docs/user-api.md',
|
|
|
|
|
'/code/user-model.js',
|
|
|
|
|
'references' // Documentation references the code it documents
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Query relationships
|
|
|
|
|
const relationships = await vfs.getRelationships('/code/user-model.js')
|
|
|
|
|
expect(relationships.length).toBe(2)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('VirtualFileSystem - Watch System', () => {
|
|
|
|
|
let vfs: VirtualFileSystem
|
|
|
|
|
let brain: Brainy
|
|
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
|
brain = new Brainy({ storage: { type: 'memory' }, silent: true })
|
|
|
|
|
await brain.init()
|
|
|
|
|
vfs = new VirtualFileSystem(brain)
|
|
|
|
|
await vfs.init()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
|
if (vfs) {
|
|
|
|
|
await vfs.close()
|
|
|
|
|
}
|
|
|
|
|
if (brain) {
|
|
|
|
|
await brain.close()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should watch for file changes', async () => {
|
|
|
|
|
const path = '/watched-file.txt'
|
|
|
|
|
const events: Array<{ type: string, path: string | null }> = []
|
|
|
|
|
|
|
|
|
|
// Set up watcher
|
|
|
|
|
const watcher = vfs.watch(path, (eventType, filename) => {
|
|
|
|
|
events.push({ type: eventType, path: filename })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Trigger events
|
|
|
|
|
await vfs.writeFile(path, 'initial') // Create
|
|
|
|
|
await vfs.writeFile(path, 'updated') // Change
|
|
|
|
|
await vfs.unlink(path) // Delete
|
|
|
|
|
|
|
|
|
|
// Verify events were triggered
|
|
|
|
|
expect(events).toHaveLength(3)
|
|
|
|
|
expect(events[0].type).toBe('rename') // Create is a rename event
|
|
|
|
|
expect(events[1].type).toBe('change')
|
|
|
|
|
expect(events[2].type).toBe('rename') // Delete is a rename event
|
|
|
|
|
|
|
|
|
|
// Clean up
|
|
|
|
|
watcher.close()
|
|
|
|
|
})
|
|
|
|
|
})
|