fast-api-tester 1.0.4 ā 1.0.6
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/dist/apiEngine.d.ts +17 -24
- package/dist/apiEngine.d.ts.map +1 -1
- package/dist/apiEngine.js +173 -43
- package/dist/apiEngine.js.map +1 -1
- package/package.json +1 -1
package/dist/apiEngine.d.ts
CHANGED
|
@@ -21,9 +21,14 @@ export interface TestExecutionRecord {
|
|
|
21
21
|
errorMsg?: string;
|
|
22
22
|
timestamp: string;
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
export interface RunSummaryHistory {
|
|
25
|
+
runId: string;
|
|
26
|
+
timestamp: string;
|
|
27
|
+
total: number;
|
|
28
|
+
passed: number;
|
|
29
|
+
failed: number;
|
|
30
|
+
avgLatencyMs: number;
|
|
31
|
+
}
|
|
27
32
|
export declare class ApiResponse<T = unknown> implements ApiResponseData<T> {
|
|
28
33
|
statusCode: number;
|
|
29
34
|
headers: Record<string, string | string[] | undefined>;
|
|
@@ -31,52 +36,40 @@ export declare class ApiResponse<T = unknown> implements ApiResponseData<T> {
|
|
|
31
36
|
latencyMs: number;
|
|
32
37
|
lastExecutionRecord?: TestExecutionRecord;
|
|
33
38
|
constructor(data: ApiResponseData<T>);
|
|
34
|
-
/**
|
|
35
|
-
* Asserts that the response latency was strictly under the specified SLA limit (in ms).
|
|
36
|
-
* @throws Error if latency meets or exceeds the target SLA.
|
|
37
|
-
*/
|
|
38
39
|
expectLatencyUnder(maxLatencyMs: number): this;
|
|
39
40
|
}
|
|
40
41
|
/**
|
|
41
|
-
*
|
|
42
|
+
* Utility to parse intervals like "1m", "3m", "5m", "30m", or "10s" into milliseconds
|
|
42
43
|
*/
|
|
44
|
+
export declare function parseCliInterval(): {
|
|
45
|
+
raw: string;
|
|
46
|
+
ms: number;
|
|
47
|
+
} | null;
|
|
43
48
|
export declare class ApiEngine {
|
|
44
49
|
private baseUrl;
|
|
45
50
|
private defaultHeaders;
|
|
46
51
|
private records;
|
|
47
52
|
private reportOutputPath;
|
|
53
|
+
private historyFilePath;
|
|
48
54
|
private autoReportEnabled;
|
|
49
|
-
/**
|
|
50
|
-
* Set the base URL for subsequent calls
|
|
51
|
-
*/
|
|
52
55
|
setBaseUrl(url: string): this;
|
|
53
|
-
/**
|
|
54
|
-
* Add default headers (e.g., Bearer tokens) across all requests
|
|
55
|
-
*/
|
|
56
56
|
setHeader(key: string, value: string): this;
|
|
57
|
-
/**
|
|
58
|
-
* Configure HTML reporting output path or disable it
|
|
59
|
-
*/
|
|
60
57
|
configureReport(options: {
|
|
61
58
|
enabled?: boolean;
|
|
62
59
|
outputPath?: string;
|
|
60
|
+
historyPath?: string;
|
|
63
61
|
}): this;
|
|
64
62
|
get<T = unknown>(path: string, options?: Omit<RequestOptions, 'body'>): Promise<ApiResponse<T>>;
|
|
65
63
|
post<T = unknown>(path: string, body?: unknown, options?: Omit<RequestOptions, 'body'>): Promise<ApiResponse<T>>;
|
|
66
64
|
put<T = unknown>(path: string, body?: unknown, options?: Omit<RequestOptions, 'body'>): Promise<ApiResponse<T>>;
|
|
67
65
|
patch<T = unknown>(path: string, body?: unknown, options?: Omit<RequestOptions, 'body'>): Promise<ApiResponse<T>>;
|
|
68
66
|
delete<T = unknown>(path: string, options?: Omit<RequestOptions, 'body'>): Promise<ApiResponse<T>>;
|
|
69
|
-
/**
|
|
70
|
-
* Core request runner using low-level undici.Pool
|
|
71
|
-
*/
|
|
72
67
|
request<T = unknown>(method: HttpMethod, path: string, options?: RequestOptions): Promise<ApiResponse<T>>;
|
|
73
68
|
/**
|
|
74
|
-
*
|
|
69
|
+
* Persists current run to history file and returns all past execution history
|
|
75
70
|
*/
|
|
71
|
+
private updateAndGetHistory;
|
|
76
72
|
private generateHtmlReport;
|
|
77
|
-
/**
|
|
78
|
-
* Gracefully close connection pools & write auto-generated HTML report
|
|
79
|
-
*/
|
|
80
73
|
destroy(): Promise<void>;
|
|
81
74
|
}
|
|
82
75
|
//# sourceMappingURL=apiEngine.d.ts.map
|
package/dist/apiEngine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiEngine.d.ts","sourceRoot":"","sources":["../src/apiEngine.ts"],"names":[],"mappings":"AAKA,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,eAAe,CAAC,CAAC,GAAG,OAAO;IAC1C,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;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED
|
|
1
|
+
{"version":3,"file":"apiEngine.d.ts","sourceRoot":"","sources":["../src/apiEngine.ts"],"names":[],"mappings":"AAKA,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,eAAe,CAAC,CAAC,GAAG,OAAO;IAC1C,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;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,WAAW,CAAC,CAAC,GAAG,OAAO,CAAE,YAAW,eAAe,CAAC,CAAC,CAAC;IAC1D,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;IAClB,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAEjD,YAAY,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,EAKnC;IAED,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAe7C;CACF;AAiBD;;GAEG;AACH,wBAAgB,gBAAgB,IAAI;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAyBrE;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,cAAc,CAEpB;IAEF,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,iBAAiB,CAAiB;IAEnC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAGnC;IAEM,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAGjD;IAEM,eAAe,CAAC,OAAO,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAKtG;IAEM,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;IAEY,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,CA0EzB;IAED;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAuB3B,OAAO,CAAC,kBAAkB;IAyMb,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAepC;CACF"}
|
package/dist/apiEngine.js
CHANGED
|
@@ -2,15 +2,11 @@ import { Pool } from 'undici';
|
|
|
2
2
|
import { URL } from 'node:url';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
/**
|
|
6
|
-
* Fluent API Response class supporting chained SLA assertions
|
|
7
|
-
*/
|
|
8
5
|
export class ApiResponse {
|
|
9
6
|
statusCode;
|
|
10
7
|
headers;
|
|
11
8
|
body;
|
|
12
9
|
latencyMs;
|
|
13
|
-
// Internal reference for auto-recording SLA assertion outcomes
|
|
14
10
|
lastExecutionRecord;
|
|
15
11
|
constructor(data) {
|
|
16
12
|
this.statusCode = data.statusCode;
|
|
@@ -18,10 +14,6 @@ export class ApiResponse {
|
|
|
18
14
|
this.body = data.body;
|
|
19
15
|
this.latencyMs = data.latencyMs;
|
|
20
16
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Asserts that the response latency was strictly under the specified SLA limit (in ms).
|
|
23
|
-
* @throws Error if latency meets or exceeds the target SLA.
|
|
24
|
-
*/
|
|
25
17
|
expectLatencyUnder(maxLatencyMs) {
|
|
26
18
|
if (this.lastExecutionRecord) {
|
|
27
19
|
this.lastExecutionRecord.slaLimitMs = maxLatencyMs;
|
|
@@ -37,9 +29,6 @@ export class ApiResponse {
|
|
|
37
29
|
return this;
|
|
38
30
|
}
|
|
39
31
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Global connection pool map to keep sockets alive across tests
|
|
42
|
-
*/
|
|
43
32
|
const poolRegistry = new Map();
|
|
44
33
|
function getOrCreatePool(origin) {
|
|
45
34
|
let pool = poolRegistry.get(origin);
|
|
@@ -54,8 +43,33 @@ function getOrCreatePool(origin) {
|
|
|
54
43
|
return pool;
|
|
55
44
|
}
|
|
56
45
|
/**
|
|
57
|
-
*
|
|
46
|
+
* Utility to parse intervals like "1m", "3m", "5m", "30m", or "10s" into milliseconds
|
|
58
47
|
*/
|
|
48
|
+
export function parseCliInterval() {
|
|
49
|
+
const args = process.argv.slice(2);
|
|
50
|
+
let intervalStr = null;
|
|
51
|
+
for (let i = 0; i < args.length; i++) {
|
|
52
|
+
if ((args[i] === '--interval' || args[i] === '-i') && args[i + 1]) {
|
|
53
|
+
intervalStr = args[i + 1];
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!intervalStr)
|
|
58
|
+
return null;
|
|
59
|
+
const match = intervalStr.match(/^(\d+)(s|m|h)$/);
|
|
60
|
+
if (!match)
|
|
61
|
+
return null;
|
|
62
|
+
const value = parseInt(match[1], 10);
|
|
63
|
+
const unit = match[2];
|
|
64
|
+
let ms = 60_000;
|
|
65
|
+
if (unit === 's')
|
|
66
|
+
ms = value * 1000;
|
|
67
|
+
if (unit === 'm')
|
|
68
|
+
ms = value * 60 * 1000;
|
|
69
|
+
if (unit === 'h')
|
|
70
|
+
ms = value * 60 * 60 * 1000;
|
|
71
|
+
return { raw: intervalStr, ms };
|
|
72
|
+
}
|
|
59
73
|
export class ApiEngine {
|
|
60
74
|
baseUrl = '';
|
|
61
75
|
defaultHeaders = {
|
|
@@ -63,32 +77,25 @@ export class ApiEngine {
|
|
|
63
77
|
};
|
|
64
78
|
records = [];
|
|
65
79
|
reportOutputPath = 'test-report.html';
|
|
80
|
+
historyFilePath = '.test-history.json';
|
|
66
81
|
autoReportEnabled = true;
|
|
67
|
-
/**
|
|
68
|
-
* Set the base URL for subsequent calls
|
|
69
|
-
*/
|
|
70
82
|
setBaseUrl(url) {
|
|
71
83
|
this.baseUrl = url.endsWith('/') ? url.slice(0, -1) : url;
|
|
72
84
|
return this;
|
|
73
85
|
}
|
|
74
|
-
/**
|
|
75
|
-
* Add default headers (e.g., Bearer tokens) across all requests
|
|
76
|
-
*/
|
|
77
86
|
setHeader(key, value) {
|
|
78
87
|
this.defaultHeaders[key.toLowerCase()] = value;
|
|
79
88
|
return this;
|
|
80
89
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Configure HTML reporting output path or disable it
|
|
83
|
-
*/
|
|
84
90
|
configureReport(options) {
|
|
85
91
|
if (options.enabled !== undefined)
|
|
86
92
|
this.autoReportEnabled = options.enabled;
|
|
87
93
|
if (options.outputPath)
|
|
88
94
|
this.reportOutputPath = options.outputPath;
|
|
95
|
+
if (options.historyPath)
|
|
96
|
+
this.historyFilePath = options.historyPath;
|
|
89
97
|
return this;
|
|
90
98
|
}
|
|
91
|
-
// --- HTTP Verb Shortcuts ---
|
|
92
99
|
get(path, options) {
|
|
93
100
|
return this.request('GET', path, options);
|
|
94
101
|
}
|
|
@@ -104,24 +111,18 @@ export class ApiEngine {
|
|
|
104
111
|
delete(path, options) {
|
|
105
112
|
return this.request('DELETE', path, options);
|
|
106
113
|
}
|
|
107
|
-
/**
|
|
108
|
-
* Core request runner using low-level undici.Pool
|
|
109
|
-
*/
|
|
110
114
|
async request(method, path, options = {}) {
|
|
111
115
|
const fullPath = path.startsWith('/') ? path : `/${path}`;
|
|
112
116
|
const targetUrl = new URL(`${this.baseUrl}${fullPath}`);
|
|
113
|
-
// Append query parameters
|
|
114
117
|
if (options.query) {
|
|
115
118
|
Object.entries(options.query).forEach(([key, val]) => {
|
|
116
119
|
targetUrl.searchParams.append(key, String(val));
|
|
117
120
|
});
|
|
118
121
|
}
|
|
119
|
-
// Merge headers
|
|
120
122
|
const headers = {
|
|
121
123
|
...this.defaultHeaders,
|
|
122
124
|
...(options.headers || {}),
|
|
123
125
|
};
|
|
124
|
-
// Serialize body if present
|
|
125
126
|
let requestBody = undefined;
|
|
126
127
|
if (options.body !== undefined) {
|
|
127
128
|
if (typeof options.body === 'object') {
|
|
@@ -136,7 +137,6 @@ export class ApiEngine {
|
|
|
136
137
|
}
|
|
137
138
|
const pool = getOrCreatePool(targetUrl.origin);
|
|
138
139
|
const startTime = process.hrtime.bigint();
|
|
139
|
-
// High-performance direct pool dispatch
|
|
140
140
|
const response = await pool.request({
|
|
141
141
|
path: `${targetUrl.pathname}${targetUrl.search}`,
|
|
142
142
|
method: method,
|
|
@@ -145,10 +145,8 @@ export class ApiEngine {
|
|
|
145
145
|
headersTimeout: options.timeoutMs ?? 10_000,
|
|
146
146
|
bodyTimeout: options.timeoutMs ?? 10_000,
|
|
147
147
|
});
|
|
148
|
-
// Calculate latency in milliseconds
|
|
149
148
|
const endTime = process.hrtime.bigint();
|
|
150
149
|
const latencyMs = Number(endTime - startTime) / 1_000_000;
|
|
151
|
-
// Parse response body
|
|
152
150
|
const rawText = await response.body.text();
|
|
153
151
|
let parsedBody;
|
|
154
152
|
try {
|
|
@@ -159,7 +157,6 @@ export class ApiEngine {
|
|
|
159
157
|
}
|
|
160
158
|
const formattedLatency = Number(latencyMs.toFixed(2));
|
|
161
159
|
const passed = response.statusCode >= 200 && response.statusCode < 400;
|
|
162
|
-
// Track execution for auto report
|
|
163
160
|
const record = {
|
|
164
161
|
method,
|
|
165
162
|
endpoint: `${targetUrl.pathname}${targetUrl.search}`,
|
|
@@ -169,7 +166,6 @@ export class ApiEngine {
|
|
|
169
166
|
timestamp: new Date().toLocaleTimeString(),
|
|
170
167
|
};
|
|
171
168
|
this.records.push(record);
|
|
172
|
-
// Wrap output in the fluent ApiResponse class instance
|
|
173
169
|
const apiResponse = new ApiResponse({
|
|
174
170
|
statusCode: response.statusCode,
|
|
175
171
|
headers: response.headers,
|
|
@@ -180,15 +176,45 @@ export class ApiEngine {
|
|
|
180
176
|
return apiResponse;
|
|
181
177
|
}
|
|
182
178
|
/**
|
|
183
|
-
*
|
|
179
|
+
* Persists current run to history file and returns all past execution history
|
|
184
180
|
*/
|
|
181
|
+
updateAndGetHistory(currentSummary) {
|
|
182
|
+
const historyFile = path.resolve(this.historyFilePath);
|
|
183
|
+
let history = [];
|
|
184
|
+
if (fs.existsSync(historyFile)) {
|
|
185
|
+
try {
|
|
186
|
+
const content = fs.readFileSync(historyFile, 'utf-8');
|
|
187
|
+
history = JSON.parse(content);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
history = [];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Add current run and keep last 10 executions
|
|
194
|
+
history.push(currentSummary);
|
|
195
|
+
if (history.length > 10) {
|
|
196
|
+
history = history.slice(-10);
|
|
197
|
+
}
|
|
198
|
+
fs.writeFileSync(historyFile, JSON.stringify(history, null, 2), 'utf-8');
|
|
199
|
+
return history;
|
|
200
|
+
}
|
|
185
201
|
generateHtmlReport() {
|
|
186
202
|
if (this.records.length === 0)
|
|
187
203
|
return;
|
|
188
204
|
const total = this.records.length;
|
|
189
205
|
const passed = this.records.filter((r) => r.passed).length;
|
|
190
206
|
const failed = total - passed;
|
|
191
|
-
const avgLatency = (this.records.reduce((acc, r) => acc + r.latencyMs, 0) / total).toFixed(1);
|
|
207
|
+
const avgLatency = Number((this.records.reduce((acc, r) => acc + r.latencyMs, 0) / total).toFixed(1));
|
|
208
|
+
const runTimestamp = new Date().toLocaleString();
|
|
209
|
+
const currentRunSummary = {
|
|
210
|
+
runId: `#${Date.now().toString().slice(-4)}`,
|
|
211
|
+
timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
|
212
|
+
total,
|
|
213
|
+
passed,
|
|
214
|
+
failed,
|
|
215
|
+
avgLatencyMs: avgLatency,
|
|
216
|
+
};
|
|
217
|
+
const historyData = this.updateAndGetHistory(currentRunSummary);
|
|
192
218
|
const tableRows = this.records
|
|
193
219
|
.map((r) => `
|
|
194
220
|
<tr>
|
|
@@ -203,65 +229,163 @@ export class ApiEngine {
|
|
|
203
229
|
</span>
|
|
204
230
|
${r.errorMsg ? `<div class="error-msg">${r.errorMsg}</div>` : ''}
|
|
205
231
|
</td>
|
|
232
|
+
</tr>`)
|
|
233
|
+
.join('');
|
|
234
|
+
const historyRows = historyData
|
|
235
|
+
.map((h) => `
|
|
236
|
+
<tr>
|
|
237
|
+
<td><strong>${h.runId}</strong></td>
|
|
238
|
+
<td>${h.timestamp}</td>
|
|
239
|
+
<td>${h.total}</td>
|
|
240
|
+
<td style="color: #16a34a; font-weight: bold;">${h.passed}</td>
|
|
241
|
+
<td style="color: #dc2626; font-weight: bold;">${h.failed}</td>
|
|
242
|
+
<td>${h.avgLatencyMs} ms</td>
|
|
206
243
|
</tr>`)
|
|
207
244
|
.join('');
|
|
208
245
|
const html = `<!DOCTYPE html>
|
|
209
246
|
<html lang="en">
|
|
210
247
|
<head>
|
|
211
248
|
<meta charset="UTF-8">
|
|
212
|
-
<title>fast-api-tester Execution Summary</title>
|
|
249
|
+
<title>fast-api-tester Analytics & Execution Summary</title>
|
|
250
|
+
<!-- CDN load Chart.js -->
|
|
251
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
213
252
|
<style>
|
|
214
253
|
*, *::before, *::after { box-sizing: border-box; }
|
|
215
254
|
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f8fafc; color: #1e293b; padding: 30px; margin: 0; }
|
|
216
255
|
.header { background: #0f172a; color: #fff; padding: 24px 28px; border-radius: 8px; margin-bottom: 24px; }
|
|
217
256
|
.header h1 { margin: 0 0 6px 0; font-size: 20pt; color: #38bdf8; }
|
|
218
257
|
.header p { margin: 0; color: #94a3b8; font-size: 10pt; }
|
|
258
|
+
|
|
219
259
|
.summary-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 24px; }
|
|
220
260
|
.card { background: #fff; border: 1px solid #e2e8f0; border-radius: 8px; padding: 18px; }
|
|
221
261
|
.card-label { font-size: 8pt; text-transform: uppercase; color: #64748b; font-weight: 600; margin-bottom: 4px; }
|
|
222
262
|
.card-value { font-size: 20pt; font-weight: 700; color: #0f172a; }
|
|
223
263
|
.card-value.pass { color: #16a34a; } .card-value.fail { color: #dc2626; } .card-value.sla { color: #2563eb; }
|
|
224
|
-
|
|
264
|
+
|
|
265
|
+
.charts-container { display: grid; grid-template-columns: 1fr 2fr; gap: 20px; margin-bottom: 24px; }
|
|
266
|
+
.chart-box { background: #fff; border: 1px solid #e2e8f0; border-radius: 8px; padding: 20px; height: 320px; }
|
|
267
|
+
.chart-box h3 { margin-top: 0; font-size: 11pt; color: #475569; margin-bottom: 15px; }
|
|
268
|
+
|
|
269
|
+
table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; border: 1px solid #e2e8f0; overflow: hidden; margin-bottom: 24px; }
|
|
225
270
|
th { background: #f1f5f9; color: #475569; text-align: left; padding: 12px 14px; font-size: 9pt; text-transform: uppercase; border-bottom: 1px solid #e2e8f0; }
|
|
226
271
|
td { padding: 12px 14px; border-bottom: 1px solid #f1f5f9; font-size: 9.5pt; vertical-align: top; }
|
|
272
|
+
|
|
227
273
|
.badge { padding: 3px 8px; border-radius: 4px; font-size: 8pt; font-weight: 700; text-transform: uppercase; }
|
|
228
274
|
.badge-get { background: #e0f2fe; color: #0369a1; }
|
|
229
275
|
.badge-post { background: #dcfce7; color: #15803d; }
|
|
230
276
|
.badge-put { background: #fef3c7; color: #b45309; }
|
|
231
277
|
.badge-patch { background: #f3e8ff; color: #6b21a8; }
|
|
232
278
|
.badge-delete { background: #fee2e2; color: #b91c1c; }
|
|
279
|
+
|
|
233
280
|
.status-tag { font-weight: 700; padding: 3px 8px; border-radius: 4px; font-size: 8pt; display: inline-block; }
|
|
234
281
|
.status-pass { background: #dcfce7; color: #166534; }
|
|
235
282
|
.status-fail { background: #fee2e2; color: #991b1b; }
|
|
236
283
|
.error-msg { margin-top: 6px; font-family: monospace; font-size: 8.5pt; color: #dc2626; background: #fef2f2; padding: 6px 8px; border-radius: 4px; }
|
|
284
|
+
|
|
285
|
+
.section-title { font-size: 13pt; color: #0f172a; margin-bottom: 12px; }
|
|
237
286
|
</style>
|
|
238
287
|
</head>
|
|
239
288
|
<body>
|
|
240
289
|
<div class="header">
|
|
241
|
-
<h1>ā” fast-api-tester Execution Summary</h1>
|
|
242
|
-
<p>Generated on ${
|
|
290
|
+
<h1>ā” fast-api-tester Analytics & Execution Summary</h1>
|
|
291
|
+
<p>Generated on ${runTimestamp}</p>
|
|
243
292
|
</div>
|
|
293
|
+
|
|
244
294
|
<div class="summary-grid">
|
|
245
295
|
<div class="card"><div class="card-label">Total Requests</div><div class="card-value">${total}</div></div>
|
|
246
296
|
<div class="card"><div class="card-label">Passed</div><div class="card-value pass">${passed}</div></div>
|
|
247
297
|
<div class="card"><div class="card-label">Failed</div><div class="card-value fail">${failed}</div></div>
|
|
248
298
|
<div class="card"><div class="card-label">Avg Latency</div><div class="card-value sla">${avgLatency} ms</div></div>
|
|
249
299
|
</div>
|
|
300
|
+
|
|
301
|
+
<div class="charts-container">
|
|
302
|
+
<div class="chart-box">
|
|
303
|
+
<h3>Current Run Status (Pie Chart)</h3>
|
|
304
|
+
<div style="height: 230px; position: relative;">
|
|
305
|
+
<canvas id="pieChart"></canvas>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
<div class="chart-box">
|
|
309
|
+
<h3>Historical Execution Trend (Pass vs Fail)</h3>
|
|
310
|
+
<div style="height: 230px; position: relative;">
|
|
311
|
+
<canvas id="barChart"></canvas>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
<h2 class="section-title">Current Test Suite Results</h2>
|
|
250
317
|
<table>
|
|
251
318
|
<thead>
|
|
252
319
|
<tr><th>Method</th><th>Endpoint</th><th>Status</th><th>Latency</th><th>SLA Target</th><th>Result</th></tr>
|
|
253
320
|
</thead>
|
|
254
321
|
<tbody>${tableRows}</tbody>
|
|
255
322
|
</table>
|
|
323
|
+
|
|
324
|
+
<h2 class="section-title">Historical Executions (Last 10 Runs)</h2>
|
|
325
|
+
<table>
|
|
326
|
+
<thead>
|
|
327
|
+
<tr><th>Run ID</th><th>Time</th><th>Total Tests</th><th>Passed</th><th>Failed</th><th>Avg Latency</th></tr>
|
|
328
|
+
</thead>
|
|
329
|
+
<tbody>${historyRows}</tbody>
|
|
330
|
+
</table>
|
|
331
|
+
|
|
332
|
+
<script>
|
|
333
|
+
new Chart(document.getElementById('pieChart'), {
|
|
334
|
+
type: 'pie',
|
|
335
|
+
data: {
|
|
336
|
+
labels: ['Passed', 'Failed'],
|
|
337
|
+
datasets: [{
|
|
338
|
+
data: [${passed}, ${failed}],
|
|
339
|
+
backgroundColor: ['#22c55e', '#ef4444'],
|
|
340
|
+
borderWidth: 1
|
|
341
|
+
}]
|
|
342
|
+
},
|
|
343
|
+
options: {
|
|
344
|
+
responsive: true,
|
|
345
|
+
maintainAspectRatio: false,
|
|
346
|
+
plugins: {
|
|
347
|
+
legend: { position: 'bottom' }
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const historyData = ${JSON.stringify(historyData)};
|
|
353
|
+
new Chart(document.getElementById('barChart'), {
|
|
354
|
+
type: 'bar',
|
|
355
|
+
data: {
|
|
356
|
+
labels: historyData.map(h => h.runId + ' (' + h.timestamp + ')'),
|
|
357
|
+
datasets: [
|
|
358
|
+
{
|
|
359
|
+
label: 'Passed',
|
|
360
|
+
data: historyData.map(h => h.passed),
|
|
361
|
+
backgroundColor: '#22c55e',
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
label: 'Failed',
|
|
365
|
+
data: historyData.map(h => h.failed),
|
|
366
|
+
backgroundColor: '#ef4444',
|
|
367
|
+
}
|
|
368
|
+
]
|
|
369
|
+
},
|
|
370
|
+
options: {
|
|
371
|
+
responsive: true,
|
|
372
|
+
maintainAspectRatio: false,
|
|
373
|
+
scales: {
|
|
374
|
+
x: { stacked: true },
|
|
375
|
+
y: { stacked: true, beginAtZero: true }
|
|
376
|
+
},
|
|
377
|
+
plugins: {
|
|
378
|
+
legend: { position: 'bottom' }
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
</script>
|
|
256
383
|
</body>
|
|
257
384
|
</html>`;
|
|
258
385
|
const fullPath = path.resolve(this.reportOutputPath);
|
|
259
386
|
fs.writeFileSync(fullPath, html, 'utf-8');
|
|
260
|
-
console.log(`\nš
|
|
387
|
+
console.log(`\nš Visual report generated: ${fullPath}`);
|
|
261
388
|
}
|
|
262
|
-
/**
|
|
263
|
-
* Gracefully close connection pools & write auto-generated HTML report
|
|
264
|
-
*/
|
|
265
389
|
async destroy() {
|
|
266
390
|
if (this.autoReportEnabled) {
|
|
267
391
|
this.generateHtmlReport();
|
|
@@ -269,6 +393,12 @@ export class ApiEngine {
|
|
|
269
393
|
const closes = Array.from(poolRegistry.values()).map((p) => p.close());
|
|
270
394
|
await Promise.all(closes);
|
|
271
395
|
poolRegistry.clear();
|
|
396
|
+
// Check if optional CLI heartbeat mode is requested (--interval 3m)
|
|
397
|
+
const cliInterval = parseCliInterval();
|
|
398
|
+
if (cliInterval) {
|
|
399
|
+
console.log(`\nš Next heartbeat scheduled in ${cliInterval.raw} (${cliInterval.ms / 1000}s)...`);
|
|
400
|
+
await new Promise((resolve) => setTimeout(resolve, cliInterval.ms));
|
|
401
|
+
}
|
|
272
402
|
}
|
|
273
403
|
}
|
|
274
404
|
//# sourceMappingURL=apiEngine.js.map
|
package/dist/apiEngine.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiEngine.js","sourceRoot":"","sources":["../src/apiEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"apiEngine.js","sourceRoot":"","sources":["../src/apiEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAsC7B,MAAM,OAAO,WAAW;IACf,UAAU,CAAS;IACnB,OAAO,CAAgD;IACvD,IAAI,CAAI;IACR,SAAS,CAAS;IAClB,mBAAmB,CAAuB;IAEjD,YAAY,IAAwB;QAClC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QAClC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IAClC,CAAC;IAED,kBAAkB,CAAC,YAAoB;QACrC,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC7B,IAAI,CAAC,mBAAmB,CAAC,UAAU,GAAG,YAAY,CAAC;QACrD,CAAC;QAED,IAAI,IAAI,CAAC,SAAS,IAAI,YAAY,EAAE,CAAC;YACnC,MAAM,QAAQ,GAAG,yDAAyD,YAAY,gBAAgB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;YAErI,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBAC7B,IAAI,CAAC,mBAAmB,CAAC,MAAM,GAAG,KAAK,CAAC;gBACxC,IAAI,CAAC,mBAAmB,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAC/C,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC5B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED,MAAM,YAAY,GAAG,IAAI,GAAG,EAAgB,CAAC;AAE7C,SAAS,eAAe,CAAC,MAAc;IACrC,IAAI,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE;YACtB,WAAW,EAAE,EAAE;YACf,gBAAgB,EAAE,MAAM;YACxB,UAAU,EAAE,CAAC;SACd,CAAC,CAAC;QACH,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,WAAW,GAAkB,IAAI,CAAC;IAEtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAClE,WAAW,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1B,MAAM;QACR,CAAC;IACH,CAAC;IAED,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAE9B,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAClD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAEtB,IAAI,EAAE,GAAG,MAAM,CAAC;IAChB,IAAI,IAAI,KAAK,GAAG;QAAE,EAAE,GAAG,KAAK,GAAG,IAAI,CAAC;IACpC,IAAI,IAAI,KAAK,GAAG;QAAE,EAAE,GAAG,KAAK,GAAG,EAAE,GAAG,IAAI,CAAC;IACzC,IAAI,IAAI,KAAK,GAAG;QAAE,EAAE,GAAG,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAE9C,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,OAAO,SAAS;IACZ,OAAO,GAAW,EAAE,CAAC;IACrB,cAAc,GAA2B;QAC/C,QAAQ,EAAE,kBAAkB;KAC7B,CAAC;IAEM,OAAO,GAA0B,EAAE,CAAC;IACpC,gBAAgB,GAAW,kBAAkB,CAAC;IAC9C,eAAe,GAAW,oBAAoB,CAAC;IAC/C,iBAAiB,GAAY,IAAI,CAAC;IAEnC,UAAU,CAAC,GAAW;QAC3B,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QAC1D,OAAO,IAAI,CAAC;IACd,CAAC;IAEM,SAAS,CAAC,GAAW,EAAE,KAAa;QACzC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,GAAG,KAAK,CAAC;QAC/C,OAAO,IAAI,CAAC;IACd,CAAC;IAEM,eAAe,CAAC,OAAyE;QAC9F,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS;YAAE,IAAI,CAAC,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;QAC5E,IAAI,OAAO,CAAC,UAAU;YAAE,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;QACnE,IAAI,OAAO,CAAC,WAAW;YAAE,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;QACpE,OAAO,IAAI,CAAC;IACd,CAAC;IAEM,GAAG,CAAc,IAAY,EAAE,OAAsC;QAC1E,OAAO,IAAI,CAAC,OAAO,CAAI,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;IAEM,IAAI,CAAc,IAAY,EAAE,IAAc,EAAE,OAAsC;QAC3F,OAAO,IAAI,CAAC,OAAO,CAAI,MAAM,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;IAEM,GAAG,CAAc,IAAY,EAAE,IAAc,EAAE,OAAsC;QAC1F,OAAO,IAAI,CAAC,OAAO,CAAI,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAEM,KAAK,CAAc,IAAY,EAAE,IAAc,EAAE,OAAsC;QAC5F,OAAO,IAAI,CAAC,OAAO,CAAI,OAAO,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,CAAC;IAEM,MAAM,CAAc,IAAY,EAAE,OAAsC;QAC7E,OAAO,IAAI,CAAC,OAAO,CAAI,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAEM,KAAK,CAAC,OAAO,CAClB,MAAkB,EAClB,IAAY,EACZ,OAAO,GAAmB,EAAE;QAE5B,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QAC1D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,GAAG,QAAQ,EAAE,CAAC,CAAC;QAExD,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE;gBACnD,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAClD,CAAC,CAAC,CAAC;QACL,CAAC;QAED,MAAM,OAAO,GAA2B;YACtC,GAAG,IAAI,CAAC,cAAc;YACtB,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;SAC3B,CAAC;QAEF,IAAI,WAAW,GAAgC,SAAS,CAAC;QACzD,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrC,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBAC3C,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;oBAC7B,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;gBAC/C,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;QAED,MAAM,IAAI,GAAG,eAAe,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QAE1C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC;YAClC,IAAI,EAAE,GAAG,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,MAAM,EAAE;YAChD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,OAAO;YAChB,IAAI,EAAE,WAAW;YACjB,cAAc,EAAE,OAAO,CAAC,SAAS,IAAI,MAAM;YAC3C,WAAW,EAAE,OAAO,CAAC,SAAS,IAAI,MAAM;SACzC,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACxC,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;QAE1D,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC3C,IAAI,UAAa,CAAC;QAElB,IAAI,CAAC;YACH,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAE,IAAU,CAAC;QAC3D,CAAC;QAAC,MAAM,CAAC;YACP,UAAU,GAAG,OAAuB,CAAC;QACvC,CAAC;QAED,MAAM,gBAAgB,GAAG,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QACtD,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,IAAI,GAAG,IAAI,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;QAEvE,MAAM,MAAM,GAAwB;YAClC,MAAM;YACN,QAAQ,EAAE,GAAG,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,MAAM,EAAE;YACpD,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,SAAS,EAAE,gBAAgB;YAC3B,MAAM;YACN,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,kBAAkB,EAAE;SAC3C,CAAC;QAEF,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAE1B,MAAM,WAAW,GAAG,IAAI,WAAW,CAAI;YACrC,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,OAAO,EAAE,QAAQ,CAAC,OAAwD;YAC1E,IAAI,EAAE,UAAU;YAChB,SAAS,EAAE,gBAAgB;SAC5B,CAAC,CAAC;QAEH,WAAW,CAAC,mBAAmB,GAAG,MAAM,CAAC;QACzC,OAAO,WAAW,CAAC;IACrB,CAAC;IAED;;OAEG;IACK,mBAAmB,CAAC,cAAiC;QAC3D,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACvD,IAAI,OAAO,GAAwB,EAAE,CAAC;QAEtC,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC/B,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;gBACtD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAChC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,GAAG,EAAE,CAAC;YACf,CAAC;QACH,CAAC;QAED,8CAA8C;QAC9C,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC7B,IAAI,OAAO,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YACxB,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/B,CAAC;QAED,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QACzE,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,kBAAkB;QACxB,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAEtC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;QAClC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;QAC3D,MAAM,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;QAC9B,MAAM,UAAU,GAAG,MAAM,CACvB,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAC3E,CAAC;QAEF,MAAM,YAAY,GAAG,IAAI,IAAI,EAAE,CAAC,cAAc,EAAE,CAAC;QACjD,MAAM,iBAAiB,GAAsB;YAC3C,KAAK,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;YAC5C,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,kBAAkB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;YACpF,KAAK;YACL,MAAM;YACN,MAAM;YACN,YAAY,EAAE,UAAU;SACzB,CAAC;QAEF,MAAM,WAAW,GAAG,IAAI,CAAC,mBAAmB,CAAC,iBAAiB,CAAC,CAAC;QAEhE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO;aAC3B,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CAAC;;uCAEwB,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,MAAM;oBACtD,CAAC,CAAC,QAAQ;sBACR,CAAC,CAAC,UAAU;cACpB,CAAC,CAAC,SAAS;cACX,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,CAAC,KAAK;;oCAEvB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa;cAC9D,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;;YAEhC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,QAAQ,QAAQ,CAAC,CAAC,CAAC,EAAE;;YAE9D,CACL;aACA,IAAI,CAAC,EAAE,CAAC,CAAC;QAEZ,MAAM,WAAW,GAAG,WAAW;aAC5B,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CAAC;;sBAEO,CAAC,CAAC,KAAK;cACf,CAAC,CAAC,SAAS;cACX,CAAC,CAAC,KAAK;yDACoC,CAAC,CAAC,MAAM;yDACR,CAAC,CAAC,MAAM;cACnD,CAAC,CAAC,YAAY;YAChB,CACL;aACA,IAAI,CAAC,EAAE,CAAC,CAAC;QAEZ,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA8CK,YAAY;;;;4FAI0D,KAAK;yFACR,MAAM;yFACN,MAAM;6FACF,UAAU;;;;;;;;;;;;;;;;;;;;;;;aAuB1F,SAAS;;;;;;;;aAQT,WAAW;;;;;;;;;mBASL,MAAM,KAAK,MAAM;;;;;;;;;;;;;;0BAcV,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAgC7C,CAAC;QAEL,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACrD,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,iCAAiC,QAAQ,EAAE,CAAC,CAAC;IAC3D,CAAC;IAEM,KAAK,CAAC,OAAO;QAClB,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC5B,CAAC;QAED,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QACvE,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1B,YAAY,CAAC,KAAK,EAAE,CAAC;QAErB,oEAAoE;QACpE,MAAM,WAAW,GAAG,gBAAgB,EAAE,CAAC;QACvC,IAAI,WAAW,EAAE,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,oCAAoC,WAAW,CAAC,GAAG,KAAK,WAAW,CAAC,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;YAClG,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;CACF"}
|