UNPKG

@wearesage/schema

Version:

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

220 lines (175 loc) 6.78 kB
import "reflect-metadata"; import { MetadataRegistry, SchemaBuilder } from "../../core"; import { DatabaseAdapter } from "../../adapters/interface"; import { Repository } from "../../adapters/repository"; import { User } from "../../examples/blog/User"; import { Post } from "../../examples/blog/Post"; // Create a mock adapter for testing class MockAdapter implements DatabaseAdapter { readonly type = "Mock"; mockData: Map<string, any[]> = new Map(); constructor(private registry: MetadataRegistry) { // Initialize with some mock data this.mockData.set("User", [ { id: "user1", name: "User 1", email: "user1@example.com" }, { id: "user2", name: "User 2", email: "user2@example.com" } ]); // Create mock User object for authors const mockUser1 = new User(); mockUser1.id = "user1"; mockUser1.name = "User 1"; // Create Posts with proper author relationship const post1 = new Post(); post1.id = "post1"; post1.title = "Post 1"; post1.content = "Content 1"; post1.author = mockUser1; const post2 = new Post(); post2.id = "post2"; post2.title = "Post 2"; post2.content = "Content 2"; post2.author = mockUser1; this.mockData.set("Post", [post1, post2]); } async query<T>(entityType: new (...args: any[]) => T, criteria: object): Promise<T | null> { const entityName = entityType.name; const entities = this.mockData.get(entityName) || []; // Find the first entity that matches all criteria const found = entities.find(entity => { return Object.entries(criteria).every(([key, value]) => entity[key] === value); }); if (!found) return null; // Convert to entity instance const instance = new entityType(); Object.assign(instance, found); return instance; } async queryMany<T>(entityType: new (...args: any[]) => T, criteria: object): Promise<T[]> { const entityName = entityType.name; const entities = this.mockData.get(entityName) || []; // Find all entities that match criteria const found = entities.filter(entity => { return Object.entries(criteria).every(([key, value]) => entity[key] === value); }); // Convert to entity instances return found.map(data => { const instance = new entityType(); Object.assign(instance, data); return instance; }); } async save<T>(entity: T): Promise<void> { const entityName = entity.constructor.name; const entities = this.mockData.get(entityName) || []; // Check if entity already exists (by id) const index = entities.findIndex(e => e.id === (entity as any).id); if (index >= 0) { // Update existing entity entities[index] = {...entity}; } else { // Add new entity entities.push({...entity}); } this.mockData.set(entityName, entities); } async delete<T>(entityType: new (...args: any[]) => T, id: string | number): Promise<void> { const entityName = entityType.name; const entities = this.mockData.get(entityName) || []; // Remove entity with matching id const filtered = entities.filter(e => e.id !== id); this.mockData.set(entityName, filtered); } async runNativeQuery<T>(query: string, params?: any): Promise<T> { // Just return empty result for mock return [] as any; } } describe("Repository", () => { let registry: MetadataRegistry; let builder: SchemaBuilder; let adapter: MockAdapter; let userRepo: Repository<User>; let postRepo: Repository<Post>; beforeEach(() => { // Set up registry, builder, and adapter registry = new MetadataRegistry(); builder = new SchemaBuilder(registry); // Register entities builder.registerEntities([User, Post]); // Create mock adapter adapter = new MockAdapter(registry); // Create repositories userRepo = new Repository<User>(User, adapter); postRepo = new Repository<Post>(Post, adapter); }); test("repository should be created for the correct entity types", () => { // We can't directly access private properties, but we can test functionality // that depends on them being set correctly expect(userRepo).toBeInstanceOf(Repository); expect(postRepo).toBeInstanceOf(Repository); }); test("should find entity by id", async () => { const user = await userRepo.findById("user1"); expect(user).toBeDefined(); expect(user?.id).toBe("user1"); expect(user?.name).toBe("User 1"); }); test("should return null when entity not found", async () => { const user = await userRepo.findById("nonexistent"); expect(user).toBeNull(); }); test("should find entities by criteria", async () => { // Use any field in our test data that we can query by const posts = await postRepo.find(); expect(posts).toHaveLength(2); expect(posts[0].title).toBe("Post 1"); expect(posts[1].title).toBe("Post 2"); }); test("should find first entity by criteria", async () => { const post = await postRepo.findOne({ id: "post1" }); expect(post).toBeDefined(); expect(post?.id).toBe("post1"); expect(post?.author.id).toBe("user1"); }); test("should find all entities with empty criteria", async () => { const users = await userRepo.find(); expect(users).toHaveLength(2); expect(users[0].name).toBe("User 1"); expect(users[1].name).toBe("User 2"); }); test("should save new entity", async () => { const newUser = new User(); newUser.id = "user3"; newUser.name = "User 3"; newUser.email = "user3@example.com"; await userRepo.save(newUser); // Verify it was saved const saved = await userRepo.findById("user3"); expect(saved).toBeDefined(); expect(saved?.name).toBe("User 3"); }); test("should update existing entity", async () => { // First fetch existing entity const user = await userRepo.findById("user1"); expect(user).toBeDefined(); if (user) { // Update and save user.name = "Updated User 1"; await userRepo.save(user); // Verify it was updated const updated = await userRepo.findById("user1"); expect(updated?.name).toBe("Updated User 1"); } }); test("should delete entity by id", async () => { await userRepo.delete("user2"); // Verify it was deleted const deleted = await userRepo.findById("user2"); expect(deleted).toBeNull(); }); test("should execute native queries", async () => { const result = await userRepo.runNativeQuery("SELECT * FROM users"); // Mock implementation returns empty array expect(Array.isArray(result)).toBe(true); }); });