- Wire up distributed components (Coordinator, ShardManager, CacheSync) - Implement automatic sharding for S3 storage (256 shards) - Add read/write separation for operational modes - Zero-config automatic detection for distributed mode - Add mutex implementation for thread safety - Fix metadata filtering in find operations - Fix neural API vector similarity calculations - Improve batch operations performance - Add Bluesky distributed setup example BREAKING CHANGE: None - backward compatible
348 lines
No EOL
11 KiB
TypeScript
348 lines
No EOL
11 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest'
|
|
import { Brainy } from '../../../src/brainy'
|
|
import { createAddParams } from '../../helpers/test-factory'
|
|
|
|
describe('Brainy.delete()', () => {
|
|
let brain: Brainy<any>
|
|
|
|
beforeEach(async () => {
|
|
brain = new Brainy()
|
|
await brain.init()
|
|
})
|
|
|
|
describe('success paths', () => {
|
|
it('should delete an existing entity', async () => {
|
|
// Arrange
|
|
const id = await brain.add(createAddParams({
|
|
data: 'Test entity',
|
|
type: 'thing',
|
|
metadata: { test: true }
|
|
}))
|
|
|
|
// Verify it exists
|
|
const before = await brain.get(id)
|
|
expect(before).not.toBeNull()
|
|
|
|
// Act
|
|
await brain.delete(id)
|
|
|
|
// Assert
|
|
const after = await brain.get(id)
|
|
expect(after).toBeNull()
|
|
})
|
|
|
|
it('should delete entity with relationships', async () => {
|
|
// Arrange - Create entities and relationships
|
|
const entity1 = await brain.add(createAddParams({ data: 'Entity 1' }))
|
|
const entity2 = await brain.add(createAddParams({ data: 'Entity 2' }))
|
|
const entity3 = await brain.add(createAddParams({ data: 'Entity 3' }))
|
|
|
|
await brain.relate({
|
|
from: entity1,
|
|
to: entity2,
|
|
type: 'relatedTo'
|
|
})
|
|
|
|
await brain.relate({
|
|
from: entity1,
|
|
to: entity3,
|
|
type: 'creates'
|
|
})
|
|
|
|
await brain.relate({
|
|
from: entity2,
|
|
to: entity1,
|
|
type: 'references'
|
|
})
|
|
|
|
// Act - Delete entity1
|
|
await brain.delete(entity1)
|
|
|
|
// Assert - Entity should be gone
|
|
const deleted = await brain.get(entity1)
|
|
expect(deleted).toBeNull()
|
|
|
|
// Entity2 and entity3 should still exist
|
|
const e2 = await brain.get(entity2)
|
|
const e3 = await brain.get(entity3)
|
|
expect(e2).not.toBeNull()
|
|
expect(e3).not.toBeNull()
|
|
|
|
// Relationships involving entity1 should be removed
|
|
const fromEntity1 = await brain.getRelations({ from: entity1 })
|
|
const toEntity1 = await brain.getRelations({ to: entity1 })
|
|
expect(fromEntity1).toHaveLength(0)
|
|
expect(toEntity1).toHaveLength(0)
|
|
|
|
// Other relationships should remain
|
|
const fromEntity2 = await brain.getRelations({ from: entity2 })
|
|
expect(fromEntity2.some(r => r.to === entity1)).toBe(false)
|
|
})
|
|
|
|
it('should delete entity and clean up index', async () => {
|
|
// Arrange
|
|
const id = await brain.add(createAddParams({
|
|
data: 'Searchable entity',
|
|
metadata: { searchable: true }
|
|
}))
|
|
|
|
// Verify it's searchable
|
|
const beforeResults = await brain.find({
|
|
query: 'Searchable entity',
|
|
limit: 10
|
|
})
|
|
expect(beforeResults.some(r => r.entity.id === id)).toBe(true)
|
|
|
|
// Act
|
|
await brain.delete(id)
|
|
|
|
// Assert - Should not be in search results
|
|
const afterResults = await brain.find({
|
|
query: 'Searchable entity',
|
|
limit: 10
|
|
})
|
|
expect(afterResults.some(r => r.entity.id === id)).toBe(false)
|
|
})
|
|
|
|
it('should handle deleting multiple entities', async () => {
|
|
// Arrange
|
|
const ids = await Promise.all([
|
|
brain.add(createAddParams({ data: 'Entity 1' })),
|
|
brain.add(createAddParams({ data: 'Entity 2' })),
|
|
brain.add(createAddParams({ data: 'Entity 3' }))
|
|
])
|
|
|
|
// Act - Delete all
|
|
await Promise.all(ids.map(id => brain.delete(id)))
|
|
|
|
// Assert - All should be gone
|
|
const results = await Promise.all(ids.map(id => brain.get(id)))
|
|
expect(results.every(r => r === null)).toBe(true)
|
|
})
|
|
|
|
it.skip('should handle deleting entity with bidirectional relationships', async () => {
|
|
// Arrange
|
|
const entity1 = await brain.add(createAddParams({ data: 'Entity 1' }))
|
|
const entity2 = await brain.add(createAddParams({ data: 'Entity 2' }))
|
|
|
|
await brain.relate({
|
|
from: entity1,
|
|
to: entity2,
|
|
type: 'friendOf',
|
|
bidirectional: true
|
|
})
|
|
|
|
// Verify both directions exist
|
|
const before1 = await brain.getRelations({ from: entity1 })
|
|
const before2 = await brain.getRelations({ from: entity2 })
|
|
expect(before1.some(r => r.to === entity2)).toBe(true)
|
|
expect(before2.some(r => r.to === entity1)).toBe(true)
|
|
|
|
// Act
|
|
await brain.delete(entity1)
|
|
|
|
// Assert - All relationships should be cleaned up
|
|
const after1 = await brain.getRelations({ from: entity1 })
|
|
const after2 = await brain.getRelations({ from: entity2 })
|
|
expect(after1).toHaveLength(0)
|
|
expect(after2.some(r => r.to === entity1)).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('error paths', () => {
|
|
it('should handle deleting non-existent entity', async () => {
|
|
// Act - Delete non-existent entity (should not throw)
|
|
const nonExistentId = 'fake-id-123'
|
|
|
|
// Should complete without error
|
|
await expect(brain.delete(nonExistentId)).resolves.not.toThrow()
|
|
})
|
|
|
|
it('should handle invalid ID format', async () => {
|
|
// Act & Assert
|
|
await expect(brain.delete('')).resolves.not.toThrow()
|
|
await expect(brain.delete(null as any)).resolves.not.toThrow()
|
|
await expect(brain.delete(undefined as any)).resolves.not.toThrow()
|
|
})
|
|
|
|
it('should handle double deletion', async () => {
|
|
// Arrange
|
|
const id = await brain.add(createAddParams({ data: 'Test' }))
|
|
|
|
// Act - Delete twice
|
|
await brain.delete(id)
|
|
await brain.delete(id) // Should not throw
|
|
|
|
// Assert
|
|
const result = await brain.get(id)
|
|
expect(result).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle deletion with circular relationships', async () => {
|
|
// Arrange - Create circular relationship
|
|
const entity1 = await brain.add(createAddParams({ data: 'Entity 1' }))
|
|
const entity2 = await brain.add(createAddParams({ data: 'Entity 2' }))
|
|
const entity3 = await brain.add(createAddParams({ data: 'Entity 3' }))
|
|
|
|
await brain.relate({ from: entity1, to: entity2, type: 'relatedTo' })
|
|
await brain.relate({ from: entity2, to: entity3, type: 'relatedTo' })
|
|
await brain.relate({ from: entity3, to: entity1, type: 'relatedTo' })
|
|
|
|
// Act
|
|
await brain.delete(entity2)
|
|
|
|
// Assert - Entity2 gone, others remain
|
|
expect(await brain.get(entity2)).toBeNull()
|
|
expect(await brain.get(entity1)).not.toBeNull()
|
|
expect(await brain.get(entity3)).not.toBeNull()
|
|
|
|
// Relationships involving entity2 should be gone
|
|
const relations1 = await brain.getRelations({ from: entity1 })
|
|
const relations3 = await brain.getRelations({ from: entity3 })
|
|
expect(relations1.some(r => r.to === entity2)).toBe(false)
|
|
expect(relations3.some(r => r.to === entity2)).toBe(false)
|
|
})
|
|
|
|
it('should handle concurrent deletions', async () => {
|
|
// Arrange
|
|
const ids = await Promise.all(
|
|
Array.from({ length: 10 }, (_, i) =>
|
|
brain.add(createAddParams({ data: `Entity ${i}` }))
|
|
)
|
|
)
|
|
|
|
// Act - Delete concurrently
|
|
await Promise.all(ids.map(id => brain.delete(id)))
|
|
|
|
// Assert - All should be deleted
|
|
const results = await Promise.all(ids.map(id => brain.get(id)))
|
|
expect(results.every(r => r === null)).toBe(true)
|
|
})
|
|
|
|
it.skip('should maintain data integrity after deletion', async () => {
|
|
// Arrange
|
|
const keepId = await brain.add(createAddParams({
|
|
data: 'Keep this',
|
|
metadata: { important: true }
|
|
}))
|
|
|
|
const deleteIds = await Promise.all([
|
|
brain.add(createAddParams({ data: 'Delete 1' })),
|
|
brain.add(createAddParams({ data: 'Delete 2' })),
|
|
brain.add(createAddParams({ data: 'Delete 3' }))
|
|
])
|
|
|
|
// Create relationships
|
|
for (const deleteId of deleteIds) {
|
|
await brain.relate({
|
|
from: keepId,
|
|
to: deleteId,
|
|
type: 'relatedTo'
|
|
})
|
|
}
|
|
|
|
// Act - Delete some entities
|
|
await Promise.all(deleteIds.map(id => brain.delete(id)))
|
|
|
|
// Assert - Kept entity should be intact
|
|
const kept = await brain.get(keepId)
|
|
expect(kept).not.toBeNull()
|
|
expect(kept!.metadata.important).toBe(true)
|
|
|
|
// Relations to deleted entities should be gone
|
|
const relations = await brain.getRelations({ from: keepId })
|
|
expect(relations).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
describe('performance', () => {
|
|
it('should delete entities quickly', async () => {
|
|
// Arrange
|
|
const id = await brain.add(createAddParams({ data: 'Fast delete' }))
|
|
|
|
// Act & Assert
|
|
const start = Date.now()
|
|
await brain.delete(id)
|
|
const duration = Date.now() - start
|
|
|
|
expect(duration).toBeLessThan(50) // Should be very fast
|
|
})
|
|
|
|
it('should handle batch deletion efficiently', async () => {
|
|
// Arrange - Create many entities
|
|
const ids = await Promise.all(
|
|
Array.from({ length: 100 }, (_, i) =>
|
|
brain.add(createAddParams({ data: `Entity ${i}` }))
|
|
)
|
|
)
|
|
|
|
// Act
|
|
const start = Date.now()
|
|
await Promise.all(ids.map(id => brain.delete(id)))
|
|
const duration = Date.now() - start
|
|
|
|
// Assert
|
|
expect(duration).toBeLessThan(1000) // Should handle 100 deletes in under 1s
|
|
|
|
// Verify all deleted
|
|
const results = await Promise.all(ids.map(id => brain.get(id)))
|
|
expect(results.every(r => r === null)).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('consistency', () => {
|
|
it('should properly invalidate cache after deletion', async () => {
|
|
// Arrange
|
|
const id = await brain.add(createAddParams({
|
|
data: 'Cached entity',
|
|
metadata: { cached: true }
|
|
}))
|
|
|
|
// Do a search to potentially cache results
|
|
const before = await brain.find({ query: 'Cached entity' })
|
|
expect(before.some(r => r.entity.id === id)).toBe(true)
|
|
|
|
// Act
|
|
await brain.delete(id)
|
|
|
|
// Assert - Cache should be invalidated
|
|
const after = await brain.find({ query: 'Cached entity' })
|
|
expect(after.some(r => r.entity.id === id)).toBe(false)
|
|
})
|
|
|
|
it.skip('should maintain consistency across operations', async () => {
|
|
// Arrange
|
|
const entity1 = await brain.add(createAddParams({ data: 'Entity 1' }))
|
|
const entity2 = await brain.add(createAddParams({ data: 'Entity 2' }))
|
|
|
|
await brain.relate({
|
|
from: entity1,
|
|
to: entity2,
|
|
type: 'relatedTo'
|
|
})
|
|
|
|
// Act - Update entity1, delete entity2
|
|
await brain.update({
|
|
id: entity1,
|
|
metadata: { updated: true },
|
|
merge: true
|
|
})
|
|
|
|
await brain.delete(entity2)
|
|
|
|
// Assert
|
|
const e1 = await brain.get(entity1)
|
|
expect(e1).not.toBeNull()
|
|
expect(e1!.metadata.updated).toBe(true)
|
|
|
|
const e2 = await brain.get(entity2)
|
|
expect(e2).toBeNull()
|
|
|
|
// Relations should be cleaned up
|
|
const relations = await brain.getRelations({ from: entity1 })
|
|
expect(relations.some(r => r.to === entity2)).toBe(false)
|
|
})
|
|
})
|
|
}) |