@wearesage/schema
Version:
A flexible schema definition and validation system for TypeScript with multi-database support
171 lines (136 loc) • 6.08 kB
text/typescript
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
class MongoDBPost extends Post {
// Additional MongoDB-specific fields or methods
()
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();
});
});