@wearesage/schema
Version:
A flexible schema definition and validation system for TypeScript with multi-database support
153 lines (124 loc) • 5.97 kB
text/typescript
import "reflect-metadata";
import { SchemaReflector } from "../../core/SchemaReflector";
import { MetadataRegistry } from "../../core/MetadataRegistry";
import { SchemaBuilder } from "../../core/SchemaBuilder";
import { Entity } from "../../core/decorators";
import { User } from "../../examples/blog/User";
import { Post } from "../../examples/blog/Post";
import { Tag } from "../../examples/blog/Tag";
describe("SchemaReflector", () => {
let registry: MetadataRegistry;
let builder: SchemaBuilder;
let reflector: SchemaReflector;
beforeEach(() => {
registry = new MetadataRegistry();
builder = new SchemaBuilder(registry);
reflector = new SchemaReflector(registry);
// Register example entities
builder.registerEntities([User, Post, Tag]);
});
test("should get entity schema with properties and relationships", () => {
// Get schema for User entity
const userSchema = reflector.getEntitySchema(User);
// Check entity metadata
expect(userSchema).toBeDefined();
expect(userSchema.name).toBe("User");
// Check properties
expect(userSchema.properties).toBeDefined();
expect(userSchema.properties.name).toEqual({ required: true });
expect(userSchema.properties.email).toEqual({ required: true, unique: true });
// Check ID properties
expect(userSchema.idProperties).toContain("id");
// Check relationships
expect(userSchema.relationships).toBeDefined();
expect(userSchema.relationships.posts).toBeDefined();
expect(userSchema.relationships.posts.name).toBe("AUTHORED");
expect(userSchema.relationships.posts.target).toBe(Post);
});
test("should validate entity with required properties", () => {
// Create valid entity
const user = new User();
user.id = "123";
user.name = "Test User";
user.email = "test@example.com";
// Validate valid entity
const validResult = reflector.validateEntity(user);
expect(validResult.valid).toBe(true);
expect(validResult.errors).toEqual([]);
// Create invalid entity (missing required property)
const invalidUser = new User();
invalidUser.id = "456";
// Missing name and email which are required
// Validate invalid entity
const invalidResult = reflector.validateEntity(invalidUser);
expect(invalidResult.valid).toBe(false);
expect(invalidResult.errors.length).toBeGreaterThan(0);
expect(invalidResult.errors).toContain("Required property 'name' is missing");
expect(invalidResult.errors).toContain("Required property 'email' is missing");
});
test("should infer relationship types when possible", () => {
// Test inferring one-to-many relationship type
const postsRelType = reflector.inferRelationshipType(User.prototype, "posts");
expect(postsRelType).toBe("one-to-many");
// Test inferring many-to-one relationship type
const authorRelType = reflector.inferRelationshipType(Post.prototype, "author");
expect(authorRelType).toBe("many-to-one");
// Test inferring many-to-many relationship type
const tagsRelType = reflector.inferRelationshipType(Post.prototype, "tags");
expect(tagsRelType).toBe("many-to-many");
});
test("should throw error for non-entity classes", () => {
// Define a non-entity class
class NonEntity {}
// Attempting to get schema for a non-entity class should throw
expect(() => reflector.getEntitySchema(NonEntity)).toThrow(/not decorated as an Entity/);
});
test("should return invalid result for non-entity instances", () => {
// Create an instance of a non-entity class
class NonEntity {}
const instance = new NonEntity();
// Validating a non-entity instance should return invalid result
const result = reflector.validateEntity(instance);
expect(result.valid).toBe(false);
expect(result.errors).toContain("Entity class is not properly decorated");
});
test("should handle case where explicit relationship type is defined", () => {
// Mock scenario where relationship type is explicitly defined
const mockTarget = {};
// Define explicit metadata on mock target
Reflect.defineMetadata('relationship:type', 'one-to-many', mockTarget, 'mockProperty');
// Test inference with explicit type
const relType = reflector.inferRelationshipType(mockTarget, 'mockProperty');
expect(relType).toBe('one-to-many');
});
test("should handle undefined relationship types", () => {
// Define a class with a non-entity property
class TestClass {
regularProperty: string;
}
// Inference should return undefined for non-relationship properties
const relType = reflector.inferRelationshipType(TestClass.prototype, 'regularProperty');
expect(relType).toBeUndefined();
});
test("should handle array properties with design type metadata", () => {
// Mock an array property with design:type metadata
const mockTarget = {};
Reflect.defineMetadata('design:type', Array, mockTarget, 'arrayProperty');
// Test inference with array type
const relType = reflector.inferRelationshipType(mockTarget, 'arrayProperty');
expect(relType).toBeUndefined(); // Should return undefined since we can't determine more specifics
});
test("should handle entity properties with design type metadata", () => {
// Create a class hierarchy to test entity type detection
()
class TestEntity {}
const mockTarget = {};
// Set design:type metadata to be an entity class
Reflect.defineMetadata('design:type', TestEntity, mockTarget, 'entityProperty');
// Register the entity in the registry first
registry.registerEntity(TestEntity);
// Test inference with entity type
const relType = reflector.inferRelationshipType(mockTarget, 'entityProperty');
expect(relType).toBeUndefined(); // Should return undefined since we can't determine cardinality
});
});