alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
272 lines (213 loc) • 7.46 kB
text/typescript
import { Alepha, z } from "alepha";
import { describe, test } from "vitest";
import { SchemaValidator } from "../providers/SchemaValidator.ts";
describe("SchemaValidator", () => {
describe("Basic validation", () => {
test("should validate simple objects", async ({ expect }) => {
const alepha = Alepha.create();
const validator = alepha.inject(SchemaValidator);
const schema = z.object({
name: z.text(),
age: z.integer(),
});
const data = {
name: "Alice",
age: 30,
};
const result = validator.validate(schema, data);
expect(result).toEqual(data);
});
test("should trim strings when schema has trim option", async ({
expect,
}) => {
const alepha = Alepha.create();
const validator = alepha.inject(SchemaValidator);
const schema = z.object({
name: z.text({ trim: true }),
});
const data = {
name: " Alice ",
};
const result = validator.validate(schema, data);
expect(result.name).toBe("Alice");
});
test("rejects null for an optional (non-nullable) field (strict)", async ({
expect,
}) => {
const alepha = Alepha.create();
const validator = alepha.inject(SchemaValidator);
const schema = z.object({
name: z.text(),
bio: z.text().optional(),
});
// Standard zod is strict: an optional field accepts `undefined`, not
// `null` (the old `nullToUndefined` coercion has been dropped).
expect(() =>
validator.validate(schema, { name: "Alice", bio: null }),
).toThrow();
const result = validator.validate(schema, { name: "Alice" });
expect(result.name).toBe("Alice");
expect(result.bio).toBeUndefined();
});
});
describe("Security - Prototype Pollution Protection", () => {
test("should filter out __proto__ key from input data", async ({
expect,
}) => {
const alepha = Alepha.create();
const validator = alepha.inject(SchemaValidator);
const schema = z.object({
name: z.text(),
});
// Input data with __proto__ key via JSON.parse (bypasses literal protection)
const data = JSON.parse('{"name":"Alice","__proto__":{"isAdmin":true}}');
const result = validator.validate(schema, data);
// __proto__ should not be an own property in the result
expect(result.name).toBe("Alice");
// Using hasOwnProperty because 'in' operator has special behavior for __proto__
expect(Object.hasOwn(result, "__proto__")).toBe(false);
});
test("should filter out constructor key from input data", async ({
expect,
}) => {
const alepha = Alepha.create();
const validator = alepha.inject(SchemaValidator);
const schema = z.object({
name: z.text(),
});
const data = {
name: "Alice",
constructor: { prototype: { isAdmin: true } },
};
const result = validator.validate(schema, data);
expect(result.name).toBe("Alice");
expect(Object.hasOwn(result, "constructor")).toBe(false);
});
test("should filter out prototype key from input data", async ({
expect,
}) => {
const alepha = Alepha.create();
const validator = alepha.inject(SchemaValidator);
const schema = z.object({
name: z.text(),
});
const data = {
name: "Alice",
prototype: { isAdmin: true },
};
const result = validator.validate(schema, data);
expect(result.name).toBe("Alice");
expect(Object.hasOwn(result, "prototype")).toBe(false);
});
test("should filter unsafe keys from nested objects", async ({
expect,
}) => {
const alepha = Alepha.create();
const validator = alepha.inject(SchemaValidator);
const schema = z.object({
user: z.object({
name: z.text(),
}),
});
const data = {
user: {
name: "Alice",
__proto__: { isAdmin: true },
},
};
const result = validator.validate(schema, data);
expect(result.user.name).toBe("Alice");
expect(Object.hasOwn(result.user, "__proto__")).toBe(false);
});
test("should not pollute Object.prototype", async ({ expect }) => {
const alepha = Alepha.create();
const validator = alepha.inject(SchemaValidator);
const schema = z.object({
name: z.text(),
});
// Attempt to pollute Object.prototype via __proto__
const maliciousData = JSON.parse(
'{"name":"Alice","__proto__":{"polluted":"yes"}}',
);
validator.validate(schema, maliciousData);
// Object.prototype should not be polluted
expect(({} as any).polluted).toBeUndefined();
});
test("should create objects without prototype chain", async ({
expect,
}) => {
const alepha = Alepha.create();
const validator = alepha.inject(SchemaValidator);
const schema = z.object({
name: z.text(),
});
const data = {
name: "Alice",
};
const result = validator.validate(schema, data);
// The result should not have Object.prototype methods directly accessible
// via hasOwnProperty (it should use Object.create(null))
expect(result.name).toBe("Alice");
});
});
describe("beforeParse", () => {
test("should handle arrays correctly", async ({ expect }) => {
const alepha = Alepha.create();
const validator = alepha.inject(SchemaValidator);
const schema = z.object({
tags: z.array(z.text({ trim: true })),
});
const data = {
tags: [" tag1 ", " tag2 "],
};
const result = validator.validate(schema, data);
expect(result.tags).toEqual(["tag1", "tag2"]);
});
test("should unwrap optional arrays and preprocess items", async ({
expect,
}) => {
const alepha = Alepha.create();
const validator = alepha.inject(SchemaValidator);
const schema = z.object({
tags: z.array(z.text({ trim: true })).optional(),
});
const data = {
tags: [" tag1 ", " tag2 "],
};
const result = validator.validate(schema, data);
expect(result.tags).toEqual(["tag1", "tag2"]);
});
test("should unwrap nullable arrays and preprocess items", async ({
expect,
}) => {
const alepha = Alepha.create();
const validator = alepha.inject(SchemaValidator);
const schema = z.object({
tags: z.union([z.array(z.text({ trim: true })), z.null()]),
});
const data = {
tags: [" tag1 ", " tag2 "],
};
const result = validator.validate(schema, data);
expect(result.tags).toEqual(["tag1", "tag2"]);
});
test("validates an optional field left undefined (strict)", async ({
expect,
}) => {
const alepha = Alepha.create();
const validator = alepha.inject(SchemaValidator);
const schema = z.object({
name: z.text(),
bio: z.text().optional(),
});
// The `deleteUndefined` option has been dropped; an undefined optional
// field simply validates to `undefined`.
const result = validator.validate(schema, {
name: "Alice",
bio: undefined,
});
expect(result.name).toBe("Alice");
expect(result.bio).toBeUndefined();
});
});
});