mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
public commit
This commit is contained in:
54
app/__test__/core/Endpoint.spec.ts
Normal file
54
app/__test__/core/Endpoint.spec.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it, test } from "bun:test";
|
||||
import { Endpoint } from "../../src/core";
|
||||
import { mockFetch2, unmockFetch } from "./helper";
|
||||
|
||||
const testC: any = {
|
||||
json: (res: any) => Response.json(res)
|
||||
};
|
||||
const testNext = async () => {};
|
||||
|
||||
describe("Endpoint", async () => {
|
||||
it("behaves as expected", async () => {
|
||||
const endpoint = new Endpoint("GET", "/test", async () => {
|
||||
return { hello: "test" };
|
||||
});
|
||||
|
||||
expect(endpoint.method).toBe("GET");
|
||||
expect(endpoint.path).toBe("/test");
|
||||
|
||||
const handler = endpoint.toHandler();
|
||||
const response = await handler(testC, testNext);
|
||||
|
||||
expect(response.ok).toBe(true);
|
||||
expect(await response.json()).toEqual({ hello: "test" });
|
||||
});
|
||||
|
||||
it("can be $request(ed)", async () => {
|
||||
const obj = { hello: "test" };
|
||||
const baseUrl = "https://local.com:123";
|
||||
const endpoint = Endpoint.get("/test", async () => obj);
|
||||
|
||||
mockFetch2(async (input: RequestInfo, init: RequestInit) => {
|
||||
expect(input).toBe(`${baseUrl}/test`);
|
||||
return new Response(JSON.stringify(obj), { status: 200 });
|
||||
});
|
||||
const response = await endpoint.$request({}, baseUrl);
|
||||
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
ok: true,
|
||||
response: obj
|
||||
});
|
||||
unmockFetch();
|
||||
});
|
||||
|
||||
it("resolves helper functions", async () => {
|
||||
const params = ["/test", () => ({ hello: "test" })];
|
||||
|
||||
["get", "post", "patch", "put", "delete"].forEach((method) => {
|
||||
const endpoint = Endpoint[method](...params);
|
||||
expect(endpoint.method).toBe(method.toUpperCase());
|
||||
expect(endpoint.path).toBe(params[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
46
app/__test__/core/EventManager.spec.ts
Normal file
46
app/__test__/core/EventManager.spec.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { Event, EventManager, NoParamEvent } from "../../src/core/events";
|
||||
|
||||
class SpecialEvent extends Event<{ foo: string }> {
|
||||
static slug = "special-event";
|
||||
|
||||
isBar() {
|
||||
return this.params.foo === "bar";
|
||||
}
|
||||
}
|
||||
|
||||
class InformationalEvent extends NoParamEvent {
|
||||
static slug = "informational-event";
|
||||
}
|
||||
|
||||
describe("EventManager", async () => {
|
||||
test("test", async () => {
|
||||
const emgr = new EventManager();
|
||||
emgr.registerEvents([SpecialEvent, InformationalEvent]);
|
||||
|
||||
emgr.onEvent(
|
||||
SpecialEvent,
|
||||
async (event, name) => {
|
||||
console.log("Event: ", name, event.params.foo, event.isBar());
|
||||
console.log("wait...");
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
console.log("done waiting");
|
||||
},
|
||||
"sync"
|
||||
);
|
||||
|
||||
emgr.onEvent(InformationalEvent, async (event, name) => {
|
||||
console.log("Event: ", name, event.params);
|
||||
});
|
||||
|
||||
await emgr.emit(new SpecialEvent({ foo: "bar" }));
|
||||
console.log("done");
|
||||
|
||||
// expect construct signatures to not cause ts errors
|
||||
new SpecialEvent({ foo: "bar" });
|
||||
new InformationalEvent();
|
||||
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
56
app/__test__/core/Registry.spec.ts
Normal file
56
app/__test__/core/Registry.spec.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { describe, test } from "bun:test";
|
||||
import type { TObject, TString } from "@sinclair/typebox";
|
||||
import { Registry } from "../../src/core/registry/Registry";
|
||||
import { type TSchema, Type } from "../../src/core/utils";
|
||||
|
||||
type Constructor<T> = new (...args: any[]) => T;
|
||||
|
||||
type ClassRef<T> = Constructor<T> & (new (...args: any[]) => T);
|
||||
|
||||
class What {
|
||||
method() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
class What2 extends What {}
|
||||
class NotAllowed {}
|
||||
|
||||
type Test1 = {
|
||||
cls: new (...args: any[]) => What;
|
||||
schema: TObject<{ type: TString }>;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
describe("Registry", () => {
|
||||
test("adds an item", async () => {
|
||||
const registry = new Registry<Test1>().set({
|
||||
first: {
|
||||
cls: What,
|
||||
schema: Type.Object({ type: Type.String(), what: Type.String() }),
|
||||
enabled: true
|
||||
}
|
||||
} satisfies Record<string, Test1>);
|
||||
|
||||
const item = registry.get("first");
|
||||
|
||||
registry.add("second", {
|
||||
cls: What2,
|
||||
schema: Type.Object({ type: Type.String(), what: Type.String() }),
|
||||
enabled: true
|
||||
});
|
||||
registry.add("third", {
|
||||
// @ts-expect-error
|
||||
cls: NotAllowed,
|
||||
schema: Type.Object({ type: Type.String({ default: "1" }), what22: Type.String() }),
|
||||
enabled: true
|
||||
});
|
||||
registry.add("fourth", {
|
||||
cls: What,
|
||||
// @ts-expect-error
|
||||
schema: Type.Object({ type: Type.Number(), what22: Type.String() }),
|
||||
enabled: true
|
||||
});
|
||||
|
||||
console.log("list", registry.all());
|
||||
});
|
||||
});
|
||||
31
app/__test__/core/benchmarks/crypto.bm.ts
Normal file
31
app/__test__/core/benchmarks/crypto.bm.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { baseline, bench, group, run } from "mitata";
|
||||
import * as crypt from "../../../src/core/utils/crypto";
|
||||
|
||||
// deno
|
||||
// import { ... } from 'npm:mitata';
|
||||
|
||||
// d8/jsc
|
||||
// import { ... } from '<path to mitata>/src/cli.mjs';
|
||||
|
||||
const small = "hello";
|
||||
const big = "hello".repeat(1000);
|
||||
|
||||
group("hashing (small)", () => {
|
||||
baseline("baseline", () => JSON.parse(JSON.stringify({ small })));
|
||||
bench("sha-1", async () => await crypt.hash.sha256(small));
|
||||
bench("sha-256", async () => await crypt.hash.sha256(small));
|
||||
});
|
||||
|
||||
group("hashing (big)", () => {
|
||||
baseline("baseline", () => JSON.parse(JSON.stringify({ big })));
|
||||
bench("sha-1", async () => await crypt.hash.sha256(big));
|
||||
bench("sha-256", async () => await crypt.hash.sha256(big));
|
||||
});
|
||||
|
||||
/*group({ name: 'group2', summary: false }, () => {
|
||||
bench('new Array(0)', () => new Array(0));
|
||||
bench('new Array(1024)', () => new Array(1024));
|
||||
});*/
|
||||
|
||||
// @ts-ignore
|
||||
await run();
|
||||
57
app/__test__/core/cache/CloudflareKvCache.native-spec.ts
vendored
Normal file
57
app/__test__/core/cache/CloudflareKvCache.native-spec.ts
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
import * as assert from "node:assert/strict";
|
||||
import { createWriteStream } from "node:fs";
|
||||
import { after, beforeEach, describe, test } from "node:test";
|
||||
import { Miniflare } from "miniflare";
|
||||
import {
|
||||
CloudflareKVCacheItem,
|
||||
CloudflareKVCachePool
|
||||
} from "../../../src/core/cache/adapters/CloudflareKvCache";
|
||||
import { runTests } from "./cache-test-suite";
|
||||
|
||||
// https://github.com/nodejs/node/issues/44372#issuecomment-1736530480
|
||||
console.log = async (message: any) => {
|
||||
const tty = createWriteStream("/dev/tty");
|
||||
const msg = typeof message === "string" ? message : JSON.stringify(message, null, 2);
|
||||
return tty.write(`${msg}\n`);
|
||||
};
|
||||
|
||||
describe("CloudflareKv", async () => {
|
||||
let mf: Miniflare;
|
||||
runTests({
|
||||
createCache: async () => {
|
||||
if (mf) {
|
||||
await mf.dispose();
|
||||
}
|
||||
|
||||
mf = new Miniflare({
|
||||
modules: true,
|
||||
script: "export default { async fetch() { return new Response(null); } }",
|
||||
kvNamespaces: ["TEST"]
|
||||
});
|
||||
const kv = await mf.getKVNamespace("TEST");
|
||||
return new CloudflareKVCachePool(kv as any);
|
||||
},
|
||||
createItem: (key, value) => new CloudflareKVCacheItem(key, value),
|
||||
tester: {
|
||||
test,
|
||||
beforeEach,
|
||||
expect: (actual?: any) => {
|
||||
return {
|
||||
toBe(expected: any) {
|
||||
assert.equal(actual, expected);
|
||||
},
|
||||
toEqual(expected: any) {
|
||||
assert.deepEqual(actual, expected);
|
||||
},
|
||||
toBeUndefined() {
|
||||
assert.equal(actual, undefined);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await mf?.dispose();
|
||||
});
|
||||
});
|
||||
15
app/__test__/core/cache/MemoryCache.spec.ts
vendored
Normal file
15
app/__test__/core/cache/MemoryCache.spec.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import { beforeEach, describe, expect, test } from "bun:test";
|
||||
import { MemoryCache, MemoryCacheItem } from "../../../src/core/cache/adapters/MemoryCache";
|
||||
import { runTests } from "./cache-test-suite";
|
||||
|
||||
describe("MemoryCache", () => {
|
||||
runTests({
|
||||
createCache: async () => new MemoryCache(),
|
||||
createItem: (key, value) => new MemoryCacheItem(key, value),
|
||||
tester: {
|
||||
test,
|
||||
beforeEach,
|
||||
expect
|
||||
}
|
||||
});
|
||||
});
|
||||
84
app/__test__/core/cache/cache-test-suite.ts
vendored
Normal file
84
app/__test__/core/cache/cache-test-suite.ts
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
//import { beforeEach as bunBeforeEach, expect as bunExpect, test as bunTest } from "bun:test";
|
||||
import type { ICacheItem, ICachePool } from "../../../src/core/cache/cache-interface";
|
||||
|
||||
export type TestOptions = {
|
||||
createCache: () => Promise<ICachePool>;
|
||||
createItem: (key: string, value: any) => ICacheItem;
|
||||
tester: {
|
||||
test: (name: string, fn: () => Promise<void>) => void;
|
||||
beforeEach: (fn: () => Promise<void>) => void;
|
||||
expect: (actual?: any) => {
|
||||
toBe(expected: any): void;
|
||||
toEqual(expected: any): void;
|
||||
toBeUndefined(): void;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export function runTests({ createCache, createItem, tester }: TestOptions) {
|
||||
let cache: ICachePool<string>;
|
||||
const { test, beforeEach, expect } = tester;
|
||||
|
||||
beforeEach(async () => {
|
||||
cache = await createCache();
|
||||
});
|
||||
|
||||
test("getItem returns correct item", async () => {
|
||||
const item = createItem("key1", "value1");
|
||||
await cache.save(item);
|
||||
const retrievedItem = await cache.get("key1");
|
||||
expect(retrievedItem.value()).toEqual(item.value());
|
||||
});
|
||||
|
||||
test("getItem returns new item when key does not exist", async () => {
|
||||
const retrievedItem = await cache.get("key1");
|
||||
expect(retrievedItem.key()).toEqual("key1");
|
||||
expect(retrievedItem.value()).toBeUndefined();
|
||||
});
|
||||
|
||||
test("getItems returns correct items", async () => {
|
||||
const item1 = createItem("key1", "value1");
|
||||
const item2 = createItem("key2", "value2");
|
||||
await cache.save(item1);
|
||||
await cache.save(item2);
|
||||
const retrievedItems = await cache.getMany(["key1", "key2"]);
|
||||
expect(retrievedItems.get("key1")?.value()).toEqual(item1.value());
|
||||
expect(retrievedItems.get("key2")?.value()).toEqual(item2.value());
|
||||
});
|
||||
|
||||
test("hasItem returns true when item exists and is a hit", async () => {
|
||||
const item = createItem("key1", "value1");
|
||||
await cache.save(item);
|
||||
expect(await cache.has("key1")).toBe(true);
|
||||
});
|
||||
|
||||
test("clear and deleteItem correctly clear the cache and delete items", async () => {
|
||||
const item = createItem("key1", "value1");
|
||||
await cache.save(item);
|
||||
|
||||
if (cache.supports().clear) {
|
||||
await cache.clear();
|
||||
} else {
|
||||
await cache.delete("key1");
|
||||
}
|
||||
|
||||
expect(await cache.has("key1")).toBe(false);
|
||||
});
|
||||
|
||||
test("save correctly saves items to the cache", async () => {
|
||||
const item = createItem("key1", "value1");
|
||||
await cache.save(item);
|
||||
expect(await cache.has("key1")).toBe(true);
|
||||
});
|
||||
|
||||
test("putItem correctly puts items in the cache ", async () => {
|
||||
await cache.put("key1", "value1", { ttl: 60 });
|
||||
const item = await cache.get("key1");
|
||||
expect(item.value()).toEqual("value1");
|
||||
expect(item.hit()).toBe(true);
|
||||
});
|
||||
|
||||
/*test("commit returns true", async () => {
|
||||
expect(await cache.commit()).toBe(true);
|
||||
});*/
|
||||
}
|
||||
14
app/__test__/core/crypto.spec.ts
Normal file
14
app/__test__/core/crypto.spec.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { describe, test } from "bun:test";
|
||||
import { checksum, hash } from "../../src/core/utils";
|
||||
|
||||
describe("crypto", async () => {
|
||||
test("sha256", async () => {
|
||||
console.log(await hash.sha256("test"));
|
||||
});
|
||||
test("sha1", async () => {
|
||||
console.log(await hash.sha1("test"));
|
||||
});
|
||||
test("checksum", async () => {
|
||||
console.log(checksum("hello world"));
|
||||
});
|
||||
});
|
||||
18
app/__test__/core/helper.ts
Normal file
18
app/__test__/core/helper.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { jest } from "bun:test";
|
||||
|
||||
let _oldFetch: typeof fetch;
|
||||
export function mockFetch(responseMethods: Partial<Response>) {
|
||||
_oldFetch = global.fetch;
|
||||
// @ts-ignore
|
||||
global.fetch = jest.fn(() => Promise.resolve(responseMethods));
|
||||
}
|
||||
|
||||
export function mockFetch2(newFetch: (input: RequestInfo, init: RequestInit) => Promise<Response>) {
|
||||
_oldFetch = global.fetch;
|
||||
// @ts-ignore
|
||||
global.fetch = jest.fn(newFetch);
|
||||
}
|
||||
|
||||
export function unmockFetch() {
|
||||
global.fetch = _oldFetch;
|
||||
}
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
111
app/__test__/core/utils.spec.ts
Normal file
111
app/__test__/core/utils.spec.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { Perf } from "../../src/core/utils";
|
||||
import * as reqres from "../../src/core/utils/reqres";
|
||||
import * as strings from "../../src/core/utils/strings";
|
||||
|
||||
async function wait(ms: number) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
describe("Core Utils", async () => {
|
||||
describe("[core] strings", async () => {
|
||||
test("objectToKeyValueArray", async () => {
|
||||
const obj = { a: 1, b: 2, c: 3 };
|
||||
const result = strings.objectToKeyValueArray(obj);
|
||||
expect(result).toEqual([
|
||||
{ key: "a", value: 1 },
|
||||
{ key: "b", value: 2 },
|
||||
{ key: "c", value: 3 }
|
||||
]);
|
||||
});
|
||||
|
||||
test("snakeToPascalWithSpaces", async () => {
|
||||
const result = strings.snakeToPascalWithSpaces("snake_to_pascal");
|
||||
expect(result).toBe("Snake To Pascal");
|
||||
});
|
||||
|
||||
test("randomString", async () => {
|
||||
const result = strings.randomString(10);
|
||||
expect(result).toHaveLength(10);
|
||||
});
|
||||
|
||||
test("pascalToKebab", async () => {
|
||||
const result = strings.pascalToKebab("PascalCase");
|
||||
expect(result).toBe("pascal-case");
|
||||
});
|
||||
|
||||
test("replaceSimplePlaceholders", async () => {
|
||||
const str = "Hello, {$name}!";
|
||||
const vars = { name: "John" };
|
||||
const result = strings.replaceSimplePlaceholders(str, vars);
|
||||
expect(result).toBe("Hello, John!");
|
||||
});
|
||||
});
|
||||
|
||||
describe("reqres", async () => {
|
||||
test("headersToObject", () => {
|
||||
const headers = new Headers();
|
||||
headers.append("Content-Type", "application/json");
|
||||
headers.append("Authorization", "Bearer 123");
|
||||
const obj = reqres.headersToObject(headers);
|
||||
expect(obj).toEqual({
|
||||
"content-type": "application/json",
|
||||
authorization: "Bearer 123"
|
||||
});
|
||||
});
|
||||
|
||||
test("replaceUrlParam", () => {
|
||||
const url = "/api/:id/:name";
|
||||
const params = { id: "123", name: "test" };
|
||||
const result = reqres.replaceUrlParam(url, params);
|
||||
expect(result).toBe("/api/123/test");
|
||||
});
|
||||
|
||||
test("encode", () => {
|
||||
const obj = { id: "123", name: "test" };
|
||||
const result = reqres.encodeSearch(obj);
|
||||
expect(result).toBe("id=123&name=test");
|
||||
|
||||
const obj2 = { id: "123", name: ["test1", "test2"] };
|
||||
const result2 = reqres.encodeSearch(obj2);
|
||||
expect(result2).toBe("id=123&name=test1&name=test2");
|
||||
|
||||
const obj3 = { id: "123", name: { test: "test" } };
|
||||
const result3 = reqres.encodeSearch(obj3, { encode: true });
|
||||
expect(result3).toBe("id=123&name=%7B%22test%22%3A%22test%22%7D");
|
||||
});
|
||||
});
|
||||
|
||||
describe("perf", async () => {
|
||||
test("marks", async () => {
|
||||
const perf = Perf.start();
|
||||
await wait(20);
|
||||
perf.mark("boot");
|
||||
|
||||
await wait(10);
|
||||
perf.mark("another");
|
||||
perf.close();
|
||||
|
||||
const perf2 = Perf.start();
|
||||
await wait(40);
|
||||
perf2.mark("booted");
|
||||
await wait(10);
|
||||
perf2.mark("what");
|
||||
perf2.close();
|
||||
|
||||
expect(perf.result().total).toBeLessThan(perf2.result().total);
|
||||
});
|
||||
|
||||
test("executes correctly", async () => {
|
||||
// write a test for "execute" method
|
||||
let count = 0;
|
||||
await Perf.execute(async () => {
|
||||
count += 1;
|
||||
}, 2);
|
||||
|
||||
expect(count).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user