chore(8.0): final pre-RC1 sweep — API consistency, named errors, orphans, zero-cast codebase
This commit is contained in:
parent
970e08c466
commit
1f7e365a4e
237 changed files with 1951 additions and 49413 deletions
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* Comprehensive testing of batch operations including:
|
||||
* - addMany: Bulk entity creation
|
||||
* - deleteMany: Bulk deletion
|
||||
* - removeMany: Bulk deletion
|
||||
* - Performance validation at scale
|
||||
* - Memory efficiency testing
|
||||
* - Error recovery scenarios
|
||||
|
|
@ -353,7 +353,7 @@ describe('Batch Operations - Public API Testing', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('deleteMany - Bulk Deletion', () => {
|
||||
describe('removeMany - Bulk Deletion', () => {
|
||||
let testIds: string[] = []
|
||||
|
||||
beforeEach(async () => {
|
||||
|
|
@ -374,14 +374,14 @@ describe('Batch Operations - Public API Testing', () => {
|
|||
const startMemory = process.memoryUsage().heapUsed
|
||||
const startTime = performance.now()
|
||||
|
||||
// Use deleteMany if available, otherwise individual deletes
|
||||
// Use removeMany if available, otherwise individual deletes
|
||||
let results: any[]
|
||||
if ('deleteMany' in brainy && typeof brainy.deleteMany === 'function') {
|
||||
const result = await brainy.deleteMany({ ids: toDelete })
|
||||
if ('removeMany' in brainy && typeof brainy.removeMany === 'function') {
|
||||
const result = await brainy.removeMany({ ids: toDelete })
|
||||
results = result.successful
|
||||
} else {
|
||||
results = await Promise.all(
|
||||
toDelete.map(id => brainy.delete(id))
|
||||
toDelete.map(id => brainy.remove(id))
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -389,7 +389,7 @@ describe('Batch Operations - Public API Testing', () => {
|
|||
const memoryUsed = process.memoryUsage().heapUsed - startMemory
|
||||
|
||||
metricsCollector.recordBatch(
|
||||
'deleteMany-100',
|
||||
'removeMany-100',
|
||||
toDelete.length,
|
||||
results,
|
||||
duration,
|
||||
|
|
@ -426,13 +426,13 @@ describe('Batch Operations - Public API Testing', () => {
|
|||
const batchSize = 100
|
||||
for (let i = 0; i < idsToDelete.length; i += batchSize) {
|
||||
const batch = idsToDelete.slice(i, i + batchSize)
|
||||
await Promise.all(batch.map(id => brainy.delete(id)))
|
||||
await Promise.all(batch.map(id => brainy.remove(id)))
|
||||
}
|
||||
|
||||
const duration = performance.now() - startTime
|
||||
|
||||
metricsCollector.recordBatch(
|
||||
'deleteMany-1k',
|
||||
'removeMany-1k',
|
||||
idsToDelete.length,
|
||||
idsToDelete.map(() => true),
|
||||
duration,
|
||||
|
|
@ -553,7 +553,7 @@ describe('Batch Operations - Public API Testing', () => {
|
|||
Promise.all(updateIds.map(id =>
|
||||
brainy.update({ id, metadata: { updated: true } })
|
||||
)),
|
||||
Promise.all(deleteIds.map(id => brainy.delete(id)))
|
||||
Promise.all(deleteIds.map(id => brainy.remove(id)))
|
||||
])
|
||||
|
||||
const duration = performance.now() - startTime
|
||||
|
|
|
|||
|
|
@ -309,7 +309,7 @@ describe('Enhanced CRUD Operations - Public API Validation', () => {
|
|||
|
||||
// Delete all entities
|
||||
for (const id of ids) {
|
||||
await brainy.delete(id)
|
||||
await brainy.remove(id)
|
||||
}
|
||||
|
||||
if (global.gc) global.gc()
|
||||
|
|
@ -612,7 +612,7 @@ describe('Enhanced CRUD Operations - Public API Validation', () => {
|
|||
})
|
||||
|
||||
const startTime = metricsCollector.startOperation('delete-single')
|
||||
const result = await brainy.delete(id)
|
||||
const result = await brainy.remove(id)
|
||||
metricsCollector.endOperation('delete-single', 1, startTime)
|
||||
|
||||
expect(result).toBe(true)
|
||||
|
|
@ -633,7 +633,7 @@ describe('Enhanced CRUD Operations - Public API Validation', () => {
|
|||
|
||||
const startTime = metricsCollector.startOperation('delete-bulk', ids.length)
|
||||
const results = await Promise.all(
|
||||
ids.map(id => brainy.delete(id))
|
||||
ids.map(id => brainy.remove(id))
|
||||
)
|
||||
metricsCollector.endOperation('delete-bulk', ids.length, startTime)
|
||||
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ describe('Error Handling and Recovery', () => {
|
|||
it('should handle deletion of non-existent entities', async () => {
|
||||
// Should not throw, just return void
|
||||
await expect(
|
||||
brainy.delete('non-existent-id')
|
||||
brainy.remove('non-existent-id')
|
||||
).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
|
|
@ -184,9 +184,9 @@ describe('Error Handling and Recovery', () => {
|
|||
type: 'document'
|
||||
})
|
||||
|
||||
await brainy.delete(id)
|
||||
await brainy.remove(id)
|
||||
// Second delete should not throw
|
||||
await expect(brainy.delete(id)).resolves.toBeUndefined()
|
||||
await expect(brainy.remove(id)).resolves.toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -364,7 +364,7 @@ describe('Error Handling and Recovery', () => {
|
|||
})
|
||||
|
||||
// Attempt concurrent deletes
|
||||
const deletes = Array(5).fill(null).map(() => brainy.delete(id))
|
||||
const deletes = Array(5).fill(null).map(() => brainy.remove(id))
|
||||
|
||||
// Should not throw
|
||||
await expect(Promise.all(deletes)).resolves.toBeDefined()
|
||||
|
|
@ -428,7 +428,7 @@ describe('Error Handling and Recovery', () => {
|
|||
)
|
||||
break
|
||||
case 3:
|
||||
promises.push(brainy.delete(`rapid-${i}`).catch(() => null))
|
||||
promises.push(brainy.remove(`rapid-${i}`).catch(() => null))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -578,7 +578,7 @@ describe('Error Handling and Recovery', () => {
|
|||
type: 'document'
|
||||
})
|
||||
|
||||
await brainy.delete(id)
|
||||
await brainy.remove(id)
|
||||
|
||||
// Operations on deleted entity should handle gracefully
|
||||
const getResult = await brainy.get(id)
|
||||
|
|
@ -592,7 +592,7 @@ describe('Error Handling and Recovery', () => {
|
|||
).rejects.toThrow(/not found/i)
|
||||
|
||||
// Should not throw for delete
|
||||
await expect(brainy.delete(id)).resolves.toBeUndefined()
|
||||
await expect(brainy.remove(id)).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
it('should handle rapid create-delete cycles', async () => {
|
||||
|
|
@ -605,7 +605,7 @@ describe('Error Handling and Recovery', () => {
|
|||
})
|
||||
|
||||
// Immediately delete
|
||||
await brainy.delete(id)
|
||||
await brainy.remove(id)
|
||||
|
||||
// Verify it's gone
|
||||
const result = await brainy.get(id)
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ describe('Performance Benchmarks - SLA Validation', () => {
|
|||
|
||||
for (const id of ids) {
|
||||
const start = performance.now()
|
||||
await brainy.delete(id)
|
||||
await brainy.remove(id)
|
||||
const latency = performance.now() - start
|
||||
benchmark.recordOperation(latency)
|
||||
}
|
||||
|
|
@ -419,7 +419,7 @@ describe('Performance Benchmarks - SLA Validation', () => {
|
|||
}
|
||||
|
||||
for (const id of ids) {
|
||||
await brainy.delete(id)
|
||||
await brainy.remove(id)
|
||||
}
|
||||
|
||||
if (global.gc) global.gc()
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ async function runBenchmark() {
|
|||
// TEST 7: Delete Operations
|
||||
start = Date.now()
|
||||
for (let i = 0; i < 100; i++) {
|
||||
await brain.delete(ids[1900 + i])
|
||||
await brain.remove(ids[1900 + i])
|
||||
}
|
||||
elapsed = Date.now() - start
|
||||
results.delete = Math.round(100 / (elapsed / 1000))
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ async function runV3Benchmark() {
|
|||
console.log('Testing delete operations...')
|
||||
const start7 = Date.now()
|
||||
for (let i = 0; i < 100; i++) {
|
||||
await brain.delete(ids[900 + i])
|
||||
await brain.remove(ids[900 + i])
|
||||
}
|
||||
const deleteTime = Date.now() - start7
|
||||
results.delete = Math.round(100 / (deleteTime / 1000))
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ describe('Brainy 3.0 API', () => {
|
|||
type: NounType.Document
|
||||
})
|
||||
|
||||
await brain.delete(id)
|
||||
await brain.remove(id)
|
||||
|
||||
const entity = await brain.get(id)
|
||||
expect(entity).toBeNull()
|
||||
|
|
@ -124,7 +124,7 @@ describe('Brainy 3.0 API', () => {
|
|||
type: VerbType.References
|
||||
})
|
||||
|
||||
const relations = await brain.getRelations({ from: doc1 })
|
||||
const relations = await brain.related({ from: doc1 })
|
||||
|
||||
expect(relations).toHaveLength(1)
|
||||
expect(relations[0].from).toBe(doc1)
|
||||
|
|
@ -150,8 +150,8 @@ describe('Brainy 3.0 API', () => {
|
|||
bidirectional: true
|
||||
})
|
||||
|
||||
const fromPerson = await brain.getRelations({ from: person })
|
||||
const toPerson = await brain.getRelations({ to: person })
|
||||
const fromPerson = await brain.related({ from: person })
|
||||
const toPerson = await brain.related({ to: person })
|
||||
|
||||
expect(fromPerson).toHaveLength(1)
|
||||
expect(toPerson).toHaveLength(1)
|
||||
|
|
@ -176,7 +176,7 @@ describe('Brainy 3.0 API', () => {
|
|||
|
||||
await brain.unrelate(relationId)
|
||||
|
||||
const relations = await brain.getRelations({ from: doc1 })
|
||||
const relations = await brain.related({ from: doc1 })
|
||||
expect(relations).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
|
@ -331,7 +331,7 @@ describe('Brainy 3.0 API', () => {
|
|||
brain.add({ data: 'Keep me', type: NounType.Document, metadata: { delete: false } })
|
||||
])
|
||||
|
||||
const result = await brain.deleteMany({
|
||||
const result = await brain.removeMany({
|
||||
where: { delete: true }
|
||||
})
|
||||
|
||||
|
|
@ -365,7 +365,7 @@ describe('Brainy 3.0 API', () => {
|
|||
type: VerbType.References // Must use enum
|
||||
})
|
||||
|
||||
const relations = await brain.getRelations({ from: doc1 })
|
||||
const relations = await brain.related({ from: doc1 })
|
||||
expect(relations[0].type).toBe(VerbType.References)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,177 +0,0 @@
|
|||
# Brainy v3.0 Comprehensive Test Validation Report
|
||||
|
||||
## Executive Summary
|
||||
|
||||
As an experienced QA engineer, I have conducted a thorough and systematic test validation of Brainy v3.0. This report summarizes the testing coverage, results, and validation status of all major components.
|
||||
|
||||
## Test Coverage Overview
|
||||
|
||||
### ✅ Components Tested
|
||||
|
||||
1. **Core CRUD Operations** - VALIDATED ✓
|
||||
- Add operations (with ID, metadata, vectors)
|
||||
- Get operations
|
||||
- Update operations (data and metadata)
|
||||
- Delete operations
|
||||
- Batch operations (addMany, deleteMany)
|
||||
|
||||
2. **Find & Triple Intelligence** - VALIDATED ✓
|
||||
- Vector search
|
||||
- Metadata filtering ($gte, $contains, $or operators)
|
||||
- Type filtering (single and multiple types)
|
||||
- Fusion strategies (adaptive, weighted)
|
||||
- Combined search (vector + metadata + type)
|
||||
|
||||
3. **Augmentation System** - VALIDATED ✓
|
||||
- Registration and discovery
|
||||
- Cache augmentation (with invalidation)
|
||||
- Index augmentation (metadata indexing)
|
||||
- Metrics augmentation
|
||||
- Display augmentation (AI-powered)
|
||||
- Pipeline execution
|
||||
|
||||
4. **Storage Adapters** - PARTIALLY VALIDATED
|
||||
- Memory storage ✓
|
||||
- Filesystem storage ✓
|
||||
- Persistence across restarts ✓
|
||||
- Other adapters (S3, R2, OPFS) - configuration validated
|
||||
|
||||
5. **Neural API** - VALIDATED ✓
|
||||
- Similarity calculations
|
||||
- Clustering (hierarchical, k-means)
|
||||
- Related entity discovery
|
||||
|
||||
6. **Performance** - VALIDATED ✓
|
||||
- Handles 1000+ items efficiently
|
||||
- Concurrent operations
|
||||
- Sub-second search performance
|
||||
- Cache effectiveness
|
||||
|
||||
## Test Results Summary
|
||||
|
||||
### Test Suites Created
|
||||
- `tests/comprehensive/core-api.test.ts` - 36 tests
|
||||
- `tests/comprehensive/find-triple-intelligence.test.ts` - 28 tests
|
||||
- `tests/comprehensive/brainy-v3-complete.test.ts` - 80+ tests
|
||||
|
||||
### Key Findings
|
||||
|
||||
#### ✅ WORKING CORRECTLY:
|
||||
1. **Core CRUD** - All basic operations work as expected
|
||||
2. **Vector Search** - Semantic search with embeddings functional
|
||||
3. **Metadata Filtering** - Complex queries supported
|
||||
4. **Type System** - NounType/VerbType validation working
|
||||
5. **Augmentations** - Pipeline execution confirmed
|
||||
6. **Batch Operations** - Efficient bulk processing
|
||||
7. **Import/Export** - Data portability functional
|
||||
8. **Statistics/Insights** - Accurate metrics collection
|
||||
|
||||
#### ⚠️ AREAS NEEDING ATTENTION:
|
||||
1. **API Naming** - Some inconsistencies (no `shutdown()` method)
|
||||
2. **Storage Config** - Path should be in options object
|
||||
3. **Neural API** - Methods need to be called as functions
|
||||
4. **Type Exports** - Some types missing from exports
|
||||
|
||||
#### 🐛 BUGS FOUND:
|
||||
1. `brain.neural.similarity()` should be `brain.neural().similarity()`
|
||||
2. Storage filesystem config should use `options.path` not `path`
|
||||
3. Missing `VerbType.WorksFor` (should use `VerbType.MemberOf`)
|
||||
4. `brain.clear()` method doesn't exist (use delete operations)
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
Based on testing with 1000+ items:
|
||||
|
||||
| Operation | Target | Actual | Status |
|
||||
|-----------|--------|--------|--------|
|
||||
| Add | <10ms | 1-2ms | ✅ PASS |
|
||||
| Get | <5ms | <1ms | ✅ PASS |
|
||||
| Search | <50ms | 5-15ms | ✅ PASS |
|
||||
| Update | <15ms | 2-3ms | ✅ PASS |
|
||||
| Batch (100) | <500ms | 50-100ms | ✅ PASS |
|
||||
|
||||
## Augmentation Validation
|
||||
|
||||
| Augmentation | Status | Functionality |
|
||||
|--------------|--------|---------------|
|
||||
| Cache | ✅ Working | Result caching with auto-invalidation |
|
||||
| Index | ✅ Working | O(1) metadata lookups |
|
||||
| Metrics | ✅ Working | Performance tracking |
|
||||
| Display | ✅ Working | AI-powered display fields |
|
||||
| WAL | ⚠️ Config Only | Needs filesystem storage |
|
||||
| Monitoring | ✅ Working | Health checks functional |
|
||||
|
||||
## Edge Case Testing
|
||||
|
||||
✅ **Handled Correctly:**
|
||||
- Empty queries
|
||||
- Very long text (100k+ characters)
|
||||
- Special characters
|
||||
- Unicode text
|
||||
- Concurrent operations
|
||||
- Non-matching filters
|
||||
- Invalid types (proper errors)
|
||||
|
||||
## Recommendations
|
||||
|
||||
### Critical Fixes Needed:
|
||||
1. **Fix type exports** - Ensure all types are properly exported
|
||||
2. **Standardize storage config** - Use consistent options structure
|
||||
3. **Document API changes** - Clear migration guide for v2 → v3
|
||||
|
||||
### Performance Optimizations:
|
||||
1. **Implement request coalescing** - Currently initialized but unused
|
||||
2. **Optimize large dataset handling** - Add pagination for 10k+ items
|
||||
3. **Enhance cache strategy** - Consider distributed caching
|
||||
|
||||
### Testing Improvements:
|
||||
1. **Add integration tests** for distributed features
|
||||
2. **Create performance benchmarks** for regression testing
|
||||
3. **Add stress tests** for 100k+ items
|
||||
4. **Test all storage adapters** with real credentials
|
||||
|
||||
## Certification
|
||||
|
||||
### ✅ PRODUCTION READY with caveats:
|
||||
|
||||
**Strengths:**
|
||||
- Core functionality is solid and performant
|
||||
- Augmentation system works as designed
|
||||
- Type safety is well-implemented
|
||||
- Error handling is appropriate
|
||||
- Performance meets targets
|
||||
|
||||
**Required Before Production:**
|
||||
1. Fix identified type issues
|
||||
2. Complete distributed feature testing
|
||||
3. Validate cloud storage adapters
|
||||
4. Update documentation for API changes
|
||||
|
||||
## Test Repeatability
|
||||
|
||||
All tests are implemented using Vitest and can be run with:
|
||||
|
||||
```bash
|
||||
# Run all comprehensive tests
|
||||
npx vitest run tests/comprehensive/
|
||||
|
||||
# Run specific test suite
|
||||
npx vitest run tests/comprehensive/core-api.test.ts
|
||||
|
||||
# Run with coverage
|
||||
npx vitest run --coverage tests/comprehensive/
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Brainy v3.0 demonstrates **strong core functionality** with an innovative augmentation system. The codebase is **production-ready for single-instance deployments** with memory or filesystem storage. Distributed features and cloud storage adapters need additional validation before enterprise deployment.
|
||||
|
||||
**Overall Quality Score: 8.5/10**
|
||||
|
||||
The system is robust, well-architected, and performant. With the recommended fixes, it will be fully production-ready for all use cases.
|
||||
|
||||
---
|
||||
*Test validation completed by: Senior QA Engineer*
|
||||
*Date: September 9, 2025*
|
||||
*Framework: Vitest 3.2.4*
|
||||
*Coverage: Core APIs, Augmentations, Storage, Neural Features*
|
||||
|
|
@ -203,7 +203,7 @@ describe('Brainy v3.0 Core API Tests', () => {
|
|||
type: NounType.Document
|
||||
})
|
||||
|
||||
const success = await brain.delete(id)
|
||||
const success = await brain.remove(id)
|
||||
expect(success).toBe(true)
|
||||
|
||||
const item = await brain.get(id)
|
||||
|
|
@ -211,7 +211,7 @@ describe('Brainy v3.0 Core API Tests', () => {
|
|||
})
|
||||
|
||||
it('should return false for non-existent ID', async () => {
|
||||
const success = await brain.delete('non-existent')
|
||||
const success = await brain.remove('non-existent')
|
||||
expect(success).toBe(false)
|
||||
})
|
||||
|
||||
|
|
@ -222,7 +222,7 @@ describe('Brainy v3.0 Core API Tests', () => {
|
|||
)
|
||||
)
|
||||
|
||||
await Promise.all(ids.map(id => brain.delete(id)))
|
||||
await Promise.all(ids.map(id => brain.remove(id)))
|
||||
// delete returns void, not boolean
|
||||
|
||||
// Verify all deleted
|
||||
|
|
@ -259,7 +259,7 @@ describe('Brainy v3.0 Core API Tests', () => {
|
|||
type: VerbType.Creates
|
||||
})
|
||||
|
||||
const relations = await brain.getRelations({ from: entityA })
|
||||
const relations = await brain.related({ from: entityA })
|
||||
|
||||
expect(relations.length).toBeGreaterThanOrEqual(1)
|
||||
expect(relations[0].from).toBe(entityA)
|
||||
|
|
@ -275,7 +275,7 @@ describe('Brainy v3.0 Core API Tests', () => {
|
|||
const success = await brain.unrelate(verbId)
|
||||
expect(success).toBe(true)
|
||||
|
||||
const relations = await brain.getRelations({ from: entityA })
|
||||
const relations = await brain.related({ from: entityA })
|
||||
const found = relations.find(r => r.id === verbId)
|
||||
expect(found).toBeUndefined()
|
||||
})
|
||||
|
|
@ -305,7 +305,7 @@ describe('Brainy v3.0 Core API Tests', () => {
|
|||
)
|
||||
|
||||
// Then delete them in batch
|
||||
const result = await brain.deleteMany({ ids })
|
||||
const result = await brain.removeMany({ ids })
|
||||
|
||||
expect(result.successful).toHaveLength(5)
|
||||
expect(result.failed).toHaveLength(0)
|
||||
|
|
|
|||
|
|
@ -133,13 +133,11 @@ describe('Brainy Public API - Complete Coverage', () => {
|
|||
expect(final?.metadata?.version).toBeGreaterThan(1)
|
||||
})
|
||||
|
||||
it('should handle update of non-existent entity gracefully', async () => {
|
||||
it('should reject update of a non-existent entity', async () => {
|
||||
const fakeId = randomUUID()
|
||||
try {
|
||||
await brain.update({ id: fakeId, metadata: { test: true } })
|
||||
} catch {
|
||||
// Brainy may throw for non-existent entity — acceptable
|
||||
}
|
||||
await expect(
|
||||
brain.update({ id: fakeId, metadata: { test: true } })
|
||||
).rejects.toThrow(/not found/)
|
||||
})
|
||||
|
||||
it('should preserve unmodified fields', async () => {
|
||||
|
|
@ -191,30 +189,27 @@ describe('Brainy Public API - Complete Coverage', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('brain.delete()', () => {
|
||||
describe('brain.remove()', () => {
|
||||
it('should handle cascade deletion of relationships', async () => {
|
||||
const id1 = await brain.add({ data: 'Entity 1', type: NounType.Person })
|
||||
const id2 = await brain.add({ data: 'Entity 2', type: NounType.Organization })
|
||||
|
||||
await brain.relate({ from: id1, to: id2, type: VerbType.WorksWith })
|
||||
|
||||
await brain.delete(id1)
|
||||
await brain.remove(id1)
|
||||
|
||||
const relations = await brain.getRelations(id2)
|
||||
const relations = await brain.related(id2)
|
||||
expect(relations.filter(r => r.from === id1 || r.to === id1)).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should handle deletion of non-existent entity gracefully', async () => {
|
||||
it('should treat deletion of a non-existent entity as a no-op', async () => {
|
||||
const fakeId = randomUUID()
|
||||
try {
|
||||
await brain.delete(fakeId)
|
||||
} catch {
|
||||
// Brainy may throw for non-existent entity — acceptable
|
||||
}
|
||||
// delete() is idempotent: unknown IDs resolve without throwing
|
||||
await expect(brain.remove(fakeId)).resolves.toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('brain.deleteMany()', () => {
|
||||
describe('brain.removeMany()', () => {
|
||||
it('should efficiently delete batches', async () => {
|
||||
const result = await brain.addMany({
|
||||
items: Array.from({ length: 5 }, (_, i) => ({
|
||||
|
|
@ -224,7 +219,7 @@ describe('Brainy Public API - Complete Coverage', () => {
|
|||
})
|
||||
|
||||
const ids = result.successful
|
||||
await brain.deleteMany({ ids })
|
||||
await brain.removeMany({ ids })
|
||||
|
||||
for (const id of ids) {
|
||||
const entity = await brain.get(id)
|
||||
|
|
@ -280,8 +275,8 @@ describe('Brainy Public API - Complete Coverage', () => {
|
|||
bidirectional: true
|
||||
})
|
||||
|
||||
const relations1 = await brain.getRelations(person1)
|
||||
const relations2 = await brain.getRelations(person2)
|
||||
const relations1 = await brain.related(person1)
|
||||
const relations2 = await brain.related(person2)
|
||||
|
||||
expect(relations1.some(r => r.to === person2)).toBe(true)
|
||||
expect(relations2.some(r => r.to === person1)).toBe(true)
|
||||
|
|
@ -294,7 +289,7 @@ describe('Brainy Public API - Complete Coverage', () => {
|
|||
await brain.relate({ from: id1, to: id2, type: VerbType.References })
|
||||
await brain.relate({ from: id1, to: id2, type: VerbType.References })
|
||||
|
||||
const relations = await brain.getRelations(id1)
|
||||
const relations = await brain.related(id1)
|
||||
const referenceRelations = relations.filter(
|
||||
r => r.type === VerbType.References && r.to === id2
|
||||
)
|
||||
|
|
@ -317,7 +312,7 @@ describe('Brainy Public API - Complete Coverage', () => {
|
|||
}
|
||||
})
|
||||
|
||||
const relations = await brain.getRelations(id1)
|
||||
const relations = await brain.related(id1)
|
||||
const relation = relations.find(r => r.id === relationId)
|
||||
|
||||
expect(relation).toBeDefined()
|
||||
|
|
@ -350,7 +345,7 @@ describe('Brainy Public API - Complete Coverage', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('brain.getRelations()', () => {
|
||||
describe('brain.related()', () => {
|
||||
it('should retrieve outgoing relationships', async () => {
|
||||
const center = await brain.add({ data: 'Center', type: NounType.Person })
|
||||
const related1 = await brain.add({ data: 'Related1', type: NounType.Organization })
|
||||
|
|
@ -359,7 +354,7 @@ describe('Brainy Public API - Complete Coverage', () => {
|
|||
await brain.relate({ from: center, to: related1, type: VerbType.WorksWith })
|
||||
await brain.relate({ from: center, to: related2, type: VerbType.Creates })
|
||||
|
||||
const outgoing = await brain.getRelations({ from: center })
|
||||
const outgoing = await brain.related({ from: center })
|
||||
|
||||
expect(outgoing).toHaveLength(2)
|
||||
expect(outgoing.some(r => r.type === VerbType.WorksWith)).toBe(true)
|
||||
|
|
@ -372,7 +367,7 @@ describe('Brainy Public API - Complete Coverage', () => {
|
|||
|
||||
await brain.relate({ from: source, to: center, type: VerbType.DependsOn })
|
||||
|
||||
const incoming = await brain.getRelations({ to: center })
|
||||
const incoming = await brain.related({ to: center })
|
||||
|
||||
expect(incoming).toHaveLength(1)
|
||||
expect(incoming[0].type).toBe(VerbType.DependsOn)
|
||||
|
|
@ -386,8 +381,8 @@ describe('Brainy Public API - Complete Coverage', () => {
|
|||
await brain.relate({ from: source, to: center, type: VerbType.Creates })
|
||||
await brain.relate({ from: center, to: target, type: VerbType.Requires })
|
||||
|
||||
const outgoing = await brain.getRelations({ from: center })
|
||||
const incoming = await brain.getRelations({ to: center })
|
||||
const outgoing = await brain.related({ from: center })
|
||||
const incoming = await brain.related({ to: center })
|
||||
|
||||
expect(outgoing).toHaveLength(1)
|
||||
expect(outgoing[0].to).toBe(target)
|
||||
|
|
@ -406,7 +401,7 @@ describe('Brainy Public API - Complete Coverage', () => {
|
|||
await brain.relate({ from: doc, to: ref2, type: VerbType.References })
|
||||
await brain.relate({ from: author, to: doc, type: VerbType.Creates })
|
||||
|
||||
const references = await brain.getRelations({
|
||||
const references = await brain.related({
|
||||
from: doc,
|
||||
type: VerbType.References
|
||||
})
|
||||
|
|
@ -683,7 +678,7 @@ describe('Brainy Public API - Complete Coverage', () => {
|
|||
const updated = await fsBrain.get(id)
|
||||
expect(updated?.metadata?.updated).toBe(true)
|
||||
|
||||
await fsBrain.delete(id)
|
||||
await fsBrain.remove(id)
|
||||
const deleted = await fsBrain.get(id)
|
||||
expect(deleted).toBeNull()
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ export default defineConfig({
|
|||
'tests/integration/**',
|
||||
'tests/**/*.integration.test.ts',
|
||||
'tests/**/*.e2e.test.ts',
|
||||
'tests/unit/graph/graphIndex-pagination.test.ts', // v6.0.0: TODO fix infinite loop
|
||||
'node_modules/**'
|
||||
],
|
||||
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ describe('CRITICAL: Error Handling and Edge Cases', () => {
|
|||
)
|
||||
)
|
||||
|
||||
const deletes = ids.map(id => brainy.delete(id))
|
||||
const deletes = ids.map(id => brainy.remove(id))
|
||||
await Promise.all(deletes)
|
||||
|
||||
for (const id of ids) {
|
||||
|
|
@ -280,7 +280,7 @@ describe('CRITICAL: Error Handling and Edge Cases', () => {
|
|||
|
||||
expect(relId).toBeDefined()
|
||||
|
||||
const relations = await brainy.getRelations({ from: id })
|
||||
const relations = await brainy.related({ from: id })
|
||||
expect(relations.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
|
|
@ -371,7 +371,7 @@ describe('CRITICAL: Error Handling and Edge Cases', () => {
|
|||
type: 'entity'
|
||||
})
|
||||
|
||||
await brainy.delete(`cleanup-${i}`)
|
||||
await brainy.remove(`cleanup-${i}`)
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
|
|
|
|||
|
|
@ -191,12 +191,12 @@ describe('CRITICAL: Real-World Neural Matching Validation', () => {
|
|||
metadata: { role: 'lead' }
|
||||
})
|
||||
|
||||
const johnsRelations = await brainy.getRelations({
|
||||
const johnsRelations = await brainy.related({
|
||||
from: johnId
|
||||
})
|
||||
expect(johnsRelations.length).toBe(2)
|
||||
|
||||
const acmeRelations = await brainy.getRelations({
|
||||
const acmeRelations = await brainy.related({
|
||||
to: acmeId
|
||||
})
|
||||
expect(acmeRelations.length).toBe(1)
|
||||
|
|
@ -236,12 +236,12 @@ describe('CRITICAL: Real-World Neural Matching Validation', () => {
|
|||
metadata: { startDate: '2023-01-01' }
|
||||
})
|
||||
|
||||
const aliceRelations = await brainy.getRelations({
|
||||
const aliceRelations = await brainy.related({
|
||||
from: aliceId
|
||||
})
|
||||
expect(aliceRelations.length).toBeGreaterThanOrEqual(3)
|
||||
|
||||
const proj2Relations = await brainy.getRelations({
|
||||
const proj2Relations = await brainy.related({
|
||||
to: proj2Id
|
||||
})
|
||||
expect(proj2Relations.length).toBe(2)
|
||||
|
|
@ -334,7 +334,7 @@ describe('CRITICAL: Real-World Neural Matching Validation', () => {
|
|||
const notFound = await brainy.get('00000000-0000-0000-0000-000000000099')
|
||||
expect(notFound).toBeNull()
|
||||
|
||||
const relations = await brainy.getRelations({
|
||||
const relations = await brainy.related({
|
||||
from: '00000000-0000-0000-0000-000000000099'
|
||||
})
|
||||
expect(relations).toEqual([])
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ describe('CRITICAL: Performance Benchmarks at Scale', () => {
|
|||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const randomNode = Math.floor(Math.random() * nodes)
|
||||
queryPromises.push(brainy.getRelations({ from: `node-${randomNode}` }))
|
||||
queryPromises.push(brainy.related({ from: `node-${randomNode}` }))
|
||||
}
|
||||
|
||||
await Promise.all(queryPromises)
|
||||
|
|
|
|||
|
|
@ -1,358 +0,0 @@
|
|||
/**
|
||||
* Test Mocks - Mock implementations and spies for Brainy tests
|
||||
* Provides mock objects and spy utilities for testing
|
||||
*/
|
||||
|
||||
import { vi } from 'vitest'
|
||||
import type { StorageAdapter } from '../../src/coreTypes'
|
||||
import type { Entity, Relation } from '../../src/types/brainy.types'
|
||||
|
||||
/**
|
||||
* Create a mock storage adapter
|
||||
*/
|
||||
export function createMockStorageAdapter(): StorageAdapter {
|
||||
const store = new Map<string, any>()
|
||||
const relationStore = new Map<string, any[]>()
|
||||
|
||||
return {
|
||||
init: vi.fn(async () => {}),
|
||||
close: vi.fn(async () => {}),
|
||||
|
||||
// Entity operations
|
||||
storeVector: vi.fn(async (id: string, vector: number[], metadata?: any) => {
|
||||
store.set(id, { id, vector, metadata })
|
||||
}),
|
||||
|
||||
getVector: vi.fn(async (id: string) => {
|
||||
const item = store.get(id)
|
||||
return item ? item.vector : null
|
||||
}),
|
||||
|
||||
getMetadata: vi.fn(async (id: string) => {
|
||||
const item = store.get(id)
|
||||
return item ? item.metadata : null
|
||||
}),
|
||||
|
||||
deleteVector: vi.fn(async (id: string) => {
|
||||
return store.delete(id)
|
||||
}),
|
||||
|
||||
updateVector: vi.fn(async (id: string, vector: number[], metadata?: any) => {
|
||||
if (!store.has(id)) return false
|
||||
store.set(id, { id, vector, metadata })
|
||||
return true
|
||||
}),
|
||||
|
||||
hasVector: vi.fn(async (id: string) => {
|
||||
return store.has(id)
|
||||
}),
|
||||
|
||||
// Relation operations
|
||||
storeRelation: vi.fn(async (from: string, to: string, type: string, metadata?: any) => {
|
||||
const relation = { from, to, type, metadata }
|
||||
const key = `${from}-${to}-${type}`
|
||||
|
||||
if (!relationStore.has(from)) {
|
||||
relationStore.set(from, [])
|
||||
}
|
||||
relationStore.get(from)!.push(relation)
|
||||
|
||||
return key
|
||||
}),
|
||||
|
||||
getRelations: vi.fn(async (id: string) => {
|
||||
return relationStore.get(id) || []
|
||||
}),
|
||||
|
||||
deleteRelation: vi.fn(async (from: string, to: string, type: string) => {
|
||||
const relations = relationStore.get(from)
|
||||
if (!relations) return false
|
||||
|
||||
const index = relations.findIndex(
|
||||
r => r.to === to && r.type === type
|
||||
)
|
||||
if (index === -1) return false
|
||||
|
||||
relations.splice(index, 1)
|
||||
return true
|
||||
}),
|
||||
|
||||
// Batch operations
|
||||
batchStore: vi.fn(async (items: Array<{ id: string; vector: number[]; metadata?: any }>) => {
|
||||
const results: boolean[] = []
|
||||
for (const item of items) {
|
||||
store.set(item.id, item)
|
||||
results.push(true)
|
||||
}
|
||||
return results
|
||||
}),
|
||||
|
||||
batchGet: vi.fn(async (ids: string[]) => {
|
||||
return ids.map(id => {
|
||||
const item = store.get(id)
|
||||
return item ? item.vector : null
|
||||
})
|
||||
}),
|
||||
|
||||
batchDelete: vi.fn(async (ids: string[]) => {
|
||||
return ids.map(id => store.delete(id))
|
||||
}),
|
||||
|
||||
// Query operations
|
||||
getAllIds: vi.fn(async () => {
|
||||
return Array.from(store.keys())
|
||||
}),
|
||||
|
||||
getCount: vi.fn(async () => {
|
||||
return store.size
|
||||
}),
|
||||
|
||||
clear: vi.fn(async () => {
|
||||
store.clear()
|
||||
relationStore.clear()
|
||||
}),
|
||||
|
||||
// Utility for tests
|
||||
_getStore: () => store,
|
||||
_getRelationStore: () => relationStore,
|
||||
} as any
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock embedding function
|
||||
*/
|
||||
export function createMockEmbeddingFunction(dimension = 1536) {
|
||||
return vi.fn(async (text: string) => {
|
||||
// Generate deterministic embedding based on text
|
||||
const hash = text.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)
|
||||
const vector = new Array(dimension)
|
||||
|
||||
for (let i = 0; i < dimension; i++) {
|
||||
vector[i] = Math.sin((hash + i) * 0.1) * 0.5
|
||||
}
|
||||
|
||||
// Normalize
|
||||
const magnitude = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0))
|
||||
return vector.map(val => val / magnitude)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock distance function
|
||||
*/
|
||||
export function createMockDistanceFunction() {
|
||||
return vi.fn((a: number[], b: number[]) => {
|
||||
// Simple cosine distance
|
||||
let dotProduct = 0
|
||||
let magnitudeA = 0
|
||||
let magnitudeB = 0
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
dotProduct += a[i] * b[i]
|
||||
magnitudeA += a[i] * a[i]
|
||||
magnitudeB += b[i] * b[i]
|
||||
}
|
||||
|
||||
const similarity = dotProduct / (Math.sqrt(magnitudeA) * Math.sqrt(magnitudeB))
|
||||
return 1 - similarity // Convert similarity to distance
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock augmentation
|
||||
*/
|
||||
export function createMockAugmentation(name = 'test-augmentation') {
|
||||
return {
|
||||
name,
|
||||
priority: 100,
|
||||
enabled: true,
|
||||
|
||||
beforeAdd: vi.fn(async (data: any) => data),
|
||||
afterAdd: vi.fn(async (entity: Entity) => entity),
|
||||
|
||||
beforeUpdate: vi.fn(async (data: any) => data),
|
||||
afterUpdate: vi.fn(async (entity: Entity) => entity),
|
||||
|
||||
beforeDelete: vi.fn(async (id: string) => id),
|
||||
afterDelete: vi.fn(async (id: string) => id),
|
||||
|
||||
beforeRelate: vi.fn(async (relation: any) => relation),
|
||||
afterRelate: vi.fn(async (relation: Relation) => relation),
|
||||
|
||||
beforeSearch: vi.fn(async (query: any) => query),
|
||||
afterSearch: vi.fn(async (results: any[]) => results),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock cache
|
||||
*/
|
||||
export function createMockCache<T = any>() {
|
||||
const cache = new Map<string, T>()
|
||||
|
||||
return {
|
||||
get: vi.fn((key: string) => cache.get(key)),
|
||||
set: vi.fn((key: string, value: T) => {
|
||||
cache.set(key, value)
|
||||
return true
|
||||
}),
|
||||
has: vi.fn((key: string) => cache.has(key)),
|
||||
delete: vi.fn((key: string) => cache.delete(key)),
|
||||
clear: vi.fn(() => cache.clear()),
|
||||
size: vi.fn(() => cache.size),
|
||||
|
||||
// Utility for tests
|
||||
_getCache: () => cache,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock event emitter
|
||||
*/
|
||||
export function createMockEventEmitter() {
|
||||
const listeners = new Map<string, Function[]>()
|
||||
|
||||
return {
|
||||
on: vi.fn((event: string, handler: Function) => {
|
||||
if (!listeners.has(event)) {
|
||||
listeners.set(event, [])
|
||||
}
|
||||
listeners.get(event)!.push(handler)
|
||||
}),
|
||||
|
||||
off: vi.fn((event: string, handler: Function) => {
|
||||
const handlers = listeners.get(event)
|
||||
if (handlers) {
|
||||
const index = handlers.indexOf(handler)
|
||||
if (index > -1) {
|
||||
handlers.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
emit: vi.fn((event: string, ...args: any[]) => {
|
||||
const handlers = listeners.get(event)
|
||||
if (handlers) {
|
||||
handlers.forEach(handler => handler(...args))
|
||||
}
|
||||
}),
|
||||
|
||||
once: vi.fn((event: string, handler: Function) => {
|
||||
const wrappedHandler = (...args: any[]) => {
|
||||
handler(...args)
|
||||
listeners.get(event)?.splice(listeners.get(event)!.indexOf(wrappedHandler), 1)
|
||||
}
|
||||
if (!listeners.has(event)) {
|
||||
listeners.set(event, [])
|
||||
}
|
||||
listeners.get(event)!.push(wrappedHandler)
|
||||
}),
|
||||
|
||||
removeAllListeners: vi.fn((event?: string) => {
|
||||
if (event) {
|
||||
listeners.delete(event)
|
||||
} else {
|
||||
listeners.clear()
|
||||
}
|
||||
}),
|
||||
|
||||
// Utility for tests
|
||||
_getListeners: () => listeners,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock logger
|
||||
*/
|
||||
export function createMockLogger() {
|
||||
return {
|
||||
debug: vi.fn((...args: any[]) => console.debug(...args)),
|
||||
info: vi.fn((...args: any[]) => console.info(...args)),
|
||||
warn: vi.fn((...args: any[]) => console.warn(...args)),
|
||||
error: vi.fn((...args: any[]) => console.error(...args)),
|
||||
log: vi.fn((...args: any[]) => console.log(...args)),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock timer for controlling time in tests
|
||||
*/
|
||||
export function createMockTimer() {
|
||||
let currentTime = Date.now()
|
||||
|
||||
return {
|
||||
now: vi.fn(() => currentTime),
|
||||
advance: vi.fn((ms: number) => {
|
||||
currentTime += ms
|
||||
}),
|
||||
reset: vi.fn(() => {
|
||||
currentTime = Date.now()
|
||||
}),
|
||||
setTime: vi.fn((time: number) => {
|
||||
currentTime = time
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock fetch function
|
||||
*/
|
||||
export function createMockFetch() {
|
||||
return vi.fn(async (url: string, options?: any) => {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
json: async () => ({ success: true, url, options }),
|
||||
text: async () => 'Mock response',
|
||||
blob: async () => new Blob(['Mock blob']),
|
||||
headers: new Headers({
|
||||
'content-type': 'application/json',
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for all pending promises to resolve
|
||||
*/
|
||||
export async function flushPromises() {
|
||||
await new Promise(resolve => setImmediate(resolve))
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a condition to be true
|
||||
*/
|
||||
export async function waitFor(
|
||||
condition: () => boolean | Promise<boolean>,
|
||||
timeout = 5000,
|
||||
interval = 100
|
||||
): Promise<void> {
|
||||
const start = Date.now()
|
||||
|
||||
while (Date.now() - start < timeout) {
|
||||
if (await condition()) {
|
||||
return
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, interval))
|
||||
}
|
||||
|
||||
throw new Error(`Timeout waiting for condition after ${timeout}ms`)
|
||||
}
|
||||
|
||||
// Export as namespace for convenience
|
||||
export const TestMocks = {
|
||||
createMockStorageAdapter,
|
||||
createMockEmbeddingFunction,
|
||||
createMockDistanceFunction,
|
||||
createMockAugmentation,
|
||||
createMockCache,
|
||||
createMockEventEmitter,
|
||||
createMockLogger,
|
||||
createMockTimer,
|
||||
createMockFetch,
|
||||
flushPromises,
|
||||
waitFor,
|
||||
}
|
||||
|
||||
export default TestMocks
|
||||
|
|
@ -174,7 +174,7 @@ describe('Aggregation Engine Integration', () => {
|
|||
metadata: { category: 'food', amount: 15 }
|
||||
})
|
||||
|
||||
await brain.delete(id1)
|
||||
await brain.remove(id1)
|
||||
|
||||
const results = await brain.find({ aggregate: 'totals' })
|
||||
expect(results).toHaveLength(1)
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@
|
|||
* 3. Production quality (no errors, proper returns)
|
||||
*
|
||||
* APIs tested:
|
||||
* - brain.add(), brain.get(), brain.update(), brain.delete()
|
||||
* - brain.add(), brain.get(), brain.update(), brain.remove()
|
||||
* - brain.find(), brain.similar()
|
||||
* - brain.relate(), brain.getRelations(), brain.unrelate()
|
||||
* - brain.addMany(), brain.updateMany(), brain.deleteMany(), brain.relateMany()
|
||||
* - brain.relate(), brain.related(), brain.unrelate()
|
||||
* - brain.addMany(), brain.updateMany(), brain.removeMany(), brain.relateMany()
|
||||
* - vfs.* (init, mkdir, writeFile, readdir, readFile, stat, exists)
|
||||
* - neural.* (similar, neighbors, outliers)
|
||||
* - import.* (would test if CSV/Excel/PDF available)
|
||||
|
|
@ -113,12 +113,12 @@ describe('Comprehensive All-APIs Test', () => {
|
|||
console.log(`✅ brain.similar() found ${similar.length} similar entities (no VFS)`)
|
||||
})
|
||||
|
||||
it('brain.delete() - should delete entity', async () => {
|
||||
await brain.delete(entityId)
|
||||
it('brain.remove() - should delete entity', async () => {
|
||||
await brain.remove(entityId)
|
||||
|
||||
const deleted = await brain.get(entityId)
|
||||
expect(deleted).toBeNull()
|
||||
console.log(`✅ brain.delete() deleted entity`)
|
||||
console.log(`✅ brain.remove() deleted entity`)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -148,20 +148,20 @@ describe('Comprehensive All-APIs Test', () => {
|
|||
console.log(`✅ brain.relate() created relation: ${relationId}`)
|
||||
})
|
||||
|
||||
it('brain.getRelations() - should retrieve relationships', async () => {
|
||||
const relations = await brain.getRelations({
|
||||
it('brain.related() - should retrieve relationships', async () => {
|
||||
const relations = await brain.related({
|
||||
from: entity1Id
|
||||
})
|
||||
|
||||
expect(relations.length).toBeGreaterThan(0)
|
||||
expect(relations.some(r => r.id === relationId)).toBe(true)
|
||||
console.log(`✅ brain.getRelations() found ${relations.length} relations`)
|
||||
console.log(`✅ brain.related() found ${relations.length} relations`)
|
||||
})
|
||||
|
||||
it('brain.unrelate() - should delete relationship', async () => {
|
||||
await brain.unrelate(relationId)
|
||||
|
||||
const relations = await brain.getRelations({
|
||||
const relations = await brain.related({
|
||||
from: entity1Id
|
||||
})
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ describe('Comprehensive All-APIs Test', () => {
|
|||
console.log(`✅ brain.relateMany() created ${relationIds.length} relations`)
|
||||
})
|
||||
|
||||
it('brain.deleteMany() - should delete multiple entities', async () => {
|
||||
it('brain.removeMany() - should delete multiple entities', async () => {
|
||||
const ids = await brain.addMany({
|
||||
items: [
|
||||
{ data: 'Delete 1', type: NounType.Document },
|
||||
|
|
@ -211,12 +211,12 @@ describe('Comprehensive All-APIs Test', () => {
|
|||
]
|
||||
})
|
||||
|
||||
const result = await brain.deleteMany({
|
||||
const result = await brain.removeMany({
|
||||
ids: ids.successful
|
||||
})
|
||||
|
||||
expect(result.successful.length).toBe(2)
|
||||
console.log(`✅ brain.deleteMany() deleted ${result.successful.length} entities`)
|
||||
console.log(`✅ brain.removeMany() deleted ${result.successful.length} entities`)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ describe('Batch Import with Immediate Relations (v5.7.3 Fix)', () => {
|
|||
expect(relationIds.length).toBe(372)
|
||||
|
||||
// 4. Verify all relationships exist (use higher limit to get all 372)
|
||||
const relationships = await brain.getRelations({ from: documentId, limit: 500 })
|
||||
const relationships = await brain.related({ from: documentId, limit: 500 })
|
||||
expect(relationships.length).toBe(372)
|
||||
})
|
||||
|
||||
|
|
@ -126,7 +126,7 @@ describe('Batch Import with Immediate Relations (v5.7.3 Fix)', () => {
|
|||
expect(relationId).toBeTruthy()
|
||||
|
||||
// Verify relationship exists
|
||||
const relations = await brain.getRelations(addResult.successful[0])
|
||||
const relations = await brain.related(addResult.successful[0])
|
||||
expect(relations.length).toBe(1)
|
||||
expect(relations[0].to).toBe(addResult.successful[1])
|
||||
})
|
||||
|
|
@ -263,7 +263,7 @@ describe('Batch Import with Immediate Relations (v5.7.3 Fix)', () => {
|
|||
expect(relationIds.length).toBe(100)
|
||||
|
||||
// 4. Verify structure
|
||||
const childrenRelations = await brain.getRelations(rootId)
|
||||
const childrenRelations = await brain.related(rootId)
|
||||
expect(childrenRelations.length).toBe(100)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ describe('Brainy 3.0 Core (Integration Tests - Real AI)', () => {
|
|||
const [alice, bob, project] = entityIds
|
||||
|
||||
// Get Alice's relationships
|
||||
const aliceRelations = await brain.getRelations({ from: alice })
|
||||
const aliceRelations = await brain.related({ from: alice })
|
||||
|
||||
expect(aliceRelations).toBeDefined()
|
||||
expect(aliceRelations.length).toBeGreaterThan(0)
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ describe('Clear Persistence Bug Fix (v5.10.4)', () => {
|
|||
// Verify data exists
|
||||
expect((await brain1.find({ type: 'person' })).length).toBe(1)
|
||||
expect((await brain1.find({ type: 'concept' })).length).toBe(1)
|
||||
expect((await brain1.getRelations({})).length).toBe(1)
|
||||
expect((await brain1.related({})).length).toBe(1)
|
||||
|
||||
// Clear
|
||||
await brain1.clear()
|
||||
|
|
@ -200,7 +200,7 @@ describe('Clear Persistence Bug Fix (v5.10.4)', () => {
|
|||
// Verify everything is cleared
|
||||
expect((await brain2.find({ type: 'person' })).length).toBe(0)
|
||||
expect((await brain2.find({ type: 'concept' })).length).toBe(0)
|
||||
expect((await brain2.getRelations({})).length).toBe(0)
|
||||
expect((await brain2.related({})).length).toBe(0)
|
||||
})
|
||||
|
||||
it('should handle clear() on empty storage', async () => {
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ describe('8.0 Db API — generational MVCC', () => {
|
|||
// ZERO ops applied: generation unchanged, the add rolled back, no edge.
|
||||
expect(brain.generation()).toBe(g0)
|
||||
expect(await brain.get(uid('atomic-b'))).toBeNull()
|
||||
expect((await brain.getRelations({ from: uid('atomic-b') })).length).toBe(0)
|
||||
expect((await brain.related({ from: uid('atomic-b') })).length).toBe(0)
|
||||
expect((await brain.find({ type: NounType.Person, limit: 10 })).length).toBe(0)
|
||||
|
||||
// No partial generation records: the staging directory was removed.
|
||||
|
|
@ -388,7 +388,7 @@ describe('8.0 Db API — generational MVCC', () => {
|
|||
for (const id of ids) {
|
||||
preState.set(id, await brain.get(id))
|
||||
}
|
||||
const preRelations = await brain.getRelations({ from: ids[0] })
|
||||
const preRelations = await brain.related({ from: ids[0] })
|
||||
expect(preRelations.length).toBe(1)
|
||||
|
||||
const snapDir = path.join(makeTempDir(), 'snapshot')
|
||||
|
|
@ -596,7 +596,7 @@ describe('8.0 Db API — generational MVCC', () => {
|
|||
expect(brain.generation()).toBe(g0)
|
||||
expect(((await brain.get(uid('with-a')))?.metadata as { v: number }).v).toBe(1)
|
||||
expect(await brain.get(uid('with-new'))).toBeNull()
|
||||
expect((await brain.getRelations({ from: uid('with-a') })).length).toBe(0)
|
||||
expect((await brain.related({ from: uid('with-a') })).length).toBe(0)
|
||||
|
||||
await spec.release()
|
||||
await db.release()
|
||||
|
|
@ -833,7 +833,7 @@ describe('8.0 Db API — generational MVCC', () => {
|
|||
expect(db.receipt!.ids[1]).toBe(uid('rcpt-bb'))
|
||||
expect(db.receipt!.ids[3]).toBe(uid('rcpt-bb'))
|
||||
const relationId = db.receipt!.ids[2]
|
||||
expect((await brain.getRelations({ from: uid('rcpt-aa') }))[0].id).toBe(relationId)
|
||||
expect((await brain.related({ from: uid('rcpt-aa') }))[0].id).toBe(relationId)
|
||||
|
||||
// Duplicate relate (same from/to/type) dedupes to the SAME id — both
|
||||
// within a batch and against committed state, mirroring relate().
|
||||
|
|
@ -841,7 +841,7 @@ describe('8.0 Db API — generational MVCC', () => {
|
|||
{ op: 'relate', from: uid('rcpt-aa'), to: uid('rcpt-bb'), type: VerbType.Knows }
|
||||
])
|
||||
expect(dup.receipt!.ids[0]).toBe(relationId)
|
||||
expect((await brain.getRelations({ from: uid('rcpt-aa') })).length).toBe(1)
|
||||
expect((await brain.related({ from: uid('rcpt-aa') })).length).toBe(1)
|
||||
|
||||
await db.release()
|
||||
await dup.release()
|
||||
|
|
@ -927,7 +927,7 @@ describe('8.0 Db API — generational MVCC', () => {
|
|||
const db = brain.now()
|
||||
|
||||
// Rewire the graph after the pin: a→b becomes a→c.
|
||||
const oldEdge = (await brain.getRelations({ from: uid('trav-a') }))[0]
|
||||
const oldEdge = (await brain.related({ from: uid('trav-a') }))[0]
|
||||
await brain.transact([
|
||||
{ op: 'unrelate', id: oldEdge.id },
|
||||
{ op: 'relate', from: uid('trav-a'), to: uid('trav-c'), type: VerbType.References }
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
/**
|
||||
* Integration Tests for getRelations() Fix (v4.1.3)
|
||||
* Integration Tests for related() call patterns
|
||||
*
|
||||
* Tests for Bug: getRelations() returns empty array when called without parameters
|
||||
* This validates that the fix allows retrieving all relationships with proper pagination
|
||||
* Regression coverage (originally a v4.1.3 fix): related() must return all
|
||||
* relationships with proper pagination when called without parameters, and
|
||||
* support the string-id shorthand.
|
||||
*
|
||||
* NO MOCKS - Real integration tests with actual storage
|
||||
*/
|
||||
|
|
@ -12,7 +13,7 @@ import { Brainy } from '../../src/brainy.js'
|
|||
import { NounType, VerbType } from '../../src/types/graphTypes.js'
|
||||
import * as fs from 'fs/promises'
|
||||
|
||||
describe('getRelations() Fix (v4.1.3)', () => {
|
||||
describe('related() call patterns', () => {
|
||||
let brain: Brainy
|
||||
const testPath = './test-brainy-get-relations-fix'
|
||||
|
||||
|
|
@ -56,7 +57,7 @@ describe('getRelations() Fix (v4.1.3)', () => {
|
|||
await brain.flush()
|
||||
|
||||
// Get all relationships - THIS IS THE BUG FIX!
|
||||
const relations = await brain.getRelations()
|
||||
const relations = await brain.related()
|
||||
|
||||
// Should return all 3 relationships
|
||||
expect(relations).toHaveLength(3)
|
||||
|
|
@ -70,7 +71,7 @@ describe('getRelations() Fix (v4.1.3)', () => {
|
|||
await brain.flush()
|
||||
|
||||
// Get all relationships
|
||||
const relations = await brain.getRelations()
|
||||
const relations = await brain.related()
|
||||
|
||||
// Should return empty array
|
||||
expect(relations).toHaveLength(0)
|
||||
|
|
@ -100,11 +101,11 @@ describe('getRelations() Fix (v4.1.3)', () => {
|
|||
await brain.flush()
|
||||
|
||||
// Get first page (limit: 10)
|
||||
const page1 = await brain.getRelations({ limit: 10 })
|
||||
const page1 = await brain.related({ limit: 10 })
|
||||
expect(page1).toHaveLength(10)
|
||||
|
||||
// Get second page (offset: 10, limit: 10)
|
||||
const page2 = await brain.getRelations({ offset: 10, limit: 10 })
|
||||
const page2 = await brain.related({ offset: 10, limit: 10 })
|
||||
expect(page2).toHaveLength(10)
|
||||
|
||||
// Ensure no duplicates between pages
|
||||
|
|
@ -127,12 +128,12 @@ describe('getRelations() Fix (v4.1.3)', () => {
|
|||
await brain.flush()
|
||||
|
||||
// Get only FriendOf relationships
|
||||
const friendRelations = await brain.getRelations({ type: VerbType.FriendOf })
|
||||
const friendRelations = await brain.related({ type: VerbType.FriendOf })
|
||||
expect(friendRelations).toHaveLength(2)
|
||||
expect(friendRelations.every(r => r.type === VerbType.FriendOf)).toBe(true)
|
||||
|
||||
// Get only WorksWith relationships
|
||||
const workRelations = await brain.getRelations({ type: VerbType.WorksWith })
|
||||
const workRelations = await brain.related({ type: VerbType.WorksWith })
|
||||
expect(workRelations).toHaveLength(1)
|
||||
expect(workRelations[0].type).toBe(VerbType.WorksWith)
|
||||
})
|
||||
|
|
@ -152,14 +153,14 @@ describe('getRelations() Fix (v4.1.3)', () => {
|
|||
await brain.flush()
|
||||
|
||||
// Get all relationships first to verify total count
|
||||
const allRelations = await brain.getRelations()
|
||||
const allRelations = await brain.related()
|
||||
expect(allRelations).toHaveLength(4)
|
||||
|
||||
// Get FriendOf and WorksWith relationships using type array filter
|
||||
// NOTE: Current storage layer may not support array filters directly
|
||||
// So we test each type separately and combine
|
||||
const friendRelations = await brain.getRelations({ type: VerbType.FriendOf })
|
||||
const workRelations = await brain.getRelations({ type: VerbType.WorksWith })
|
||||
const friendRelations = await brain.related({ type: VerbType.FriendOf })
|
||||
const workRelations = await brain.related({ type: VerbType.WorksWith })
|
||||
|
||||
expect(friendRelations).toHaveLength(2)
|
||||
expect(workRelations).toHaveLength(1)
|
||||
|
|
@ -169,7 +170,7 @@ describe('getRelations() Fix (v4.1.3)', () => {
|
|||
})
|
||||
|
||||
describe('String ID Shorthand Syntax', () => {
|
||||
it('should support string ID shorthand: getRelations(id)', async () => {
|
||||
it('should support string ID shorthand: related(id)', async () => {
|
||||
// Create entities
|
||||
const person1 = await brain.add({ data: 'Alice', type: NounType.Person })
|
||||
const person2 = await brain.add({ data: 'Bob', type: NounType.Person })
|
||||
|
|
@ -181,14 +182,14 @@ describe('getRelations() Fix (v4.1.3)', () => {
|
|||
await brain.flush()
|
||||
|
||||
// Use string shorthand - THIS IS THE NEW SIGNATURE!
|
||||
const relations = await brain.getRelations(person1)
|
||||
const relations = await brain.related(person1)
|
||||
|
||||
// Should return both relationships from person1
|
||||
expect(relations).toHaveLength(2)
|
||||
expect(relations.every(r => r.from === person1)).toBe(true)
|
||||
})
|
||||
|
||||
it('should be equivalent to getRelations({ from: id })', async () => {
|
||||
it('should be equivalent to related({ from: id })', async () => {
|
||||
// Create entities and relationships
|
||||
const person1 = await brain.add({ data: 'Alice', type: NounType.Person })
|
||||
const person2 = await brain.add({ data: 'Bob', type: NounType.Person })
|
||||
|
|
@ -196,8 +197,8 @@ describe('getRelations() Fix (v4.1.3)', () => {
|
|||
await brain.flush()
|
||||
|
||||
// Both syntaxes should return the same results
|
||||
const shorthand = await brain.getRelations(person1)
|
||||
const explicit = await brain.getRelations({ from: person1 })
|
||||
const shorthand = await brain.related(person1)
|
||||
const explicit = await brain.related({ from: person1 })
|
||||
|
||||
expect(shorthand).toEqual(explicit)
|
||||
})
|
||||
|
|
@ -217,7 +218,7 @@ describe('getRelations() Fix (v4.1.3)', () => {
|
|||
await brain.flush()
|
||||
|
||||
// Get relationships from person1
|
||||
const relations = await brain.getRelations({ from: person1 })
|
||||
const relations = await brain.related({ from: person1 })
|
||||
|
||||
expect(relations).toHaveLength(2)
|
||||
expect(relations.every(r => r.from === person1)).toBe(true)
|
||||
|
|
@ -235,7 +236,7 @@ describe('getRelations() Fix (v4.1.3)', () => {
|
|||
await brain.flush()
|
||||
|
||||
// Get relationships to person3
|
||||
const relations = await brain.getRelations({ to: person3 })
|
||||
const relations = await brain.related({ to: person3 })
|
||||
|
||||
expect(relations).toHaveLength(2)
|
||||
expect(relations.every(r => r.to === person3)).toBe(true)
|
||||
|
|
@ -253,7 +254,7 @@ describe('getRelations() Fix (v4.1.3)', () => {
|
|||
await brain.flush()
|
||||
|
||||
// Get only FriendOf relationships from person1
|
||||
const relations = await brain.getRelations({
|
||||
const relations = await brain.related({
|
||||
from: person1,
|
||||
type: VerbType.FriendOf
|
||||
})
|
||||
|
|
@ -290,7 +291,7 @@ describe('getRelations() Fix (v4.1.3)', () => {
|
|||
|
||||
// Should handle query without issues
|
||||
const startTime = Date.now()
|
||||
const relations = await brain.getRelations({ limit: 100 })
|
||||
const relations = await brain.related({ limit: 100 })
|
||||
const duration = Date.now() - startTime
|
||||
|
||||
expect(relations).toHaveLength(100)
|
||||
|
|
@ -331,11 +332,11 @@ describe('getRelations() Fix (v4.1.3)', () => {
|
|||
await brain.flush()
|
||||
|
||||
// Verify we have 150 total relationships
|
||||
const allRelations = await brain.getRelations({ limit: 200 })
|
||||
const allRelations = await brain.related({ limit: 200 })
|
||||
expect(allRelations.length).toBe(150)
|
||||
|
||||
// Call without limit - should default to 100
|
||||
const relations = await brain.getRelations()
|
||||
const relations = await brain.related()
|
||||
|
||||
// Should return exactly 100 (default limit)
|
||||
expect(relations).toHaveLength(100)
|
||||
|
|
@ -364,11 +365,11 @@ describe('getRelations() Fix (v4.1.3)', () => {
|
|||
await brain.flush()
|
||||
|
||||
// Verify we have 50 total
|
||||
const all = await brain.getRelations({ limit: 100 })
|
||||
const all = await brain.related({ limit: 100 })
|
||||
expect(all.length).toBe(50)
|
||||
|
||||
// Custom limit of 25
|
||||
const relations = await brain.getRelations({ limit: 25 })
|
||||
const relations = await brain.related({ limit: 25 })
|
||||
expect(relations).toHaveLength(25)
|
||||
})
|
||||
})
|
||||
|
|
@ -377,7 +378,7 @@ describe('getRelations() Fix (v4.1.3)', () => {
|
|||
it('should reproduce and fix the a consumer team bug scenario', async () => {
|
||||
// Reproduce the exact scenario from the bug report:
|
||||
// - 524 relationships exist in GraphAdjacencyIndex
|
||||
// - brain.getRelations() was returning empty array
|
||||
// - brain.related() was returning empty array
|
||||
|
||||
// Create entities similar to Consumer import
|
||||
const entities = []
|
||||
|
|
@ -407,7 +408,7 @@ describe('getRelations() Fix (v4.1.3)', () => {
|
|||
await brain.flush()
|
||||
|
||||
// BUG FIX TEST: This should now return relationships, not empty array!
|
||||
const relations = await brain.getRelations()
|
||||
const relations = await brain.related()
|
||||
|
||||
// CRITICAL: Should NOT be empty
|
||||
expect(relations.length).toBeGreaterThan(0)
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@
|
|||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { Brainy } from '../../../src/brainy.js'
|
||||
import { NounType, VerbType } from '../../../src/types/graphTypes.js'
|
||||
import { Brainy } from '../../src/brainy.js'
|
||||
import { NounType, VerbType } from '../../src/types/graphTypes.js'
|
||||
|
||||
describe('GraphAdjacencyIndex Pagination', () => {
|
||||
let brain: Brainy
|
||||
|
|
@ -413,7 +413,7 @@ describe('HNSW Index Rebuild (Integration Tests)', () => {
|
|||
|
||||
// Verify all indexes work
|
||||
const searchResults = await brain1.find({ query: 'engineer', limit: 5 })
|
||||
const relations = await brain1.getRelations({ from: alice })
|
||||
const relations = await brain1.related({ from: alice })
|
||||
const metadataResults = await brain1.find({ where: { role: 'engineer' } })
|
||||
|
||||
expect(searchResults.length).toBeGreaterThan(0)
|
||||
|
|
@ -450,7 +450,7 @@ describe('HNSW Index Rebuild (Integration Tests)', () => {
|
|||
console.log('✅ HNSW index operational')
|
||||
|
||||
// 2. Graph Adjacency Index (Bug #1 fix)
|
||||
const relations2 = await brain2.getRelations({ from: alice })
|
||||
const relations2 = await brain2.related({ from: alice })
|
||||
expect(relations2.length).toBe(relations.length)
|
||||
console.log('✅ Graph index operational')
|
||||
|
||||
|
|
|
|||
|
|
@ -183,11 +183,11 @@ describe('Metadata-Only Comprehensive Integration (v5.11.1)', () => {
|
|||
expect(entity!.vector).toEqual([]) // Still metadata-only
|
||||
})
|
||||
|
||||
it('brain.delete() should work after metadata-only get', async () => {
|
||||
it('brain.remove() should work after metadata-only get', async () => {
|
||||
const entity = await brain.get(entityId)
|
||||
expect(entity).toBeTruthy()
|
||||
|
||||
await brain.delete(entityId)
|
||||
await brain.remove(entityId)
|
||||
|
||||
const deleted = await brain.get(entityId)
|
||||
expect(deleted).toBeNull()
|
||||
|
|
|
|||
|
|
@ -58,13 +58,11 @@ describe('Metadata Vector Exclusion Fix', () => {
|
|||
// Check _system directory for chunk files
|
||||
const systemDir = join(testDir, '_system')
|
||||
|
||||
if (!existsSync(systemDir)) {
|
||||
// No chunk files created - this is acceptable
|
||||
expect(true).toBe(true)
|
||||
return
|
||||
}
|
||||
|
||||
const chunkFiles = readdirSync(systemDir).filter(f => f.startsWith('__chunk__'))
|
||||
// No _system dir means no chunk files at all — the invariant below
|
||||
// (no vector-dimension chunks) holds trivially on an empty list.
|
||||
const chunkFiles = existsSync(systemDir)
|
||||
? readdirSync(systemDir).filter(f => f.startsWith('__chunk__'))
|
||||
: []
|
||||
|
||||
// Should have at most a few chunk files (name, email, tags)
|
||||
// NOT hundreds of files from vector dimensions
|
||||
|
|
@ -105,12 +103,10 @@ describe('Metadata Vector Exclusion Fix', () => {
|
|||
|
||||
const systemDir = join(testDir, '_system')
|
||||
|
||||
if (!existsSync(systemDir)) {
|
||||
expect(true).toBe(true)
|
||||
return
|
||||
}
|
||||
|
||||
const chunkFiles = readdirSync(systemDir).filter(f => f.startsWith('__chunk__'))
|
||||
// No _system dir means no chunk files — the assertions below hold on [].
|
||||
const chunkFiles = existsSync(systemDir)
|
||||
? readdirSync(systemDir).filter(f => f.startsWith('__chunk__'))
|
||||
: []
|
||||
|
||||
// CRITICAL: Check that NO chunk files have numeric field names
|
||||
// This is the v3.50.2 fix - prevents vectors-as-objects from being indexed
|
||||
|
|
@ -167,12 +163,10 @@ describe('Metadata Vector Exclusion Fix', () => {
|
|||
// Check chunk count - should NOT create 100 chunk files
|
||||
const systemDir = join(testDir, '_system')
|
||||
|
||||
if (!existsSync(systemDir)) {
|
||||
expect(true).toBe(true)
|
||||
return
|
||||
}
|
||||
|
||||
const chunkFiles = readdirSync(systemDir).filter(f => f.startsWith('__chunk__'))
|
||||
// No _system dir means no chunk files — the assertions below hold on [].
|
||||
const chunkFiles = existsSync(systemDir)
|
||||
? readdirSync(systemDir).filter(f => f.startsWith('__chunk__'))
|
||||
: []
|
||||
|
||||
// Should have minimal chunk files (just 'name' field)
|
||||
expect(chunkFiles.length).toBeLessThan(5)
|
||||
|
|
@ -295,12 +289,10 @@ describe('Metadata Vector Exclusion Fix', () => {
|
|||
// Check total chunk files
|
||||
const systemDir = join(testDir, '_system')
|
||||
|
||||
if (!existsSync(systemDir)) {
|
||||
expect(true).toBe(true)
|
||||
return
|
||||
}
|
||||
|
||||
const chunkFiles = readdirSync(systemDir).filter(f => f.startsWith('__chunk__'))
|
||||
// No _system dir means no chunk files — the assertions below hold on [].
|
||||
const chunkFiles = existsSync(systemDir)
|
||||
? readdirSync(systemDir).filter(f => f.startsWith('__chunk__'))
|
||||
: []
|
||||
|
||||
// Should have reasonable number of chunks (not 7,210 for 10 entities!)
|
||||
// Expected: ~30 chunks (name, email, tags fields across 10 entities)
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ describe('Multi-process safety + read-only mode', () => {
|
|||
await expect(reader.add({ data: 'x', type: NounType.Concept })).rejects.toThrow(/read-only/i)
|
||||
await expect(reader.addMany({ items: [] })).rejects.toThrow(/read-only/i)
|
||||
await expect(reader.update({ id: 'x', data: 'y' })).rejects.toThrow(/read-only/i)
|
||||
await expect(reader.delete('x')).rejects.toThrow(/read-only/i)
|
||||
await expect(reader.deleteMany({ ids: ['x'] } as any)).rejects.toThrow(/read-only/i)
|
||||
await expect(reader.remove('x')).rejects.toThrow(/read-only/i)
|
||||
await expect(reader.removeMany({ ids: ['x'] } as any)).rejects.toThrow(/read-only/i)
|
||||
await expect(reader.relate({ from: 'x', to: 'y' } as any)).rejects.toThrow(/read-only/i)
|
||||
await expect(reader.unrelate('x')).rejects.toThrow(/read-only/i)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ describe('Read-After-Write Consistency (v5.7.2 Bug Fix)', () => {
|
|||
expect(typeof relationId).toBe('string')
|
||||
|
||||
// Verify relationship exists and is correct
|
||||
const relations = await brain.getRelations(sourceId)
|
||||
const relations = await brain.related(sourceId)
|
||||
expect(relations).toHaveLength(1)
|
||||
expect(relations[0].to).toBe(targetId)
|
||||
expect(relations[0].type).toBe(VerbType.Contains)
|
||||
|
|
@ -170,7 +170,7 @@ describe('Read-After-Write Consistency (v5.7.2 Bug Fix)', () => {
|
|||
console.log(`Created 100 relationships in ${Date.now() - relStartTime}ms`)
|
||||
|
||||
// Verify all relationships created successfully
|
||||
const relations = await brain.getRelations(documentId)
|
||||
const relations = await brain.related(documentId)
|
||||
expect(relations.length).toBe(100)
|
||||
|
||||
// Verify each entity is still queryable
|
||||
|
|
@ -202,7 +202,7 @@ describe('Read-After-Write Consistency (v5.7.2 Bug Fix)', () => {
|
|||
expect(entity?.metadata?.updated).toBe(true)
|
||||
})
|
||||
|
||||
it('should verify entity is deleted after brain.delete()', async () => {
|
||||
it('should verify entity is deleted after brain.remove()', async () => {
|
||||
// Create entity
|
||||
const entityId = await brain.add({
|
||||
data: 'Entity to be deleted',
|
||||
|
|
@ -214,7 +214,7 @@ describe('Read-After-Write Consistency (v5.7.2 Bug Fix)', () => {
|
|||
expect(beforeDelete).not.toBeNull()
|
||||
|
||||
// Delete
|
||||
await brain.delete(entityId)
|
||||
await brain.remove(entityId)
|
||||
|
||||
// Verify it's deleted (should return null)
|
||||
const afterDelete = await brain.get(entityId)
|
||||
|
|
@ -260,13 +260,13 @@ describe('Read-After-Write Consistency (v5.7.2 Bug Fix)', () => {
|
|||
await brain.relate({ from: concept2Id, to: concept3Id, type: VerbType.RelatedTo })
|
||||
|
||||
// Verify all relationships exist
|
||||
const docRelations = await brain.getRelations(documentId)
|
||||
const docRelations = await brain.related(documentId)
|
||||
expect(docRelations.length).toBe(3)
|
||||
|
||||
const concept1Relations = await brain.getRelations(concept1Id)
|
||||
const concept1Relations = await brain.related(concept1Id)
|
||||
expect(concept1Relations.length).toBeGreaterThan(0)
|
||||
|
||||
const concept2Relations = await brain.getRelations(concept2Id)
|
||||
const concept2Relations = await brain.related(concept2Id)
|
||||
expect(concept2Relations.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
|
|
@ -325,7 +325,7 @@ describe('Read-After-Write Consistency (v5.7.2 Bug Fix)', () => {
|
|||
}
|
||||
|
||||
// Step 5: Verify relationships exist
|
||||
const relations = await brain.getRelations(documentId)
|
||||
const relations = await brain.related(documentId)
|
||||
expect(relations.length).toBe(10)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ describe('Relationship Intelligence', () => {
|
|||
console.log('='.repeat(80))
|
||||
|
||||
// Get all relationships
|
||||
const allRelations = await brain.getRelations()
|
||||
const allRelations = await brain.related()
|
||||
console.log(`\n📊 Total relationships: ${allRelations.length}`)
|
||||
|
||||
// Find relationships involving Arrowhead
|
||||
|
|
@ -104,7 +104,7 @@ describe('Relationship Intelligence', () => {
|
|||
})
|
||||
expect(arrowheadEntity.length).toBe(1)
|
||||
|
||||
const arrowheadRelations = await brain.getRelations({
|
||||
const arrowheadRelations = await brain.related({
|
||||
from: arrowheadEntity[0].id
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ describe('S3 Storage Integration', () => {
|
|||
metadata: {}
|
||||
});
|
||||
|
||||
const deleted = await brain.delete(id);
|
||||
const deleted = await brain.remove(id);
|
||||
expect(deleted).toBe(true);
|
||||
|
||||
const retrieved = await brain.get(id);
|
||||
|
|
|
|||
|
|
@ -359,7 +359,7 @@ projects:
|
|||
expect(monaLisaEntities.length).toBeGreaterThan(0)
|
||||
|
||||
// Get relationships for Mona Lisa
|
||||
const relationships = await brain.getRelations(monaLisaEntities[0].id)
|
||||
const relationships = await brain.related(monaLisaEntities[0].id)
|
||||
expect(relationships.length).toBeGreaterThan(0)
|
||||
|
||||
// Should have CreatedBy relationship (not just RelatedTo)
|
||||
|
|
@ -406,7 +406,7 @@ projects:
|
|||
expect(stanfordEntities.length).toBeGreaterThan(0)
|
||||
|
||||
// Get relationships
|
||||
const relationships = await brain.getRelations(stanfordEntities[0].id)
|
||||
const relationships = await brain.related(stanfordEntities[0].id)
|
||||
expect(relationships.length).toBeGreaterThan(0)
|
||||
|
||||
// Should have LocatedAt relationship
|
||||
|
|
@ -453,7 +453,7 @@ projects:
|
|||
expect(heartEntities.length).toBeGreaterThan(0)
|
||||
|
||||
// Get relationships
|
||||
const relationships = await brain.getRelations(heartEntities[0].id)
|
||||
const relationships = await brain.related(heartEntities[0].id)
|
||||
expect(relationships.length).toBeGreaterThan(0)
|
||||
|
||||
// Should have PartOf relationship
|
||||
|
|
@ -500,7 +500,7 @@ projects:
|
|||
expect(aliceEntities.length).toBeGreaterThan(0)
|
||||
|
||||
// Get relationships
|
||||
const relationships = await brain.getRelations(aliceEntities[0].id)
|
||||
const relationships = await brain.related(aliceEntities[0].id)
|
||||
expect(relationships.length).toBeGreaterThan(0)
|
||||
|
||||
// Should have WorksWith or MemberOf relationship (not just RelatedTo)
|
||||
|
|
@ -548,7 +548,7 @@ projects:
|
|||
expect(iphoneEntities.length).toBeGreaterThan(0)
|
||||
|
||||
// Get relationships
|
||||
const relationships = await brain.getRelations(iphoneEntities[0].id)
|
||||
const relationships = await brain.related(iphoneEntities[0].id)
|
||||
expect(relationships.length).toBeGreaterThan(0)
|
||||
|
||||
// Should have CreatedBy relationship with high confidence
|
||||
|
|
@ -593,7 +593,7 @@ project:
|
|||
expect(projectEntities.length).toBeGreaterThan(0)
|
||||
|
||||
// Get relationships
|
||||
const relationships = await brain.getRelations(projectEntities[0].id)
|
||||
const relationships = await brain.related(projectEntities[0].id)
|
||||
expect(relationships.length).toBeGreaterThan(0)
|
||||
|
||||
// Should have Contains relationship (hierarchical)
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ describe('Subtype + Facets (7.29.0)', () => {
|
|||
const id = await brain.add({ data: 'g', type: NounType.Person, subtype: 'employee' })
|
||||
expect(brain.counts.bySubtype(NounType.Person, 'employee')).toBe(3)
|
||||
|
||||
await brain.delete(id)
|
||||
await brain.remove(id)
|
||||
expect(brain.counts.bySubtype(NounType.Person, 'employee')).toBe(2)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* opt-in enforcement primitives:
|
||||
*
|
||||
* - Layer V1: `subtype` on `relate()` / `Relation` / `RelateParams` round-trips
|
||||
* through `getRelations({ subtype })`, the new `updateRelation()` method, and
|
||||
* through `related({ subtype })`, the new `updateRelation()` method, and
|
||||
* the persisted verb-subtype rollup.
|
||||
* - Layer V2: `brain.counts.byRelationshipSubtype` / `topRelationshipSubtypes`
|
||||
* / `brain.relationshipSubtypesOf` mirror the noun-side counts API.
|
||||
|
|
@ -33,7 +33,7 @@ describe('Verb subtype + enforcement (7.30.0)', () => {
|
|||
})
|
||||
|
||||
describe('Layer V1: subtype on relationships', () => {
|
||||
it('persists subtype on relate() and round-trips through getRelations()', async () => {
|
||||
it('persists subtype on relate() and round-trips through related()', async () => {
|
||||
const fromId = await brain.add({ data: 'Avery', type: NounType.Person })
|
||||
const toId = await brain.add({ data: 'Jordan', type: NounType.Person })
|
||||
|
||||
|
|
@ -44,14 +44,14 @@ describe('Verb subtype + enforcement (7.30.0)', () => {
|
|||
subtype: 'direct'
|
||||
})
|
||||
|
||||
const all = await brain.getRelations({ from: fromId })
|
||||
const all = await brain.related({ from: fromId })
|
||||
const found = all.find(r => r.id === relId)
|
||||
expect(found).toBeDefined()
|
||||
expect(found!.subtype).toBe('direct')
|
||||
expect(found!.type).toBe(VerbType.ReportsTo)
|
||||
})
|
||||
|
||||
it('getRelations({ subtype }) filters by single value', async () => {
|
||||
it('related({ subtype }) filters by single value', async () => {
|
||||
const a = await brain.add({ data: 'A', type: NounType.Person })
|
||||
const b = await brain.add({ data: 'B', type: NounType.Person })
|
||||
const c = await brain.add({ data: 'C', type: NounType.Person })
|
||||
|
|
@ -59,13 +59,13 @@ describe('Verb subtype + enforcement (7.30.0)', () => {
|
|||
await brain.relate({ from: a, to: b, type: VerbType.ReportsTo, subtype: 'direct' })
|
||||
await brain.relate({ from: a, to: c, type: VerbType.ReportsTo, subtype: 'dotted-line' })
|
||||
|
||||
const direct = await brain.getRelations({ from: a, subtype: 'direct' })
|
||||
const direct = await brain.related({ from: a, subtype: 'direct' })
|
||||
expect(direct).toHaveLength(1)
|
||||
expect(direct[0].to).toBe(b)
|
||||
expect(direct[0].subtype).toBe('direct')
|
||||
})
|
||||
|
||||
it('getRelations({ subtype: [...] }) filters by set membership', async () => {
|
||||
it('related({ subtype: [...] }) filters by set membership', async () => {
|
||||
const a = await brain.add({ data: 'A', type: NounType.Person })
|
||||
const b = await brain.add({ data: 'B', type: NounType.Person })
|
||||
const c = await brain.add({ data: 'C', type: NounType.Person })
|
||||
|
|
@ -75,7 +75,7 @@ describe('Verb subtype + enforcement (7.30.0)', () => {
|
|||
await brain.relate({ from: a, to: c, type: VerbType.ReportsTo, subtype: 'dotted-line' })
|
||||
await brain.relate({ from: a, to: d, type: VerbType.ReportsTo, subtype: 'matrix' })
|
||||
|
||||
const both = await brain.getRelations({
|
||||
const both = await brain.related({
|
||||
from: a,
|
||||
type: VerbType.ReportsTo,
|
||||
subtype: ['direct', 'dotted-line']
|
||||
|
|
@ -96,7 +96,7 @@ describe('Verb subtype + enforcement (7.30.0)', () => {
|
|||
|
||||
await brain.updateRelation({ id: relId, subtype: 'dotted-line' })
|
||||
|
||||
const after = await brain.getRelations({ from: a, type: VerbType.ReportsTo })
|
||||
const after = await brain.related({ from: a, type: VerbType.ReportsTo })
|
||||
const found = after.find(r => r.id === relId)
|
||||
expect(found!.subtype).toBe('dotted-line')
|
||||
})
|
||||
|
|
@ -113,7 +113,7 @@ describe('Verb subtype + enforcement (7.30.0)', () => {
|
|||
|
||||
await brain.updateRelation({ id: relId, weight: 0.5 })
|
||||
|
||||
const after = await brain.getRelations({ from: a, type: VerbType.ReportsTo })
|
||||
const after = await brain.related({ from: a, type: VerbType.ReportsTo })
|
||||
const found = after.find(r => r.id === relId)
|
||||
expect(found!.subtype).toBe('direct')
|
||||
expect(found!.weight).toBe(0.5)
|
||||
|
|
@ -253,7 +253,7 @@ describe('Verb subtype + enforcement (7.30.0)', () => {
|
|||
expect(result.errors).toEqual([])
|
||||
expect(result.migrated).toBe(1)
|
||||
|
||||
const after = await brain.getRelations({ from: a })
|
||||
const after = await brain.related({ from: a })
|
||||
const found = after.find(r => r.id === relId)
|
||||
expect(found!.subtype).toBe('spouse')
|
||||
expect((found!.metadata as any).kind).toBeUndefined()
|
||||
|
|
@ -289,7 +289,7 @@ describe('Verb subtype + enforcement (7.30.0)', () => {
|
|||
expect(ea!.subtype).toBe('employee')
|
||||
const eb = await brain.get(b)
|
||||
expect(eb!.subtype).toBe('employee')
|
||||
const rels = await brain.getRelations({ from: a })
|
||||
const rels = await brain.related({ from: a })
|
||||
expect(rels[0].subtype).toBe('colleague')
|
||||
})
|
||||
|
||||
|
|
@ -311,7 +311,7 @@ describe('Verb subtype + enforcement (7.30.0)', () => {
|
|||
})
|
||||
expect(result.migrated).toBe(1)
|
||||
|
||||
const rels = await brain.getRelations({ from: a })
|
||||
const rels = await brain.related({ from: a })
|
||||
const found = rels.find(r => r.id === relId)
|
||||
expect(found!.subtype).toBe('sibling')
|
||||
expect((found!.metadata as any).kind).toBe('sibling')
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ describe('VFS API Wiring Verification', () => {
|
|||
console.log(` Created relation: ${relationId}`)
|
||||
|
||||
// Verify relationship exists
|
||||
const relations = await brain.getRelations({
|
||||
const relations = await brain.related({
|
||||
from: conceptId,
|
||||
to: vfsFile[0].id
|
||||
})
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ describe('VFS-Knowledge Separation (Option 3C)', () => {
|
|||
console.log(` To: ${vfsFile[0].metadata?.path} (VFS file)`)
|
||||
|
||||
// Verify relationship exists
|
||||
const relations = await brain.getRelations({
|
||||
const relations = await brain.related({
|
||||
from: knowledgeEntity[0].id,
|
||||
to: vfsFile[0].id
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
/**
|
||||
* Debug: Check metadata index for isVFS field
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
||||
import { Brainy } from '../../src/brainy.js'
|
||||
import { NounType } from '../../src/types/graphTypes.js'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
describe('Metadata Index Debug', () => {
|
||||
const testDir = path.join(process.cwd(), 'test-metadata-index-debug')
|
||||
let brain: Brainy
|
||||
|
||||
beforeAll(async () => {
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true })
|
||||
}
|
||||
fs.mkdirSync(testDir, { recursive: true })
|
||||
|
||||
brain = new Brainy({ requireSubtype: false,
|
||||
storage: {
|
||||
type: 'filesystem',
|
||||
options: { path: testDir }
|
||||
}
|
||||
})
|
||||
await brain.init()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('should index custom metadata fields', async () => {
|
||||
// Add entity with custom metadata field
|
||||
const entity1 = await brain.add({
|
||||
data: 'Test entity with custom field',
|
||||
type: NounType.Document,
|
||||
metadata: {
|
||||
customFlag: true,
|
||||
customString: 'hello',
|
||||
customNumber: 42
|
||||
}
|
||||
})
|
||||
|
||||
console.log('\n📋 Added entity:', entity1.id)
|
||||
console.log(' Metadata:', entity1.metadata)
|
||||
|
||||
// Try to find by custom fields
|
||||
console.log('\n📋 Query 1: where: { customFlag: true }')
|
||||
const result1 = await brain.find({
|
||||
where: { customFlag: true },
|
||||
limit: 10
|
||||
})
|
||||
console.log(` Results: ${result1.length}`)
|
||||
if (result1.length > 0) {
|
||||
console.log(` Found: ${result1[0].id}`)
|
||||
console.log(` Metadata.customFlag: ${result1[0].metadata?.customFlag}`)
|
||||
}
|
||||
|
||||
console.log('\n📋 Query 2: where: { customString: "hello" }')
|
||||
const result2 = await brain.find({
|
||||
where: { customString: 'hello' },
|
||||
limit: 10
|
||||
})
|
||||
console.log(` Results: ${result2.length}`)
|
||||
|
||||
console.log('\n📋 Query 3: where: { customNumber: 42 }')
|
||||
const result3 = await brain.find({
|
||||
where: { customNumber: 42 },
|
||||
limit: 10
|
||||
})
|
||||
console.log(` Results: ${result3.length}`)
|
||||
|
||||
// Check what fields are indexed
|
||||
console.log('\n📋 Indexed fields:')
|
||||
const fields = await brain.getFilterFields()
|
||||
console.log(` ${fields.join(', ')}`)
|
||||
|
||||
expect(result1.length).toBeGreaterThan(0)
|
||||
expect(result2.length).toBeGreaterThan(0)
|
||||
expect(result3.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
/**
|
||||
* Simple VFS filtering test
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
||||
import { Brainy } from '../../src/brainy.js'
|
||||
import { NounType } from '../../src/types/graphTypes.js'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
describe('Simple VFS Filter Test', () => {
|
||||
const testDir = path.join(process.cwd(), 'test-simple-vfs-filter')
|
||||
let brain: Brainy
|
||||
|
||||
beforeAll(async () => {
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true })
|
||||
}
|
||||
fs.mkdirSync(testDir, { recursive: true })
|
||||
|
||||
brain = new Brainy({ requireSubtype: false,
|
||||
storage: {
|
||||
type: 'filesystem',
|
||||
options: { path: testDir }
|
||||
}
|
||||
})
|
||||
await brain.init()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('should demonstrate VFS filtering', async () => {
|
||||
console.log('\n=== TEST START ===\n')
|
||||
|
||||
// 1. Create knowledge entity
|
||||
console.log('1. Creating knowledge entity...')
|
||||
const knowledgeId = await brain.add({
|
||||
data: 'Knowledge document',
|
||||
type: NounType.Document,
|
||||
metadata: { title: 'Knowledge' }
|
||||
})
|
||||
console.log(` Created: ${knowledgeId}`)
|
||||
|
||||
// 2. Create VFS file
|
||||
console.log('\n2. Creating VFS file...')
|
||||
const vfs = brain.vfs
|
||||
await vfs.init()
|
||||
await vfs.writeFile('/test.txt', 'VFS file')
|
||||
console.log(' VFS file created')
|
||||
|
||||
// 3. Query all (should exclude VFS)
|
||||
console.log('\n3. Query: brain.find({}) [excludes VFS by default]')
|
||||
const all = await brain.find({ limit: 100 })
|
||||
console.log(` Results: ${all.length}`)
|
||||
for (const r of all) {
|
||||
console.log(` - ${r.id}, type: ${r.type}, isVFS: ${r.metadata?.isVFS}, title: ${r.metadata?.title}`)
|
||||
}
|
||||
|
||||
// 4. Query all with VFS
|
||||
console.log('\n4. Query: brain.find({ includeVFS: true })')
|
||||
const allWithVFS = await brain.find({ includeVFS: true, limit: 100 })
|
||||
console.log(` Results: ${allWithVFS.length}`)
|
||||
for (const r of allWithVFS) {
|
||||
console.log(` - ${r.id}, type: ${r.type}, isVFS: ${r.metadata?.isVFS}, path: ${r.metadata?.path || 'n/a'}`)
|
||||
}
|
||||
|
||||
// 5. Query by type (should exclude VFS)
|
||||
console.log('\n5. Query: brain.find({ type: NounType.Document }) [excludes VFS]')
|
||||
const docs = await brain.find({ type: NounType.Document, limit: 100 })
|
||||
console.log(` Results: ${docs.length}`)
|
||||
for (const r of docs) {
|
||||
console.log(` - ${r.id}, type: ${r.type}, isVFS: ${r.metadata?.isVFS}`)
|
||||
}
|
||||
|
||||
// 6. Query by type with VFS
|
||||
console.log('\n6. Query: brain.find({ type: NounType.Document, includeVFS: true })' )
|
||||
const docsWithVFS = await brain.find({ type: NounType.Document, includeVFS: true, limit: 100 })
|
||||
console.log(` Results: ${docsWithVFS.length}`)
|
||||
for (const r of docsWithVFS) {
|
||||
console.log(` - ${r.id}, type: ${r.type}, isVFS: ${r.metadata?.isVFS}, path: ${r.metadata?.path || 'n/a'}`)
|
||||
}
|
||||
|
||||
console.log('\n=== TEST END ===\n')
|
||||
|
||||
// Assertions
|
||||
expect(all.length).toBe(1) // Only knowledge entity
|
||||
expect(all.some(r => r.id === knowledgeId)).toBe(true)
|
||||
|
||||
expect(allWithVFS.length).toBeGreaterThanOrEqual(2) // Knowledge + VFS entities
|
||||
|
||||
expect(docs.length).toBe(1) // Only knowledge entity
|
||||
expect(docs.some(r => r.id === knowledgeId)).toBe(true)
|
||||
|
||||
expect(docsWithVFS.length).toBeGreaterThanOrEqual(2) // Knowledge + VFS file (both documents)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
/**
|
||||
* Manual test to reproduce a consumer's type filtering issue
|
||||
*
|
||||
* This test verifies that brain.find({ type: NounType.Person }) actually works
|
||||
* as documented in the API.
|
||||
*/
|
||||
|
||||
import { Brainy, NounType } from '../../src/index.js'
|
||||
|
||||
async function testTypeFiltering() {
|
||||
console.log('\n🔬 Testing Type Filtering Issue from A Consumer Team\n')
|
||||
console.log('=' .repeat(60))
|
||||
|
||||
// Create in-memory instance for testing
|
||||
const brain = new Brainy({ requireSubtype: false,
|
||||
storage: { type: 'memory' }
|
||||
})
|
||||
await brain.init()
|
||||
|
||||
console.log('\n1️⃣ Adding test entities with different types...\n')
|
||||
|
||||
// Add 5 person entities
|
||||
const people = []
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const id = await brain.add({
|
||||
data: `Person ${i + 1}`,
|
||||
type: NounType.Person,
|
||||
metadata: { name: `Person ${i + 1}` }
|
||||
})
|
||||
people.push(id)
|
||||
}
|
||||
console.log(`✅ Added ${people.length} person entities`)
|
||||
|
||||
// Add 3 location entities
|
||||
const locations = []
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const id = await brain.add({
|
||||
data: `Location ${i + 1}`,
|
||||
type: NounType.Location,
|
||||
metadata: { name: `Location ${i + 1}` }
|
||||
})
|
||||
locations.push(id)
|
||||
}
|
||||
console.log(`✅ Added ${locations.length} location entities`)
|
||||
|
||||
// Add 2 concept entities
|
||||
const concepts = []
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const id = await brain.add({
|
||||
data: `Concept ${i + 1}`,
|
||||
type: NounType.Concept,
|
||||
metadata: { name: `Concept ${i + 1}` }
|
||||
})
|
||||
concepts.push(id)
|
||||
}
|
||||
console.log(`✅ Added ${concepts.length} concept entities`)
|
||||
|
||||
console.log(`\n📊 Total: ${people.length + locations.length + concepts.length} entities`)
|
||||
|
||||
console.log('\n' + '='.repeat(60))
|
||||
console.log('\n2️⃣ Testing find() without type filter...\n')
|
||||
|
||||
const allResults = await brain.find({ limit: 100 })
|
||||
console.log(`✅ brain.find({}) returned ${allResults.length} entities`)
|
||||
|
||||
// Check types in results
|
||||
const typeCounts = allResults.reduce((acc, r) => {
|
||||
acc[r.type || 'unknown'] = (acc[r.type || 'unknown'] || 0) + 1
|
||||
return acc
|
||||
}, {} as Record<string, number>)
|
||||
|
||||
console.log('\nTypes in results:')
|
||||
for (const [type, count] of Object.entries(typeCounts)) {
|
||||
console.log(` - ${type}: ${count}`)
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(60))
|
||||
console.log('\n3️⃣ Testing find() WITH type filter (using enum)...\n')
|
||||
|
||||
// Test 1: Filter by NounType.Person (enum value)
|
||||
console.log('Test 1: brain.find({ type: NounType.Person })')
|
||||
const personResults = await brain.find({ type: NounType.Person, limit: 100 })
|
||||
console.log(` Result: ${personResults.length} entities`)
|
||||
console.log(` Expected: ${people.length} entities`)
|
||||
if (personResults.length === people.length) {
|
||||
console.log(' ✅ PASS')
|
||||
} else {
|
||||
console.log(' ❌ FAIL - Type filtering not working!')
|
||||
}
|
||||
|
||||
// Test 2: Filter by NounType.Location
|
||||
console.log('\nTest 2: brain.find({ type: NounType.Location })')
|
||||
const locationResults = await brain.find({ type: NounType.Location, limit: 100 })
|
||||
console.log(` Result: ${locationResults.length} entities`)
|
||||
console.log(` Expected: ${locations.length} entities`)
|
||||
if (locationResults.length === locations.length) {
|
||||
console.log(' ✅ PASS')
|
||||
} else {
|
||||
console.log(' ❌ FAIL - Type filtering not working!')
|
||||
}
|
||||
|
||||
// Test 3: Filter by NounType.Concept
|
||||
console.log('\nTest 3: brain.find({ type: NounType.Concept })')
|
||||
const conceptResults = await brain.find({ type: NounType.Concept, limit: 100 })
|
||||
console.log(` Result: ${conceptResults.length} entities`)
|
||||
console.log(` Expected: ${concepts.length} entities`)
|
||||
if (conceptResults.length === concepts.length) {
|
||||
console.log(' ✅ PASS')
|
||||
} else {
|
||||
console.log(' ❌ FAIL - Type filtering not working!')
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(60))
|
||||
console.log('\n4️⃣ Testing find() WITH type filter (using string)...\n')
|
||||
|
||||
// Test 4: Filter by string 'person'
|
||||
console.log('Test 4: brain.find({ type: "person" })')
|
||||
const personResults2 = await brain.find({ type: 'person' as any, limit: 100 })
|
||||
console.log(` Result: ${personResults2.length} entities`)
|
||||
console.log(` Expected: ${people.length} entities`)
|
||||
if (personResults2.length === people.length) {
|
||||
console.log(' ✅ PASS')
|
||||
} else {
|
||||
console.log(' ❌ FAIL - String type filtering not working!')
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(60))
|
||||
console.log('\n5️⃣ Checking entity storage (metadata.noun)...\n')
|
||||
|
||||
// Get a person entity and check its storage
|
||||
const personEntity = await brain.get(people[0])
|
||||
console.log('Person entity structure:')
|
||||
console.log(' - id:', personEntity?.id)
|
||||
console.log(' - type:', personEntity?.type)
|
||||
console.log(' - metadata.noun:', (personEntity as any)?.metadata?.noun)
|
||||
console.log(' - metadata.name:', personEntity?.metadata?.name)
|
||||
|
||||
console.log('\n' + '='.repeat(60))
|
||||
console.log('\n📋 Summary\n')
|
||||
|
||||
const tests = [
|
||||
{ name: 'Filter by NounType.Person', passed: personResults.length === people.length },
|
||||
{ name: 'Filter by NounType.Location', passed: locationResults.length === locations.length },
|
||||
{ name: 'Filter by NounType.Concept', passed: conceptResults.length === concepts.length },
|
||||
{ name: 'Filter by string "person"', passed: personResults2.length === people.length }
|
||||
]
|
||||
|
||||
const passedCount = tests.filter(t => t.passed).length
|
||||
const totalCount = tests.length
|
||||
|
||||
console.log(`Tests passed: ${passedCount}/${totalCount}\n`)
|
||||
|
||||
for (const test of tests) {
|
||||
console.log(`${test.passed ? '✅' : '❌'} ${test.name}`)
|
||||
}
|
||||
|
||||
if (passedCount === totalCount) {
|
||||
console.log('\n🎉 All tests passed! Type filtering works correctly.')
|
||||
console.log('\n💡 The a consumer team might be experiencing a different issue.')
|
||||
console.log(' Check: storage persistence, Brainy instance reuse, or data migration')
|
||||
} else {
|
||||
console.log('\n❌ Type filtering is BROKEN in Brainy!')
|
||||
console.log('\n🐛 This is a bug that needs to be fixed.')
|
||||
console.log(' a consumer team was right - it\'s not user error.')
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(60) + '\n')
|
||||
}
|
||||
|
||||
// Run the test
|
||||
testTypeFiltering().catch(console.error)
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
/**
|
||||
* Diagnostic: Check if isVFS flag is actually being stored
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
||||
import { Brainy } from '../../src/brainy.js'
|
||||
import { NounType } from '../../src/types/graphTypes.js'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
describe('isVFS Flag Diagnostic', () => {
|
||||
const testDir = path.join(process.cwd(), 'test-isvfs-diagnostic')
|
||||
let brain: Brainy
|
||||
|
||||
beforeAll(async () => {
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true })
|
||||
}
|
||||
fs.mkdirSync(testDir, { recursive: true })
|
||||
|
||||
brain = new Brainy({ requireSubtype: false,
|
||||
storage: {
|
||||
type: 'filesystem',
|
||||
options: { path: testDir }
|
||||
}
|
||||
})
|
||||
await brain.init()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('should show what entities are created', async () => {
|
||||
// Create VFS file
|
||||
const vfs = brain.vfs
|
||||
await vfs.init()
|
||||
await vfs.writeFile('/test.txt', 'Hello World')
|
||||
|
||||
// Get ALL entities
|
||||
console.log('\n📋 All entities in database:')
|
||||
const allEntities = await brain.find({ limit: 100, includeVFS: true })
|
||||
console.log(` Total: ${allEntities.length}`)
|
||||
|
||||
for (const entity of allEntities) {
|
||||
console.log(`\n Entity: ${entity.id}`)
|
||||
console.log(` Type: ${entity.type}`)
|
||||
console.log(` Metadata keys: ${Object.keys(entity.metadata || {}).join(', ')}`)
|
||||
if (entity.metadata?.path) {
|
||||
console.log(` Path: ${entity.metadata.path}`)
|
||||
}
|
||||
if (entity.metadata?.vfsType) {
|
||||
console.log(` VfsType: ${entity.metadata.vfsType}`)
|
||||
}
|
||||
if (entity.metadata?.isVFS !== undefined) {
|
||||
console.log(` isVFS: ${entity.metadata.isVFS}`)
|
||||
}
|
||||
if (entity.metadata?.name) {
|
||||
console.log(` Name: ${entity.metadata.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Try different queries
|
||||
console.log('\n\n📋 Query 1: brain.find({ limit: 100 }) [default, no includeVFS]')
|
||||
const query1 = await brain.find({ limit: 100 })
|
||||
console.log(` Results: ${query1.length}`)
|
||||
|
||||
console.log('\n📋 Query 2: brain.find({ limit: 100, includeVFS: true })')
|
||||
const query2 = await brain.find({ limit: 100, includeVFS: true })
|
||||
console.log(` Results: ${query2.length}`)
|
||||
|
||||
console.log('\n📋 Query 3: brain.find({ where: { isVFS: true } })')
|
||||
const query3 = await brain.find({ where: { isVFS: true }, limit: 100 })
|
||||
console.log(` Results: ${query3.length}`)
|
||||
|
||||
console.log('\n📋 Query 4: brain.find({ where: { path: "/test.txt" } })')
|
||||
const query4 = await brain.find({ where: { path: '/test.txt' }, limit: 100 })
|
||||
console.log(` Results: ${query4.length}`)
|
||||
|
||||
console.log('\n📋 Query 5: brain.find({ type: NounType.Document })')
|
||||
const query5 = await brain.find({ type: NounType.Document, limit: 100 })
|
||||
console.log(` Results: ${query5.length}`)
|
||||
|
||||
console.log('\n📋 Query 6: brain.find({ type: NounType.Document, includeVFS: true })')
|
||||
const query6 = await brain.find({ type: NounType.Document, includeVFS: true, limit: 100 })
|
||||
console.log(` Results: ${query6.length}`)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,207 +0,0 @@
|
|||
/**
|
||||
* VFS Multiple Init() Diagnostic Test
|
||||
*
|
||||
* Tests a consumer's issue: Does calling vfs.init() multiple times
|
||||
* create duplicate root entities?
|
||||
*
|
||||
* Scenario:
|
||||
* - Create brain instance
|
||||
* - Call vfs.init() multiple times (simulating multiple requests)
|
||||
* - Check if multiple root entities are created
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
||||
import { Brainy } from '../../src/brainy.js'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
describe('VFS Multiple Init Diagnostic', () => {
|
||||
const testDir = path.join(process.cwd(), 'test-vfs-multi-init')
|
||||
let brain: Brainy
|
||||
|
||||
beforeAll(async () => {
|
||||
// Clean up test directory
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true })
|
||||
}
|
||||
fs.mkdirSync(testDir, { recursive: true })
|
||||
|
||||
// Create brain with filesystem storage
|
||||
brain = new Brainy({ requireSubtype: false,
|
||||
storage: {
|
||||
type: 'filesystem',
|
||||
path: testDir
|
||||
}
|
||||
})
|
||||
await brain.init()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
// Clean up
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('should not create duplicate roots when calling vfs.init() multiple times on SAME instance', async () => {
|
||||
const vfs = brain.vfs
|
||||
|
||||
// Call init multiple times
|
||||
await vfs.init()
|
||||
await vfs.init()
|
||||
await vfs.init()
|
||||
|
||||
// Query for root entities using workaround (where clause is broken)
|
||||
const collections = await brain.find({
|
||||
type: 'collection',
|
||||
limit: 100
|
||||
})
|
||||
|
||||
const roots = collections.filter(e =>
|
||||
e.metadata?.path === '/' &&
|
||||
e.metadata?.vfsType === 'directory'
|
||||
)
|
||||
|
||||
console.log(`\n✅ Test 1: Same VFS instance`)
|
||||
console.log(` Total collections: ${collections.length}`)
|
||||
console.log(` Roots found: ${roots.length}`)
|
||||
roots.forEach((root, i) => {
|
||||
console.log(` Root ${i + 1}: ${root.id}`)
|
||||
})
|
||||
|
||||
expect(roots.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should not create duplicate roots when creating MULTIPLE VFS instances (the reported scenario)', async () => {
|
||||
// Simulate the reported scenario: Getting VFS on multiple requests
|
||||
// brain.vfs returns cached instance, so this should be safe
|
||||
const vfs1 = brain.vfs
|
||||
const vfs2 = brain.vfs
|
||||
const vfs3 = brain.vfs
|
||||
|
||||
await vfs1.init()
|
||||
await vfs2.init()
|
||||
await vfs3.init()
|
||||
|
||||
// Query for root entities using workaround
|
||||
const collections = await brain.find({
|
||||
type: 'collection',
|
||||
limit: 100
|
||||
})
|
||||
|
||||
const roots = collections.filter(e =>
|
||||
e.metadata?.path === '/' &&
|
||||
e.metadata?.vfsType === 'directory'
|
||||
)
|
||||
|
||||
console.log(`\n✅ Test 2: Multiple VFS references (cached)`)
|
||||
console.log(` VFS instances are same? ${vfs1 === vfs2 && vfs2 === vfs3}`)
|
||||
console.log(` Roots found: ${roots.length}`)
|
||||
roots.forEach((root, i) => {
|
||||
console.log(` Root ${i + 1}: ${root.id}`)
|
||||
})
|
||||
|
||||
expect(vfs1).toBe(vfs2) // Should be same instance (cached)
|
||||
expect(roots.length).toBe(1) // Should still be 1 root
|
||||
})
|
||||
|
||||
it('should handle NEW brain instances gracefully (per-user scenario)', async () => {
|
||||
// Simulate creating separate brain instances per user
|
||||
// This is closer to a consumer's getUserBrainy() pattern
|
||||
const brain2 = new Brainy({ requireSubtype: false,
|
||||
storage: {
|
||||
type: 'filesystem',
|
||||
path: testDir // Same storage!
|
||||
}
|
||||
})
|
||||
await brain2.init()
|
||||
|
||||
const vfs2 = brain2.vfs()
|
||||
await vfs2.init()
|
||||
|
||||
// Query for roots using workaround
|
||||
const collections = await brain.find({
|
||||
type: 'collection',
|
||||
limit: 100
|
||||
})
|
||||
|
||||
const roots = collections.filter(e =>
|
||||
e.metadata?.path === '/' &&
|
||||
e.metadata?.vfsType === 'directory'
|
||||
)
|
||||
|
||||
console.log(`\n✅ Test 3: New Brainy instance (same storage)`)
|
||||
console.log(` Roots found: ${roots.length}`)
|
||||
roots.forEach((root, i) => {
|
||||
console.log(` Root ${i + 1}: ${root.id} (created: ${root.metadata?.createdAt})`)
|
||||
})
|
||||
|
||||
// This is the CRITICAL test - should find existing root, not create new one
|
||||
expect(roots.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should verify readdir works after multiple inits', async () => {
|
||||
// Create a test directory
|
||||
const vfs = brain.vfs
|
||||
await vfs.mkdir('/test-dir', { recursive: true })
|
||||
await vfs.writeFile('/test-dir/test.txt', 'Hello')
|
||||
|
||||
// Call init again (simulating new request)
|
||||
const brain3 = new Brainy({ requireSubtype: false,
|
||||
storage: {
|
||||
type: 'filesystem',
|
||||
path: testDir
|
||||
}
|
||||
})
|
||||
await brain3.init()
|
||||
const vfs3 = brain3.vfs()
|
||||
await vfs3.init()
|
||||
|
||||
// Try to read root directory
|
||||
const entries = await vfs3.readdir('/', { withFileTypes: true })
|
||||
|
||||
console.log(`\n✅ Test 4: readdir after new brain instance`)
|
||||
console.log(` Entries found: ${entries.length}`)
|
||||
entries.forEach((entry: any) => {
|
||||
console.log(` - ${entry.name} (${entry.type})`)
|
||||
})
|
||||
|
||||
expect(entries.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should show if Contains relationships are created', async () => {
|
||||
const vfs = brain.vfs
|
||||
|
||||
// Get root entity ID using workaround
|
||||
const collections = await brain.find({
|
||||
type: 'collection',
|
||||
limit: 100
|
||||
})
|
||||
|
||||
const roots = collections.filter(e =>
|
||||
e.metadata?.path === '/' &&
|
||||
e.metadata?.vfsType === 'directory'
|
||||
)
|
||||
|
||||
expect(roots.length).toBeGreaterThanOrEqual(1)
|
||||
const rootId = roots[0].id
|
||||
|
||||
// Check for Contains relationships FROM root
|
||||
const relations = await brain.getRelations({
|
||||
from: rootId
|
||||
})
|
||||
|
||||
console.log(`\n✅ Test 5: Contains relationships`)
|
||||
console.log(` Root ID: ${rootId}`)
|
||||
console.log(` Relationships from root: ${relations.length}`)
|
||||
relations.forEach((rel) => {
|
||||
console.log(` - ${rel.from} -> ${rel.to} (${rel.type})`)
|
||||
})
|
||||
|
||||
// Root should have at least one Contains relationship (to /test-dir)
|
||||
const containsRels = relations.filter(r => r.type === 'contains')
|
||||
console.log(` Contains relationships: ${containsRels.length}`)
|
||||
|
||||
expect(containsRels.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
/**
|
||||
* Debug VFS search to understand what's being returned
|
||||
*/
|
||||
|
||||
import { describe, it, beforeAll, afterAll } from 'vitest'
|
||||
import { Brainy } from '../../src/brainy.js'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
describe('VFS Search Debug', () => {
|
||||
const testDir = path.join(process.cwd(), 'test-vfs-search-debug')
|
||||
let brain: Brainy
|
||||
|
||||
beforeAll(async () => {
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true })
|
||||
}
|
||||
fs.mkdirSync(testDir, { recursive: true })
|
||||
|
||||
brain = new Brainy({ requireSubtype: false,
|
||||
storage: {
|
||||
type: 'filesystem',
|
||||
options: { path: testDir }
|
||||
}
|
||||
})
|
||||
await brain.init()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('should debug vfs.search() results', async () => {
|
||||
const vfs = brain.vfs
|
||||
await vfs.init()
|
||||
await vfs.writeFile('/test.txt', 'Hello world test content')
|
||||
|
||||
console.log('\n📋 VFS Search Debug')
|
||||
|
||||
// Search
|
||||
const results = await vfs.search('test', { limit: 10 })
|
||||
|
||||
console.log(`Results: ${results.length}`)
|
||||
console.log('Result structure:', JSON.stringify(results[0], null, 2))
|
||||
|
||||
// Also check raw brain.find
|
||||
const brainResults = await brain.find({
|
||||
where: { vfsType: 'file' },
|
||||
includeVFS: true,
|
||||
limit: 10
|
||||
})
|
||||
|
||||
console.log(`\nDirect brain.find results: ${brainResults.length}`)
|
||||
if (brainResults.length > 0) {
|
||||
console.log('Brain result entity:', JSON.stringify({
|
||||
id: brainResults[0].id,
|
||||
type: brainResults[0].type,
|
||||
metadata: brainResults[0].metadata
|
||||
}, null, 2))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
/**
|
||||
* VFS Where Clause Diagnostic
|
||||
*
|
||||
* The real issue: brain.find({ where: { 'metadata.path': '/' } }) returns 0 results
|
||||
* even though the root entity exists!
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
||||
import { Brainy } from '../../src/brainy.js'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
describe('VFS Where Clause Diagnostic', () => {
|
||||
const testDir = path.join(process.cwd(), 'test-where-clause')
|
||||
let brain: Brainy
|
||||
|
||||
beforeAll(async () => {
|
||||
// Clean up
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true })
|
||||
}
|
||||
fs.mkdirSync(testDir, { recursive: true })
|
||||
|
||||
brain = new Brainy({ requireSubtype: false,
|
||||
storage: {
|
||||
type: 'filesystem',
|
||||
path: testDir
|
||||
}
|
||||
})
|
||||
await brain.init()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('should find root entity using where clause', async () => {
|
||||
const vfs = brain.vfs
|
||||
await vfs.init()
|
||||
|
||||
// Method 1: Where clause (FIXED in v4.3.3!)
|
||||
console.log('\n📋 Method 1: Where clause (CORRECT field names)')
|
||||
const whereResult = await brain.find({
|
||||
where: {
|
||||
path: '/', // ✅ Fixed: Use flat field name
|
||||
vfsType: 'directory' // ✅ Fixed: Use flat field name
|
||||
},
|
||||
limit: 10
|
||||
})
|
||||
console.log(` Found ${whereResult.length} entities`)
|
||||
|
||||
// Method 2: Find all and filter (what actually works)
|
||||
console.log('\n📋 Method 2: Find all + filter')
|
||||
const allEntities = await brain.find({ limit: 100 })
|
||||
console.log(` Total entities: ${allEntities.length}`)
|
||||
|
||||
const filtered = allEntities.filter(e =>
|
||||
e.metadata?.path === '/' &&
|
||||
e.metadata?.vfsType === 'directory'
|
||||
)
|
||||
console.log(` Filtered to roots: ${filtered.length}`)
|
||||
|
||||
if (filtered.length > 0) {
|
||||
console.log(` Root entity:`)
|
||||
console.log(` ID: ${filtered[0].id}`)
|
||||
console.log(` Type: ${filtered[0].type}`)
|
||||
console.log(` Metadata.path: ${filtered[0].metadata?.path}`)
|
||||
console.log(` Metadata.vfsType: ${filtered[0].metadata?.vfsType}`)
|
||||
}
|
||||
|
||||
// Method 3: Type-based search
|
||||
console.log('\n📋 Method 3: Type query (collection)')
|
||||
const typeResult = await brain.find({
|
||||
type: 'collection',
|
||||
limit: 10
|
||||
})
|
||||
console.log(` Found ${typeResult.length} collection entities`)
|
||||
|
||||
// FIXED (v4.3.3): Where clause now works with correct field names!
|
||||
expect(whereResult.length).toBe(1) // ✅ WHERE CLAUSE WORKS NOW!
|
||||
expect(filtered.length).toBe(1) // ✅ MANUAL FILTER ALSO WORKS
|
||||
})
|
||||
|
||||
it('should find regular VFS files using where clause', async () => {
|
||||
const vfs = brain.vfs
|
||||
await vfs.mkdir('/test', { recursive: true })
|
||||
await vfs.writeFile('/test/hello.txt', 'Hello World')
|
||||
|
||||
// Try to find the file using where clause (FIXED field names)
|
||||
console.log('\n📋 Finding /test/hello.txt')
|
||||
|
||||
const whereResult = await brain.find({
|
||||
where: {
|
||||
path: '/test/hello.txt', // ✅ Fixed: Use flat field name
|
||||
vfsType: 'file' // ✅ Fixed: Use flat field name
|
||||
},
|
||||
limit: 10
|
||||
})
|
||||
console.log(` Where clause: ${whereResult.length} results`)
|
||||
|
||||
const allEntities = await brain.find({ limit: 100 })
|
||||
const filtered = allEntities.filter(e =>
|
||||
e.metadata?.path === '/test/hello.txt'
|
||||
)
|
||||
console.log(` Manual filter: ${filtered.length} results`)
|
||||
|
||||
if (filtered.length > 0) {
|
||||
console.log(` File metadata:`)
|
||||
console.log(` Path: ${filtered[0].metadata?.path}`)
|
||||
console.log(` VfsType: ${filtered[0].metadata?.vfsType}`)
|
||||
console.log(` Name: ${filtered[0].metadata?.name}`)
|
||||
}
|
||||
|
||||
// FIXED (v4.3.3): Where clause works with correct field names!
|
||||
expect(whereResult.length).toBe(1) // ✅ FIXED!
|
||||
expect(filtered.length).toBe(1) // ✅ ALSO WORKS
|
||||
})
|
||||
|
||||
it('should show metadata index status', async () => {
|
||||
console.log('\n📋 Metadata Index Status')
|
||||
|
||||
const allEntities = await brain.find({ limit: 100 })
|
||||
console.log(` Total entities: ${allEntities.length}`)
|
||||
|
||||
const withMetadata = allEntities.filter(e => e.metadata && Object.keys(e.metadata).length > 0)
|
||||
console.log(` With metadata: ${withMetadata.length}`)
|
||||
|
||||
const withPath = allEntities.filter(e => e.metadata?.path)
|
||||
console.log(` With metadata.path: ${withPath.length}`)
|
||||
|
||||
const withVfsType = allEntities.filter(e => e.metadata?.vfsType)
|
||||
console.log(` With metadata.vfsType: ${withVfsType.length}`)
|
||||
|
||||
// Show sample metadata
|
||||
if (withPath.length > 0) {
|
||||
console.log(`\n Sample entity with path:`)
|
||||
const sample = withPath[0]
|
||||
console.log(` ID: ${sample.id}`)
|
||||
console.log(` Type: ${sample.type}`)
|
||||
console.log(` Metadata keys: ${Object.keys(sample.metadata || {}).join(', ')}`)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -143,7 +143,7 @@ function calculatePackageSize(): {
|
|||
describe('Package Size Breakdown', () => {
|
||||
it('should report the estimated package size and largest files', () => {
|
||||
const { totalSize, includedFiles } = calculatePackageSize()
|
||||
|
||||
|
||||
console.log('Estimated package size: ' + totalSize.toFixed(2) + ' MB')
|
||||
console.log('\nLargest files:')
|
||||
for (let i = 0; i < Math.min(10, includedFiles.length); i++) {
|
||||
|
|
@ -151,26 +151,18 @@ describe('Package Size Breakdown', () => {
|
|||
`${includedFiles[i].path}: ${includedFiles[i].size.toFixed(2)} MB`
|
||||
)
|
||||
}
|
||||
|
||||
// Basic sanity check
|
||||
expect(totalSize).toBeGreaterThan(0)
|
||||
expect(includedFiles.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should identify files that contribute significantly to package size', () => {
|
||||
const { includedFiles } = calculatePackageSize()
|
||||
|
||||
// Find files larger than 1MB
|
||||
|
||||
// Informational: files larger than 1MB (model + WASM assets expected)
|
||||
const largeFiles = includedFiles.filter(file => file.size > 1)
|
||||
|
||||
if (largeFiles.length > 0) {
|
||||
console.log('\nFiles larger than 1MB:')
|
||||
largeFiles.forEach(file => {
|
||||
console.log(`${file.path}: ${file.size.toFixed(2)} MB`)
|
||||
})
|
||||
}
|
||||
|
||||
// This is not a failure condition, just informational
|
||||
expect(true).toBe(true)
|
||||
|
||||
// Basic sanity check
|
||||
expect(totalSize).toBeGreaterThan(0)
|
||||
expect(includedFiles.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
|
@ -113,6 +113,10 @@ describe('TypeAware Performance Benchmarks', () => {
|
|||
// At 1K entities: ~1-3x speedup (small dataset)
|
||||
// At 1M entities: ~5-10x speedup (projected, not measured)
|
||||
// At 1B entities: ~10x speedup (PROJECTED, extrapolated)
|
||||
// Assert measurement validity, not a perf number (CI hardware varies).
|
||||
expect(typeDuration).toBeGreaterThan(0)
|
||||
expect(speedup).toBeGreaterThan(0)
|
||||
expect(Number.isFinite(speedup)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Regression tests: metadata index cleanup after delete / deleteMany
|
||||
* Regression tests: metadata index cleanup after remove / removeMany
|
||||
*
|
||||
* Bug report (wickworks): brain.deleteMany() was not removing metadata index
|
||||
* Bug report (wickworks): brain.removeMany() was not removing metadata index
|
||||
* entries for deleted entities. The same defect also existed in delete().
|
||||
*
|
||||
* Root causes fixed:
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
* Fix: entityForIndexing now uses the same conditional spreading pattern as
|
||||
* storageMetadata for confidence, weight, and createdBy.
|
||||
*
|
||||
* 3. result.successful updated inside transaction builder — deleteMany() pushed
|
||||
* 3. result.successful updated inside transaction builder — removeMany() pushed
|
||||
* ids to result.successful during the builder phase, before transaction.execute()
|
||||
* ran. A transaction rollback would leave result.successful containing ids that
|
||||
* were never actually deleted.
|
||||
|
|
@ -39,7 +39,7 @@ const DIM = 384
|
|||
const makeVec = (seed = 1) =>
|
||||
new Float32Array(DIM).map((_, i) => ((i + seed) % DIM) / DIM)
|
||||
|
||||
describe('Metadata index cleanup after delete / deleteMany', () => {
|
||||
describe('Metadata index cleanup after remove / removeMany', () => {
|
||||
let brain: Brainy
|
||||
|
||||
beforeEach(async () => {
|
||||
|
|
@ -80,7 +80,7 @@ describe('Metadata index cleanup after delete / deleteMany', () => {
|
|||
describe('delete()', () => {
|
||||
it('removes entity from type index so find({ type }) returns 0', async () => {
|
||||
const id = await addEntity({ type: 'product' })
|
||||
await brain.delete(id)
|
||||
await brain.remove(id)
|
||||
|
||||
const results = await brain.find({ type: 'product' as any })
|
||||
expect(results).toHaveLength(0)
|
||||
|
|
@ -90,7 +90,7 @@ describe('Metadata index cleanup after delete / deleteMany', () => {
|
|||
// Add one entity with service 'alpha', delete it.
|
||||
// A ne:'beta' query should return 0, not the deleted entity.
|
||||
const id = await addEntity({ service: 'alpha', type: 'thing' })
|
||||
await brain.delete(id)
|
||||
await brain.remove(id)
|
||||
|
||||
const results = await brain.find({ where: { service: { ne: 'beta' } } })
|
||||
const ids = results.map(r => r.id)
|
||||
|
|
@ -101,7 +101,7 @@ describe('Metadata index cleanup after delete / deleteMany', () => {
|
|||
// Entity added without a custom 'archivedAt' field.
|
||||
// After deletion it must not appear in an exists:false query.
|
||||
const id = await addEntity({ type: 'thing' })
|
||||
await brain.delete(id)
|
||||
await brain.remove(id)
|
||||
|
||||
const results = await brain.find({ where: { archivedAt: { exists: false } } })
|
||||
const ids = results.map(r => r.id)
|
||||
|
|
@ -110,10 +110,10 @@ describe('Metadata index cleanup after delete / deleteMany', () => {
|
|||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// deleteMany() — batch deletion
|
||||
// removeMany() — batch deletion
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('deleteMany()', () => {
|
||||
describe('removeMany()', () => {
|
||||
it('removes all entities from type index so find({ type }) returns 0', async () => {
|
||||
const ids = await Promise.all([
|
||||
addEntity({ type: 'concept' }),
|
||||
|
|
@ -121,7 +121,7 @@ describe('Metadata index cleanup after delete / deleteMany', () => {
|
|||
addEntity({ type: 'concept' }),
|
||||
])
|
||||
|
||||
await brain.deleteMany({ ids })
|
||||
await brain.removeMany({ ids })
|
||||
|
||||
const results = await brain.find({ type: 'concept' as any, limit: 50 })
|
||||
expect(results).toHaveLength(0)
|
||||
|
|
@ -140,7 +140,7 @@ describe('Metadata index cleanup after delete / deleteMany', () => {
|
|||
addEntity({ service: 'beta', type: 'thing' }),
|
||||
])
|
||||
|
||||
await brain.deleteMany({ ids: alphaIds })
|
||||
await brain.removeMany({ ids: alphaIds })
|
||||
|
||||
const results = await brain.find({ where: { service: { ne: 'beta' } } })
|
||||
const resultIds = results.map(r => r.id)
|
||||
|
|
@ -168,7 +168,7 @@ describe('Metadata index cleanup after delete / deleteMany', () => {
|
|||
// One surviving entity also without closedAt — it SHOULD appear in results
|
||||
const survivorId = await addEntity({ type: 'thing', metadata: { region: 'us', active: true } })
|
||||
|
||||
await brain.deleteMany({ ids: deletedIds })
|
||||
await brain.removeMany({ ids: deletedIds })
|
||||
|
||||
const results = await brain.find({ where: { closedAt: { exists: false } } })
|
||||
const resultIds = results.map(r => r.id)
|
||||
|
|
@ -189,7 +189,7 @@ describe('Metadata index cleanup after delete / deleteMany', () => {
|
|||
addEntity({ type: 'thing' }),
|
||||
])
|
||||
|
||||
const result = await brain.deleteMany({ ids })
|
||||
const result = await brain.removeMany({ ids })
|
||||
|
||||
// All should succeed — verify successful list is correct
|
||||
expect(result.successful).toHaveLength(ids.length)
|
||||
|
|
@ -206,7 +206,7 @@ describe('Metadata index cleanup after delete / deleteMany', () => {
|
|||
})
|
||||
|
||||
it('handles empty ids array gracefully', async () => {
|
||||
const result = await brain.deleteMany({ ids: [] })
|
||||
const result = await brain.removeMany({ ids: [] })
|
||||
expect(result.successful).toHaveLength(0)
|
||||
expect(result.failed).toHaveLength(0)
|
||||
})
|
||||
|
|
@ -217,7 +217,7 @@ describe('Metadata index cleanup after delete / deleteMany', () => {
|
|||
Array.from({ length: 25 }, (_, i) => addEntity({ type: 'document', metadata: { i } }))
|
||||
)
|
||||
|
||||
const result = await brain.deleteMany({ ids })
|
||||
const result = await brain.removeMany({ ids })
|
||||
|
||||
expect(result.successful).toHaveLength(25)
|
||||
expect(result.failed).toHaveLength(0)
|
||||
|
|
@ -267,7 +267,7 @@ describe('Metadata index cleanup after delete / deleteMany', () => {
|
|||
// After deletion the confidence/__NULL__ and weight/__NULL__ bitmap entries
|
||||
// (if any were created) must not be surfaced through any query.
|
||||
const id = await addEntity({ type: 'thing' })
|
||||
await brain.delete(id)
|
||||
await brain.remove(id)
|
||||
|
||||
// Entity must not appear in any confidence query
|
||||
const existsTrue = await brain.find({ where: { confidence: { exists: true } } })
|
||||
|
|
@ -282,7 +282,7 @@ describe('Metadata index cleanup after delete / deleteMany', () => {
|
|||
// Survivors are unaffected
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('partial deleteMany does not affect surviving entities', () => {
|
||||
describe('partial removeMany does not affect surviving entities', () => {
|
||||
it('surviving entities remain queryable after deleting others of the same type', async () => {
|
||||
const toDelete = await Promise.all([
|
||||
addEntity({ type: 'collection', metadata: { group: 'a' } }),
|
||||
|
|
@ -294,7 +294,7 @@ describe('Metadata index cleanup after delete / deleteMany', () => {
|
|||
addEntity({ type: 'collection', metadata: { group: 'b' } }),
|
||||
])
|
||||
|
||||
await brain.deleteMany({ ids: toDelete })
|
||||
await brain.removeMany({ ids: toDelete })
|
||||
|
||||
const results = await brain.find({ type: 'collection' as any, limit: 50 })
|
||||
const resultIds = results.map(r => r.id)
|
||||
|
|
@ -315,7 +315,7 @@ describe('Metadata index cleanup after delete / deleteMany', () => {
|
|||
const b = await addEntity({ type: 'task', service: 'svc' })
|
||||
const c = await addEntity({ type: 'task', service: 'svc' })
|
||||
|
||||
await brain.delete(a)
|
||||
await brain.remove(a)
|
||||
|
||||
const results = await brain.find({ type: 'task' as any, limit: 50 })
|
||||
const ids = results.map(r => r.id)
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ describe('v5.7.0 Deadlock Regression', () => {
|
|||
const start = Date.now()
|
||||
|
||||
// Force GraphAdjacencyIndex usage by querying relationships
|
||||
const relations = await brain.getRelations({ from: entities[0] })
|
||||
const relations = await brain.related({ from: entities[0] })
|
||||
|
||||
const elapsed = Date.now() - start
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ describe('v5.7.0 Deadlock Regression', () => {
|
|||
const start = Date.now()
|
||||
|
||||
// This would trigger initialization if not already done
|
||||
const relations = await brain.getRelations({ from: entity1 })
|
||||
const relations = await brain.related({ from: entity1 })
|
||||
|
||||
const elapsed = Date.now() - start
|
||||
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Script to migrate tests from old Brainy API to v3.0 API
|
||||
# This updates deprecated methods and types
|
||||
|
||||
echo "🔄 Migrating tests to Brainy 3.0 API..."
|
||||
|
||||
# List of test files to update
|
||||
TEST_FILES=(
|
||||
"tests/integration/brainy-core.integration.test.ts"
|
||||
"tests/integration/brainy-complete.integration.test.ts"
|
||||
)
|
||||
|
||||
for file in "${TEST_FILES[@]}"; do
|
||||
if [ -f "$file" ]; then
|
||||
echo "📝 Updating $file..."
|
||||
|
||||
# Backup original
|
||||
cp "$file" "$file.backup"
|
||||
|
||||
# Replace BrainyData with Brainy
|
||||
sed -i 's/BrainyData/Brainy/g' "$file"
|
||||
|
||||
# Replace addNoun with add (using proper params)
|
||||
sed -i "s/brain\.addNoun(\([^,)]*\))/brain.add({ data: \1, type: 'thing' })/g" "$file"
|
||||
sed -i "s/brain\.addNoun(\([^,]*\), \([^)]*\))/brain.add({ data: \1, type: 'thing', metadata: \2 })/g" "$file"
|
||||
|
||||
# Replace addVerb with relate
|
||||
sed -i "s/brain\.addVerb(\([^,]*\), \([^,]*\), \([^)]*\))/brain.relate({ from: \1, to: \2, type: \3 })/g" "$file"
|
||||
|
||||
# Replace getNoun with get
|
||||
sed -i 's/brain\.getNoun/brain.get/g' "$file"
|
||||
|
||||
# Replace getVerb with getRelations
|
||||
sed -i 's/brain\.getVerb/brain.getRelations/g' "$file"
|
||||
|
||||
# Replace GraphNoun with Entity
|
||||
sed -i 's/GraphNoun/Entity/g' "$file"
|
||||
|
||||
# Replace GraphVerb with Relation
|
||||
sed -i 's/GraphVerb/Relation/g' "$file"
|
||||
|
||||
# Update import statements
|
||||
sed -i "s/import { Brainy } from '..\/..\/dist\/index.js'/import { Brainy } from '..\/..\/src\/brainy'/g" "$file"
|
||||
|
||||
# Fix forceMemoryStorage to new format
|
||||
sed -i "s/storage: { forceMemoryStorage: true }/storage: { type: 'memory' }/g" "$file"
|
||||
|
||||
echo "✅ Updated $file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "📊 Migration complete! Review the changes and run tests to verify."
|
||||
echo "💡 Backup files created with .backup extension"
|
||||
|
|
@ -138,7 +138,7 @@ describe('Transactions + Distributed Storage Integration', () => {
|
|||
|
||||
// Verify relationships
|
||||
for (let i = 0; i < entities.length - 1; i++) {
|
||||
const relations = await brain.getRelations({ from: entities[i] })
|
||||
const relations = await brain.related({ from: entities[i] })
|
||||
expect(relations).toHaveLength(1)
|
||||
}
|
||||
})
|
||||
|
|
@ -260,7 +260,7 @@ describe('Transactions + Distributed Storage Integration', () => {
|
|||
expect(entity).toBeTruthy()
|
||||
|
||||
// Delete atomically
|
||||
await brain.delete(id)
|
||||
await brain.remove(id)
|
||||
|
||||
// Verify deleted
|
||||
entity = await brain.get(id)
|
||||
|
|
@ -286,14 +286,14 @@ describe('Transactions + Distributed Storage Integration', () => {
|
|||
})
|
||||
|
||||
// Delete first entity (should delete relationships)
|
||||
await brain.delete(id1)
|
||||
await brain.remove(id1)
|
||||
|
||||
// Verify entity deleted
|
||||
const entity1 = await brain.get(id1)
|
||||
expect(entity1).toBeNull()
|
||||
|
||||
// Verify relationships deleted
|
||||
const relations = await brain.getRelations({ from: id1 })
|
||||
const relations = await brain.related({ from: id1 })
|
||||
expect(relations).toHaveLength(0)
|
||||
|
||||
// Entity 2 should still exist
|
||||
|
|
@ -325,7 +325,7 @@ describe('Transactions + Distributed Storage Integration', () => {
|
|||
expect(entity?.data.name).toBe('Updated via Adapter')
|
||||
|
||||
// Delete entity
|
||||
await brain.delete(id)
|
||||
await brain.remove(id)
|
||||
const deletedEntity = await brain.get(id)
|
||||
expect(deletedEntity).toBeNull()
|
||||
})
|
||||
|
|
@ -359,7 +359,7 @@ describe('Transactions + Distributed Storage Integration', () => {
|
|||
// Verify all operations succeeded (regardless of latency)
|
||||
const entity1 = await brain.get(id1)
|
||||
const entity2 = await brain.get(id2)
|
||||
const relations = await brain.getRelations({ from: id1 })
|
||||
const relations = await brain.related({ from: id1 })
|
||||
|
||||
expect(entity1).toBeTruthy()
|
||||
expect(entity2).toBeTruthy()
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ describe('Transactions + Sharding Integration', () => {
|
|||
// Verify all entities exist
|
||||
const entity1 = await brain.get(id1)
|
||||
const entity2 = await brain.get(id2)
|
||||
const relations = await brain.getRelations({ from: id1 })
|
||||
const relations = await brain.related({ from: id1 })
|
||||
|
||||
expect(entity1).toBeTruthy()
|
||||
expect(entity2).toBeTruthy()
|
||||
|
|
@ -213,9 +213,9 @@ describe('Transactions + Sharding Integration', () => {
|
|||
})
|
||||
|
||||
// Verify all relationships exist
|
||||
const relations0 = await brain.getRelations({ from: entities[0].id })
|
||||
const relations1 = await brain.getRelations({ from: entities[1].id })
|
||||
const relations2 = await brain.getRelations({ from: entities[2].id })
|
||||
const relations0 = await brain.related({ from: entities[0].id })
|
||||
const relations1 = await brain.related({ from: entities[1].id })
|
||||
const relations2 = await brain.related({ from: entities[2].id })
|
||||
|
||||
expect(relations0).toHaveLength(1)
|
||||
expect(relations1).toHaveLength(1)
|
||||
|
|
@ -249,7 +249,7 @@ describe('Transactions + Sharding Integration', () => {
|
|||
})
|
||||
|
||||
// Delete first entity (should also delete relationship)
|
||||
await brain.delete(id1)
|
||||
await brain.remove(id1)
|
||||
|
||||
// Verify entity deleted from shard G
|
||||
const entity1 = await brain.get(id1)
|
||||
|
|
@ -260,7 +260,7 @@ describe('Transactions + Sharding Integration', () => {
|
|||
expect(entity2).toBeTruthy()
|
||||
|
||||
// Relationship should be deleted
|
||||
const relations = await brain.getRelations({ from: id1 })
|
||||
const relations = await brain.related({ from: id1 })
|
||||
expect(relations).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -272,8 +272,8 @@ describe('Transactions + TypeAware Storage Integration', () => {
|
|||
})
|
||||
|
||||
// Verify relationships exist
|
||||
const personRelations = await brain.getRelations({ from: personId })
|
||||
const orgRelations = await brain.getRelations({ from: orgId })
|
||||
const personRelations = await brain.related({ from: personId })
|
||||
const orgRelations = await brain.related({ from: orgId })
|
||||
|
||||
expect(personRelations).toHaveLength(2)
|
||||
expect(orgRelations).toHaveLength(1)
|
||||
|
|
@ -310,7 +310,7 @@ describe('Transactions + TypeAware Storage Integration', () => {
|
|||
})
|
||||
|
||||
// Delete person (should cascade delete relationships)
|
||||
await brain.delete(personId)
|
||||
await brain.remove(personId)
|
||||
|
||||
// Verify person deleted
|
||||
const person = await brain.get(personId)
|
||||
|
|
@ -323,7 +323,7 @@ describe('Transactions + TypeAware Storage Integration', () => {
|
|||
expect(task).toBeTruthy()
|
||||
|
||||
// Verify relationships from person are deleted
|
||||
const relations = await brain.getRelations({ from: personId })
|
||||
const relations = await brain.related({ from: personId })
|
||||
expect(relations).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ describe('Brainy 3.0 Core (Unit Tests)', () => {
|
|||
expect(await brain.get(id)).toBeTruthy()
|
||||
|
||||
// Delete it
|
||||
await brain.delete(id)
|
||||
await brain.remove(id)
|
||||
|
||||
// Verify it's gone
|
||||
expect(await brain.get(id)).toBeNull()
|
||||
|
|
@ -96,7 +96,7 @@ describe('Brainy 3.0 Core (Unit Tests)', () => {
|
|||
})).rejects.toThrow()
|
||||
|
||||
// delete should not throw for non-existent ID
|
||||
await expect(brain.delete(fakeId)).resolves.not.toThrow()
|
||||
await expect(brain.remove(fakeId)).resolves.not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -199,9 +199,11 @@ describe('Brainy 3.0 Core (Unit Tests)', () => {
|
|||
data: { name: 'Test2' },
|
||||
type: NounType.Concept
|
||||
})
|
||||
|
||||
// Statistics would be available through augmentation system
|
||||
// The exact API depends on augmentation configuration
|
||||
|
||||
const stats = await brain.stats()
|
||||
expect(stats.mode).toBe('writer')
|
||||
expect(stats.entityCount).toBeGreaterThanOrEqual(2)
|
||||
expect(stats.entitiesByType[NounType.Concept]).toBeGreaterThanOrEqual(2)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ describe('Brainy Batch Operations', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('deleteMany - Batch Deletion', () => {
|
||||
describe('removeMany - Batch Deletion', () => {
|
||||
let testIds: string[]
|
||||
|
||||
beforeEach(async () => {
|
||||
|
|
@ -171,7 +171,7 @@ describe('Brainy Batch Operations', () => {
|
|||
})
|
||||
|
||||
it('should delete multiple entities at once', async () => {
|
||||
await brain.deleteMany({ ids: testIds })
|
||||
await brain.removeMany({ ids: testIds })
|
||||
|
||||
// All should be gone
|
||||
for (const id of testIds) {
|
||||
|
|
@ -185,7 +185,7 @@ describe('Brainy Batch Operations', () => {
|
|||
const toDelete = [testIds[0], testIds[2], testIds[4]]
|
||||
const toKeep = [testIds[1], testIds[3]]
|
||||
|
||||
await brain.deleteMany({ ids: toDelete })
|
||||
await brain.removeMany({ ids: toDelete })
|
||||
|
||||
// Deleted ones should be gone
|
||||
for (const id of toDelete) {
|
||||
|
|
@ -212,7 +212,7 @@ describe('Brainy Batch Operations', () => {
|
|||
await brain.relate({ from: person2, to: org, type: VerbType.MemberOf as any })
|
||||
|
||||
// Delete the organization
|
||||
await brain.deleteMany({ ids: [org] })
|
||||
await brain.removeMany({ ids: [org] })
|
||||
|
||||
// Organization should be gone
|
||||
const deletedOrg = await brain.get(org)
|
||||
|
|
@ -237,7 +237,7 @@ describe('Brainy Batch Operations', () => {
|
|||
const manyIds = manyResult.successful
|
||||
|
||||
const startTime = Date.now()
|
||||
await brain.deleteMany({ ids: manyIds })
|
||||
await brain.removeMany({ ids: manyIds })
|
||||
const duration = Date.now() - startTime
|
||||
|
||||
// v5.4.0: Increased to 14500ms for type-first storage + system load variance
|
||||
|
|
@ -281,7 +281,7 @@ describe('Brainy Batch Operations', () => {
|
|||
expect(relationIds).toHaveLength(3)
|
||||
|
||||
// Verify relationships exist
|
||||
const person1Relations = await brain.getRelations({ from: entities[0] })
|
||||
const person1Relations = await brain.related({ from: entities[0] })
|
||||
expect(person1Relations.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
|
|
@ -297,13 +297,13 @@ describe('Brainy Batch Operations', () => {
|
|||
expect(relationIds).toHaveLength(3)
|
||||
|
||||
// Verify different types
|
||||
const friendRelations = await brain.getRelations({
|
||||
const friendRelations = await brain.related({
|
||||
from: entities[0],
|
||||
type: VerbType.FriendOf
|
||||
})
|
||||
expect(friendRelations.length).toBeGreaterThan(0)
|
||||
|
||||
const workRelations = await brain.getRelations({
|
||||
const workRelations = await brain.related({
|
||||
from: entities[0],
|
||||
type: VerbType.WorksWith
|
||||
})
|
||||
|
|
@ -321,10 +321,10 @@ describe('Brainy Batch Operations', () => {
|
|||
expect(relationIds).toHaveLength(2)
|
||||
|
||||
// Both should have the relationship
|
||||
const person1Friends = await brain.getRelations({ from: entities[0] })
|
||||
const person1Friends = await brain.related({ from: entities[0] })
|
||||
expect(person1Friends.length).toBeGreaterThan(0)
|
||||
|
||||
const person2Friends = await brain.getRelations({ from: entities[1] })
|
||||
const person2Friends = await brain.related({ from: entities[1] })
|
||||
expect(person2Friends.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
|
|
@ -358,7 +358,7 @@ describe('Brainy Batch Operations', () => {
|
|||
expect(duration).toBeLessThan(1000) // Should be fast
|
||||
|
||||
// Verify company has all relationships
|
||||
const companyRelations = await brain.getRelations({ to: company })
|
||||
const companyRelations = await brain.related({ to: company })
|
||||
expect(companyRelations.length).toBeGreaterThanOrEqual(50)
|
||||
})
|
||||
|
||||
|
|
@ -460,7 +460,7 @@ describe('Brainy Batch Operations', () => {
|
|||
await brain.relateMany({ items: relationships })
|
||||
|
||||
// 4. Delete some entities
|
||||
await brain.deleteMany({ ids: initialIds.slice(15) })
|
||||
await brain.removeMany({ ids: initialIds.slice(15) })
|
||||
|
||||
const totalTime = Date.now() - startTime
|
||||
|
||||
|
|
@ -473,7 +473,7 @@ describe('Brainy Batch Operations', () => {
|
|||
const deleted = await brain.get(initialIds[19])
|
||||
expect(deleted).toBeNull()
|
||||
|
||||
const relations = await brain.getRelations({ from: initialIds[0] })
|
||||
const relations = await brain.related({ from: initialIds[0] })
|
||||
expect(relations.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
|
@ -487,7 +487,7 @@ describe('Brainy Batch Operations', () => {
|
|||
expect(result.successful).toHaveLength(0)
|
||||
|
||||
await brain.updateMany({ items: [] })
|
||||
await brain.deleteMany({ ids: [] })
|
||||
await brain.removeMany({ ids: [] })
|
||||
// Should not throw
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -169,9 +169,9 @@ describe('Brainy.fillSubtypes()', () => {
|
|||
expect(report.filled).toBe(1)
|
||||
expect(report.byType).toEqual({ relatedTo: 1 })
|
||||
|
||||
const relations = await brain.getRelations({ from: a })
|
||||
const relations = await brain.related({ from: a })
|
||||
expect(relations.find((r) => r.id === relId)!.subtype).toBe('unspecified')
|
||||
const reverse = await brain.getRelations({ from: b })
|
||||
const reverse = await brain.related({ from: b })
|
||||
expect(reverse.find((r) => r.id === labeled)!.subtype).toBe('colleague') // untouched
|
||||
})
|
||||
|
||||
|
|
@ -195,7 +195,7 @@ describe('Brainy.fillSubtypes()', () => {
|
|||
})
|
||||
|
||||
expect(report.filled).toBe(1)
|
||||
const relations = await brain.getRelations({ from: a })
|
||||
const relations = await brain.related({ from: a })
|
||||
expect(relations[0].subtype).toBe('dotted-line')
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ describe('Brainy.get()', () => {
|
|||
data: 'To be deleted',
|
||||
type: 'thing'
|
||||
}))
|
||||
await brain.delete(id)
|
||||
await brain.remove(id)
|
||||
|
||||
// Act
|
||||
const entity = await brain.get(id)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ describe('Duplicate Check Optimization', () => {
|
|||
expect(relationId2).toBe(relationId1)
|
||||
|
||||
// Verify only one relationship exists
|
||||
const relations = await brain.getRelations({ from: personId })
|
||||
const relations = await brain.related({ from: personId })
|
||||
expect(relations).toHaveLength(1)
|
||||
expect(relations[0].id).toBe(relationId1)
|
||||
})
|
||||
|
|
@ -85,7 +85,7 @@ describe('Duplicate Check Optimization', () => {
|
|||
expect(relationId2).not.toBe(relationId1)
|
||||
|
||||
// Verify both relationships exist
|
||||
const relations = await brain.getRelations({ from: personId })
|
||||
const relations = await brain.related({ from: personId })
|
||||
expect(relations).toHaveLength(2)
|
||||
// Both relations should exist with different IDs
|
||||
const relationIds = relations.map(r => r.id)
|
||||
|
|
@ -129,7 +129,7 @@ describe('Duplicate Check Optimization', () => {
|
|||
expect(elapsed).toBeLessThan(10)
|
||||
|
||||
// Verify duplicate was detected
|
||||
const relations = await brain.getRelations({ from: sourceId })
|
||||
const relations = await brain.related({ from: sourceId })
|
||||
expect(relations).toHaveLength(50) // No duplicate created
|
||||
})
|
||||
|
||||
|
|
@ -198,7 +198,7 @@ describe('Duplicate Check Optimization', () => {
|
|||
})
|
||||
|
||||
// Verify both relationships exist
|
||||
let relations = await brain.getRelations({ from: person })
|
||||
let relations = await brain.related({ from: person })
|
||||
expect(relations).toHaveLength(2)
|
||||
const relationIds = relations.map(r => r.id)
|
||||
expect(relationIds).toContain(rel1)
|
||||
|
|
@ -218,7 +218,7 @@ describe('Duplicate Check Optimization', () => {
|
|||
expect(duplicate).toBe(rel1)
|
||||
|
||||
// Verify still same number of relationships (no duplicate added)
|
||||
const finalRelations = await brain.getRelations({ from: person })
|
||||
const finalRelations = await brain.related({ from: person })
|
||||
expect(finalRelations.length).toBe(relations.length)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ describe('Brainy.relate()', () => {
|
|||
expect(typeof relationId).toBe('string')
|
||||
|
||||
// Verify relationship exists
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
expect(relations.length).toBeGreaterThan(0)
|
||||
expect(relations.some(r => r.to === entity2Id)).toBe(true)
|
||||
})
|
||||
|
|
@ -76,7 +76,7 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
const relation = relations.find(r => r.to === entity2Id)
|
||||
expect(relation).toBeDefined()
|
||||
expect(relation!.weight).toBe(0.8)
|
||||
|
|
@ -99,7 +99,7 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
const relation = relations.find(r => r.to === entity2Id)
|
||||
expect(relation).toBeDefined()
|
||||
expect(relation!.metadata || {}).toMatchObject(metadata)
|
||||
|
|
@ -115,8 +115,8 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert - Check both directions
|
||||
const forwardRelations = await brain.getRelations({ from: entity1Id })
|
||||
const reverseRelations = await brain.getRelations({ from: entity2Id })
|
||||
const forwardRelations = await brain.related({ from: entity1Id })
|
||||
const reverseRelations = await brain.related({ from: entity2Id })
|
||||
|
||||
expect(forwardRelations.some(r => r.to === entity2Id)).toBe(true)
|
||||
expect(reverseRelations.some(r => r.to === entity1Id)).toBe(true)
|
||||
|
|
@ -137,7 +137,7 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
expect(relations.length).toBe(2)
|
||||
expect(relations.some(r => r.to === entity2Id)).toBe(true)
|
||||
expect(relations.some(r => r.to === entity3Id)).toBe(true)
|
||||
|
|
@ -158,7 +158,7 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
const toEntity2 = relations.filter(r => r.to === entity2Id)
|
||||
expect(toEntity2.length).toBe(2)
|
||||
expect(toEntity2.some(r => r.type === 'worksWith')).toBe(true)
|
||||
|
|
@ -175,7 +175,7 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
const selfRelation = relations.find(r => r.to === entity1Id)
|
||||
expect(selfRelation).toBeDefined()
|
||||
expect(selfRelation!.metadata?.type).toBe('self-reference')
|
||||
|
|
@ -241,7 +241,7 @@ describe('Brainy.relate()', () => {
|
|||
// Second call should return existing relationship ID instead of creating duplicate
|
||||
expect(id1).toBe(id2)
|
||||
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
const matches = relations.filter(r =>
|
||||
r.to === entity2Id && r.type === 'worksWith'
|
||||
)
|
||||
|
|
@ -267,7 +267,7 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
const relation = relations.find(r => r.to === entity2Id)
|
||||
expect(relation).toBeDefined()
|
||||
expect(relation!.metadata?.bigArray).toHaveLength(100)
|
||||
|
|
@ -291,7 +291,7 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
const relation = relations.find(r => r.to === entity2Id)
|
||||
expect(relation!.metadata || {}).toMatchObject(specialMetadata)
|
||||
})
|
||||
|
|
@ -356,8 +356,8 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert - Multiple queries should return same data
|
||||
const relations1 = await brain.getRelations({ from: entity1Id })
|
||||
const relations2 = await brain.getRelations({ from: entity1Id })
|
||||
const relations1 = await brain.related({ from: entity1Id })
|
||||
const relations2 = await brain.related({ from: entity1Id })
|
||||
|
||||
expect(relations1.length).toBe(relations2.length)
|
||||
const rel1 = relations1.find(r => r.to === entity2Id)
|
||||
|
|
@ -386,7 +386,7 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert - Relationship should still exist
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
expect(relations.some(r => r.to === entity2Id)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
|
@ -407,8 +407,8 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Act - Get relationships step by step
|
||||
const step1 = await brain.getRelations({ from: entity1Id })
|
||||
const entity2Relations = await brain.getRelations({ from: entity2Id })
|
||||
const step1 = await brain.related({ from: entity1Id })
|
||||
const entity2Relations = await brain.related({ from: entity2Id })
|
||||
|
||||
// Assert
|
||||
expect(step1.some(r => r.to === entity2Id)).toBe(true)
|
||||
|
|
@ -436,9 +436,9 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert - All relationships should exist
|
||||
const rel1 = await brain.getRelations({ from: entity1Id })
|
||||
const rel2 = await brain.getRelations({ from: entity2Id })
|
||||
const rel3 = await brain.getRelations({ from: entity3Id })
|
||||
const rel1 = await brain.related({ from: entity1Id })
|
||||
const rel2 = await brain.related({ from: entity2Id })
|
||||
const rel3 = await brain.related({ from: entity3Id })
|
||||
|
||||
expect(rel1.some(r => r.to === entity2Id)).toBe(true)
|
||||
expect(rel2.some(r => r.to === entity3Id)).toBe(true)
|
||||
|
|
|
|||
|
|
@ -284,7 +284,7 @@ describe('reserved-field metadata remap (8.0 contract)', () => {
|
|||
expect(result?.entity._rev).toBe(1)
|
||||
})
|
||||
|
||||
it('getRelations() by target surfaces reserved fields top-level, custom-only metadata', async () => {
|
||||
it('related() by target surfaces reserved fields top-level, custom-only metadata', async () => {
|
||||
const a = await brain.add({ type: NounType.Person, subtype: 'employee', data: 'src' })
|
||||
const b = await brain.add({ type: NounType.Person, subtype: 'employee', data: 'tgt' })
|
||||
const relId = await brain.relate({
|
||||
|
|
@ -298,7 +298,7 @@ describe('reserved-field metadata remap (8.0 contract)', () => {
|
|||
metadata: { note: 'target path' }
|
||||
})
|
||||
|
||||
const relations = await brain.getRelations({ to: b })
|
||||
const relations = await brain.related({ to: b })
|
||||
const rel = relations.find((r) => r.id === relId)
|
||||
expect(rel).toBeDefined()
|
||||
expect(rel?.metadata).toEqual({ note: 'target path' })
|
||||
|
|
@ -329,7 +329,7 @@ describe('reserved-field metadata remap (8.0 contract)', () => {
|
|||
service: 'orders'
|
||||
})
|
||||
|
||||
const relations = await brain.getRelations({ from: a })
|
||||
const relations = await brain.related({ from: a })
|
||||
const rel = relations.find((r) => r.id === relId)
|
||||
expect(rel?.confidence).toBe(0.77)
|
||||
expect(rel?.service).toBe('orders')
|
||||
|
|
@ -344,7 +344,7 @@ describe('reserved-field metadata remap (8.0 contract)', () => {
|
|||
metadata: { confidence: 0.4, weight: 0.3, role: 'peer' } as object
|
||||
})
|
||||
|
||||
const relations = await brain.getRelations({ from: a })
|
||||
const relations = await brain.related({ from: a })
|
||||
const rel = relations.find((r) => r.id === relId)
|
||||
expect(rel?.confidence).toBe(0.4)
|
||||
expect(rel?.weight).toBe(0.3)
|
||||
|
|
@ -360,7 +360,7 @@ describe('reserved-field metadata remap (8.0 contract)', () => {
|
|||
metadata: { note: 'no echo' }
|
||||
})
|
||||
|
||||
const relations = await brain.getRelations({ from: a })
|
||||
const relations = await brain.related({ from: a })
|
||||
const rel = relations.find((r) => r.id === relId)
|
||||
expect(rel?.type).toBe(VerbType.RelatedTo)
|
||||
expect((rel?.metadata as Record<string, unknown>)?.verb).toBeUndefined()
|
||||
|
|
@ -382,7 +382,7 @@ describe('reserved-field metadata remap (8.0 contract)', () => {
|
|||
metadata: { confidence: 0.55, subtype: 'dotted-line', extra: 'applied' } as object
|
||||
})
|
||||
|
||||
const relations = await brain.getRelations({ from: a })
|
||||
const relations = await brain.related({ from: a })
|
||||
const rel = relations.find((r) => r.id === relId)
|
||||
expect(rel?.confidence).toBe(0.55)
|
||||
expect(rel?.subtype).toBe('dotted-line')
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
* - `getNeighbors` speaks entity ints both ways via the shared idMapper
|
||||
* - unmapped entity ints / UUIDs produce empty reads, never errors
|
||||
* - a missing idMapper fails loudly (wiring bug, not a silent fallback)
|
||||
* - coordinator-level behavior: relate → getRelations/neighbors → unrelate
|
||||
* - coordinator-level behavior: relate → related/neighbors → unrelate
|
||||
* works end-to-end over the BigInt boundary (public API unchanged)
|
||||
*/
|
||||
|
||||
|
|
@ -156,7 +156,7 @@ describe('Coordinator — relate/related end-to-end over the BigInt boundary', (
|
|||
await brain.close()
|
||||
})
|
||||
|
||||
it('relate → getRelations → neighbors round-trips (public API unchanged)', async () => {
|
||||
it('relate → related → neighbors round-trips (public API unchanged)', async () => {
|
||||
const personId = await brain.add({
|
||||
data: { name: 'Ada' },
|
||||
type: NounType.Person
|
||||
|
|
@ -172,8 +172,8 @@ describe('Coordinator — relate/related end-to-end over the BigInt boundary', (
|
|||
type: VerbType.WorksWith
|
||||
})
|
||||
|
||||
// getRelations resolves verb ints back to verb-id strings internally.
|
||||
const relations = await brain.getRelations({ from: personId })
|
||||
// related resolves verb ints back to verb-id strings internally.
|
||||
const relations = await brain.related({ from: personId })
|
||||
expect(relations).toHaveLength(1)
|
||||
expect(relations[0].id).toBe(relId)
|
||||
expect(relations[0].to).toBe(projectId)
|
||||
|
|
@ -208,7 +208,7 @@ describe('Coordinator — relate/related end-to-end over the BigInt boundary', (
|
|||
const relId = await brain.relate({ from: a, to: b, type: VerbType.RelatedTo })
|
||||
await brain.unrelate(relId)
|
||||
|
||||
const relations = await brain.getRelations({ from: a })
|
||||
const relations = await brain.related({ from: a })
|
||||
expect(relations).toHaveLength(0)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -611,8 +611,10 @@ describe('SmartExtractor', () => {
|
|||
|
||||
extractor.addToHistory('Test Entity', NounType.Person, vector)
|
||||
|
||||
// History is internal, just ensure no errors
|
||||
expect(true).toBe(true)
|
||||
// History lives on the embedding signal the extractor delegates to.
|
||||
const history = (extractor as any).embeddingSignal.historicalEntities
|
||||
expect(history).toHaveLength(1)
|
||||
expect(history[0].text).toBe('Test Entity')
|
||||
})
|
||||
|
||||
it('should clear history', () => {
|
||||
|
|
@ -621,8 +623,8 @@ describe('SmartExtractor', () => {
|
|||
|
||||
extractor.clearHistory()
|
||||
|
||||
// History cleared, no errors
|
||||
expect(true).toBe(true)
|
||||
const history = (extractor as any).embeddingSignal.historicalEntities
|
||||
expect(history).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
* 1. Option A: Never skip a verb type that's explicitly requested in the filter
|
||||
* 2. Option B: Added fast path for sourceId + verbType combo (common VFS pattern)
|
||||
*
|
||||
* Bug Report: /home/dpsifr/Projects/workshop/docs/BRAINY_VFS_BUG_REPORT.md
|
||||
* Originally reported by a downstream application using the VFS.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
||||
|
|
@ -84,7 +84,7 @@ describe('VFS mkdir() Bug Fix (v6.2.9)', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('getRelations should query Contains relationships correctly', () => {
|
||||
describe('related should query Contains relationships correctly', () => {
|
||||
it('should find Contains relationships via sourceId + verbType filter', async () => {
|
||||
// Create some entities and relationships
|
||||
const parent = await brain.add({
|
||||
|
|
@ -110,7 +110,7 @@ describe('VFS mkdir() Bug Fix (v6.2.9)', () => {
|
|||
await brain.relate({ from: parent, to: child2, type: VerbType.Contains })
|
||||
|
||||
// Query relationships (this is what getChildren() does)
|
||||
const relations = await brain.getRelations({
|
||||
const relations = await brain.related({
|
||||
from: parent,
|
||||
type: VerbType.Contains
|
||||
})
|
||||
|
|
@ -132,7 +132,7 @@ describe('VFS mkdir() Bug Fix (v6.2.9)', () => {
|
|||
await brain.relate({ from: entityA, to: entityC, type: VerbType.RelatedTo })
|
||||
|
||||
// Query only Contains relationships
|
||||
const containsRelations = await brain.getRelations({
|
||||
const containsRelations = await brain.related({
|
||||
from: entityA,
|
||||
type: VerbType.Contains
|
||||
})
|
||||
|
|
@ -141,7 +141,7 @@ describe('VFS mkdir() Bug Fix (v6.2.9)', () => {
|
|||
expect(containsRelations[0].to).toBe(entityB)
|
||||
|
||||
// Query only RelatedTo relationships
|
||||
const relatedRelations = await brain.getRelations({
|
||||
const relatedRelations = await brain.related({
|
||||
from: entityA,
|
||||
type: VerbType.RelatedTo
|
||||
})
|
||||
|
|
@ -174,7 +174,7 @@ describe('VFS mkdir() Bug Fix (v6.2.9)', () => {
|
|||
|
||||
// Query should use the new fast path
|
||||
const startTime = performance.now()
|
||||
const relations = await brain.getRelations({
|
||||
const relations = await brain.related({
|
||||
from: parent,
|
||||
type: VerbType.Contains
|
||||
})
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ describe('EntityIdMapper stability (foundation for 2.4.0)', () => {
|
|||
const deletedInt = beforeInts[2]
|
||||
const maxBefore = Math.max(...beforeInts)
|
||||
|
||||
await brain.delete(deletedId)
|
||||
await brain.remove(deletedId)
|
||||
expect(getInt(deletedId)).toBeUndefined()
|
||||
|
||||
const newId = await addEntity('f', 6)
|
||||
|
|
|
|||
|
|
@ -255,14 +255,9 @@ describe('MetadataIndexManager - Phase 1b: Type-Aware Features', { timeout: 120_
|
|||
})
|
||||
|
||||
it('should preload sparse indices for top fields of top types', async () => {
|
||||
const managerAny = manager as any
|
||||
|
||||
// Warm cache
|
||||
await manager.warmCacheForTopTypes(2)
|
||||
|
||||
// Check that sparse indices were loaded (by checking UnifiedCache)
|
||||
// This is implementation-dependent, so we just verify no errors occurred
|
||||
expect(true).toBe(true) // If we got here, warming succeeded
|
||||
// Warming must complete cleanly against populated storage; the cache
|
||||
// contents are an implementation detail, the contract is clean resolution.
|
||||
await expect(manager.warmCacheForTopTypes(2)).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
it('should handle empty database gracefully', async () => {
|
||||
|
|
|
|||
|
|
@ -71,12 +71,14 @@ describe('Mutex Safety Tests', () => {
|
|||
})
|
||||
|
||||
it('should not deadlock with nested different keys', async () => {
|
||||
let innerRan = false
|
||||
await mutex.runExclusive('key1', async () => {
|
||||
await mutex.runExclusive('key2', async () => {
|
||||
// Should not deadlock
|
||||
expect(true).toBe(true)
|
||||
innerRan = true
|
||||
})
|
||||
})
|
||||
// Reaching here at all proves no deadlock; assert the inner body ran.
|
||||
expect(innerRan).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle errors in exclusive function', async () => {
|
||||
|
|
@ -89,9 +91,11 @@ describe('Mutex Safety Tests', () => {
|
|||
).rejects.toThrow('Test error')
|
||||
|
||||
// Lock should be released, so we can acquire it again
|
||||
let reacquired = false
|
||||
await mutex.runExclusive('error-test', async () => {
|
||||
expect(true).toBe(true)
|
||||
reacquired = true
|
||||
})
|
||||
expect(reacquired).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle high concurrency without resource exhaustion', async () => {
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ describe('VFS restart persistence', () => {
|
|||
const entries1 = await brain.vfs.readdir('/')
|
||||
expect(entries1.length).toBeGreaterThan(0)
|
||||
|
||||
// In-session getRelations: root should have Contains relationship to the file
|
||||
const relations1 = await brain.getRelations({ from: rootId, type: VerbType.Contains })
|
||||
// In-session related: root should have Contains relationship to the file
|
||||
const relations1 = await brain.related({ from: rootId, type: VerbType.Contains })
|
||||
expect(relations1.length).toBeGreaterThan(0)
|
||||
|
||||
await brain.close()
|
||||
|
|
@ -68,8 +68,8 @@ describe('VFS restart persistence', () => {
|
|||
expect(entries2.length).toBeGreaterThan(0)
|
||||
expect(entries2).toContain('chapter-1.txt')
|
||||
|
||||
// getRelations should find the Contains relationship
|
||||
const relations2 = await brain.getRelations({ from: rootId, type: VerbType.Contains })
|
||||
// related should find the Contains relationship
|
||||
const relations2 = await brain.related({ from: rootId, type: VerbType.Contains })
|
||||
expect(relations2.length).toBeGreaterThan(0)
|
||||
|
||||
await brain.close()
|
||||
|
|
@ -104,8 +104,8 @@ describe('VFS restart persistence', () => {
|
|||
const docEntries1 = await brain.vfs.readdir('/docs')
|
||||
expect(docEntries1.length).toBe(2) // guide.txt + api.txt
|
||||
|
||||
// In-session getRelations: root should have Contains relationships
|
||||
const rootRelations1 = await brain.getRelations({ from: rootId, type: VerbType.Contains })
|
||||
// In-session related: root should have Contains relationships
|
||||
const rootRelations1 = await brain.related({ from: rootId, type: VerbType.Contains })
|
||||
expect(rootRelations1.length).toBeGreaterThan(0)
|
||||
|
||||
await brain.close()
|
||||
|
|
@ -130,7 +130,7 @@ describe('VFS restart persistence', () => {
|
|||
expect(docEntries2.sort()).toEqual(['api.txt', 'guide.txt'])
|
||||
|
||||
// Root relations
|
||||
const rootRelations = await brain.getRelations({ from: rootId, type: VerbType.Contains })
|
||||
const rootRelations = await brain.related({ from: rootId, type: VerbType.Contains })
|
||||
expect(rootRelations.length).toBeGreaterThan(0)
|
||||
|
||||
await brain.close()
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ describe('VFS Graph Relationships', () => {
|
|||
const readmeId = await vfs.resolvePath('/projects/brainy/README.md')
|
||||
|
||||
// Verify relationships are created properly
|
||||
const projectRelations = await brain.getRelations({
|
||||
const projectRelations = await brain.related({
|
||||
from: projectsId,
|
||||
type: VerbType.Contains
|
||||
})
|
||||
|
|
@ -43,7 +43,7 @@ describe('VFS Graph Relationships', () => {
|
|||
expect(projectRelations[0].to).toBe(brainyId)
|
||||
|
||||
// Verify brainy directory contains its files
|
||||
const brainyRelations = await brain.getRelations({
|
||||
const brainyRelations = await brain.related({
|
||||
from: brainyId,
|
||||
type: VerbType.Contains
|
||||
})
|
||||
|
|
@ -89,7 +89,7 @@ describe('VFS Graph Relationships', () => {
|
|||
const doc1Id = await vfs.resolvePath('/doc1.md')
|
||||
const doc2Id = await vfs.resolvePath('/doc2.md')
|
||||
|
||||
const directRelations = await brain.getRelations({
|
||||
const directRelations = await brain.related({
|
||||
from: doc1Id,
|
||||
type: VerbType.References
|
||||
})
|
||||
|
|
@ -114,7 +114,7 @@ describe('VFS Graph Relationships', () => {
|
|||
const testId = entity.id
|
||||
|
||||
// Check that root contains test.txt via relationships
|
||||
const rootRelations = await brain.getRelations({
|
||||
const rootRelations = await brain.related({
|
||||
from: rootId,
|
||||
type: VerbType.Contains
|
||||
})
|
||||
|
|
@ -135,7 +135,7 @@ describe('VFS Graph Relationships', () => {
|
|||
|
||||
// Verify it's using relationships, not metadata queries
|
||||
const manyId = await vfs.resolvePath('/many')
|
||||
const relations = await brain.getRelations({
|
||||
const relations = await brain.related({
|
||||
from: manyId,
|
||||
type: VerbType.Contains
|
||||
})
|
||||
|
|
@ -158,14 +158,14 @@ describe('VFS Graph Relationships', () => {
|
|||
const fileId = await vfs.resolvePath('/dest/file.txt')
|
||||
|
||||
// Source should not contain file anymore
|
||||
const sourceRelations = await brain.getRelations({
|
||||
const sourceRelations = await brain.related({
|
||||
from: sourceId,
|
||||
type: VerbType.Contains
|
||||
})
|
||||
expect(sourceRelations).toHaveLength(0)
|
||||
|
||||
// Dest should contain file
|
||||
const destRelations = await brain.getRelations({
|
||||
const destRelations = await brain.related({
|
||||
from: destId,
|
||||
type: VerbType.Contains
|
||||
})
|
||||
|
|
@ -204,7 +204,7 @@ describe('VFS Graph Relationships', () => {
|
|||
const fileEntityId = await vfs.resolvePath('/test-new.txt')
|
||||
|
||||
// Check that Contains relationship exists
|
||||
const relations = await brain.getRelations({
|
||||
const relations = await brain.related({
|
||||
from: rootId,
|
||||
to: fileEntityId,
|
||||
type: VerbType.Contains
|
||||
|
|
@ -221,7 +221,7 @@ describe('VFS Graph Relationships', () => {
|
|||
await vfs.writeFile('/test-new.txt', 'Updated content')
|
||||
|
||||
// Relationship should still exist after update
|
||||
const relationsAfterUpdate = await brain.getRelations({
|
||||
const relationsAfterUpdate = await brain.related({
|
||||
from: rootId,
|
||||
to: fileEntityId,
|
||||
type: VerbType.Contains
|
||||
|
|
@ -237,7 +237,7 @@ describe('VFS Graph Relationships', () => {
|
|||
await vfs.writeFile('/test-new.txt', 'Another update')
|
||||
await vfs.writeFile('/test-new.txt', 'Yet another update')
|
||||
|
||||
const finalRelations = await brain.getRelations({
|
||||
const finalRelations = await brain.related({
|
||||
from: rootId,
|
||||
to: fileEntityId,
|
||||
type: VerbType.Contains
|
||||
|
|
@ -258,7 +258,7 @@ describe('VFS Graph Relationships', () => {
|
|||
const fileEntityId = await vfs.resolvePath('/orphan-test.txt')
|
||||
|
||||
// Manually delete the Contains relationship to simulate the bug
|
||||
const initialRelations = await brain.getRelations({
|
||||
const initialRelations = await brain.related({
|
||||
from: rootId,
|
||||
to: fileEntityId,
|
||||
type: VerbType.Contains
|
||||
|
|
@ -270,7 +270,7 @@ describe('VFS Graph Relationships', () => {
|
|||
}
|
||||
|
||||
// Verify the relationship is gone
|
||||
const brokenRelations = await brain.getRelations({
|
||||
const brokenRelations = await brain.related({
|
||||
from: rootId,
|
||||
to: fileEntityId,
|
||||
type: VerbType.Contains
|
||||
|
|
@ -285,7 +285,7 @@ describe('VFS Graph Relationships', () => {
|
|||
await vfs.writeFile('/orphan-test.txt', 'Fixed content')
|
||||
|
||||
// Verify the relationship is restored
|
||||
const fixedRelations = await brain.getRelations({
|
||||
const fixedRelations = await brain.related({
|
||||
from: rootId,
|
||||
to: fileEntityId,
|
||||
type: VerbType.Contains
|
||||
|
|
@ -314,7 +314,7 @@ describe('VFS Graph Relationships', () => {
|
|||
const fileEntityId = await vfs.resolvePath('/level1/level2/level3/deep.txt')
|
||||
|
||||
// Verify Contains relationship exists
|
||||
const relations = await brain.getRelations({
|
||||
const relations = await brain.related({
|
||||
from: level3Id,
|
||||
to: fileEntityId,
|
||||
type: VerbType.Contains
|
||||
|
|
@ -329,7 +329,7 @@ describe('VFS Graph Relationships', () => {
|
|||
// Update the file and verify relationship persists
|
||||
await vfs.writeFile('/level1/level2/level3/deep.txt', 'Updated deep content')
|
||||
|
||||
const relationsAfterUpdate = await brain.getRelations({
|
||||
const relationsAfterUpdate = await brain.related({
|
||||
from: level3Id,
|
||||
to: fileEntityId,
|
||||
type: VerbType.Contains
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue