27 test files matched no vitest config, so they never ran in CI and gave false coverage confidence (the drift the audit flagged). - Re-homed 10 functional suites into the gate (renamed to *.unit.test.ts / *.integration.test.ts): Transaction + TransactionManager, type-utils, integrations/core + odata, comprehensive/public-api-complete, regression (metadata-index-cleanup + v5.7.0-deadlock), vfs/tree-operations + vfs-bulkwrite-race. ~192 previously-dark tests now run and pass. - Deleted 8 bit-rotted/redundant files that fail against 8.0 and never ran: one had a literal syntax error; one used filesystem writer-locks that polluted parallel runs; the rest assert old "Brainy 3.0/v3.0" APIs already covered by the live suites (api/batch-operations + crud-operations-enhanced, brainy-3, comprehensive/core-api + find-triple-intelligence, streaming-pipeline, vfs/vfs-relationships, transaction/integration/typeaware-transactions). - Added a coverage guard (tests/unit/test-suite-coverage-guard.test.ts) that FAILS CI if any *.test.ts ever again falls outside every config, with an explicit, conscious MANUAL_ONLY allowlist for the 9 genuine benchmark / perf / model-load files that are run by hand. Gate green: unit 1712, integration 607. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
337 lines
No EOL
12 KiB
TypeScript
337 lines
No EOL
12 KiB
TypeScript
/**
|
|
* VFS Tree Operations Tests
|
|
* Ensures tree methods prevent recursion and work correctly
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach } from 'vitest'
|
|
import { Brainy } from '../../src/brainy.js'
|
|
import { VirtualFileSystem } from '../../src/vfs/VirtualFileSystem.js'
|
|
import { VFSTreeUtils } from '../../src/vfs/TreeUtils.js'
|
|
|
|
describe('VFS Tree Operations', () => {
|
|
let brain: Brainy
|
|
let vfs: VirtualFileSystem
|
|
|
|
beforeEach(async () => {
|
|
brain = new Brainy({ requireSubtype: false })
|
|
await brain.init({
|
|
storage: {
|
|
type: 'memory' // Use in-memory storage for tests
|
|
}
|
|
})
|
|
|
|
vfs = new VirtualFileSystem(brain)
|
|
await vfs.init()
|
|
})
|
|
|
|
describe('Critical: No Self-Inclusion Bug', () => {
|
|
it('should NEVER return a directory as its own child', async () => {
|
|
// Create test structure
|
|
await vfs.mkdir('/test-dir')
|
|
await vfs.writeFile('/test-dir/file1.txt', 'content1')
|
|
await vfs.writeFile('/test-dir/file2.txt', 'content2')
|
|
await vfs.mkdir('/test-dir/subdir')
|
|
|
|
// Test getDirectChildren - should NOT include /test-dir itself
|
|
const children = await vfs.getDirectChildren('/test-dir')
|
|
|
|
// Critical assertion - directory should NOT be in its own children
|
|
const selfIncluded = children.some(child => child.metadata.path === '/test-dir')
|
|
expect(selfIncluded).toBe(false)
|
|
|
|
// Should have exactly 3 children
|
|
expect(children.length).toBe(3)
|
|
expect(children.map(c => c.metadata.name).sort()).toEqual(['file1.txt', 'file2.txt', 'subdir'])
|
|
})
|
|
|
|
it('should handle root directory correctly', async () => {
|
|
await vfs.mkdir('/dir1')
|
|
await vfs.mkdir('/dir2')
|
|
|
|
const rootChildren = await vfs.getDirectChildren('/')
|
|
|
|
// Root should not be in its own children
|
|
const rootInChildren = rootChildren.some(child => child.metadata.path === '/')
|
|
expect(rootInChildren).toBe(false)
|
|
|
|
expect(rootChildren.length).toBe(2)
|
|
})
|
|
|
|
it('should prevent recursion in tree structure', async () => {
|
|
// Create deeper structure
|
|
await vfs.mkdir('/a')
|
|
await vfs.mkdir('/a/b')
|
|
await vfs.mkdir('/a/b/c')
|
|
await vfs.writeFile('/a/b/c/file.txt', 'deep')
|
|
|
|
const tree = await vfs.getTreeStructure('/a')
|
|
|
|
// Validate no cycles
|
|
const validation = VFSTreeUtils.validateTree(tree)
|
|
expect(validation.valid).toBe(true)
|
|
expect(validation.errors).toHaveLength(0)
|
|
|
|
// Check tree structure
|
|
expect(tree.path).toBe('/a')
|
|
expect(tree.children).toBeDefined()
|
|
expect(tree.children!.length).toBe(1) // Only 'b'
|
|
expect(tree.children![0].name).toBe('b')
|
|
expect(tree.children![0].children!.length).toBe(1) // Only 'c'
|
|
expect(tree.children![0].children![0].name).toBe('c')
|
|
})
|
|
})
|
|
|
|
describe('getDirectChildren', () => {
|
|
it('should return only immediate children', async () => {
|
|
await vfs.mkdir('/parent')
|
|
await vfs.mkdir('/parent/child1')
|
|
await vfs.mkdir('/parent/child2')
|
|
await vfs.mkdir('/parent/child1/grandchild')
|
|
await vfs.writeFile('/parent/file.txt', 'test')
|
|
|
|
const children = await vfs.getDirectChildren('/parent')
|
|
|
|
expect(children.length).toBe(3) // child1, child2, file.txt
|
|
expect(children.map(c => c.metadata.name).sort()).toEqual(['child1', 'child2', 'file.txt'])
|
|
|
|
// Should NOT include grandchild
|
|
const hasGrandchild = children.some(c => c.metadata.name === 'grandchild')
|
|
expect(hasGrandchild).toBe(false)
|
|
})
|
|
|
|
it('should throw error for non-directory', async () => {
|
|
await vfs.writeFile('/file.txt', 'content')
|
|
|
|
await expect(vfs.getDirectChildren('/file.txt')).rejects.toThrow('Not a directory')
|
|
})
|
|
})
|
|
|
|
describe('getTreeStructure', () => {
|
|
it('should build correct tree with depth limit', async () => {
|
|
// Create multi-level structure
|
|
await vfs.mkdir('/root')
|
|
await vfs.mkdir('/root/level1')
|
|
await vfs.mkdir('/root/level1/level2')
|
|
await vfs.mkdir('/root/level1/level2/level3')
|
|
await vfs.writeFile('/root/level1/level2/level3/deep.txt', 'very deep')
|
|
|
|
const tree = await vfs.getTreeStructure('/root', { maxDepth: 2 })
|
|
|
|
expect(tree.children).toBeDefined()
|
|
expect(tree.children![0].name).toBe('level1')
|
|
expect(tree.children![0].children![0].name).toBe('level2')
|
|
// Level 3 should be cut off due to maxDepth
|
|
expect(tree.children![0].children![0].children).toBeUndefined()
|
|
})
|
|
|
|
it('should sort tree nodes correctly', async () => {
|
|
await vfs.mkdir('/sorted')
|
|
await vfs.writeFile('/sorted/zebra.txt', 'z')
|
|
await vfs.writeFile('/sorted/apple.txt', 'a')
|
|
await vfs.mkdir('/sorted/banana')
|
|
await vfs.mkdir('/sorted/cherry')
|
|
|
|
const tree = await vfs.getTreeStructure('/sorted', { sort: 'name' })
|
|
|
|
// Directories should come first, then files, both sorted by name
|
|
const names = tree.children!.map(c => c.name)
|
|
expect(names).toEqual(['banana', 'cherry', 'apple.txt', 'zebra.txt'])
|
|
})
|
|
|
|
it('should filter hidden files', async () => {
|
|
await vfs.mkdir('/hidden-test')
|
|
await vfs.writeFile('/hidden-test/.hidden', 'secret')
|
|
await vfs.writeFile('/hidden-test/visible.txt', 'public')
|
|
await vfs.mkdir('/hidden-test/.secret-dir')
|
|
|
|
const tree = await vfs.getTreeStructure('/hidden-test', { includeHidden: false })
|
|
|
|
expect(tree.children!.length).toBe(1)
|
|
expect(tree.children![0].name).toBe('visible.txt')
|
|
})
|
|
})
|
|
|
|
describe('getDescendants', () => {
|
|
it('should return all descendants flat', async () => {
|
|
await vfs.mkdir('/desc')
|
|
await vfs.mkdir('/desc/a')
|
|
await vfs.mkdir('/desc/a/b')
|
|
await vfs.writeFile('/desc/a/b/file.txt', 'deep')
|
|
await vfs.writeFile('/desc/file1.txt', 'top')
|
|
|
|
const descendants = await vfs.getDescendants('/desc')
|
|
|
|
expect(descendants.length).toBe(4) // a, a/b, a/b/file.txt, file1.txt
|
|
|
|
// Should NOT include /desc itself by default
|
|
const hasSelf = descendants.some(d => d.metadata.path === '/desc')
|
|
expect(hasSelf).toBe(false)
|
|
})
|
|
|
|
it('should include ancestor when requested', async () => {
|
|
await vfs.mkdir('/ancestor')
|
|
await vfs.mkdir('/ancestor/child')
|
|
|
|
const withAncestor = await vfs.getDescendants('/ancestor', { includeAncestor: true })
|
|
const withoutAncestor = await vfs.getDescendants('/ancestor', { includeAncestor: false })
|
|
|
|
expect(withAncestor.length).toBe(2) // ancestor + child
|
|
expect(withoutAncestor.length).toBe(1) // only child
|
|
})
|
|
|
|
it('should filter by type', async () => {
|
|
await vfs.mkdir('/typed')
|
|
await vfs.mkdir('/typed/dir1')
|
|
await vfs.mkdir('/typed/dir2')
|
|
await vfs.writeFile('/typed/file1.txt', 'f1')
|
|
await vfs.writeFile('/typed/file2.txt', 'f2')
|
|
|
|
const dirsOnly = await vfs.getDescendants('/typed', { type: 'directory' })
|
|
const filesOnly = await vfs.getDescendants('/typed', { type: 'file' })
|
|
|
|
expect(dirsOnly.length).toBe(2)
|
|
expect(filesOnly.length).toBe(2)
|
|
expect(dirsOnly.every(d => d.metadata.vfsType === 'directory')).toBe(true)
|
|
expect(filesOnly.every(f => f.metadata.vfsType === 'file')).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('inspect', () => {
|
|
it('should return comprehensive information', async () => {
|
|
await vfs.mkdir('/inspect-test')
|
|
await vfs.mkdir('/inspect-test/child1')
|
|
await vfs.writeFile('/inspect-test/file.txt', 'content')
|
|
|
|
const result = await vfs.inspect('/inspect-test/child1')
|
|
|
|
expect(result.node.metadata.name).toBe('child1')
|
|
expect(result.node.metadata.path).toBe('/inspect-test/child1')
|
|
expect(result.children).toEqual([]) // Empty directory
|
|
expect(result.parent).toBeDefined()
|
|
expect(result.parent!.metadata.path).toBe('/inspect-test')
|
|
expect(result.stats).toBeDefined()
|
|
expect(result.stats.isDirectory()).toBe(true)
|
|
})
|
|
|
|
it('should handle root directory specially', async () => {
|
|
await vfs.mkdir('/root-child')
|
|
|
|
const result = await vfs.inspect('/')
|
|
|
|
expect(result.node.metadata.path).toBe('/')
|
|
expect(result.parent).toBeNull() // Root has no parent
|
|
expect(result.children.length).toBeGreaterThan(0)
|
|
expect(result.stats.isDirectory()).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('VFSTreeUtils', () => {
|
|
it('should validate tree structure correctly', async () => {
|
|
// Create a valid tree
|
|
await vfs.mkdir('/valid')
|
|
await vfs.mkdir('/valid/sub1')
|
|
await vfs.mkdir('/valid/sub2')
|
|
|
|
const tree = await vfs.getTreeStructure('/valid')
|
|
const validation = VFSTreeUtils.validateTree(tree)
|
|
|
|
expect(validation.valid).toBe(true)
|
|
expect(validation.errors).toHaveLength(0)
|
|
})
|
|
|
|
it('should detect cycles in tree', () => {
|
|
// Manually create an invalid tree with cycle
|
|
const childNode: any = {
|
|
name: 'child',
|
|
path: '/root/child',
|
|
type: 'directory' as const,
|
|
children: []
|
|
}
|
|
|
|
const invalidTree = {
|
|
name: 'root',
|
|
path: '/root',
|
|
type: 'directory' as const,
|
|
children: [childNode]
|
|
}
|
|
|
|
// Create a cycle by adding the child back to itself
|
|
childNode.children.push(childNode) // Child contains itself = cycle
|
|
|
|
const validation = VFSTreeUtils.validateTree(invalidTree)
|
|
expect(validation.valid).toBe(false)
|
|
expect(validation.errors.length).toBeGreaterThan(0)
|
|
// The error could be either "Cycle detected" or "Directory contains itself"
|
|
const hasExpectedError = validation.errors.some(e =>
|
|
e.includes('Cycle detected') || e.includes('Directory contains itself')
|
|
)
|
|
expect(hasExpectedError).toBe(true)
|
|
})
|
|
|
|
it('should detect self-inclusion', () => {
|
|
const badTree = {
|
|
name: 'dir',
|
|
path: '/dir',
|
|
type: 'directory' as const,
|
|
children: [{
|
|
name: 'dir',
|
|
path: '/dir', // Same as parent!
|
|
type: 'directory' as const
|
|
}]
|
|
}
|
|
|
|
const validation = VFSTreeUtils.validateTree(badTree)
|
|
expect(validation.valid).toBe(false)
|
|
expect(validation.errors).toContainEqual('Directory contains itself: /dir')
|
|
})
|
|
|
|
it('should calculate tree statistics', async () => {
|
|
await vfs.mkdir('/stats')
|
|
await vfs.mkdir('/stats/dir1')
|
|
await vfs.mkdir('/stats/dir2')
|
|
await vfs.writeFile('/stats/file1.txt', 'a'.repeat(100))
|
|
await vfs.writeFile('/stats/dir1/file2.txt', 'b'.repeat(200))
|
|
|
|
const tree = await vfs.getTreeStructure('/stats')
|
|
const stats = VFSTreeUtils.getTreeStats(tree)
|
|
|
|
expect(stats.totalNodes).toBe(5) // /stats (root), dir1, dir2, file1, file2
|
|
expect(stats.directories).toBe(3) // /stats, dir1, dir2
|
|
expect(stats.files).toBe(2)
|
|
expect(stats.maxDepth).toBe(2)
|
|
expect(stats.totalSize).toBe(300)
|
|
})
|
|
})
|
|
|
|
describe('Performance with large trees', () => {
|
|
it('should handle large directory structures efficiently', async () => {
|
|
// Create a reasonably large structure
|
|
const dirs = 10
|
|
const filesPerDir = 5
|
|
|
|
await vfs.mkdir('/perf-test')
|
|
|
|
for (let i = 0; i < dirs; i++) {
|
|
await vfs.mkdir(`/perf-test/dir${i}`)
|
|
for (let j = 0; j < filesPerDir; j++) {
|
|
await vfs.writeFile(`/perf-test/dir${i}/file${j}.txt`, `content-${i}-${j}`)
|
|
}
|
|
}
|
|
|
|
const startTime = Date.now()
|
|
const tree = await vfs.getTreeStructure('/perf-test')
|
|
const elapsed = Date.now() - startTime
|
|
|
|
// Should complete reasonably fast (under 1 second for this size)
|
|
expect(elapsed).toBeLessThan(1000)
|
|
|
|
// Validate structure
|
|
expect(tree.children!.length).toBe(dirs)
|
|
expect(tree.children![0].children!.length).toBe(filesPerDir)
|
|
|
|
// No cycles
|
|
const validation = VFSTreeUtils.validateTree(tree)
|
|
expect(validation.valid).toBe(true)
|
|
})
|
|
})
|
|
}) |