UNPKG

@wearesage/schema

Version:

A flexible schema definition and validation system for TypeScript with multi-database support

153 lines (124 loc) 5.97 kB
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 @Entity() 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 }); });