441 lines
No EOL
11 KiB
TypeScript
441 lines
No EOL
11 KiB
TypeScript
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
|
import { Brainy } from '../src/brainy'
|
|
|
|
describe('CRITICAL: Error Handling and Edge Cases', () => {
|
|
let brainy: Brainy
|
|
|
|
beforeAll(async () => {
|
|
brainy = new Brainy({ requireSubtype: false,
|
|
storage: { type: 'memory' }
|
|
})
|
|
await brainy.init()
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await brainy.close()
|
|
})
|
|
|
|
describe('Invalid Input Handling', () => {
|
|
it('should handle null and undefined gracefully', async () => {
|
|
await expect(brainy.add({
|
|
data: null as any,
|
|
type: 'document'
|
|
})).rejects.toThrow()
|
|
|
|
await expect(brainy.add({
|
|
data: undefined as any,
|
|
type: 'document'
|
|
})).rejects.toThrow()
|
|
|
|
const result = await brainy.get(null as any)
|
|
expect(result).toBeNull()
|
|
})
|
|
|
|
it('should validate noun types', async () => {
|
|
await expect(brainy.add({
|
|
data: { content: 'test' },
|
|
type: 'invalid-type' as any
|
|
})).rejects.toThrow()
|
|
})
|
|
|
|
it('should validate verb types in relationships', async () => {
|
|
await brainy.add({ id: 'node1', data: { name: 'Node 1' }, type: 'entity' })
|
|
await brainy.add({ id: 'node2', data: { name: 'Node 2' }, type: 'entity' })
|
|
|
|
await expect(brainy.relate({
|
|
from: 'node1',
|
|
to: 'node2',
|
|
type: 'invalid-verb' as any
|
|
})).rejects.toThrow()
|
|
})
|
|
|
|
it('should handle extremely long strings', async () => {
|
|
const longString = 'x'.repeat(1000000)
|
|
|
|
const id = await brainy.add({
|
|
data: { content: longString },
|
|
type: 'document'
|
|
})
|
|
|
|
expect(id).toBeDefined()
|
|
|
|
const retrieved = await brainy.get(id)
|
|
expect(retrieved).toBeDefined()
|
|
})
|
|
|
|
it('should handle circular references in data', async () => {
|
|
const circular: any = { name: 'test' }
|
|
circular.self = circular
|
|
|
|
await expect(brainy.add({
|
|
data: circular,
|
|
type: 'entity'
|
|
})).rejects.toThrow()
|
|
})
|
|
|
|
it('should handle special characters in IDs', async () => {
|
|
const specialIds = [
|
|
'id with spaces',
|
|
'id/with/slashes',
|
|
'id\\with\\backslashes',
|
|
'id:with:colons',
|
|
'id|with|pipes',
|
|
'id"with"quotes',
|
|
'id<with>brackets'
|
|
]
|
|
|
|
for (const id of specialIds) {
|
|
const result = await brainy.add({
|
|
id,
|
|
data: { content: `Test ${id}` },
|
|
type: 'document'
|
|
})
|
|
|
|
expect(result).toBe(id)
|
|
|
|
const retrieved = await brainy.get(id)
|
|
expect(retrieved).toBeDefined()
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('Concurrent Operation Safety', () => {
|
|
it('should handle concurrent writes to same ID', async () => {
|
|
const id = 'concurrent-test'
|
|
|
|
const promises = Array.from({ length: 10 }, (_, i) =>
|
|
brainy.add({
|
|
id,
|
|
data: { content: `Version ${i}` },
|
|
type: 'document'
|
|
})
|
|
)
|
|
|
|
await Promise.all(promises)
|
|
|
|
const final = await brainy.get(id)
|
|
expect(final).toBeDefined()
|
|
})
|
|
|
|
it('should handle concurrent updates', async () => {
|
|
const id = await brainy.add({
|
|
data: { counter: 0 },
|
|
type: 'entity'
|
|
})
|
|
|
|
const updates = Array.from({ length: 10 }, (_, i) =>
|
|
brainy.update({
|
|
id,
|
|
data: { counter: i }
|
|
})
|
|
)
|
|
|
|
await Promise.all(updates)
|
|
|
|
const final = await brainy.get(id)
|
|
expect(final).toBeDefined()
|
|
})
|
|
|
|
it('should handle concurrent deletes', async () => {
|
|
const ids = await Promise.all(
|
|
Array.from({ length: 10 }, (_, i) =>
|
|
brainy.add({
|
|
data: { index: i },
|
|
type: 'entity'
|
|
})
|
|
)
|
|
)
|
|
|
|
const deletes = ids.map(id => brainy.remove(id))
|
|
await Promise.all(deletes)
|
|
|
|
for (const id of ids) {
|
|
const result = await brainy.get(id)
|
|
expect(result).toBeNull()
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('Memory and Resource Management', () => {
|
|
it('should not leak memory on failed operations', async () => {
|
|
const memBefore = process.memoryUsage().heapUsed
|
|
|
|
for (let i = 0; i < 1000; i++) {
|
|
try {
|
|
await brainy.add({
|
|
data: null as any,
|
|
type: 'invalid' as any
|
|
})
|
|
} catch {
|
|
// Expected to fail
|
|
}
|
|
}
|
|
|
|
global.gc?.()
|
|
const memAfter = process.memoryUsage().heapUsed
|
|
const leak = memAfter - memBefore
|
|
|
|
expect(leak).toBeLessThan(10 * 1024 * 1024) // Less than 10MB
|
|
})
|
|
|
|
it('should handle out of memory scenarios gracefully', async () => {
|
|
const hugeArray = Array.from({ length: 100000 }, (_, i) => ({
|
|
id: `huge-${i}`,
|
|
data: {
|
|
content: 'x'.repeat(10000),
|
|
metadata: Array.from({ length: 100 }, () => Math.random())
|
|
},
|
|
type: 'document' as const
|
|
}))
|
|
|
|
try {
|
|
await brainy.addMany({ items: hugeArray })
|
|
} catch (error: any) {
|
|
expect(error).toBeDefined()
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('Query Edge Cases', () => {
|
|
it('should handle empty search queries', async () => {
|
|
const results = await brainy.find('')
|
|
expect(Array.isArray(results)).toBe(true)
|
|
})
|
|
|
|
it('should handle queries with only special characters', async () => {
|
|
const specialQueries = [
|
|
'!!!',
|
|
'???',
|
|
'...',
|
|
'---',
|
|
'___',
|
|
'@#$%^&*()',
|
|
'{}[]|\\',
|
|
'<>?/~`'
|
|
]
|
|
|
|
for (const query of specialQueries) {
|
|
const results = await brainy.find(query)
|
|
expect(Array.isArray(results)).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('should handle metadata filters with undefined values', async () => {
|
|
await brainy.add({
|
|
data: { name: 'Test', value: undefined },
|
|
type: 'entity'
|
|
})
|
|
|
|
const results = await brainy.find({
|
|
where: { value: undefined as any }
|
|
})
|
|
|
|
expect(Array.isArray(results)).toBe(true)
|
|
})
|
|
|
|
it('should handle complex nested metadata queries', async () => {
|
|
await brainy.add({
|
|
data: {
|
|
nested: {
|
|
deep: {
|
|
deeper: {
|
|
value: 42
|
|
}
|
|
}
|
|
}
|
|
},
|
|
type: 'entity'
|
|
})
|
|
|
|
const results = await brainy.find({
|
|
where: {
|
|
'nested.deep.deeper.value': 42
|
|
}
|
|
})
|
|
|
|
expect(results.length).toBeGreaterThan(0)
|
|
})
|
|
})
|
|
|
|
describe('Relationship Error Cases', () => {
|
|
it('should handle relationships to non-existent nodes', async () => {
|
|
await expect(brainy.relate({
|
|
from: 'non-existent-1',
|
|
to: 'non-existent-2',
|
|
type: 'relatedTo'
|
|
})).rejects.toThrow()
|
|
})
|
|
|
|
it('should handle self-referential relationships', async () => {
|
|
const id = await brainy.add({
|
|
data: { name: 'Self' },
|
|
type: 'entity'
|
|
})
|
|
|
|
const relId = await brainy.relate({
|
|
from: id,
|
|
to: id,
|
|
type: 'relatedTo'
|
|
})
|
|
|
|
expect(relId).toBeDefined()
|
|
|
|
const relations = await brainy.related({ from: id })
|
|
expect(relations.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('should handle duplicate relationships', async () => {
|
|
const id1 = await brainy.add({ data: { name: 'A' }, type: 'entity' })
|
|
const id2 = await brainy.add({ data: { name: 'B' }, type: 'entity' })
|
|
|
|
const rel1 = await brainy.relate({
|
|
from: id1,
|
|
to: id2,
|
|
type: 'relatedTo'
|
|
})
|
|
|
|
const rel2 = await brainy.relate({
|
|
from: id1,
|
|
to: id2,
|
|
type: 'relatedTo'
|
|
})
|
|
|
|
expect(rel1).not.toBe(rel2)
|
|
})
|
|
})
|
|
|
|
describe('Data Validation', () => {
|
|
it('should validate vector dimensions', async () => {
|
|
const wrongDimVector = new Array(100).fill(0)
|
|
|
|
await brainy.add({
|
|
data: { test: 'first' },
|
|
type: 'entity'
|
|
})
|
|
|
|
await expect(brainy.add({
|
|
vector: wrongDimVector,
|
|
type: 'entity'
|
|
})).rejects.toThrow()
|
|
})
|
|
|
|
it('should handle NaN and Infinity in vectors', async () => {
|
|
const badVector = [NaN, Infinity, -Infinity, 0.5]
|
|
|
|
await expect(brainy.add({
|
|
vector: badVector as any,
|
|
type: 'entity'
|
|
})).rejects.toThrow()
|
|
})
|
|
|
|
it('should validate metadata field names', async () => {
|
|
const invalidMetadata = {
|
|
'$invalid': 'value',
|
|
'also.invalid': 'value',
|
|
'normal': 'value'
|
|
}
|
|
|
|
const id = await brainy.add({
|
|
data: invalidMetadata,
|
|
type: 'entity'
|
|
})
|
|
|
|
const retrieved = await brainy.get(id)
|
|
expect(retrieved).toBeDefined()
|
|
})
|
|
})
|
|
|
|
describe('Recovery and Cleanup', () => {
|
|
it('should recover from partial batch failures', async () => {
|
|
const items = [
|
|
{ id: 'valid1', data: { content: 'Valid 1' }, type: 'document' as const },
|
|
{ id: 'invalid', data: null as any, type: 'invalid' as any },
|
|
{ id: 'valid2', data: { content: 'Valid 2' }, type: 'document' as const }
|
|
]
|
|
|
|
const result = await brainy.addMany({ items })
|
|
|
|
expect(result.successful).toBeGreaterThan(0)
|
|
expect(result.failed).toBeGreaterThan(0)
|
|
expect(result.errors.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('should clean up after failed operations', async () => {
|
|
const memBefore = process.memoryUsage().heapUsed
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
try {
|
|
await brainy.add({
|
|
id: `cleanup-${i}`,
|
|
data: { content: 'test' },
|
|
type: 'entity'
|
|
})
|
|
|
|
await brainy.remove(`cleanup-${i}`)
|
|
} catch {
|
|
// Ignore errors
|
|
}
|
|
}
|
|
|
|
global.gc?.()
|
|
const memAfter = process.memoryUsage().heapUsed
|
|
const diff = memAfter - memBefore
|
|
|
|
expect(diff).toBeLessThan(5 * 1024 * 1024) // Less than 5MB growth
|
|
})
|
|
})
|
|
|
|
describe('Boundary Conditions', () => {
|
|
it('should handle zero-length arrays and strings', async () => {
|
|
const id = await brainy.add({
|
|
data: {
|
|
emptyString: '',
|
|
emptyArray: [],
|
|
zero: 0,
|
|
false: false,
|
|
null: null
|
|
},
|
|
type: 'entity'
|
|
})
|
|
|
|
const retrieved = await brainy.get(id)
|
|
expect(retrieved?.data.emptyString).toBe('')
|
|
expect(retrieved?.data.emptyArray).toEqual([])
|
|
})
|
|
|
|
it('should handle maximum safe integers', async () => {
|
|
const id = await brainy.add({
|
|
data: {
|
|
maxSafe: Number.MAX_SAFE_INTEGER,
|
|
minSafe: Number.MIN_SAFE_INTEGER,
|
|
maxValue: Number.MAX_VALUE,
|
|
minValue: Number.MIN_VALUE
|
|
},
|
|
type: 'entity'
|
|
})
|
|
|
|
const retrieved = await brainy.get(id)
|
|
expect(retrieved?.data.maxSafe).toBe(Number.MAX_SAFE_INTEGER)
|
|
})
|
|
|
|
it('should handle Unicode and emoji correctly', async () => {
|
|
const unicodeData = {
|
|
emoji: '😀🎉🚀❤️',
|
|
chinese: '你好世界',
|
|
arabic: 'مرحبا بالعالم',
|
|
hebrew: 'שלום עולם',
|
|
special: '™®©℠',
|
|
zalgo: 'ẗ̸̢̼͉͈́̏͊̈ë̵̺́͊s̴̱̈́ẗ̸͎̆'
|
|
}
|
|
|
|
const id = await brainy.add({
|
|
data: unicodeData,
|
|
type: 'entity'
|
|
})
|
|
|
|
const retrieved = await brainy.get(id)
|
|
expect(retrieved?.data.emoji).toBe(unicodeData.emoji)
|
|
expect(retrieved?.data.chinese).toBe(unicodeData.chinese)
|
|
})
|
|
})
|
|
}) |