fortify-schema
Version:
TypeScript interface-like schema validation system that's easier to use than Zod
263 lines (225 loc) • 7.99 kB
text/typescript
/**
* Comprehensive tests for conditional validation implementation
*/
import { When } from "../core/schema/extensions";
import { InterfaceSchema } from "../core/schema/mode/interfaces/InterfaceSchema";
describe("Conditional Validation Implementation", () => {
describe("Revolutionary *? Syntax", () => {
it("should validate when condition *? then : else syntax", () => {
const schema = new InterfaceSchema({
role: "string",
permissions: "when role=admin *? string[] : string[]?",
});
// Admin should require permissions array
const adminResult = schema.validate({
role: "admin",
permissions: ["read", "write"],
});
expect(adminResult.success).toBe(true);
// Non-admin can have optional permissions
const userResult = schema.validate({
role: "user",
permissions: undefined,
});
expect(userResult.success).toBe(true);
// Admin without permissions should fail
const adminNoPermResult = schema.validate({
role: "admin",
permissions: undefined,
});
expect(adminNoPermResult.success).toBe(false);
});
it("should handle numeric conditions with *? syntax", () => {
const schema = new InterfaceSchema({
age: "number",
drinkType: "when age>=21 *? string : =water",
});
// Adult can have any drink
const adultResult = schema.validate({
age: 25,
drinkType: "beer",
});
expect(adultResult.success).toBe(true);
// Minor must have water
const minorResult = schema.validate({
age: 18,
drinkType: "water",
});
expect(minorResult.success).toBe(true);
// Minor with alcohol should fail
const minorAlcoholResult = schema.validate({
age: 18,
drinkType: "beer",
});
expect(minorAlcoholResult.success).toBe(false);
});
});
describe("Parentheses Syntax", () => {
it("should validate when(condition) then(schema) else(schema) syntax", () => {
const schema = new InterfaceSchema({
userType: "string",
accessLevel: "when(userType=premium) then(number) else(=1)",
});
// Premium user can have any access level
const premiumResult = schema.validate({
userType: "premium",
accessLevel: 5,
});
expect(premiumResult.success).toBe(true);
// Regular user must have access level 1
const regularResult = schema.validate({
userType: "regular",
accessLevel: 1,
});
expect(regularResult.success).toBe(true);
// Regular user with higher access should fail
const regularHighResult = schema.validate({
userType: "regular",
accessLevel: 5,
});
expect(regularHighResult.success).toBe(false);
});
});
describe("When Builder API", () => {
it("should validate using When.field() builder", () => {
const schema = new InterfaceSchema({
status: "string",
details: When.field("status").is("active").then("string").else("string?"),
});
// Active status requires details
const activeResult = schema.validate({
status: "active",
details: "System is running",
});
expect(activeResult.success).toBe(true);
// Inactive status can have optional details
const inactiveResult = schema.validate({
status: "inactive",
details: undefined,
});
expect(inactiveResult.success).toBe(true);
// Active without details should fail
const activeNoDetailsResult = schema.validate({
status: "active",
details: undefined,
});
expect(activeNoDetailsResult.success).toBe(false);
});
it("should handle complex conditions with When builder", () => {
const schema = new InterfaceSchema({
category: "string",
price: "number",
discount: When.field("category")
.in(["electronics", "books"])
.then("number")
.else("number?"),
});
// Electronics should have discount
const electronicsResult = schema.validate({
category: "electronics",
price: 100,
discount: 10,
});
expect(electronicsResult.success).toBe(true);
// Clothing can have optional discount
const clothingResult = schema.validate({
category: "clothing",
price: 50,
discount: undefined,
});
expect(clothingResult.success).toBe(true);
});
});
describe("Advanced Operators", () => {
it("should handle exists/!exists operators", () => {
const schema = new InterfaceSchema({
email: "string?",
notifications: "when email.exists *? boolean : =false",
});
// With email, notifications can be boolean
const withEmailResult = schema.validate({
email: "user@example.com",
notifications: true,
});
expect(withEmailResult.success).toBe(true);
// Without email, notifications must be false
const withoutEmailResult = schema.validate({
email: undefined,
notifications: false,
});
expect(withoutEmailResult.success).toBe(true);
});
it("should handle string operators (startsWith, contains)", () => {
const schema = new InterfaceSchema({
filename: "string",
compression: "when filename.endsWith(.zip) *? =high : =none",
});
// ZIP files should have high compression
const zipResult = schema.validate({
filename: "archive.zip",
compression: "high",
});
expect(zipResult.success).toBe(true);
// Other files should have no compression
const txtResult = schema.validate({
filename: "document.txt",
compression: "none",
});
expect(txtResult.success).toBe(true);
});
});
describe("Error Handling", () => {
it("should provide meaningful error messages for conditional validation failures", () => {
const schema = new InterfaceSchema({
role: "string",
permissions: "when role=admin *? string[] : string[]?",
});
const result = schema.validate({
role: "admin",
permissions: "invalid",
});
expect(result.success).toBe(false);
expect(result.errors).toContain(
expect.stringMatching(/permissions.*Expected array/)
);
});
it("should handle invalid conditional syntax gracefully", () => {
const schema = new InterfaceSchema({
field: "when invalid syntax",
});
const result = schema.validate({
field: "value",
});
// Should fall back to basic validation or provide clear error
expect(result.success).toBe(false);
expect(result.errors.length).toBeGreaterThan(0);
});
});
describe("Performance and Edge Cases", () => {
it("should handle nested conditional validation", () => {
const schema = new InterfaceSchema({
userType: "string",
subscription: "string?",
features: "when userType=premium *? string[] : when subscription.exists *? string[] : string[]?",
});
// Premium user should have features
const premiumResult = schema.validate({
userType: "premium",
subscription: undefined,
features: ["advanced", "priority"],
});
expect(premiumResult.success).toBe(true);
});
it("should validate conditional fields without full data context", () => {
const schema = new InterfaceSchema({
conditionalField: When.field("otherField").is("value").then("string").else("number"),
});
// Should handle gracefully when dependent field is missing
const result = schema.validate({
conditionalField: "test",
});
// Should either pass with warning or provide clear error
expect(result.warnings.length > 0 || result.errors.length > 0).toBe(true);
});
});
});