fast-api-tester 1.0.0

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.
@@ -0,0 +1,42 @@
1
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
2
+ export interface RequestOptions {
3
+ headers?: Record<string, string>;
4
+ query?: Record<string, string | number | boolean>;
5
+ body?: unknown;
6
+ timeoutMs?: number;
7
+ }
8
+ export interface ApiResponse<T = unknown> {
9
+ statusCode: number;
10
+ headers: Record<string, string | string[] | undefined>;
11
+ body: T;
12
+ latencyMs: number;
13
+ }
14
+ /**
15
+ * Fluent API Test Client
16
+ */
17
+ export declare class ApiEngine {
18
+ private baseUrl;
19
+ private defaultHeaders;
20
+ /**
21
+ * Set the base URL for subsequent calls
22
+ */
23
+ setBaseUrl(url: string): this;
24
+ /**
25
+ * Add default headers (e.g., Bearer tokens) across all requests
26
+ */
27
+ setHeader(key: string, value: string): this;
28
+ get<T = unknown>(path: string, options?: Omit<RequestOptions, 'body'>): Promise<ApiResponse<T>>;
29
+ post<T = unknown>(path: string, body?: unknown, options?: Omit<RequestOptions, 'body'>): Promise<ApiResponse<T>>;
30
+ put<T = unknown>(path: string, body?: unknown, options?: Omit<RequestOptions, 'body'>): Promise<ApiResponse<T>>;
31
+ patch<T = unknown>(path: string, body?: unknown, options?: Omit<RequestOptions, 'body'>): Promise<ApiResponse<T>>;
32
+ delete<T = unknown>(path: string, options?: Omit<RequestOptions, 'body'>): Promise<ApiResponse<T>>;
33
+ /**
34
+ * Core request runner using low-level undici.Pool
35
+ */
36
+ request<T = unknown>(method: HttpMethod, path: string, options?: RequestOptions): Promise<ApiResponse<T>>;
37
+ /**
38
+ * Gracefully close all connection pools when tests complete
39
+ */
40
+ destroy(): Promise<void>;
41
+ }
42
+ //# sourceMappingURL=apiEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apiEngine.d.ts","sourceRoot":"","sources":["../src/apiEngine.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;AAErE,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IAClD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,IAAI,EAAE,CAAC,CAAC;IACR,SAAS,EAAE,MAAM,CAAC;CACnB;AAoBD;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,cAAc,CAEpB;IAEF;;OAEG;IACI,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAGnC;IAED;;OAEG;IACI,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAGjD;IAIM,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,2BAE3E;IAEM,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,2BAE5F;IAEM,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,2BAE3F;IAEM,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,2BAE7F;IAEM,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,2BAE9E;IAED;;OAEG;IACU,OAAO,CAAC,CAAC,GAAG,OAAO,EAC9B,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAgEzB;IAED;;OAEG;IACU,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAIpC;CACF"}
@@ -0,0 +1,126 @@
1
+ import { Pool } from 'undici';
2
+ import { URL } from 'node:url';
3
+ /**
4
+ * Global connection pool map to keep sockets alive across tests
5
+ */
6
+ const poolRegistry = new Map();
7
+ function getOrCreatePool(origin) {
8
+ let pool = poolRegistry.get(origin);
9
+ if (!pool) {
10
+ pool = new Pool(origin, {
11
+ connections: 50, // Max concurrent sockets per host
12
+ keepAliveTimeout: 60_000, // Reuse sockets for up to 60 seconds
13
+ pipelining: 1,
14
+ });
15
+ poolRegistry.set(origin, pool);
16
+ }
17
+ return pool;
18
+ }
19
+ /**
20
+ * Fluent API Test Client
21
+ */
22
+ export class ApiEngine {
23
+ baseUrl = '';
24
+ defaultHeaders = {
25
+ 'accept': 'application/json',
26
+ };
27
+ /**
28
+ * Set the base URL for subsequent calls
29
+ */
30
+ setBaseUrl(url) {
31
+ this.baseUrl = url.endsWith('/') ? url.slice(0, -1) : url;
32
+ return this;
33
+ }
34
+ /**
35
+ * Add default headers (e.g., Bearer tokens) across all requests
36
+ */
37
+ setHeader(key, value) {
38
+ this.defaultHeaders[key.toLowerCase()] = value;
39
+ return this;
40
+ }
41
+ // --- HTTP Verb Shortcuts ---
42
+ get(path, options) {
43
+ return this.request('GET', path, options);
44
+ }
45
+ post(path, body, options) {
46
+ return this.request('POST', path, { ...options, body });
47
+ }
48
+ put(path, body, options) {
49
+ return this.request('PUT', path, { ...options, body });
50
+ }
51
+ patch(path, body, options) {
52
+ return this.request('PATCH', path, { ...options, body });
53
+ }
54
+ delete(path, options) {
55
+ return this.request('DELETE', path, options);
56
+ }
57
+ /**
58
+ * Core request runner using low-level undici.Pool
59
+ */
60
+ async request(method, path, options = {}) {
61
+ const fullPath = path.startsWith('/') ? path : `/${path}`;
62
+ const targetUrl = new URL(`${this.baseUrl}${fullPath}`);
63
+ // Append query parameters
64
+ if (options.query) {
65
+ Object.entries(options.query).forEach(([key, val]) => {
66
+ targetUrl.searchParams.append(key, String(val));
67
+ });
68
+ }
69
+ // Merge headers
70
+ const headers = {
71
+ ...this.defaultHeaders,
72
+ ...(options.headers || {}),
73
+ };
74
+ // Serialize body if present
75
+ let requestBody = undefined;
76
+ if (options.body !== undefined) {
77
+ if (typeof options.body === 'object') {
78
+ requestBody = JSON.stringify(options.body);
79
+ if (!headers['content-type']) {
80
+ headers['content-type'] = 'application/json';
81
+ }
82
+ }
83
+ else {
84
+ requestBody = String(options.body);
85
+ }
86
+ }
87
+ const pool = getOrCreatePool(targetUrl.origin);
88
+ const startTime = process.hrtime.bigint();
89
+ // High-performance direct pool dispatch
90
+ const response = await pool.request({
91
+ path: `${targetUrl.pathname}${targetUrl.search}`,
92
+ method: method,
93
+ headers: headers,
94
+ body: requestBody,
95
+ headersTimeout: options.timeoutMs ?? 10_000,
96
+ bodyTimeout: options.timeoutMs ?? 10_000,
97
+ });
98
+ // Calculate latency in milliseconds
99
+ const endTime = process.hrtime.bigint();
100
+ const latencyMs = Number(endTime - startTime) / 1_000_000;
101
+ // Parse response body
102
+ const rawText = await response.body.text();
103
+ let parsedBody;
104
+ try {
105
+ parsedBody = rawText ? JSON.parse(rawText) : null;
106
+ }
107
+ catch {
108
+ parsedBody = rawText;
109
+ }
110
+ return {
111
+ statusCode: response.statusCode,
112
+ headers: response.headers,
113
+ body: parsedBody,
114
+ latencyMs: Number(latencyMs.toFixed(2)),
115
+ };
116
+ }
117
+ /**
118
+ * Gracefully close all connection pools when tests complete
119
+ */
120
+ async destroy() {
121
+ const closes = Array.from(poolRegistry.values()).map((p) => p.close());
122
+ await Promise.all(closes);
123
+ poolRegistry.clear();
124
+ }
125
+ }
126
+ //# sourceMappingURL=apiEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apiEngine.js","sourceRoot":"","sources":["../src/apiEngine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { ApiEngine } from './apiEngine.js';
2
+ export type { HttpMethod, RequestOptions, ApiResponse } from './apiEngine.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ // src/index.ts
2
+ export { ApiEngine } from './apiEngine.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAe;AACf,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "fast-api-tester",
3
+ "version": "1.0.0",
4
+ "description": "Ultra-fast, connection-pooled API testing library for Node.js",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "clean": "rimraf dist",
19
+ "build": "npm run clean && tsc",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "keywords": [
23
+ "api",
24
+ "testing",
25
+ "http",
26
+ "fast",
27
+ "undici",
28
+ "postman-alternative",
29
+ "playwright"
30
+ ],
31
+ "author": "Kushan Amarasiri",
32
+ "license": "MIT",
33
+ "dependencies": {
34
+ "undici": "^6.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^20.0.0",
38
+ "rimraf": "^5.0.0",
39
+ "typescript": "^5.0.0"
40
+ }
41
+ }