@wearesage/schema
Version:
A flexible schema definition and validation system for TypeScript with multi-database support
219 lines (169 loc) • 9.5 kB
text/typescript
import {
MetadataRegistry,
SchemaBuilder
} from "../../core";
import { User } from "../../examples/blog/User";
import { Post } from "../../examples/blog/Post";
import { Tag } from "../../examples/blog/Tag";
import "reflect-metadata";
describe("SchemaBuilder", () => {
test("should register entities with their properties and relationships", () => {
// Create registry and builder
const registry = new MetadataRegistry();
const builder = new SchemaBuilder(registry);
// Register entities
builder.registerEntities([User, Post]);
// Verify entity registration
expect(registry.getEntityMetadata(User)).toBeDefined();
expect(registry.getEntityMetadata(Post)).toBeDefined();
// Verify property registration
expect(registry.getPropertyMetadata(User, "name")).toEqual({ required: true });
expect(registry.getPropertyMetadata(User, "email")).toEqual({ required: true, unique: true });
expect(registry.getPropertyMetadata(Post, "title")).toEqual({ required: true });
expect(registry.getPropertyMetadata(Post, "content")).toEqual({ required: true });
// Verify ID registration
expect(registry.getIdProperties(User)?.has("id")).toBe(true);
expect(registry.getIdProperties(Post)?.has("id")).toBe(true);
// Verify relationship registration
const userPostsRel = registry.getRelationshipMetadata(User, "posts");
expect(userPostsRel).toBeDefined();
expect(userPostsRel?.cardinality).toBe("many");
expect(userPostsRel?.inverse).toBe("author");
expect(userPostsRel?.name).toBe("AUTHORED");
const postAuthorRel = registry.getRelationshipMetadata(Post, "author");
expect(postAuthorRel).toBeDefined();
expect(postAuthorRel?.cardinality).toBe("one");
expect(postAuthorRel?.inverse).toBe("posts");
expect(postAuthorRel?.name).toBe("AUTHORED");
});
test("should allow registering entities individually", () => {
// Create registry and builder
const registry = new MetadataRegistry();
const builder = new SchemaBuilder(registry);
// Register just one entity
builder.registerEntity(User);
// Verify only User is registered
expect(registry.getEntityMetadata(User)).toBeDefined();
expect(registry.getEntityMetadata(Post)).toBeUndefined();
// Now register Post
builder.registerEntity(Post);
expect(registry.getEntityMetadata(Post)).toBeDefined();
});
test("should handle decorators with reflection metadata correctly", () => {
// Create registry and builder
const registry = new MetadataRegistry();
const builder = new SchemaBuilder(registry);
// Register entities
builder.registerEntities([User, Post]);
// Verify that ID properties are also registered as properties
const idProp = registry.getPropertyMetadata(User, "id");
expect(idProp).toBeDefined();
expect(idProp?.required).toBe(true);
expect(idProp?.unique).toBe(true);
});
test("getRegistry should return the underlying registry instance", () => {
// Create registry and builder
const registry = new MetadataRegistry();
const builder = new SchemaBuilder(registry);
// Verify the registry instance is returned correctly
expect(builder.getRegistry()).toBe(registry);
expect(builder.getRegistry()).toBeInstanceOf(MetadataRegistry);
});
test("should handle entities with missing metadata", () => {
// Create registry and builder
const registry = new MetadataRegistry();
const builder = new SchemaBuilder(registry);
// Create a mock class without proper decorator metadata
class MockEntityNoMetadata {}
// Register the entity - should not throw
expect(() => builder.registerEntity(MockEntityNoMetadata)).not.toThrow();
// Entity should be registered but with default empty options
const metadata = registry.getEntityMetadata(MockEntityNoMetadata);
expect(metadata).toEqual({});
});
test("should handle entities with missing property metadata", () => {
// Create registry and builder
const registry = new MetadataRegistry();
const builder = new SchemaBuilder(registry);
// Create a mock class with entity metadata but no property metadata
class MockEntityNoProps {}
// Set only entity metadata, but not properties array
Reflect.defineMetadata("entity:options", { name: "mock_entity" }, MockEntityNoProps);
// Register without throwing
expect(() => builder.registerEntity(MockEntityNoProps)).not.toThrow();
// Should have entity metadata but no properties
expect(registry.getEntityMetadata(MockEntityNoProps)).toEqual({ name: "mock_entity" });
});
test("should handle entities with missing relationship metadata", () => {
// Create registry and builder
const registry = new MetadataRegistry();
const builder = new SchemaBuilder(registry);
// Mock class with entity and property metadata but no relationship metadata
class MockEntityNoRels {}
// Set entity and property metadata but not relationships array
Reflect.defineMetadata("entity:options", { name: "mock_entity" }, MockEntityNoRels);
Reflect.defineMetadata("entity:properties", ["name"], MockEntityNoRels);
Reflect.defineMetadata("property:options", { required: true }, MockEntityNoRels.prototype, "name");
// Register without throwing
expect(() => builder.registerEntity(MockEntityNoRels)).not.toThrow();
// Should have entity metadata and property metadata but no relationships
expect(registry.getEntityMetadata(MockEntityNoRels)).toEqual({ name: "mock_entity" });
expect(registry.getPropertyMetadata(MockEntityNoRels, "name")).toEqual({ required: true });
});
test("should handle properties with missing options metadata", () => {
// Create registry and builder
const registry = new MetadataRegistry();
const builder = new SchemaBuilder(registry);
// Create a mock class with property keys but no options metadata
class MockEntityNoPropertyOptions {}
// Set entity metadata and properties array, but no property:options metadata
Reflect.defineMetadata("entity:options", { name: "mock_entity" }, MockEntityNoPropertyOptions);
Reflect.defineMetadata("entity:properties", ["propWithNoOptions"], MockEntityNoPropertyOptions);
// Register without throwing
expect(() => builder.registerEntity(MockEntityNoPropertyOptions)).not.toThrow();
// Property should be registered with empty options
expect(registry.getPropertyMetadata(MockEntityNoPropertyOptions, "propWithNoOptions")).toEqual({});
});
test("should handle relationships with missing options or type metadata", () => {
// Create registry and builder
const registry = new MetadataRegistry();
const builder = new SchemaBuilder(registry);
// Create a mock class with relationship keys but missing metadata
class MockEntityIncompleteRel {}
// Set entity metadata and relationships array, but no relationship metadata
Reflect.defineMetadata("entity:options", { name: "mock_entity" }, MockEntityIncompleteRel);
Reflect.defineMetadata("entity:relationships", ["relWithNoOptions"], MockEntityIncompleteRel);
// Register without throwing
expect(() => builder.registerEntity(MockEntityIncompleteRel)).not.toThrow();
// Relationship should be registered with default values
const relMetadata = registry.getRelationshipMetadata(MockEntityIncompleteRel, "relWithNoOptions");
expect(relMetadata).toBeDefined();
expect(relMetadata?.cardinality).toBe("one"); // Default cardinality when type is missing
});
test("should correctly handle different relationship types for cardinality", () => {
// Create registry and builder
const registry = new MetadataRegistry();
const builder = new SchemaBuilder(registry);
// Create mock classes for testing relationship cardinality
class MockEntity {}
// Set up different relationship types
Reflect.defineMetadata("entity:options", {}, MockEntity);
Reflect.defineMetadata("entity:relationships", ["oneToOne", "oneToMany", "manyToOne", "manyToMany"], MockEntity);
// Set relationship types
Reflect.defineMetadata("relationship:options", {}, MockEntity.prototype, "oneToOne");
Reflect.defineMetadata("relationship:type", "one-to-one", MockEntity.prototype, "oneToOne");
Reflect.defineMetadata("relationship:options", {}, MockEntity.prototype, "oneToMany");
Reflect.defineMetadata("relationship:type", "one-to-many", MockEntity.prototype, "oneToMany");
Reflect.defineMetadata("relationship:options", {}, MockEntity.prototype, "manyToOne");
Reflect.defineMetadata("relationship:type", "many-to-one", MockEntity.prototype, "manyToOne");
Reflect.defineMetadata("relationship:options", {}, MockEntity.prototype, "manyToMany");
Reflect.defineMetadata("relationship:type", "many-to-many", MockEntity.prototype, "manyToMany");
// Register without throwing
builder.registerEntity(MockEntity);
// Check cardinality for each relationship type
expect(registry.getRelationshipMetadata(MockEntity, "oneToOne")?.cardinality).toBe("one");
expect(registry.getRelationshipMetadata(MockEntity, "oneToMany")?.cardinality).toBe("many");
expect(registry.getRelationshipMetadata(MockEntity, "manyToOne")?.cardinality).toBe("one");
expect(registry.getRelationshipMetadata(MockEntity, "manyToMany")?.cardinality).toBe("many");
});
});