@wearesage/schema
Version:
A flexible schema definition and validation system for TypeScript with multi-database support
220 lines (175 loc) • 6.78 kB
text/typescript
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);
});
});