UNPKG

@wearesage/schema

Version:

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

171 lines (136 loc) 6.08 kB
import "reflect-metadata"; import { MongoDBAdapter, Collection, Index, MongoDB } from "../../adapters/mongodb"; import { MetadataRegistry } from "../../core/MetadataRegistry"; import { SchemaBuilder } from "../../core/SchemaBuilder"; import { SchemaReflector } from "../../core/SchemaReflector"; import { Type } from "../../core/types"; import { Property } from "../../core/decorators"; // Import example blog entities import { User } from "../../examples/blog/User"; import { Post } from "../../examples/blog/Post"; import { Tag } from "../../examples/blog/Tag"; // Add MongoDB-specific decorators to the imported entities Collection("blog_posts")(Post); Index({ title: 1 })(Post); // MongoDB-specific entity extension @MongoDB class MongoDBPost extends Post { // Additional MongoDB-specific fields or methods @Property() viewCount: number; } describe("MongoDBAdapter", () => { let registry: MetadataRegistry; let builder: SchemaBuilder; let adapter: MongoDBAdapter; beforeEach(() => { // Set up registry, builder, and adapter for each test registry = new MetadataRegistry(); builder = new SchemaBuilder(registry); // Register entities builder.registerEntities([User, Post, Tag, MongoDBPost]); // Create adapter after entities are registered adapter = new MongoDBAdapter(registry, "mongodb://localhost:27017/test"); }); test("should use custom collection name when specified", () => { // Use reflection to access the private method const getCollectionName = (adapter as any).getCollectionName.bind(adapter); // Check if custom collection name is used for Post expect(getCollectionName(Post)).toBe("blog_posts"); // Check if default pluralized name is used for User expect(getCollectionName(User)).toBe("users"); }); test("should create a client for MongoDB connection", () => { // Mock the connection string const connectionString = "mongodb://localhost:27017/test"; // Get the connection string from private field via reflection expect((adapter as any).connectionString).toBe(connectionString); }); test("should get MongoDB indexes", () => { // Add index metadata to Post class const indexDef = { keys: { title: 1 }, options: { unique: true } }; Reflect.defineMetadata("mongodb:indexes", [indexDef], Post); // Use reflection to access the private method const getIndexes = (adapter as any).getIndexes.bind(adapter); // Get indexes for Post const indexes = getIndexes(Post); // Check if indexes were retrieved correctly expect(indexes).toEqual([indexDef]); }); test("should convert MongoDB document to entity", () => { // Create a document const document = { id: "123", name: "Test User", email: "test@example.com" }; // Use reflection to access the private method const documentToEntity = (adapter as any).documentToEntity.bind(adapter); // Convert document to entity const user = documentToEntity(User, document); // Check if conversion was correct expect(user).toBeInstanceOf(User); expect(user.id).toBe("123"); expect(user.name).toBe("Test User"); expect(user.email).toBe("test@example.com"); }); test("should register MongoDB-specific entity extensions", () => { // Check if MongoDB adapter recognizes the extended entity expect(MongoDBPost.prototype).toBeInstanceOf(Post); // Check if dbType property was set correctly expect((MongoDBPost as any).dbType).toBe("MongoDB"); }); test("should handle database-specific decorators", () => { // Check if MongoDB-specific collection decorator was applied const collectionName = Reflect.getMetadata("mongodb:collection", Post); expect(collectionName).toBe("blog_posts"); // Check if MongoDB-specific index decorator was applied const indexes = Reflect.getMetadata("mongodb:indexes", Post) || []; expect(indexes.length).toBeGreaterThan(0); expect(indexes[0].keys).toEqual({ title: 1 }); }); test("should validate entities before saving", async () => { // Create an invalid entity (missing required field) const post = new Post(); post.id = "123"; // Missing title which is required post.content = "This is a test"; // Saving should throw an error for validation await expect(adapter.save(post)).rejects.toThrow(/Invalid entity/); }); test("should handle mock query execution", async () => { // Query for an entity that exists in mock const entity = await adapter.query(User, { id: "123" }); // Should return a mock entity with id 123 expect(entity).not.toBeNull(); expect(entity?.id).toBe("123"); // Query for non-existent entity const nonExistent = await adapter.query(User, { id: "456" }); expect(nonExistent).toBeNull(); }); test("should handle queryMany operation", async () => { // Mock method that returns multiple results await adapter.queryMany(User, { status: "active" }); // We're just testing that the method exists and runs without throwing // The console log output would show the query execution if we needed to verify details }); test("should handle delete operation", async () => { // Just test the method exists and doesn't throw await expect(adapter.delete(User, "user123")).resolves.not.toThrow(); // Verify expected logs by checking console output // This is indirectly testing the implementation but is safer than mocking internals }); test("should handle runNativeQuery", async () => { // Test the adapter's ability to run a native query const mockQuery = "{ 'find': 'collection', 'filter': { 'field': 'value' } }"; const mockParams = { param1: "value1" }; // We can't verify the actual query execution since it would require a real DB // but we can at least check that the method exists and doesn't throw await expect(adapter.runNativeQuery(mockQuery, mockParams)).resolves.not.toThrow(); }); });