brainy/tests/vfs/semantic/parser.unit.test.ts
David Snelling dd50d89ad6 feat: add neural extraction APIs with NounType taxonomy
Add brain.extract() and brain.extractConcepts() methods that use
NeuralEntityExtractor with embeddings and sophisticated NounType
taxonomy (30+ entity types) for semantic entity and concept extraction.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-29 13:51:47 -07:00

424 lines
No EOL
14 KiB
TypeScript

/**
* Tests for SemanticPathParser
* REAL TESTS - No mocks, pure logic testing
*/
import { describe, test, expect } from 'vitest'
import {
SemanticPathParser,
ParsedSemanticPath,
RelationshipValue,
SimilarityValue
} from '../../../src/vfs/semantic/SemanticPathParser.js'
describe('SemanticPathParser', () => {
const parser = new SemanticPathParser()
describe('Traditional Paths', () => {
test('parses simple traditional path', () => {
const result = parser.parse('/src/auth.ts')
expect(result.dimension).toBe('traditional')
expect(result.value).toBe('/src/auth.ts')
expect(result.subpath).toBeUndefined()
})
test('parses nested traditional path', () => {
const result = parser.parse('/src/lib/utils/helper.ts')
expect(result.dimension).toBe('traditional')
expect(result.value).toBe('/src/lib/utils/helper.ts')
})
test('parses root path', () => {
const result = parser.parse('/')
expect(result.dimension).toBe('traditional')
expect(result.value).toBe('/')
})
test('normalizes paths with trailing slash', () => {
const result = parser.parse('/src/auth.ts/')
expect(result.dimension).toBe('traditional')
expect(result.value).toBe('/src/auth.ts')
})
test('normalizes paths with multiple slashes', () => {
const result = parser.parse('//src///auth.ts')
expect(result.dimension).toBe('traditional')
expect(result.value).toBe('/src/auth.ts')
})
test('adds leading slash if missing', () => {
const result = parser.parse('src/auth.ts')
expect(result.dimension).toBe('traditional')
expect(result.value).toBe('/src/auth.ts')
})
})
describe('Concept Paths', () => {
test('parses concept path without subpath', () => {
const result = parser.parse('/by-concept/authentication')
expect(result.dimension).toBe('concept')
expect(result.value).toBe('authentication')
expect(result.subpath).toBeUndefined()
})
test('parses concept path with subpath', () => {
const result = parser.parse('/by-concept/authentication/login.ts')
expect(result.dimension).toBe('concept')
expect(result.value).toBe('authentication')
expect(result.subpath).toBe('login.ts')
})
test('parses concept path with nested subpath', () => {
const result = parser.parse('/by-concept/security/src/auth/login.ts')
expect(result.dimension).toBe('concept')
expect(result.value).toBe('security')
expect(result.subpath).toBe('src/auth/login.ts')
})
test('parses concept with dashes', () => {
const result = parser.parse('/by-concept/dependency-injection/service.ts')
expect(result.dimension).toBe('concept')
expect(result.value).toBe('dependency-injection')
expect(result.subpath).toBe('service.ts')
})
})
describe('Author Paths', () => {
test('parses author path without subpath', () => {
const result = parser.parse('/by-author/alice')
expect(result.dimension).toBe('author')
expect(result.value).toBe('alice')
expect(result.subpath).toBeUndefined()
})
test('parses author path with subpath', () => {
const result = parser.parse('/by-author/alice/code.ts')
expect(result.dimension).toBe('author')
expect(result.value).toBe('alice')
expect(result.subpath).toBe('code.ts')
})
test('parses author path with nested subpath', () => {
const result = parser.parse('/by-author/bob/projects/2024/file.ts')
expect(result.dimension).toBe('author')
expect(result.value).toBe('bob')
expect(result.subpath).toBe('projects/2024/file.ts')
})
})
describe('Time Paths', () => {
test('parses time path without subpath', () => {
const result = parser.parse('/as-of/2024-03-15')
expect(result.dimension).toBe('time')
expect(result.value).toBeInstanceOf(Date)
expect((result.value as Date).getFullYear()).toBe(2024)
expect((result.value as Date).getMonth()).toBe(2) // March is index 2
expect((result.value as Date).getDate()).toBe(15)
expect(result.subpath).toBeUndefined()
})
test('parses time path with subpath', () => {
const result = parser.parse('/as-of/2024-03-15/src/auth.ts')
expect(result.dimension).toBe('time')
expect(result.value).toBeInstanceOf(Date)
expect(result.subpath).toBe('src/auth.ts')
})
test('treats invalid date format as traditional path', () => {
// Doesn't match the time pattern, so becomes traditional
const result = parser.parse('/as-of/2024-03/file.ts')
expect(result.dimension).toBe('traditional')
})
test('treats invalid date components as traditional path', () => {
// Doesn't match the time pattern, so becomes traditional
const result = parser.parse('/as-of/invalid-date/file.ts')
expect(result.dimension).toBe('traditional')
})
test('throws on invalid year', () => {
expect(() => parser.parse('/as-of/1800-03-15/file.ts')).toThrow('Invalid year')
})
test('throws on invalid month', () => {
expect(() => parser.parse('/as-of/2024-13-15/file.ts')).toThrow('Invalid month')
})
test('throws on invalid day', () => {
expect(() => parser.parse('/as-of/2024-03-32/file.ts')).toThrow('Invalid day')
})
})
describe('Relationship Paths', () => {
test('parses relationship path without depth', () => {
const result = parser.parse('/related-to/src/auth.ts')
expect(result.dimension).toBe('relationship')
const value = result.value as RelationshipValue
expect(value.targetPath).toBe('src/auth.ts')
expect(value.depth).toBeUndefined()
expect(value.relationshipTypes).toBeUndefined()
})
test('parses relationship path with depth', () => {
const result = parser.parse('/related-to/src/auth.ts/depth-2')
expect(result.dimension).toBe('relationship')
const value = result.value as RelationshipValue
expect(value.targetPath).toBe('src/auth.ts')
expect(value.depth).toBe(2)
})
test('parses relationship path with types', () => {
const result = parser.parse('/related-to/src/auth.ts/types-uses,imports')
expect(result.dimension).toBe('relationship')
const value = result.value as RelationshipValue
expect(value.targetPath).toBe('src/auth.ts')
expect(value.relationshipTypes).toEqual(['uses', 'imports'])
})
test('parses relationship path with depth, types, and subpath', () => {
const result = parser.parse('/related-to/src/auth.ts/depth-3/types-uses,imports/helper.ts')
expect(result.dimension).toBe('relationship')
const value = result.value as RelationshipValue
expect(value.targetPath).toBe('src/auth.ts')
expect(value.depth).toBe(3)
expect(value.relationshipTypes).toEqual(['uses', 'imports'])
expect(result.subpath).toBe('helper.ts')
})
})
describe('Similarity Paths', () => {
test('parses similarity path without threshold', () => {
const result = parser.parse('/similar-to/src/auth.ts')
expect(result.dimension).toBe('similar')
const value = result.value as SimilarityValue
expect(value.targetPath).toBe('src/auth.ts')
expect(value.threshold).toBeUndefined()
})
test('parses similarity path with threshold', () => {
const result = parser.parse('/similar-to/src/auth.ts/threshold-0.85')
expect(result.dimension).toBe('similar')
const value = result.value as SimilarityValue
expect(value.targetPath).toBe('src/auth.ts')
expect(value.threshold).toBe(0.85)
})
test('parses similarity path with threshold and subpath', () => {
const result = parser.parse('/similar-to/src/auth.ts/threshold-0.7/login.ts')
expect(result.dimension).toBe('similar')
const value = result.value as SimilarityValue
expect(value.targetPath).toBe('src/auth.ts')
expect(value.threshold).toBe(0.7)
expect(result.subpath).toBe('login.ts')
})
})
describe('Tag Paths', () => {
test('parses tag path without subpath', () => {
const result = parser.parse('/by-tag/security')
expect(result.dimension).toBe('tag')
expect(result.value).toBe('security')
expect(result.subpath).toBeUndefined()
})
test('parses tag path with subpath', () => {
const result = parser.parse('/by-tag/security/auth.ts')
expect(result.dimension).toBe('tag')
expect(result.value).toBe('security')
expect(result.subpath).toBe('auth.ts')
})
test('parses tag with dashes', () => {
const result = parser.parse('/by-tag/code-review/file.ts')
expect(result.dimension).toBe('tag')
expect(result.value).toBe('code-review')
expect(result.subpath).toBe('file.ts')
})
})
describe('isSemanticPath', () => {
test('returns false for traditional paths', () => {
expect(parser.isSemanticPath('/src/auth.ts')).toBe(false)
expect(parser.isSemanticPath('/projects/main/file.ts')).toBe(false)
expect(parser.isSemanticPath('/')).toBe(false)
})
test('returns true for concept paths', () => {
expect(parser.isSemanticPath('/by-concept/auth')).toBe(true)
expect(parser.isSemanticPath('/by-concept/auth/file.ts')).toBe(true)
})
test('returns true for author paths', () => {
expect(parser.isSemanticPath('/by-author/alice')).toBe(true)
})
test('returns true for time paths', () => {
expect(parser.isSemanticPath('/as-of/2024-03-15')).toBe(true)
})
test('returns true for relationship paths', () => {
expect(parser.isSemanticPath('/related-to/src/auth.ts')).toBe(true)
})
test('returns true for similarity paths', () => {
expect(parser.isSemanticPath('/similar-to/src/auth.ts')).toBe(true)
})
test('returns true for tag paths', () => {
expect(parser.isSemanticPath('/by-tag/security')).toBe(true)
})
test('handles invalid input gracefully', () => {
expect(parser.isSemanticPath('')).toBe(false)
expect(parser.isSemanticPath(null as any)).toBe(false)
expect(parser.isSemanticPath(undefined as any)).toBe(false)
})
})
describe('getDimension', () => {
test('returns correct dimension for each path type', () => {
expect(parser.getDimension('/src/auth.ts')).toBe('traditional')
expect(parser.getDimension('/by-concept/auth')).toBe('concept')
expect(parser.getDimension('/by-author/alice')).toBe('author')
expect(parser.getDimension('/as-of/2024-03-15')).toBe('time')
expect(parser.getDimension('/related-to/src/file.ts')).toBe('relationship')
expect(parser.getDimension('/similar-to/src/file.ts')).toBe('similar')
expect(parser.getDimension('/by-tag/security')).toBe('tag')
})
})
describe('validate', () => {
test('validates correct parsed paths', () => {
const valid: ParsedSemanticPath = {
dimension: 'concept',
value: 'authentication'
}
expect(parser.validate(valid)).toBe(true)
})
test('validates time paths with Date objects', () => {
const valid: ParsedSemanticPath = {
dimension: 'time',
value: new Date('2024-03-15')
}
expect(parser.validate(valid)).toBe(true)
})
test('validates relationship paths', () => {
const valid: ParsedSemanticPath = {
dimension: 'relationship',
value: { targetPath: '/src/auth.ts', depth: 2 }
}
expect(parser.validate(valid)).toBe(true)
})
test('validates similarity paths', () => {
const valid: ParsedSemanticPath = {
dimension: 'similar',
value: { targetPath: '/src/auth.ts', threshold: 0.8 }
}
expect(parser.validate(valid)).toBe(true)
})
test('rejects invalid structures', () => {
expect(parser.validate(null as any)).toBe(false)
expect(parser.validate(undefined as any)).toBe(false)
expect(parser.validate({} as any)).toBe(false)
})
test('rejects missing dimension', () => {
expect(parser.validate({ value: 'test' } as any)).toBe(false)
})
test('rejects missing value', () => {
expect(parser.validate({ dimension: 'concept' } as any)).toBe(false)
})
test('rejects invalid time values', () => {
const invalid: ParsedSemanticPath = {
dimension: 'time',
value: 'not-a-date' as any
}
expect(parser.validate(invalid)).toBe(false)
})
test('rejects invalid relationship values', () => {
const invalid: ParsedSemanticPath = {
dimension: 'relationship',
value: { targetPath: '' } as any
}
expect(parser.validate(invalid)).toBe(false)
})
})
describe('Error Handling', () => {
test('throws on null path', () => {
expect(() => parser.parse(null as any)).toThrow('Path must be a non-empty string')
})
test('throws on undefined path', () => {
expect(() => parser.parse(undefined as any)).toThrow('Path must be a non-empty string')
})
test('throws on empty string', () => {
expect(() => parser.parse('')).toThrow('Path must be a non-empty string')
})
test('throws on non-string path', () => {
expect(() => parser.parse(123 as any)).toThrow('Path must be a non-empty string')
})
})
describe('Edge Cases', () => {
test('handles paths with special characters in concepts', () => {
const result = parser.parse('/by-concept/oauth-2.0/auth.ts')
expect(result.dimension).toBe('concept')
expect(result.value).toBe('oauth-2.0')
})
test('handles paths with underscores', () => {
const result = parser.parse('/by-concept/user_authentication/login.ts')
expect(result.dimension).toBe('concept')
expect(result.value).toBe('user_authentication')
})
test('handles deeply nested subpaths', () => {
const result = parser.parse('/by-concept/auth/src/lib/utils/helpers/validators/email.ts')
expect(result.dimension).toBe('concept')
expect(result.value).toBe('auth')
expect(result.subpath).toBe('src/lib/utils/helpers/validators/email.ts')
})
test('handles paths with dots', () => {
const result = parser.parse('/by-author/alice/v2.0.0/file.ts')
expect(result.dimension).toBe('author')
expect(result.value).toBe('alice')
expect(result.subpath).toBe('v2.0.0/file.ts')
})
})
})