@hazejs/testing 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present hazejs contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,26 @@
1
- # Temporary Holding Version
1
+ # @hazejs/testing
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Vitest integration for hazejs: test app, HTTP client, `actingAs()` and database helpers.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add -D @hazejs/testing
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ const t = useTestApp()
15
+
16
+ it('lists posts', async () => {
17
+ const res = await (await t.actingAs(user)).get('/api/posts')
18
+ expect(res.status).toBe(200)
19
+ })
20
+ ```
21
+
22
+ Part of [hazejs](https://github.com/penna-org/haze) — see the main README for documentation.
23
+
24
+ ## License
25
+
26
+ [MIT](./LICENSE)
@@ -0,0 +1,11 @@
1
+ import { HazePluginOptions } from "@hazejs/vite";
2
+ import { ViteUserConfig } from "vitest/config";
3
+ export interface HazeTestConfig extends ViteUserConfig {
4
+ /** 传给 haze() 插件的选项,例如全栈项目的 `{ srcDir: 'server' }`(默认读取 haze.config.ts) */
5
+ haze?: Omit<HazePluginOptions, 'standalone'>;
6
+ }
7
+ /**
8
+ * 后端测试的 Vitest 配置:注册 haze 插件(提供 `virtual:haze/app` 与 `~` 别名),
9
+ * 并让 Vite 处理 `@hazejs/testing`,使其能导入虚拟模块。
10
+ */
11
+ export declare function defineHazeTestConfig(config?: HazeTestConfig): ViteUserConfig;
@@ -0,0 +1,21 @@
1
+ import { haze } from "@hazejs/vite";
2
+ import { mergeConfig } from "vitest/config";
3
+ // src/config.ts
4
+ /**
5
+ * 后端测试的 Vitest 配置:注册 haze 插件(提供 `virtual:haze/app` 与 `~` 别名),
6
+ * 并让 Vite 处理 `@hazejs/testing`,使其能导入虚拟模块。
7
+ */
8
+ function defineHazeTestConfig(config = {}) {
9
+ const { haze: pluginOptions, ...rest } = config;
10
+ return mergeConfig({
11
+ plugins: [haze({
12
+ ...pluginOptions,
13
+ standalone: true
14
+ })],
15
+ test: {
16
+ environment: "node",
17
+ server: { deps: { inline: ["@hazejs/testing"] } }
18
+ }
19
+ }, rest);
20
+ }
21
+ export { defineHazeTestConfig };
@@ -0,0 +1,53 @@
1
+ import { Application, ApplicationOptions, Container, InjectionKey } from "@hazejs/core";
2
+ export interface TestAppOptions extends ApplicationOptions {
3
+ /** 替换绑定(在提供者 register 之后、boot 之前执行),例如 `c => c.instance(Repo, fakeRepo)` */
4
+ override?: (container: Container, app: Application) => void | Promise<void>;
5
+ }
6
+ export interface TestRequestInit extends Omit<RequestInit, 'body'> {
7
+ query?: Record<string, string | number | boolean>;
8
+ }
9
+ export interface TestResponse<T = any> {
10
+ status: number;
11
+ ok: boolean;
12
+ headers: Headers;
13
+ /** 按 content-type 解析:JSON 为对象,其余为文本 */
14
+ body: T;
15
+ text: string;
16
+ }
17
+ export interface TestClient {
18
+ get: <T = any>(path: string, init?: TestRequestInit) => Promise<TestResponse<T>>;
19
+ delete: <T = any>(path: string, init?: TestRequestInit) => Promise<TestResponse<T>>;
20
+ post: <T = any>(path: string, body?: unknown, init?: TestRequestInit) => Promise<TestResponse<T>>;
21
+ put: <T = any>(path: string, body?: unknown, init?: TestRequestInit) => Promise<TestResponse<T>>;
22
+ patch: <T = any>(path: string, body?: unknown, init?: TestRequestInit) => Promise<TestResponse<T>>;
23
+ /** 返回带默认请求头的新客户端,例如 `withHeaders({ authorization: 'Bearer x' })` */
24
+ withHeaders: (headers: Record<string, string>) => TestClient;
25
+ /**
26
+ * 以指定用户登录的客户端:JWT Guard 签发真实令牌(Authorization 头),
27
+ * Session Guard 生成真实的会话 Cookie。默认使用 config/auth.ts 的默认 Guard。
28
+ */
29
+ actingAs: (user: unknown, guard?: string) => Promise<TestClient>;
30
+ }
31
+ export interface TestApp extends TestClient {
32
+ readonly app: Application;
33
+ /** 原始 fetch 风格请求 */
34
+ request: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
35
+ make: <T>(key: InjectionKey<T>) => T;
36
+ close: () => Promise<void>;
37
+ }
38
+ /**
39
+ * 创建测试用应用(不监听端口)。使用 `virtual:haze/app`,与 dev / build 发现同一套
40
+ * config / route / provider;默认 mode 为 `test`,会加载 `.env.test`。
41
+ */
42
+ export declare function createTestApp(options?: TestAppOptions): Promise<TestApp>;
43
+ /**
44
+ * 每个用例前创建全新的应用、用例后释放,返回的对象在用例内直接使用:
45
+ *
46
+ * ```ts
47
+ * const t = useTestApp({ override: c => c.instance(UserRepository, new FakeRepo()) })
48
+ * it('lists users', async () => {
49
+ * expect((await t.get('/api/users')).status).toBe(200)
50
+ * })
51
+ * ```
52
+ */
53
+ export declare function useTestApp(options?: TestAppOptions): TestApp;
package/dist/index.mjs ADDED
@@ -0,0 +1,110 @@
1
+ import { issueTestCredentials } from "@hazejs/core";
2
+ import { createApp } from "virtual:haze/app";
3
+ import { afterEach, beforeEach } from "vitest";
4
+ // src/index.ts
5
+ /**
6
+ * 创建测试用应用(不监听端口)。使用 `virtual:haze/app`,与 dev / build 发现同一套
7
+ * config / route / provider;默认 mode 为 `test`,会加载 `.env.test`。
8
+ */
9
+ async function createTestApp(options = {}) {
10
+ const { override, setup, ...rest } = options;
11
+ const app = await createApp({
12
+ mode: "test",
13
+ ...rest,
14
+ async setup(app) {
15
+ await setup?.(app);
16
+ await override?.(app.container, app);
17
+ }
18
+ });
19
+ return {
20
+ app,
21
+ request: (input, init) => app.request(input, init),
22
+ make: (key) => app.make(key),
23
+ close: () => app.dispose(),
24
+ ...createClient(app, {})
25
+ };
26
+ }
27
+ /**
28
+ * 每个用例前创建全新的应用、用例后释放,返回的对象在用例内直接使用:
29
+ *
30
+ * ```ts
31
+ * const t = useTestApp({ override: c => c.instance(UserRepository, new FakeRepo()) })
32
+ * it('lists users', async () => {
33
+ * expect((await t.get('/api/users')).status).toBe(200)
34
+ * })
35
+ * ```
36
+ */
37
+ function useTestApp(options = {}) {
38
+ let current;
39
+ beforeEach(async () => {
40
+ current = await createTestApp(options);
41
+ });
42
+ afterEach(async () => {
43
+ await current?.close();
44
+ current = void 0;
45
+ });
46
+ const active = () => {
47
+ if (!current) throw new Error("useTestApp(): the app is only available inside tests (it/test or hooks after beforeEach)");
48
+ return current;
49
+ };
50
+ return {
51
+ get app() {
52
+ return active().app;
53
+ },
54
+ request: (input, init) => active().request(input, init),
55
+ make: (key) => active().make(key),
56
+ close: () => active().close(),
57
+ get: (path, init) => active().get(path, init),
58
+ delete: (path, init) => active().delete(path, init),
59
+ post: (path, body, init) => active().post(path, body, init),
60
+ put: (path, body, init) => active().put(path, body, init),
61
+ patch: (path, body, init) => active().patch(path, body, init),
62
+ withHeaders: (headers) => active().withHeaders(headers),
63
+ actingAs: (user, guard) => active().actingAs(user, guard)
64
+ };
65
+ }
66
+ function createClient(app, defaults) {
67
+ const send = async (method, path, body, init = {}) => {
68
+ const { query, headers, ...rest } = init;
69
+ const url = query ? `${path}${path.includes("?") ? "&" : "?"}${new URLSearchParams(Object.entries(query).map(([k, v]) => [k, String(v)]))}` : path;
70
+ const merged = new Headers({ ...defaults });
71
+ new Headers(headers).forEach((value, key) => merged.set(key, value));
72
+ let payload;
73
+ if (body !== void 0) {
74
+ const raw = typeof body === "string" || body instanceof FormData || body instanceof URLSearchParams || body instanceof Blob;
75
+ payload = raw ? body : JSON.stringify(body);
76
+ if (!raw && !merged.has("content-type")) merged.set("content-type", "application/json");
77
+ }
78
+ const res = await app.request(url, {
79
+ ...rest,
80
+ method,
81
+ headers: merged,
82
+ body: payload
83
+ });
84
+ const text = await res.text();
85
+ const isJson = res.headers.get("content-type")?.includes("json") && text;
86
+ return {
87
+ status: res.status,
88
+ ok: res.ok,
89
+ headers: res.headers,
90
+ body: isJson ? JSON.parse(text) : text,
91
+ text
92
+ };
93
+ };
94
+ return {
95
+ get: (path, init) => send("GET", path, void 0, init),
96
+ delete: (path, init) => send("DELETE", path, void 0, init),
97
+ post: (path, body, init) => send("POST", path, body, init),
98
+ put: (path, body, init) => send("PUT", path, body, init),
99
+ patch: (path, body, init) => send("PATCH", path, body, init),
100
+ withHeaders: (headers) => createClient(app, {
101
+ ...defaults,
102
+ ...headers
103
+ }),
104
+ actingAs: async (user, guard) => createClient(app, {
105
+ ...defaults,
106
+ ...await issueTestCredentials(app, user, guard)
107
+ })
108
+ };
109
+ }
110
+ export { createTestApp, useTestApp };
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "@hazejs/testing",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "type": "module",
4
+ "version": "0.1.1",
5
+ "description": "Vitest helpers for hazejs: test app, HTTP client, actingAs and fakes",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/penna-org/haze/tree/main/packages/testing#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/penna-org/haze.git",
11
+ "directory": "packages/testing"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/penna-org/haze/issues"
15
+ },
16
+ "keywords": [
17
+ "hazejs",
18
+ "haze",
19
+ "nodejs",
20
+ "typescript",
21
+ "testing",
22
+ "vitest"
23
+ ],
24
+ "sideEffects": false,
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.mts",
28
+ "default": "./dist/index.mjs"
29
+ },
30
+ "./config": {
31
+ "types": "./dist/config.d.mts",
32
+ "default": "./dist/config.mjs"
33
+ },
34
+ "./package.json": "./package.json"
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "engines": {
40
+ "node": ">=22.12.0"
41
+ },
42
+ "peerDependencies": {
43
+ "vitest": "^5.0.0",
44
+ "@hazejs/core": "^0.1.1",
45
+ "@hazejs/vite": "^0.1.1"
46
+ },
47
+ "devDependencies": {
48
+ "vitest": "^5.0.5",
49
+ "@hazejs/core": "0.1.1",
50
+ "@hazejs/vite": "0.1.1"
51
+ },
52
+ "scripts": {
53
+ "build": "tsdown",
54
+ "dev": "tsdown --watch"
55
+ }
6
56
  }