mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 04:46:05 +00:00
public commit
This commit is contained in:
332
app/__test__/core/object/SchemaObject.spec.ts
Normal file
332
app/__test__/core/object/SchemaObject.spec.ts
Normal file
@@ -0,0 +1,332 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { SchemaObject } from "../../../src/core";
|
||||
import { Type } from "../../../src/core/utils";
|
||||
|
||||
describe("SchemaObject", async () => {
|
||||
test("basic", async () => {
|
||||
const m = new SchemaObject(
|
||||
Type.Object({ a: Type.String({ default: "b" }) }),
|
||||
{ a: "test" },
|
||||
{
|
||||
forceParse: true
|
||||
}
|
||||
);
|
||||
|
||||
expect(m.get()).toEqual({ a: "test" });
|
||||
expect(m.default()).toEqual({ a: "b" });
|
||||
|
||||
// direct modification is not allowed
|
||||
expect(() => {
|
||||
m.get().a = "test2";
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test("patch", async () => {
|
||||
const m = new SchemaObject(
|
||||
Type.Object({
|
||||
s: Type.Object(
|
||||
{
|
||||
a: Type.String({ default: "b" }),
|
||||
b: Type.Object(
|
||||
{
|
||||
c: Type.String({ default: "d" }),
|
||||
e: Type.String({ default: "f" })
|
||||
},
|
||||
{ default: {} }
|
||||
)
|
||||
},
|
||||
{ default: {}, additionalProperties: false }
|
||||
)
|
||||
})
|
||||
);
|
||||
expect(m.get()).toEqual({ s: { a: "b", b: { c: "d", e: "f" } } });
|
||||
|
||||
await m.patch("s.a", "c");
|
||||
|
||||
// non-existing path on no additional properties
|
||||
expect(() => m.patch("s.s.s", "c")).toThrow();
|
||||
// wrong type
|
||||
expect(() => m.patch("s.a", 1)).toThrow();
|
||||
|
||||
// should have only the valid change applied
|
||||
expect(m.get().s.b.c).toBe("d");
|
||||
expect(m.get()).toEqual({ s: { a: "c", b: { c: "d", e: "f" } } });
|
||||
|
||||
await m.patch("s.b.c", "d2");
|
||||
expect(m.get()).toEqual({ s: { a: "c", b: { c: "d2", e: "f" } } });
|
||||
});
|
||||
|
||||
test("patch array", async () => {
|
||||
const m = new SchemaObject(
|
||||
Type.Object({
|
||||
methods: Type.Array(Type.String(), { default: ["GET", "PATCH"] })
|
||||
})
|
||||
);
|
||||
expect(m.get()).toEqual({ methods: ["GET", "PATCH"] });
|
||||
|
||||
// array values are fully overwritten, whether accessed by index ...
|
||||
m.patch("methods[0]", "POST");
|
||||
expect(m.get()).toEqual({ methods: ["POST"] });
|
||||
|
||||
// or by path!
|
||||
m.patch("methods", ["GET", "DELETE"]);
|
||||
expect(m.get()).toEqual({ methods: ["GET", "DELETE"] });
|
||||
});
|
||||
|
||||
test("remove", async () => {
|
||||
const m = new SchemaObject(
|
||||
Type.Object({
|
||||
s: Type.Object(
|
||||
{
|
||||
a: Type.String({ default: "b" }),
|
||||
b: Type.Object(
|
||||
{
|
||||
c: Type.String({ default: "d" })
|
||||
},
|
||||
{ default: {} }
|
||||
)
|
||||
},
|
||||
{ default: {} }
|
||||
)
|
||||
})
|
||||
);
|
||||
expect(m.get()).toEqual({ s: { a: "b", b: { c: "d" } } });
|
||||
|
||||
// expect no change, because the default then applies
|
||||
m.remove("s.a");
|
||||
expect(m.get()).toEqual({ s: { a: "b", b: { c: "d" } } });
|
||||
|
||||
// adding another path, and then deleting it
|
||||
m.patch("s.c", "d");
|
||||
expect(m.get()).toEqual({ s: { a: "b", b: { c: "d" }, c: "d" } } as any);
|
||||
|
||||
// now it should be removed without applying again
|
||||
m.remove("s.c");
|
||||
expect(m.get()).toEqual({ s: { a: "b", b: { c: "d" } } });
|
||||
});
|
||||
|
||||
test("set", async () => {
|
||||
const m = new SchemaObject(
|
||||
Type.Object({
|
||||
methods: Type.Array(Type.String(), { default: ["GET", "PATCH"] })
|
||||
})
|
||||
);
|
||||
expect(m.get()).toEqual({ methods: ["GET", "PATCH"] });
|
||||
|
||||
m.set({ methods: ["GET", "POST"] });
|
||||
expect(m.get()).toEqual({ methods: ["GET", "POST"] });
|
||||
|
||||
// wrong type
|
||||
expect(() => m.set({ methods: [1] as any })).toThrow();
|
||||
});
|
||||
|
||||
test("listener", async () => {
|
||||
let called = false;
|
||||
let result: any;
|
||||
const m = new SchemaObject(
|
||||
Type.Object({
|
||||
methods: Type.Array(Type.String(), { default: ["GET", "PATCH"] })
|
||||
}),
|
||||
undefined,
|
||||
{
|
||||
onUpdate: async (config) => {
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
called = true;
|
||||
result = config;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
await m.set({ methods: ["GET", "POST"] });
|
||||
expect(called).toBe(true);
|
||||
expect(result).toEqual({ methods: ["GET", "POST"] });
|
||||
});
|
||||
|
||||
test("throwIfRestricted", async () => {
|
||||
const m = new SchemaObject(Type.Object({}), undefined, {
|
||||
restrictPaths: ["a.b"]
|
||||
});
|
||||
|
||||
expect(() => m.throwIfRestricted("a.b")).toThrow();
|
||||
expect(m.throwIfRestricted("a.c")).toBeUndefined();
|
||||
expect(() => m.throwIfRestricted({ a: { b: "c" } })).toThrow();
|
||||
expect(m.throwIfRestricted({ a: { c: "d" } })).toBeUndefined();
|
||||
});
|
||||
|
||||
test("restriction bypass", async () => {
|
||||
const m = new SchemaObject(
|
||||
Type.Object({
|
||||
s: Type.Object(
|
||||
{
|
||||
a: Type.String({ default: "b" }),
|
||||
b: Type.Object(
|
||||
{
|
||||
c: Type.String({ default: "d" })
|
||||
},
|
||||
{ default: {} }
|
||||
)
|
||||
},
|
||||
{ default: {} }
|
||||
)
|
||||
}),
|
||||
undefined,
|
||||
{
|
||||
restrictPaths: ["s.b"]
|
||||
}
|
||||
);
|
||||
|
||||
expect(() => m.patch("s.b.c", "e")).toThrow();
|
||||
expect(m.bypass().patch("s.b.c", "e")).toBeDefined();
|
||||
expect(() => m.patch("s.b.c", "f")).toThrow();
|
||||
expect(m.get()).toEqual({ s: { a: "b", b: { c: "e" } } });
|
||||
});
|
||||
|
||||
const dataEntitiesSchema = Type.Object(
|
||||
{
|
||||
entities: Type.Object(
|
||||
{},
|
||||
{
|
||||
additionalProperties: Type.Object({
|
||||
fields: Type.Object(
|
||||
{},
|
||||
{
|
||||
additionalProperties: Type.Object({
|
||||
type: Type.String(),
|
||||
config: Type.Optional(
|
||||
Type.Object({}, { additionalProperties: Type.String() })
|
||||
)
|
||||
})
|
||||
}
|
||||
),
|
||||
config: Type.Optional(Type.Object({}, { additionalProperties: Type.String() }))
|
||||
})
|
||||
}
|
||||
)
|
||||
},
|
||||
{
|
||||
additionalProperties: false
|
||||
}
|
||||
);
|
||||
test("patch safe object, overwrite", async () => {
|
||||
const data = {
|
||||
entities: {
|
||||
some: {
|
||||
fields: {
|
||||
a: { type: "string", config: { some: "thing" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const m = new SchemaObject(dataEntitiesSchema, data, {
|
||||
forceParse: true,
|
||||
overwritePaths: [/^entities\..*\.fields\..*\.config/]
|
||||
});
|
||||
|
||||
m.patch("entities.some.fields.a", { type: "string", config: { another: "one" } });
|
||||
|
||||
expect(m.get()).toEqual({
|
||||
entities: {
|
||||
some: {
|
||||
fields: {
|
||||
a: { type: "string", config: { another: "one" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("patch safe object, overwrite 2", async () => {
|
||||
const data = {
|
||||
entities: {
|
||||
users: {
|
||||
fields: {
|
||||
email: { type: "string" },
|
||||
password: { type: "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const m = new SchemaObject(dataEntitiesSchema, data, {
|
||||
forceParse: true,
|
||||
overwritePaths: [/^entities\..*\.fields\..*\.config\.html_config$/]
|
||||
});
|
||||
|
||||
m.patch("entities.test", {
|
||||
fields: {
|
||||
content: {
|
||||
type: "text"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(m.get()).toEqual({
|
||||
entities: {
|
||||
users: {
|
||||
fields: {
|
||||
email: { type: "string" },
|
||||
password: { type: "string" }
|
||||
}
|
||||
},
|
||||
test: {
|
||||
fields: {
|
||||
content: {
|
||||
type: "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("patch safe object, overwrite 3", async () => {
|
||||
const data = {
|
||||
entities: {
|
||||
users: {
|
||||
fields: {
|
||||
email: { type: "string" },
|
||||
password: { type: "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const m = new SchemaObject(dataEntitiesSchema, data, {
|
||||
forceParse: true,
|
||||
overwritePaths: [/^entities\..*\.fields\..*\.config\.html_config$/]
|
||||
});
|
||||
|
||||
expect(m.patch("desc", "entities.users.config.sort_dir")).rejects.toThrow();
|
||||
|
||||
m.patch("entities.test", {
|
||||
fields: {
|
||||
content: {
|
||||
type: "text"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
m.patch("entities.users.config", {
|
||||
sort_dir: "desc"
|
||||
});
|
||||
|
||||
expect(m.get()).toEqual({
|
||||
entities: {
|
||||
users: {
|
||||
fields: {
|
||||
email: { type: "string" },
|
||||
password: { type: "string" }
|
||||
},
|
||||
config: {
|
||||
sort_dir: "desc"
|
||||
}
|
||||
},
|
||||
test: {
|
||||
fields: {
|
||||
content: {
|
||||
type: "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
83
app/__test__/core/object/object-query.spec.ts
Normal file
83
app/__test__/core/object/object-query.spec.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { type ObjectQuery, convert, validate } from "../../../src/core/object/query/object-query";
|
||||
import { deprecated__whereRepoSchema } from "../../../src/data";
|
||||
|
||||
describe("object-query", () => {
|
||||
const q: ObjectQuery = { name: "Michael" };
|
||||
const q2: ObjectQuery = { name: { $isnull: 1 } };
|
||||
const q3: ObjectQuery = { name: "Michael", age: { $gt: 18 } };
|
||||
const bag = { q, q2, q3 };
|
||||
|
||||
test("translates into legacy", async () => {
|
||||
for (const [key, value] of Object.entries(bag)) {
|
||||
const obj = convert(value);
|
||||
try {
|
||||
const parsed = deprecated__whereRepoSchema.parse(obj);
|
||||
expect(parsed).toBeDefined();
|
||||
} catch (e) {
|
||||
console.log("errored", { obj, value });
|
||||
console.error(key, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("validates", async () => {
|
||||
const converted = convert({
|
||||
name: { $eq: "ch" }
|
||||
});
|
||||
validate(converted, { name: "Michael" });
|
||||
});
|
||||
|
||||
test("single validation", () => {
|
||||
const tests: [ObjectQuery, any, boolean][] = [
|
||||
[{ name: { $eq: 1 } }, { name: "Michael" }, false],
|
||||
[{ name: "Michael", age: 40 }, { name: "Michael", age: 40 }, true],
|
||||
[{ name: "Michael", age: 40 }, { name: "Michael", age: 41 }, false],
|
||||
[{ name: { $eq: "Michael" } }, { name: "Michael" }, true],
|
||||
[{ int: { $between: [1, 2] } }, { int: 1 }, true],
|
||||
[{ int: { $between: [1, 2] } }, { int: 3 }, false],
|
||||
[{ some: { $isnull: 1 } }, { some: null }, true],
|
||||
[{ some: { $isnull: true } }, { some: null }, true],
|
||||
[{ some: { $isnull: 0 } }, { some: null }, false],
|
||||
[{ some: { $isnull: false } }, { some: null }, false],
|
||||
[{ some: { $isnull: 1 } }, { some: 1 }, false],
|
||||
[{ val: { $notnull: 1 } }, { val: 1 }, true],
|
||||
[{ val: { $notnull: 1 } }, { val: null }, false],
|
||||
[{ val: { $regex: ".*" } }, { val: "test" }, true],
|
||||
[{ val: { $regex: /^t.*/ } }, { val: "test" }, true],
|
||||
[{ val: { $regex: /^b.*/ } }, { val: "test" }, false]
|
||||
];
|
||||
|
||||
for (const [query, object, expected] of tests) {
|
||||
const result = validate(query, object);
|
||||
expect(result).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
test("multiple validations", () => {
|
||||
const tests: [ObjectQuery, any, boolean][] = [
|
||||
// multiple constraints per property
|
||||
[{ val: { $lt: 10, $gte: 3 } }, { val: 7 }, true],
|
||||
[{ val: { $lt: 10, $gte: 3 } }, { val: 2 }, false],
|
||||
[{ val: { $lt: 10, $gte: 3 } }, { val: 11 }, false],
|
||||
|
||||
// multiple properties
|
||||
[{ val1: { $eq: "foo" }, val2: { $eq: "bar" } }, { val1: "foo", val2: "bar" }, true],
|
||||
[{ val1: { $eq: "foo" }, val2: { $eq: "bar" } }, { val1: "bar", val2: "foo" }, false],
|
||||
|
||||
// or constructs
|
||||
[
|
||||
{ $or: { val1: { $eq: "foo" }, val2: { $eq: "bar" } } },
|
||||
{ val1: "foo", val2: "bar" },
|
||||
true
|
||||
],
|
||||
[{ val1: { $eq: 1 }, $or: { val1: { $eq: 2 } } }, { val1: 1 }, true],
|
||||
[{ val1: { $eq: 1 }, $or: { val1: { $eq: 2 } } }, { val1: 3 }, false]
|
||||
];
|
||||
|
||||
for (const [query, object, expected] of tests) {
|
||||
const result = validate(query, object);
|
||||
expect(result).toBe(expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user