public commit

This commit is contained in:
dswbx
2024-11-16 12:01:47 +01:00
commit 90f80c4280
582 changed files with 49291 additions and 0 deletions

View 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]);
});
});
});

View 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);
});
});

View 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());
});
});

View 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();

View 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();
});
});

View 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
}
});
});

View 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);
});*/
}

View 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"));
});
});

View 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;
}

View 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"
}
}
}
}
});
});
});

View 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);
}
});
});

View 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);
});
});
});