This commit is contained in:
384
frontend/__tests__/utils/sanitize.spec.ts
Normal file
384
frontend/__tests__/utils/sanitize.spec.ts
Normal file
@@ -0,0 +1,384 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { z } from "zod";
|
||||
import { sanitize } from "../../src/ts/utils/sanitize";
|
||||
|
||||
describe("sanitize function", () => {
|
||||
describe("arrays", () => {
|
||||
const numberArraySchema = z.array(z.number());
|
||||
const numbersArrayMin2Schema = numberArraySchema.min(2);
|
||||
|
||||
const testCases: {
|
||||
input: number[];
|
||||
expected: {
|
||||
numbers: number[] | boolean;
|
||||
numbersMin: number[] | boolean;
|
||||
};
|
||||
}[] = [
|
||||
{ input: [], expected: { numbers: true, numbersMin: false } },
|
||||
{ input: [1, 2], expected: { numbers: true, numbersMin: true } },
|
||||
{
|
||||
input: [1, "2" as any],
|
||||
expected: { numbers: [1], numbersMin: false },
|
||||
},
|
||||
{
|
||||
input: ["one", "two"] as any,
|
||||
expected: { numbers: [], numbersMin: false },
|
||||
},
|
||||
];
|
||||
it.for(testCases)("number array with $input", ({ input, expected }) => {
|
||||
const sanitized = expect(
|
||||
expected.numbers === false
|
||||
? () => sanitize(numberArraySchema, input)
|
||||
: sanitize(numberArraySchema, input),
|
||||
);
|
||||
|
||||
if (expected.numbers === false) {
|
||||
sanitized.toThrow();
|
||||
} else if (expected.numbers === true) {
|
||||
sanitized.toStrictEqual(input);
|
||||
} else {
|
||||
sanitized.toStrictEqual(expected.numbers);
|
||||
}
|
||||
});
|
||||
it.for(testCases)(
|
||||
"number array.min(2) with $input",
|
||||
({ input, expected }) => {
|
||||
const sanitized = expect(
|
||||
expected.numbersMin === false
|
||||
? () => sanitize(numbersArrayMin2Schema, input)
|
||||
: sanitize(numbersArrayMin2Schema, input),
|
||||
);
|
||||
|
||||
if (expected.numbersMin === false) {
|
||||
sanitized.toThrow();
|
||||
} else if (expected.numbersMin === true) {
|
||||
sanitized.toStrictEqual(input);
|
||||
} else {
|
||||
sanitized.toStrictEqual(expected.numbersMin);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
describe("objects", () => {
|
||||
const objectSchema = z.object({
|
||||
name: z.string(),
|
||||
age: z.number().positive(),
|
||||
tags: z.array(z.string()),
|
||||
enumArray: z.array(z.enum(["one", "two"])).min(2),
|
||||
});
|
||||
const objectSchemaFullPartial = objectSchema.partial().strip();
|
||||
const objectSchemaWithOptional = objectSchema.partial({
|
||||
tags: true,
|
||||
enumArray: true,
|
||||
});
|
||||
|
||||
const testCases: {
|
||||
input: z.infer<typeof objectSchemaFullPartial>;
|
||||
expected: {
|
||||
mandatory: z.infer<typeof objectSchema> | boolean;
|
||||
partial: z.infer<typeof objectSchemaFullPartial> | boolean;
|
||||
optional: z.infer<typeof objectSchemaWithOptional> | boolean;
|
||||
};
|
||||
}[] = [
|
||||
{
|
||||
input: {},
|
||||
expected: { mandatory: false, partial: true, optional: false },
|
||||
},
|
||||
{
|
||||
input: {
|
||||
name: "Alice",
|
||||
age: 23,
|
||||
tags: ["one", "two"],
|
||||
enumArray: ["one", "two"],
|
||||
},
|
||||
expected: { mandatory: true, partial: true, optional: true },
|
||||
},
|
||||
{
|
||||
input: {
|
||||
name: "Alice",
|
||||
age: 23,
|
||||
},
|
||||
expected: { mandatory: false, partial: true, optional: true },
|
||||
},
|
||||
{
|
||||
input: {
|
||||
name: "Alice",
|
||||
age: "sixty" as any,
|
||||
},
|
||||
expected: {
|
||||
mandatory: false,
|
||||
partial: { name: "Alice" },
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: {
|
||||
name: "Alice",
|
||||
age: 23,
|
||||
tags: ["one", 2 as any],
|
||||
enumArray: "one" as any,
|
||||
},
|
||||
expected: {
|
||||
mandatory: false,
|
||||
partial: { name: "Alice", age: 23, tags: ["one"] },
|
||||
optional: { name: "Alice", age: 23, tags: ["one"] },
|
||||
},
|
||||
},
|
||||
{
|
||||
input: {
|
||||
name: "Alice",
|
||||
age: 23,
|
||||
tags: [1, 2] as any,
|
||||
enumArray: [1, 2] as any,
|
||||
},
|
||||
expected: {
|
||||
mandatory: false,
|
||||
partial: { name: "Alice", age: 23 },
|
||||
optional: { name: "Alice", age: 23 },
|
||||
},
|
||||
},
|
||||
{
|
||||
input: {
|
||||
name: "Alice",
|
||||
age: 23,
|
||||
extraArray: [],
|
||||
extraObject: {},
|
||||
extraString: "",
|
||||
} as any,
|
||||
expected: {
|
||||
mandatory: false,
|
||||
partial: { name: "Alice", age: 23 },
|
||||
optional: { name: "Alice", age: 23 },
|
||||
},
|
||||
},
|
||||
{
|
||||
input: {
|
||||
name: "Alice",
|
||||
//results in two errors on the same path. array with invalid value and not enough items
|
||||
enumArray: ["invalid" as any],
|
||||
},
|
||||
expected: {
|
||||
mandatory: false,
|
||||
partial: { name: "Alice" }, //enumArray is removed
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
it.for(testCases)("object mandatory with $input", ({ input, expected }) => {
|
||||
const sanitized = expect(
|
||||
expected.mandatory === false
|
||||
? () => sanitize(objectSchema, input as any)
|
||||
: sanitize(objectSchema, input as any),
|
||||
);
|
||||
|
||||
if (expected.mandatory === false) {
|
||||
sanitized.toThrow();
|
||||
} else if (expected.mandatory === true) {
|
||||
sanitized.toStrictEqual(input);
|
||||
} else {
|
||||
sanitized.toStrictEqual(expected.mandatory);
|
||||
}
|
||||
});
|
||||
it.for(testCases)(
|
||||
"object full partial with $input",
|
||||
({ input, expected }) => {
|
||||
const sanitized = expect(
|
||||
expected.partial === false
|
||||
? () => sanitize(objectSchemaFullPartial, input as any)
|
||||
: sanitize(objectSchemaFullPartial, input as any),
|
||||
);
|
||||
|
||||
if (expected.partial === false) {
|
||||
sanitized.toThrow();
|
||||
} else if (expected.partial === true) {
|
||||
sanitized.toStrictEqual(input);
|
||||
} else {
|
||||
sanitized.toStrictEqual(expected.partial);
|
||||
}
|
||||
},
|
||||
);
|
||||
it.for(testCases)("object optional with $input", ({ input, expected }) => {
|
||||
const sanitized = expect(
|
||||
expected.optional === false
|
||||
? () => sanitize(objectSchemaWithOptional, input as any)
|
||||
: sanitize(objectSchemaWithOptional, input as any),
|
||||
);
|
||||
|
||||
if (expected.optional === false) {
|
||||
sanitized.toThrow();
|
||||
} else if (expected.optional === true) {
|
||||
sanitized.toStrictEqual(input);
|
||||
} else {
|
||||
sanitized.toStrictEqual(expected.optional);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("nested", () => {
|
||||
const itemSchema = z.object({
|
||||
name: z.string(),
|
||||
enumArray: z.array(z.enum(["one", "two"])).min(2),
|
||||
});
|
||||
const nestedSchema = z.object({
|
||||
nested: z.array(itemSchema),
|
||||
});
|
||||
|
||||
const nestedSchemaFullPartial = z
|
||||
.object({
|
||||
nested: z.array(itemSchema.partial()),
|
||||
})
|
||||
.partial();
|
||||
const nestedSchemaWithMin2Array = z.object({
|
||||
nested: z.array(itemSchema).min(2),
|
||||
});
|
||||
|
||||
const testCases: {
|
||||
input: z.infer<typeof nestedSchema>;
|
||||
expected: {
|
||||
mandatory: z.infer<typeof nestedSchema> | boolean;
|
||||
partial: z.infer<typeof nestedSchemaFullPartial> | boolean;
|
||||
minArray: z.infer<typeof nestedSchemaWithMin2Array> | boolean;
|
||||
};
|
||||
}[] = [
|
||||
{
|
||||
input: {} as any,
|
||||
expected: {
|
||||
mandatory: false,
|
||||
partial: true,
|
||||
minArray: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: {
|
||||
nested: [
|
||||
{ name: "Alice", enumArray: ["one", "two"] },
|
||||
{ name: "Bob", enumArray: ["one", "two"] },
|
||||
],
|
||||
},
|
||||
expected: {
|
||||
mandatory: true,
|
||||
partial: true,
|
||||
minArray: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: {
|
||||
nested: [
|
||||
{ name: "Alice", enumArray: ["one", "two"] },
|
||||
{ name: "Bob" } as any,
|
||||
],
|
||||
},
|
||||
expected: {
|
||||
mandatory: {
|
||||
nested: [{ name: "Alice", enumArray: ["one", "two"] }],
|
||||
},
|
||||
partial: true,
|
||||
minArray: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: {
|
||||
nested: [
|
||||
{ enumArray: ["one", "two"] } as any,
|
||||
{ name: "Bob" } as any,
|
||||
],
|
||||
},
|
||||
expected: {
|
||||
mandatory: false,
|
||||
partial: true,
|
||||
minArray: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
it.for(testCases)("nested mandatory with $input", ({ input, expected }) => {
|
||||
const sanitized = expect(
|
||||
expected.mandatory === false
|
||||
? () => sanitize(nestedSchema, input as any)
|
||||
: sanitize(nestedSchema, input as any),
|
||||
);
|
||||
|
||||
if (expected.mandatory === false) {
|
||||
sanitized.toThrow();
|
||||
} else if (expected.mandatory === true) {
|
||||
sanitized.toStrictEqual(input);
|
||||
} else {
|
||||
sanitized.toStrictEqual(expected.mandatory);
|
||||
}
|
||||
});
|
||||
it.for(testCases)("nested partial with $input", ({ input, expected }) => {
|
||||
const sanitized = expect(
|
||||
expected.partial === false
|
||||
? () => sanitize(nestedSchemaFullPartial, input as any)
|
||||
: sanitize(nestedSchemaFullPartial, input as any),
|
||||
);
|
||||
|
||||
if (expected.partial === false) {
|
||||
sanitized.toThrow();
|
||||
} else if (expected.partial === true) {
|
||||
sanitized.toStrictEqual(input);
|
||||
} else {
|
||||
sanitized.toStrictEqual(expected.partial);
|
||||
}
|
||||
});
|
||||
it.for(testCases)(
|
||||
"nested array min(2) with $input",
|
||||
({ input, expected }) => {
|
||||
const sanitized = expect(
|
||||
expected.minArray === false
|
||||
? () => sanitize(nestedSchemaWithMin2Array, input as any)
|
||||
: sanitize(nestedSchemaWithMin2Array, input as any),
|
||||
);
|
||||
|
||||
if (expected.minArray === false) {
|
||||
sanitized.toThrow();
|
||||
} else if (expected.minArray === true) {
|
||||
sanitized.toStrictEqual(input);
|
||||
} else {
|
||||
sanitized.toStrictEqual(expected.minArray);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const schema = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
age: z.number().positive(),
|
||||
tags: z.array(z.string()),
|
||||
enumArray: z.array(z.enum(["one", "two"])).min(2),
|
||||
})
|
||||
.partial()
|
||||
.strip();
|
||||
|
||||
it("should strip extra keys", () => {
|
||||
const obj = {
|
||||
name: "bob",
|
||||
age: 30,
|
||||
tags: ["developer", "coder"],
|
||||
powerLevel: 9001,
|
||||
} as any;
|
||||
const stripped = sanitize(schema.strip(), obj);
|
||||
expect(stripped).not.toHaveProperty("powerLevel");
|
||||
});
|
||||
it("should strip extra keys on error", () => {
|
||||
const obj = {
|
||||
name: "bob",
|
||||
age: 30,
|
||||
powerLevel: 9001,
|
||||
} as any;
|
||||
const stripped = sanitize(schema.strip(), obj);
|
||||
expect(stripped).not.toHaveProperty("powerLevel");
|
||||
});
|
||||
it("should provide a readable error message", () => {
|
||||
const obj = {
|
||||
arrayOneTwo: ["one", "nonexistent"],
|
||||
} as any;
|
||||
expect(() => {
|
||||
sanitize(schema.required().strip(), obj);
|
||||
}).toThrow(
|
||||
"unable to sanitize: name: Required, age: Required, tags: Required, enumArray: Required",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user