@velajs/testing 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,8 +1,36 @@
1
1
  {
2
2
  "name": "@velajs/testing",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Testing utilities for Vela framework",
5
+ "keywords": [
6
+ "edge",
7
+ "framework",
8
+ "mock",
9
+ "override",
10
+ "test",
11
+ "testing",
12
+ "vela"
13
+ ],
14
+ "homepage": "https://github.com/velajs/testing#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/velajs/testing/issues"
17
+ },
18
+ "license": "MIT",
19
+ "author": "ksh",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/velajs/testing.git"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "README.md",
27
+ "LICENSE",
28
+ "CHANGELOG.md"
29
+ ],
5
30
  "type": "module",
31
+ "sideEffects": [
32
+ "./dist/websocket-node/index.js"
33
+ ],
6
34
  "main": "./dist/index.js",
7
35
  "types": "./dist/index.d.ts",
8
36
  "exports": {
@@ -15,43 +43,27 @@
15
43
  "import": "./dist/websocket-node/index.js"
16
44
  }
17
45
  },
18
- "files": [
19
- "dist",
20
- "README.md",
21
- "LICENSE",
22
- "CHANGELOG.md"
23
- ],
24
- "sideEffects": [
25
- "./dist/websocket-node/index.js"
26
- ],
27
- "keywords": [
28
- "vela",
29
- "testing",
30
- "test",
31
- "mock",
32
- "override",
33
- "edge",
34
- "framework"
35
- ],
36
- "author": "ksh",
37
- "license": "MIT",
38
- "repository": {
39
- "type": "git",
40
- "url": "git+https://github.com/velajs/testing.git"
41
- },
42
- "homepage": "https://github.com/velajs/testing#readme",
43
- "bugs": {
44
- "url": "https://github.com/velajs/testing/issues"
45
- },
46
- "engines": {
47
- "node": ">=20"
46
+ "devDependencies": {
47
+ "@arethetypeswrong/cli": "^0.18.5",
48
+ "@changesets/cli": "^2.31.0",
49
+ "@swc/core": "^1.15.43",
50
+ "@types/node": "^24.13.3",
51
+ "@velajs/vela": "^1.12.0",
52
+ "hono": "^4.12.26",
53
+ "oxfmt": "^0.58.0",
54
+ "oxlint": "^1.73.0",
55
+ "publint": "^0.3.21",
56
+ "tsdown": "^0.22.4",
57
+ "typescript": "^7.0.2",
58
+ "unplugin-swc": "^1.5.9",
59
+ "vitest": "^4.1.10"
48
60
  },
49
61
  "peerDependencies": {
62
+ "@hono/node-server": ">=1",
63
+ "@hono/node-ws": ">=1",
50
64
  "@velajs/vela": ">=1.11.0",
51
65
  "hono": ">=4",
52
- "vitest": ">=3",
53
- "@hono/node-ws": ">=1",
54
- "@hono/node-server": ">=1"
66
+ "vitest": ">=3"
55
67
  },
56
68
  "peerDependenciesMeta": {
57
69
  "@hono/node-ws": {
@@ -61,19 +73,21 @@
61
73
  "optional": true
62
74
  }
63
75
  },
64
- "devDependencies": {
65
- "@swc/cli": "^0.8.1",
66
- "@swc/core": "^1.15.41",
67
- "@types/bun": "latest",
68
- "@velajs/vela": "^1.12.0",
69
- "hono": "^4.12.26",
70
- "typescript": "^6.0.3",
71
- "unplugin-swc": "^1.5.9",
72
- "vitest": "^4.1.9"
76
+ "engines": {
77
+ "node": ">=24"
73
78
  },
74
79
  "scripts": {
75
- "build": "rm -rf dist && swc src -d dist --strip-leading-paths && tsc --emitDeclarationOnly",
80
+ "build": "tsdown",
76
81
  "test": "vitest run",
77
- "typecheck": "tsc --noEmit"
82
+ "typecheck": "tsc --noEmit",
83
+ "lint": "oxlint .",
84
+ "format": "oxfmt .",
85
+ "format:check": "oxfmt --check .",
86
+ "publint": "publint",
87
+ "attw": "attw --pack . --profile esm-only",
88
+ "changeset": "changeset",
89
+ "version-packages": "changeset version",
90
+ "release": "pnpm build && changeset publish",
91
+ "verify": "pnpm lint && pnpm format:check && pnpm build && pnpm typecheck && pnpm test && pnpm publint && pnpm attw"
78
92
  }
79
93
  }
@@ -1,27 +0,0 @@
1
- /**
2
- * TestDatabase
3
- *
4
- * A minimal driver contract for database assertions in tests. Vela has no ORM
5
- * in core, so the harness ships only this interface plus thin assertion
6
- * wrappers (`module.assertDatabaseHas/Missing/Count`). A consumer — or a future
7
- * `@velajs/crud` driver — provides the concrete implementation; the harness
8
- * never couples to a specific database library.
9
- *
10
- * @example
11
- * ```ts
12
- * class MyTestDb implements TestDatabase {
13
- * async truncate() { ... }
14
- * async has(table, where) { ... }
15
- * async count(table) { ... }
16
- * }
17
- * await module.assertDatabaseHas(new MyTestDb(), 'user', { email: 'a@b.com' });
18
- * ```
19
- */
20
- export interface TestDatabase {
21
- /** Remove all rows from every table (reset between tests). */
22
- truncate(): Promise<void>;
23
- /** Whether a row matching `where` exists in `table`. */
24
- has(table: string, where: Record<string, unknown>): Promise<boolean>;
25
- /** The number of rows in `table`. */
26
- count(table: string): Promise<number>;
27
- }
@@ -1,19 +0,0 @@
1
- /**
2
- * TestDatabase
3
- *
4
- * A minimal driver contract for database assertions in tests. Vela has no ORM
5
- * in core, so the harness ships only this interface plus thin assertion
6
- * wrappers (`module.assertDatabaseHas/Missing/Count`). A consumer — or a future
7
- * `@velajs/crud` driver — provides the concrete implementation; the harness
8
- * never couples to a specific database library.
9
- *
10
- * @example
11
- * ```ts
12
- * class MyTestDb implements TestDatabase {
13
- * async truncate() { ... }
14
- * async has(table, where) { ... }
15
- * async count(table) { ... }
16
- * }
17
- * await module.assertDatabaseHas(new MyTestDb(), 'user', { email: 'a@b.com' });
18
- * ```
19
- */ export { };
@@ -1,11 +0,0 @@
1
- /**
2
- * Read the value at a dot-notation path (e.g. `data.user.id`).
3
- * Returns `undefined` when any segment along the way is null/undefined.
4
- */
5
- export declare function getValueAtPath(obj: unknown, path: string): unknown;
6
- /**
7
- * Whether a dot-notation path exists on the object, even when the value at the
8
- * path is `null`/`undefined`. Distinguishes "key present but null" from "key
9
- * absent".
10
- */
11
- export declare function hasValueAtPath(obj: unknown, path: string): boolean;
@@ -1,37 +0,0 @@
1
- // Ported from @stratal/testing (MIT, © Temitayo Fadojutimi).
2
- /**
3
- * Read the value at a dot-notation path (e.g. `data.user.id`).
4
- * Returns `undefined` when any segment along the way is null/undefined.
5
- */ export function getValueAtPath(obj, path) {
6
- const parts = path.split('.');
7
- let current = obj;
8
- for (const part of parts){
9
- if (current === null || current === undefined) {
10
- return undefined;
11
- }
12
- current = current[part];
13
- }
14
- return current;
15
- }
16
- /**
17
- * Whether a dot-notation path exists on the object, even when the value at the
18
- * path is `null`/`undefined`. Distinguishes "key present but null" from "key
19
- * absent".
20
- */ export function hasValueAtPath(obj, path) {
21
- const parts = path.split('.');
22
- let current = obj;
23
- for (const part of parts){
24
- if (current === null || current === undefined) {
25
- return false;
26
- }
27
- if (typeof current !== 'object') {
28
- return false;
29
- }
30
- const record = current;
31
- if (!(part in record)) {
32
- return false;
33
- }
34
- current = record[part];
35
- }
36
- return true;
37
- }
@@ -1,37 +0,0 @@
1
- import type { TestingModule } from '../testing-module.js';
2
- import { TestHttpRequest } from './test-http-request.js';
3
- /**
4
- * TestHttpClient
5
- *
6
- * Fluent entry point for test HTTP requests. `forHost`/`withHeaders` return a
7
- * new immutable client; the verb methods start a {@link TestHttpRequest}.
8
- *
9
- * @example
10
- * ```ts
11
- * const res = await module.http
12
- * .forHost('example.com')
13
- * .post('/users')
14
- * .withBody({ name: 'A' })
15
- * .send();
16
- * res.assertCreated();
17
- * ```
18
- */
19
- export declare class TestHttpClient {
20
- private readonly module;
21
- private readonly host;
22
- private readonly defaultHeaders;
23
- constructor(module: TestingModule, host?: string | null, defaultHeaders?: Headers);
24
- /**
25
- * Return a new client bound to `host`. Also sets the `Host` header so domain
26
- * routing works even when the runtime reads the header rather than the URL.
27
- */
28
- forHost(host: string): TestHttpClient;
29
- /** Return a new client with additional default headers on every request. */
30
- withHeaders(headers: Record<string, string>): TestHttpClient;
31
- get(path: string): TestHttpRequest;
32
- post(path: string): TestHttpRequest;
33
- put(path: string): TestHttpRequest;
34
- patch(path: string): TestHttpRequest;
35
- delete(path: string): TestHttpRequest;
36
- private createRequest;
37
- }
@@ -1,61 +0,0 @@
1
- // Adapted from @stratal/testing (MIT, © Temitayo Fadojutimi). Stratal's i18n
2
- // `withLocale` is dropped (vela i18n differs — optional follow-up).
3
- import { TestHttpRequest } from "./test-http-request.js";
4
- /**
5
- * TestHttpClient
6
- *
7
- * Fluent entry point for test HTTP requests. `forHost`/`withHeaders` return a
8
- * new immutable client; the verb methods start a {@link TestHttpRequest}.
9
- *
10
- * @example
11
- * ```ts
12
- * const res = await module.http
13
- * .forHost('example.com')
14
- * .post('/users')
15
- * .withBody({ name: 'A' })
16
- * .send();
17
- * res.assertCreated();
18
- * ```
19
- */ export class TestHttpClient {
20
- module;
21
- host;
22
- defaultHeaders;
23
- constructor(module, host = null, defaultHeaders = new Headers()){
24
- this.module = module;
25
- this.host = host;
26
- this.defaultHeaders = defaultHeaders;
27
- }
28
- /**
29
- * Return a new client bound to `host`. Also sets the `Host` header so domain
30
- * routing works even when the runtime reads the header rather than the URL.
31
- */ forHost(host) {
32
- const headers = new Headers(this.defaultHeaders);
33
- headers.set('Host', host);
34
- return new TestHttpClient(this.module, host, headers);
35
- }
36
- /** Return a new client with additional default headers on every request. */ withHeaders(headers) {
37
- const next = new Headers(this.defaultHeaders);
38
- for (const [key, value] of Object.entries(headers)){
39
- next.set(key, value);
40
- }
41
- return new TestHttpClient(this.module, this.host, next);
42
- }
43
- get(path) {
44
- return this.createRequest('GET', path);
45
- }
46
- post(path) {
47
- return this.createRequest('POST', path);
48
- }
49
- put(path) {
50
- return this.createRequest('PUT', path);
51
- }
52
- patch(path) {
53
- return this.createRequest('PATCH', path);
54
- }
55
- delete(path) {
56
- return this.createRequest('DELETE', path);
57
- }
58
- createRequest(method, path) {
59
- return new TestHttpRequest(method, path, this.defaultHeaders, this.module, this.host);
60
- }
61
- }
@@ -1,46 +0,0 @@
1
- import type { ActingAsResolver, TestPrincipal, TestingModule } from '../testing-module.js';
2
- import { TestResponse } from './test-response.js';
3
- /**
4
- * TestHttpRequest
5
- *
6
- * Fluent builder for a single test HTTP request. `send()` builds a `Request`
7
- * and drives it through `module.fetch()` (the full Hono pipeline).
8
- *
9
- * @example
10
- * ```ts
11
- * const res = await module.http
12
- * .post('/users')
13
- * .withBody({ name: 'A' })
14
- * .withHeaders({ 'X-Trace': '1' })
15
- * .send();
16
- * res.assertCreated();
17
- * ```
18
- */
19
- export declare class TestHttpRequest {
20
- private readonly method;
21
- private readonly path;
22
- private readonly module;
23
- private readonly host;
24
- private body;
25
- private readonly requestHeaders;
26
- private principal;
27
- private resolver;
28
- constructor(method: string, path: string, headers: Headers, module: TestingModule, host?: string | null);
29
- /** Set the request body (JSON-serialized on send). */
30
- withBody(data: unknown): this;
31
- /** Merge additional headers. */
32
- withHeaders(headers: Record<string, string>): this;
33
- /** Set `Content-Type: application/json`. */
34
- asJson(): this;
35
- /**
36
- * Authenticate the request as `principal`. The `resolver` (or a default one
37
- * registered via `module.setAuthResolver`) turns the principal into request
38
- * headers. The resolver signature `(module, principal) => Promise<Headers>`
39
- * is the cross-package contract sibling packages (e.g. `@velajs/better-auth`)
40
- * build against.
41
- */
42
- actingAs(principal: TestPrincipal, resolver?: ActingAsResolver): this;
43
- /** Build the `Request` and send it through `module.fetch()`. */
44
- send(): Promise<TestResponse>;
45
- private applyAuthentication;
46
- }
@@ -1,87 +0,0 @@
1
- // Adapted from @stratal/testing (MIT, © Temitayo Fadojutimi): stratal's hard
2
- // AuthService import is replaced by a generic auth-resolver seam so
3
- // @velajs/testing stays free of optional-package dependencies.
4
- import { TestResponse } from "./test-response.js";
5
- /**
6
- * TestHttpRequest
7
- *
8
- * Fluent builder for a single test HTTP request. `send()` builds a `Request`
9
- * and drives it through `module.fetch()` (the full Hono pipeline).
10
- *
11
- * @example
12
- * ```ts
13
- * const res = await module.http
14
- * .post('/users')
15
- * .withBody({ name: 'A' })
16
- * .withHeaders({ 'X-Trace': '1' })
17
- * .send();
18
- * res.assertCreated();
19
- * ```
20
- */ export class TestHttpRequest {
21
- method;
22
- path;
23
- module;
24
- host;
25
- body = undefined;
26
- requestHeaders;
27
- principal = null;
28
- resolver = null;
29
- constructor(method, path, headers, module, host = null){
30
- this.method = method;
31
- this.path = path;
32
- this.module = module;
33
- this.host = host;
34
- this.requestHeaders = new Headers(headers);
35
- }
36
- /** Set the request body (JSON-serialized on send). */ withBody(data) {
37
- this.body = data;
38
- return this;
39
- }
40
- /** Merge additional headers. */ withHeaders(headers) {
41
- for (const [key, value] of Object.entries(headers)){
42
- this.requestHeaders.set(key, value);
43
- }
44
- return this;
45
- }
46
- /** Set `Content-Type: application/json`. */ asJson() {
47
- this.requestHeaders.set('Content-Type', 'application/json');
48
- return this;
49
- }
50
- /**
51
- * Authenticate the request as `principal`. The `resolver` (or a default one
52
- * registered via `module.setAuthResolver`) turns the principal into request
53
- * headers. The resolver signature `(module, principal) => Promise<Headers>`
54
- * is the cross-package contract sibling packages (e.g. `@velajs/better-auth`)
55
- * build against.
56
- */ actingAs(principal, resolver) {
57
- this.principal = principal;
58
- this.resolver = resolver ?? null;
59
- return this;
60
- }
61
- /** Build the `Request` and send it through `module.fetch()`. */ async send() {
62
- await this.applyAuthentication();
63
- const hasBody = this.body !== undefined && this.body !== null;
64
- if (hasBody && !this.requestHeaders.has('Content-Type')) {
65
- this.requestHeaders.set('Content-Type', 'application/json');
66
- }
67
- const url = new URL(this.path, `http://${this.host ?? 'localhost'}`);
68
- const request = new Request(url.toString(), {
69
- method: this.method,
70
- headers: this.requestHeaders,
71
- body: hasBody ? JSON.stringify(this.body) : null
72
- });
73
- const response = await this.module.fetch(request);
74
- return new TestResponse(response);
75
- }
76
- async applyAuthentication() {
77
- if (!this.principal) return;
78
- const resolver = this.resolver ?? this.module.getAuthResolver();
79
- if (!resolver) {
80
- throw new Error('actingAs() requires an auth resolver. Pass one explicitly — ' + 'actingAs(principal, resolver) — or register a default with ' + 'module.setAuthResolver(resolver). For better-auth: ' + 'import { actingAs } from "@velajs/better-auth/testing".');
81
- }
82
- const headers = await resolver(this.module, this.principal);
83
- for (const [key, value] of headers.entries()){
84
- this.requestHeaders.set(key, value);
85
- }
86
- }
87
- }
@@ -1,76 +0,0 @@
1
- /**
2
- * TestResponse
3
- *
4
- * Wraps a `Response` with fluent, chainable assertions. Synchronous status /
5
- * header assertions return `this`; JSON assertions (which must read the body)
6
- * return `Promise<this>`.
7
- *
8
- * @example
9
- * ```ts
10
- * const res = await module.http.get('/users/1').send();
11
- * res.assertOk();
12
- * await res.assertJsonPath('data.id', 1);
13
- * ```
14
- */
15
- export declare class TestResponse {
16
- private readonly response;
17
- private jsonData;
18
- private textData;
19
- constructor(response: Response);
20
- /** The raw `Response`. */
21
- get raw(): Response;
22
- /** The response status code. */
23
- get status(): number;
24
- /** The response headers. */
25
- get headers(): Headers;
26
- /** Parse (and cache) the response body as JSON. */
27
- json<T = unknown>(): Promise<T>;
28
- /** Read (and cache) the response body as text. */
29
- text(): Promise<string>;
30
- /** Assert status is 200 OK. */
31
- assertOk(): this;
32
- /** Assert status is 201 Created. */
33
- assertCreated(): this;
34
- /** Assert status is 204 No Content. */
35
- assertNoContent(): this;
36
- /** Assert status is 400 Bad Request. */
37
- assertBadRequest(): this;
38
- /** Assert status is 401 Unauthorized. */
39
- assertUnauthorized(): this;
40
- /** Assert status is 403 Forbidden. */
41
- assertForbidden(): this;
42
- /** Assert status is 404 Not Found. */
43
- assertNotFound(): this;
44
- /** Assert status is 422 Unprocessable Entity. */
45
- assertUnprocessable(): this;
46
- /** Assert status is 500 Internal Server Error. */
47
- assertServerError(): this;
48
- /** Assert the response has the given status code. */
49
- assertStatus(expected: number): this;
50
- /** Assert the status is in the 2xx range. */
51
- assertSuccessful(): this;
52
- /** Assert each key in `expected` equals the corresponding top-level value. */
53
- assertJson(expected: Record<string, unknown>): Promise<this>;
54
- /** Assert the value at a dot-notation path equals `expected`. */
55
- assertJsonPath(path: string, expected: unknown): Promise<this>;
56
- /** Assert every path/value pair in `expectations` matches (batch assert). */
57
- assertJsonPaths(expectations: Record<string, unknown>): Promise<this>;
58
- /** Assert the top-level JSON object has every key in `structure`. */
59
- assertJsonStructure(structure: string[]): Promise<this>;
60
- /** Assert a path exists (value may be anything, including `null`). */
61
- assertJsonPathExists(path: string): Promise<this>;
62
- /** Assert a path does not exist. */
63
- assertJsonPathMissing(path: string): Promise<this>;
64
- /** Assert the value at a path satisfies a predicate. */
65
- assertJsonPathMatches(path: string, matcher: (value: unknown) => boolean): Promise<this>;
66
- /** Assert the string value at a path contains `substring`. */
67
- assertJsonPathContains(path: string, substring: string): Promise<this>;
68
- /** Assert the array value at a path includes `item`. */
69
- assertJsonPathIncludes(path: string, item: unknown): Promise<this>;
70
- /** Assert the array value at a path has `count` items. */
71
- assertJsonPathCount(path: string, count: number): Promise<this>;
72
- /** Assert a header is present, optionally equal to `expected`. */
73
- assertHeader(name: string, expected?: string): this;
74
- /** Assert a header is absent. */
75
- assertHeaderMissing(name: string): this;
76
- }