@wearesage/schema
Version:
A flexible schema definition and validation system for TypeScript with multi-database support
167 lines (138 loc) • 5.08 kB
text/typescript
import "reflect-metadata";
import { Neo4jAdapter } from "../../adapters/neo4j";
import { MetadataRegistry } from "../../core/MetadataRegistry";
import { SchemaBuilder } from "../../core/SchemaBuilder";
import { Entity, Property, Id, Labels } from "../../core/decorators";
// Simple test entity to isolate the property extraction bug
()
(['TestEntity'])
class SimpleTestEntity {
()
id!: string;
({ required: true })
name!: string;
()
description?: string;
()
count?: number;
}
describe('Neo4jAdapter Property Extraction Bug', () => {
let adapter: Neo4jAdapter;
let registry: MetadataRegistry;
beforeAll(async () => {
registry = new MetadataRegistry();
const builder = new SchemaBuilder(registry);
builder.registerEntities([SimpleTestEntity]);
adapter = new Neo4jAdapter(registry, "bolt://127.0.0.1:7687", {
username: "neo4j",
password: "password"
});
// Skip if Neo4j not available
try {
await adapter.runNativeQuery("RETURN 1");
} catch (error) {
console.log("⚠️ Skipping Neo4j tests - database not available");
return;
}
});
afterAll(async () => {
try {
// Clean up any test data
await adapter.runNativeQuery("MATCH (n:TestEntity) DETACH DELETE n");
if (typeof (adapter as any).close === 'function') {
await (adapter as any).close();
}
} catch (error) {
// Ignore cleanup errors
}
});
beforeEach(async () => {
// Skip if Neo4j not available
try {
await adapter.runNativeQuery("RETURN 1");
} catch (error) {
return;
}
});
it('should extract properties correctly from entity', async () => {
const entity = new SimpleTestEntity();
entity.id = 'test-123';
entity.name = 'Test Entity';
entity.description = 'A test entity for property extraction';
entity.count = 42;
// Debug: Check what the schema reflector sees
const entityType = entity.constructor;
const schema = (adapter as any).reflector.getEntitySchema(entityType);
console.log('🔍 Entity schema:', JSON.stringify(schema, null, 2));
// Access the private method using type assertion
const properties = (adapter as any).entityToProperties(entity);
console.log('🔍 Extracted properties:', properties);
expect(properties).toEqual({
id: 'test-123',
name: 'Test Entity',
description: 'A test entity for property extraction',
count: 42
});
});
it('should handle entities with only id property', async () => {
const entity = new SimpleTestEntity();
entity.id = 'test-456';
// Only set id, no other properties
const properties = (adapter as any).entityToProperties(entity);
// Should still return the id
expect(properties).toEqual({
id: 'test-456'
});
});
it('should generate valid Cypher when entity has multiple properties', async () => {
const entity = new SimpleTestEntity();
entity.id = 'test-multi-' + Date.now();
entity.name = 'Multi Property Test';
entity.description = 'Testing multiple properties';
entity.count = 99;
// This should NOT throw a syntax error
await expect(adapter.save(entity)).resolves.not.toThrow();
// Verify it was saved
const result = await adapter.query(SimpleTestEntity, { id: entity.id });
expect(result).toBeTruthy();
expect(result).toMatchObject({
id: entity.id,
name: 'Multi Property Test',
description: 'Testing multiple properties',
count: 99
});
// Clean up
await adapter.delete(SimpleTestEntity, entity.id);
});
it('should generate valid Cypher when entity has only id (the bug case)', async () => {
const entity = new SimpleTestEntity();
entity.id = 'test-id-only-' + Date.now();
// Intentionally leave other properties undefined to trigger the bug
// This should NOT throw a syntax error - this is the bug we're fixing!
await expect(adapter.save(entity)).resolves.not.toThrow();
// Verify it was saved (even with minimal data)
const result = await adapter.query(SimpleTestEntity, { id: entity.id });
expect(result).toBeTruthy();
expect(result?.id).toBe(entity.id);
// Clean up
await adapter.delete(SimpleTestEntity, entity.id);
});
it('should handle entities with some undefined properties', async () => {
const entity = new SimpleTestEntity();
entity.id = 'test-partial-' + Date.now();
entity.name = 'Partial Entity';
// description and count are undefined
await expect(adapter.save(entity)).resolves.not.toThrow();
const result = await adapter.query(SimpleTestEntity, { id: entity.id });
expect(result).toBeTruthy();
expect(result).toMatchObject({
id: entity.id,
name: 'Partial Entity'
});
// undefined properties shouldn't be in the result
expect(result?.description).toBeUndefined();
expect(result?.count).toBeUndefined();
// Clean up
await adapter.delete(SimpleTestEntity, entity.id);
});
});