@wearesage/schema
Version:
A flexible schema definition and validation system for TypeScript with multi-database support
200 lines (154 loc) • 6.88 kB
text/typescript
import "reflect-metadata";
import { SchemaReflector } from "../../core/SchemaReflector";
import { Entity, Property, Id, Min, Max, Email, MinLength } from "../../core/decorators";
describe("SchemaReflector - Validation Integration", () => {
let schemaReflector: SchemaReflector;
beforeEach(() => {
schemaReflector = new SchemaReflector();
});
({ name: "User" })
class User {
()
id: string;
({ required: true })
(2)
name: string;
({ required: true })
({ value: 0 })
({ value: 120 })
age: number;
({ required: true })
()
email: string;
()
bio?: string;
constructor(id: string, name: string, age: number, email: string, bio?: string) {
this.id = id;
this.name = name;
this.age = age;
this.email = email;
this.bio = bio;
}
}
describe("validateEntityWithRules", () => {
it("should validate a completely valid entity", async () => {
const user = new User("1", "John Doe", 25, "john@example.com", "Software developer");
const result = await schemaReflector.validateEntityWithRules(user);
expect(result.isValid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it("should catch both required field violations and validation rule violations", async () => {
const user = new User("1", "", 150, "invalid-email");
const result = await schemaReflector.validateEntityWithRules(user);
expect(result.isValid).toBe(false);
expect(result.errors.length).toBeGreaterThan(0);
// Should have validation rule errors
const ruleTypes = result.errors.map(e => e.rule);
expect(ruleTypes).toContain("minLength"); // name too short
expect(ruleTypes).toContain("max"); // age too high
expect(ruleTypes).toContain("email"); // invalid email
});
it("should catch missing required fields", async () => {
const user = new User("1", "John", 25, "john@example.com");
// Simulate missing required field
(user as any).name = undefined;
const result = await schemaReflector.validateEntityWithRules(user);
expect(result.isValid).toBe(false);
expect(result.errors.some(e => e.rule === "required")).toBe(true);
});
it("should allow optional fields to be empty while validating provided values", async () => {
const user = new User("1", "John Doe", 25, "john@example.com");
// bio is optional and not set
const result = await schemaReflector.validateEntityWithRules(user);
expect(result.isValid).toBe(true);
expect(result.errors).toHaveLength(0);
});
});
describe("validateProperty", () => {
it("should validate individual properties correctly", async () => {
const user = new User("1", "John Doe", 25, "john@example.com");
const nameResult = await schemaReflector.validateProperty(user, "name");
const ageResult = await schemaReflector.validateProperty(user, "age");
const emailResult = await schemaReflector.validateProperty(user, "email");
expect(nameResult.isValid).toBe(true);
expect(ageResult.isValid).toBe(true);
expect(emailResult.isValid).toBe(true);
});
it("should catch individual property validation failures", async () => {
const user = new User("1", "J", 150, "bad-email");
const nameResult = await schemaReflector.validateProperty(user, "name");
const ageResult = await schemaReflector.validateProperty(user, "age");
const emailResult = await schemaReflector.validateProperty(user, "email");
expect(nameResult.isValid).toBe(false);
expect(nameResult.errors[0].rule).toBe("minLength");
expect(ageResult.isValid).toBe(false);
expect(ageResult.errors[0].rule).toBe("max");
expect(emailResult.isValid).toBe(false);
expect(emailResult.errors[0].rule).toBe("email");
});
it("should return valid for properties without validation rules", async () => {
const user = new User("1", "John Doe", 25, "john@example.com");
const idResult = await schemaReflector.validateProperty(user, "id");
expect(idResult.isValid).toBe(true);
expect(idResult.errors).toHaveLength(0);
});
});
describe("Backward Compatibility", () => {
it("should maintain backward compatibility with old validateEntity method", () => {
const user = new User("1", "John Doe", 25, "john@example.com");
const result = schemaReflector.validateEntity(user);
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it("should catch required field violations in old validateEntity method", () => {
const user = new User("1", "John Doe", 25, "john@example.com");
(user as any).name = undefined;
const result = schemaReflector.validateEntity(user);
expect(result.valid).toBe(false);
expect(result.errors.length).toBeGreaterThan(0);
expect(result.errors[0]).toContain("Required property 'name' is missing");
});
});
describe("Complex Validation Scenarios", () => {
()
class Product {
()
id: string;
({ required: true })
(3)
name: string;
({ required: true })
({ value: 0.01, message: "Price must be greater than 0" })
({ value: 999999.99, message: "Price too high" })
price: number;
()
(10, "Description must be at least 10 characters")
description?: string;
constructor(id: string, name: string, price: number, description?: string) {
this.id = id;
this.name = name;
this.price = price;
this.description = description;
}
}
it("should provide detailed error messages", async () => {
const product = new Product("1", "AB", 0, "Short");
const result = await schemaReflector.validateEntityWithRules(product);
expect(result.isValid).toBe(false);
const nameError = result.errors.find(e => e.property === "name");
const priceError = result.errors.find(e => e.property === "price");
const descError = result.errors.find(e => e.property === "description");
expect(nameError).toBeDefined();
expect(priceError).toBeDefined();
expect(priceError?.message).toBe("Price must be greater than 0");
expect(descError).toBeDefined();
expect(descError?.message).toBe("Description must be at least 10 characters");
});
it("should handle edge cases gracefully", async () => {
const product = new Product("1", "Valid Product", 99.99);
// description is optional and not provided
const result = await schemaReflector.validateEntityWithRules(product);
expect(result.isValid).toBe(true);
});
});
});