@elench/testkit 0.1.25 → 0.1.26

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/README.md CHANGED
@@ -83,6 +83,9 @@ import { check, group, http } from "@elench/testkit/runtime";
83
83
 
84
84
  `testkit` bundles these imports before execution, so tests do not need
85
85
  generated `_testkit` files, direct package-manager path imports, or any separate engine installation.
86
+ The published package also ships first-party TypeScript declarations for both
87
+ `@elench/testkit` and `@elench/testkit/runtime`, so consumer repos do not need
88
+ local ambient module shims for the supported authoring surface.
86
89
 
87
90
  Legacy compatibility:
88
91
 
@@ -21,94 +21,6 @@ export function parseDotenvString(source) {
21
21
  return env;
22
22
  }
23
23
 
24
- export function validateRunnerManifest(raw, manifestName = "runner.manifest.json", manifestPath = manifestName) {
25
- if (!isObject(raw.services)) {
26
- throw new Error(`${manifestName} must have a "services" object (${manifestPath})`);
27
- }
28
-
29
- for (const [serviceName, service] of Object.entries(raw.services)) {
30
- if (!isObject(service)) {
31
- throw new Error(`Service "${serviceName}" in ${manifestName} must be an object`);
32
- }
33
- if (!isObject(service.suites)) {
34
- throw new Error(`Service "${serviceName}" in ${manifestName} must define suites`);
35
- }
36
-
37
- for (const [suiteType, suites] of Object.entries(service.suites)) {
38
- if (!Array.isArray(suites)) {
39
- throw new Error(
40
- `Service "${serviceName}" suite type "${suiteType}" must be an array`
41
- );
42
- }
43
-
44
- const seenNames = new Set();
45
- for (const suite of suites) {
46
- if (!isObject(suite)) {
47
- throw new Error(
48
- `Service "${serviceName}" suite type "${suiteType}" contains a non-object suite`
49
- );
50
- }
51
- if (typeof suite.name !== "string" || !suite.name.length) {
52
- throw new Error(
53
- `Service "${serviceName}" suite type "${suiteType}" has a suite with no name`
54
- );
55
- }
56
- if (seenNames.has(suite.name)) {
57
- throw new Error(
58
- `Service "${serviceName}" suite type "${suiteType}" has duplicate suite name "${suite.name}"`
59
- );
60
- }
61
- seenNames.add(suite.name);
62
-
63
- if (!Array.isArray(suite.files) || suite.files.length === 0) {
64
- throw new Error(
65
- `Service "${serviceName}" suite "${suite.name}" must define one or more files`
66
- );
67
- }
68
- for (const file of suite.files) {
69
- if (typeof file !== "string" || !file.length) {
70
- throw new Error(
71
- `Service "${serviceName}" suite "${suite.name}" contains an invalid file entry`
72
- );
73
- }
74
- }
75
-
76
- const framework = suite.framework || "k6";
77
- if (!VALID_FRAMEWORKS.has(framework)) {
78
- throw new Error(
79
- `Service "${serviceName}" suite "${suite.name}" uses unsupported framework "${framework}"`
80
- );
81
- }
82
-
83
- if (suite.testkit !== undefined) {
84
- if (!isObject(suite.testkit)) {
85
- throw new Error(
86
- `Service "${serviceName}" suite "${suite.name}" testkit config must be an object`
87
- );
88
- }
89
- if (
90
- suite.testkit.maxFileConcurrency !== undefined &&
91
- (!Number.isInteger(suite.testkit.maxFileConcurrency) ||
92
- suite.testkit.maxFileConcurrency <= 0)
93
- ) {
94
- throw new Error(
95
- `Service "${serviceName}" suite "${suite.name}" testkit.maxFileConcurrency must be a positive integer`
96
- );
97
- }
98
- if (
99
- suite.testkit.weight !== undefined &&
100
- (!Number.isInteger(suite.testkit.weight) || suite.testkit.weight <= 0)
101
- ) {
102
- throw new Error(
103
- `Service "${serviceName}" suite "${suite.name}" testkit.weight must be a positive integer`
104
- );
105
- }
106
- }
107
- }
108
- }
109
- }
110
- }
111
-
112
24
  export function validateConfigCoverage(
113
25
  config,
114
26
  configName = "testkit.config.json"
@@ -8,7 +8,6 @@ import {
8
8
  resolveSelectedDatabase,
9
9
  validateConfigCoverage,
10
10
  validateLifecycleConfig,
11
- validateRunnerManifest,
12
11
  validateServiceConfig,
13
12
  validateTelemetryConfig,
14
13
  } from "./model.mjs";
@@ -29,36 +28,6 @@ QUX='zap'
29
28
  });
30
29
  });
31
30
 
32
- it("validates runner manifests", () => {
33
- expect(() =>
34
- validateRunnerManifest({
35
- services: {
36
- api: {
37
- suites: {
38
- integration: [
39
- { name: "health", files: ["tests/health.js"] },
40
- ],
41
- },
42
- },
43
- },
44
- })
45
- ).not.toThrow();
46
-
47
- expect(() =>
48
- validateRunnerManifest({
49
- services: {
50
- api: {
51
- suites: {
52
- integration: [
53
- { name: "health", files: ["a.js"], framework: "jest" },
54
- ],
55
- },
56
- },
57
- },
58
- })
59
- ).toThrow("unsupported framework");
60
- });
61
-
62
31
  it("validates config coverage", () => {
63
32
  const config = {
64
33
  services: {
package/lib/index.d.ts ADDED
@@ -0,0 +1,92 @@
1
+ import type {
2
+ HttpClient,
3
+ HttpClientConfig,
4
+ RuntimeDb,
5
+ RuntimeDalContext,
6
+ RuntimeEnv,
7
+ RuntimeHeaders,
8
+ RuntimeOptions,
9
+ RuntimeResponse,
10
+ } from "./runtime/index";
11
+
12
+ export interface TestkitSuite<TSetup = unknown> {
13
+ options: RuntimeOptions;
14
+ setup: () => TSetup | null;
15
+ exec: (setupData: TSetup | null) => unknown;
16
+ }
17
+
18
+ export interface HeaderBuilderContext {
19
+ env: RuntimeEnv;
20
+ }
21
+
22
+ export type HeaderBuilder<TSetup = unknown> = (
23
+ setupData?: TSetup | null,
24
+ context?: HeaderBuilderContext
25
+ ) => RuntimeHeaders | void;
26
+
27
+ export interface AuthAdapter<TSetup = unknown> {
28
+ setup?: (context: { env: RuntimeEnv }) => TSetup;
29
+ headers?: HeaderBuilder<TSetup>;
30
+ }
31
+
32
+ export interface HttpSuiteContext<TSetup = unknown> {
33
+ env: RuntimeEnv;
34
+ req: HttpClient<TSetup>["request"];
35
+ rawReq: HttpClient["raw"];
36
+ getWithHeaders: HttpClient<TSetup>["getWithHeaders"];
37
+ setupData: TSetup | null;
38
+ session: TSetup | null;
39
+ }
40
+
41
+ export interface HttpSuiteConfig<TSetup = unknown> {
42
+ auth?: AuthAdapter<TSetup> | null;
43
+ env?: RuntimeEnv;
44
+ headers?: HeaderBuilder<TSetup>;
45
+ rawHeaders?: HeaderBuilder<never>;
46
+ options?: RuntimeOptions;
47
+ }
48
+
49
+ export interface DalSuiteContext<TSetup = unknown> {
50
+ db: RuntimeDb;
51
+ dal: RuntimeDalContext;
52
+ setupData: TSetup | null;
53
+ }
54
+
55
+ export interface DalSuiteConfig<TSetup = unknown> {
56
+ db?: RuntimeDb;
57
+ options?: RuntimeOptions;
58
+ setup?: (context: { db: RuntimeDb; dal: RuntimeDalContext }) => TSetup;
59
+ }
60
+
61
+ export declare function defineHttpSuite<TSetup = unknown>(
62
+ run: (context: HttpSuiteContext<TSetup>) => unknown
63
+ ): TestkitSuite<TSetup>;
64
+
65
+ export declare function defineHttpSuite<TSetup = unknown>(
66
+ config: HttpSuiteConfig<TSetup>,
67
+ run: (context: HttpSuiteContext<TSetup>) => unknown
68
+ ): TestkitSuite<TSetup>;
69
+
70
+ export declare function defineDalSuite<TSetup = unknown>(
71
+ run: (context: DalSuiteContext<TSetup>) => unknown
72
+ ): TestkitSuite<TSetup>;
73
+
74
+ export declare function defineDalSuite<TSetup = unknown>(
75
+ config: DalSuiteConfig<TSetup>,
76
+ run: (context: DalSuiteContext<TSetup>) => unknown
77
+ ): TestkitSuite<TSetup>;
78
+
79
+ export declare function createAuthAdapter<TSetup = unknown>(
80
+ adapter?: AuthAdapter<TSetup>
81
+ ): AuthAdapter<TSetup>;
82
+
83
+ export type {
84
+ HttpClient,
85
+ HttpClientConfig,
86
+ RuntimeDb,
87
+ RuntimeDalContext,
88
+ RuntimeEnv,
89
+ RuntimeHeaders,
90
+ RuntimeOptions,
91
+ RuntimeResponse,
92
+ };
@@ -0,0 +1,24 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { describe, expect, it } from "vitest";
5
+
6
+ const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
7
+ const packageJsonPath = path.join(rootDir, "package.json");
8
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
9
+
10
+ describe("package metadata", () => {
11
+ it("ships first-party type declarations for the public exports", () => {
12
+ expect(packageJson.types).toBe("./lib/index.d.ts");
13
+ expect(packageJson.exports["."]).toEqual({
14
+ types: "./lib/index.d.ts",
15
+ default: "./lib/index.mjs",
16
+ });
17
+ expect(packageJson.exports["./runtime"]).toEqual({
18
+ types: "./lib/runtime/index.d.ts",
19
+ default: "./lib/runtime/index.mjs",
20
+ });
21
+ expect(fs.existsSync(path.join(rootDir, "lib", "index.d.ts"))).toBe(true);
22
+ expect(fs.existsSync(path.join(rootDir, "lib", "runtime", "index.d.ts"))).toBe(true);
23
+ });
24
+ });
@@ -832,12 +832,15 @@ function printRunSummary(results, durationMs) {
832
832
  );
833
833
  const failedSuites = executedServices.reduce((sum, result) => sum + result.failedSuiteCount, 0);
834
834
  const passedSuites = completedSuites - failedSuites;
835
+ const totalFiles = executedServices.reduce((sum, result) => sum + (result.totalFileCount || 0), 0);
836
+ const passedFiles = executedServices.reduce((sum, result) => sum + (result.passedFileCount || 0), 0);
835
837
 
836
838
  console.log("\n══ Summary ══");
837
839
  console.log(
838
840
  [
839
841
  `services ${passedServices.length}/${executedServices.length} passed`,
840
842
  `suites ${passedSuites}/${totalSuites} passed`,
843
+ totalFiles > 0 ? `files ${passedFiles}/${totalFiles} passed` : null,
841
844
  skippedServices.length > 0 ? `${skippedServices.length} skipped` : null,
842
845
  `duration ${formatDuration(durationMs)}`,
843
846
  ]
@@ -156,6 +156,19 @@ export function finalizeServiceResult(tracker, startedAt, finishedAt) {
156
156
  (suite) => suite.completedFileCount === suite.fileCount
157
157
  ).length;
158
158
  const failedSuiteCount = suites.filter((suite) => suite.failedFiles.length > 0).length;
159
+ const totalFileCount = suites.reduce((sum, suite) => sum + suite.fileCount, 0);
160
+ const completedFileCount = suites.reduce(
161
+ (sum, suite) => sum + suite.completedFileCount,
162
+ 0
163
+ );
164
+ const failedFileCount = suites.reduce((sum, suite) => sum + suite.failedFiles.length, 0);
165
+ const passedFileCount = suites.reduce(
166
+ (sum, suite) =>
167
+ sum +
168
+ suite.fileResults.filter((file) => file.status === "passed").length,
169
+ 0
170
+ );
171
+ const notRunFileCount = totalFileCount - completedFileCount;
159
172
  const accumulatedDurationMs = suites.reduce((sum, suite) => sum + suite.durationMs, 0);
160
173
  const durationMs =
161
174
  tracker.firstTaskAt && tracker.lastTaskAt
@@ -173,6 +186,11 @@ export function finalizeServiceResult(tracker, startedAt, finishedAt) {
173
186
  suiteCount: tracker.suiteCount,
174
187
  completedSuiteCount,
175
188
  failedSuiteCount,
189
+ totalFileCount,
190
+ completedFileCount,
191
+ passedFileCount,
192
+ failedFileCount,
193
+ notRunFileCount,
176
194
  durationMs,
177
195
  suites: suites.map((suite) => ({
178
196
  name: suite.name,
@@ -180,6 +198,10 @@ export function finalizeServiceResult(tracker, startedAt, finishedAt) {
180
198
  framework: formatFrameworkForArtifact(suite.framework),
181
199
  failed: suite.failedFiles.length > 0,
182
200
  fileCount: suite.fileCount,
201
+ completedFileCount: suite.completedFileCount,
202
+ passedFileCount: suite.fileResults.filter((file) => file.status === "passed").length,
203
+ failedFileCount: suite.failedFiles.length,
204
+ notRunFileCount: suite.fileCount - suite.completedFileCount,
183
205
  failedFiles: suite.failedFiles,
184
206
  durationMs: suite.durationMs,
185
207
  error: suite.error,
@@ -299,6 +321,10 @@ export function buildRunArtifact({
299
321
  const totalSuites = executed.reduce((sum, result) => sum + result.suiteCount, 0);
300
322
  const completedSuites = executed.reduce((sum, result) => sum + result.completedSuiteCount, 0);
301
323
  const failedSuites = executed.reduce((sum, result) => sum + result.failedSuiteCount, 0);
324
+ const totalFiles = executed.reduce((sum, result) => sum + (result.totalFileCount || 0), 0);
325
+ const passedFiles = executed.reduce((sum, result) => sum + (result.passedFileCount || 0), 0);
326
+ const failedFiles = executed.reduce((sum, result) => sum + (result.failedFileCount || 0), 0);
327
+ const notRunFiles = executed.reduce((sum, result) => sum + (result.notRunFileCount || 0), 0);
302
328
  const dbBackend = summarizeDbBackend(results);
303
329
 
304
330
  return {
@@ -339,6 +365,12 @@ export function buildRunArtifact({
339
365
  passed: completedSuites - failedSuites,
340
366
  failed: failedSuites,
341
367
  },
368
+ files: {
369
+ total: totalFiles,
370
+ passed: passedFiles,
371
+ failed: failedFiles,
372
+ notRun: notRunFiles,
373
+ },
342
374
  },
343
375
  services: results.map((result) => ({
344
376
  name: result.name,
@@ -347,6 +379,11 @@ export function buildRunArtifact({
347
379
  suiteCount: result.suiteCount,
348
380
  completedSuiteCount: result.completedSuiteCount,
349
381
  failedSuiteCount: result.failedSuiteCount,
382
+ totalFileCount: result.totalFileCount,
383
+ completedFileCount: result.completedFileCount,
384
+ passedFileCount: result.passedFileCount,
385
+ failedFileCount: result.failedFileCount,
386
+ notRunFileCount: result.notRunFileCount,
350
387
  durationMs: result.durationMs,
351
388
  dbBackend: result.dbBackend,
352
389
  suites: result.suites,
@@ -372,10 +409,15 @@ export function formatDuration(durationMs) {
372
409
 
373
410
  export function formatServiceSummary(result) {
374
411
  const passedSuites = result.completedSuiteCount - result.failedSuiteCount;
375
- const notRun = result.suiteCount - result.completedSuiteCount;
412
+ const notRunSuites = result.suiteCount - result.completedSuiteCount;
376
413
  let detail = `${passedSuites}/${result.suiteCount} suites passed`;
377
- if (notRun > 0) {
378
- detail += `, ${notRun} not run`;
414
+ if ((result.totalFileCount || 0) > 0) {
415
+ detail += `, ${result.passedFileCount}/${result.totalFileCount} files passed`;
416
+ }
417
+ if (notRunSuites > 0) {
418
+ detail += `, ${notRunSuites} ${pluralize(notRunSuites, "suite", "suites")} not run`;
419
+ } else if ((result.notRunFileCount || 0) > 0) {
420
+ detail += `, ${result.notRunFileCount} ${pluralize(result.notRunFileCount, "file", "files")} not run`;
379
421
  }
380
422
  return detail;
381
423
  }
@@ -404,3 +446,7 @@ function sanitizeErrorMessage(message) {
404
446
  .replace(/Command failed with exit code (\d+): k6 run\b/g, "Default runtime failed with exit code $1:")
405
447
  .replace(/[\\/]vendor[\\/]k6\b/g, "default-runtime");
406
448
  }
449
+
450
+ function pluralize(value, singular, plural) {
451
+ return value === 1 ? singular : plural;
452
+ }
@@ -64,8 +64,12 @@ describe("runner-results", () => {
64
64
  const result = finalizeServiceResult(tracker, 1000, 1500);
65
65
  expect(result.failed).toBe(true);
66
66
  expect(result.failedSuiteCount).toBe(1);
67
+ expect(result.totalFileCount).toBe(1);
68
+ expect(result.failedFileCount).toBe(1);
69
+ expect(result.passedFileCount).toBe(0);
67
70
  expect(result.errors).toEqual(["worker failed", "graph failed"]);
68
71
  expect(result.suites[0].framework).toBe("default");
72
+ expect(result.suites[0].failedFileCount).toBe(1);
69
73
  expect(result.suites[0].files).toEqual([
70
74
  {
71
75
  path: "tests/health.js",
@@ -86,6 +90,11 @@ describe("runner-results", () => {
86
90
  suiteCount: 1,
87
91
  completedSuiteCount: 1,
88
92
  failedSuiteCount: 0,
93
+ totalFileCount: 3,
94
+ completedFileCount: 3,
95
+ passedFileCount: 3,
96
+ failedFileCount: 0,
97
+ notRunFileCount: 0,
89
98
  durationMs: 1200,
90
99
  dbBackend: "local",
91
100
  suites: [],
@@ -98,6 +107,11 @@ describe("runner-results", () => {
98
107
  suiteCount: 0,
99
108
  completedSuiteCount: 0,
100
109
  failedSuiteCount: 0,
110
+ totalFileCount: 0,
111
+ completedFileCount: 0,
112
+ passedFileCount: 0,
113
+ failedFileCount: 0,
114
+ notRunFileCount: 0,
101
115
  durationMs: 0,
102
116
  dbBackend: null,
103
117
  suites: [],
@@ -134,6 +148,12 @@ describe("runner-results", () => {
134
148
 
135
149
  expect(artifact.product.name).toBe("my-product");
136
150
  expect(artifact.summary.services.total).toBe(1);
151
+ expect(artifact.summary.files).toEqual({
152
+ total: 3,
153
+ passed: 3,
154
+ failed: 0,
155
+ notRun: 0,
156
+ });
137
157
  expect(summarizeDbBackend(results)).toBe("local");
138
158
  expect(formatDuration(65_000)).toBe("1m 05s");
139
159
  expect(
@@ -141,8 +161,11 @@ describe("runner-results", () => {
141
161
  completedSuiteCount: 2,
142
162
  failedSuiteCount: 1,
143
163
  suiteCount: 3,
164
+ totalFileCount: 6,
165
+ passedFileCount: 5,
166
+ notRunFileCount: 1,
144
167
  })
145
- ).toBe("1/3 suites passed, 1 not run");
168
+ ).toBe("1/3 suites passed, 5/6 files passed, 1 suite not run");
146
169
  expect(formatError(new Error("boom"))).toBe("boom");
147
170
  });
148
171
 
@@ -0,0 +1,183 @@
1
+ export type RuntimeMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
2
+
3
+ export interface RuntimeHeaders {
4
+ [key: string]: string;
5
+ }
6
+
7
+ export interface RuntimeCookie {
8
+ value: string;
9
+ }
10
+
11
+ export interface RuntimeResponse {
12
+ body: string;
13
+ cookies?: Record<string, RuntimeCookie[]>;
14
+ headers?: Record<string, string | string[] | undefined>;
15
+ status: number;
16
+ timings?: {
17
+ duration: number;
18
+ };
19
+ }
20
+
21
+ export interface RuntimeOptions {
22
+ [key: string]: unknown;
23
+ thresholds?: Record<string, unknown>;
24
+ }
25
+
26
+ export interface RuntimeEnv {
27
+ BASE: string;
28
+ MACHINE_ID?: string;
29
+ routeParams: RuntimeHeaders;
30
+ }
31
+
32
+ export interface RuntimeDb {
33
+ exec(sql: string): unknown;
34
+ query<T = Record<string, unknown>>(sql: string): T[];
35
+ }
36
+
37
+ export interface RuntimeDalContext {
38
+ db: RuntimeDb;
39
+ truncate(...tables: string[]): void;
40
+ }
41
+
42
+ export interface HttpRequestParams {
43
+ headers?: RuntimeHeaders;
44
+ redirects?: number;
45
+ }
46
+
47
+ export interface RuntimeHttpClient {
48
+ del(url: string, body?: unknown, params?: HttpRequestParams): RuntimeResponse;
49
+ file(data: unknown, filename?: string, contentType?: string): unknown;
50
+ get(url: string, params?: HttpRequestParams): RuntimeResponse;
51
+ patch(url: string, body?: unknown, params?: HttpRequestParams): RuntimeResponse;
52
+ post(url: string, body?: unknown, params?: HttpRequestParams): RuntimeResponse;
53
+ put(url: string, body?: unknown, params?: HttpRequestParams): RuntimeResponse;
54
+ }
55
+
56
+ export interface Metric {
57
+ add(value: number): void;
58
+ }
59
+
60
+ export declare class Rate implements Metric {
61
+ constructor(name: string, isTime?: boolean);
62
+ add(value: number): void;
63
+ }
64
+
65
+ export declare class Trend implements Metric {
66
+ constructor(name: string, isTime?: boolean);
67
+ add(value: number): void;
68
+ }
69
+
70
+ export interface HttpClientConfig<TSetup = unknown> {
71
+ baseUrl: string;
72
+ defaultHeaders?: RuntimeHeaders;
73
+ getHeaders?: (setupData?: TSetup | null) => RuntimeHeaders | void;
74
+ getRawHeaders?: (setupData?: TSetup | null) => RuntimeHeaders | void;
75
+ routeHeaders?: RuntimeHeaders;
76
+ }
77
+
78
+ export interface HttpClient<TSetup = unknown> {
79
+ delete(path: string, setupData?: TSetup | null, extraHeaders?: RuntimeHeaders): RuntimeResponse;
80
+ get(path: string, setupData?: TSetup | null, extraHeaders?: RuntimeHeaders): RuntimeResponse;
81
+ getWithHeaders(
82
+ path: string,
83
+ setupData?: TSetup | null,
84
+ extraHeaders?: RuntimeHeaders
85
+ ): RuntimeResponse;
86
+ patch(
87
+ path: string,
88
+ setupData?: TSetup | null,
89
+ body?: unknown,
90
+ extraHeaders?: RuntimeHeaders
91
+ ): RuntimeResponse;
92
+ post(
93
+ path: string,
94
+ setupData?: TSetup | null,
95
+ body?: unknown,
96
+ extraHeaders?: RuntimeHeaders
97
+ ): RuntimeResponse;
98
+ put(
99
+ path: string,
100
+ setupData?: TSetup | null,
101
+ body?: unknown,
102
+ extraHeaders?: RuntimeHeaders
103
+ ): RuntimeResponse;
104
+ raw(
105
+ method: RuntimeMethod,
106
+ path: string,
107
+ body?: unknown,
108
+ extraHeaders?: RuntimeHeaders
109
+ ): RuntimeResponse;
110
+ rawDelete(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
111
+ rawGet(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
112
+ rawPatch(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
113
+ rawPost(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
114
+ rawPut(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
115
+ request(
116
+ method: RuntimeMethod,
117
+ path: string,
118
+ setupData?: TSetup | null,
119
+ body?: unknown,
120
+ extraHeaders?: RuntimeHeaders
121
+ ): RuntimeResponse;
122
+ }
123
+
124
+ export declare const check: <T>(
125
+ value: T,
126
+ checks: Record<string, (value: T) => boolean>
127
+ ) => boolean;
128
+ export declare const fail: (message: string) => never;
129
+ export declare const group: (name: string, fn: () => void) => void;
130
+ export declare const sleep: (seconds?: number) => void;
131
+
132
+ export declare const http: RuntimeHttpClient;
133
+
134
+ export declare function file(data: unknown, filename?: string, contentType?: string): unknown;
135
+ export declare function json<T = unknown>(response: Pick<RuntimeResponse, "body">): T;
136
+ export declare function contains<T extends Record<string, unknown>>(
137
+ rows: T[],
138
+ field: keyof T | string,
139
+ value: unknown
140
+ ): boolean;
141
+ export declare function allMatch<T>(
142
+ rows: T[],
143
+ predicate: (row: T) => boolean
144
+ ): boolean;
145
+ export declare function isSorted<T extends Record<string, unknown>>(
146
+ rows: T[],
147
+ field: keyof T | string,
148
+ direction?: "asc" | "desc"
149
+ ): boolean;
150
+
151
+ export declare function singleIterationOptions(overrides?: RuntimeOptions): RuntimeOptions;
152
+ export declare const defaultOptions: RuntimeOptions;
153
+ export declare const httpDefaultOptions: RuntimeOptions;
154
+
155
+ export declare function createDalContext(db?: RuntimeDb): RuntimeDalContext;
156
+ export declare function openDb(): RuntimeDb;
157
+ export declare function truncate(db: RuntimeDb, ...tables: string[]): void;
158
+
159
+ export declare function getEnv(): RuntimeEnv;
160
+ export declare function createHttpClient<TSetup = unknown>(
161
+ config: HttpClientConfig<TSetup>
162
+ ): HttpClient<TSetup>;
163
+ export declare function makeReq<TSetup = unknown>(
164
+ baseUrl: string,
165
+ routeHeaders?: RuntimeHeaders,
166
+ getHeaders?: (setupData?: TSetup | null) => RuntimeHeaders | void
167
+ ): HttpClient<TSetup>["request"];
168
+ export declare function makeRawReq(
169
+ baseUrl: string,
170
+ routeHeaders?: RuntimeHeaders,
171
+ getRawHeaders?: (setupData?: never) => RuntimeHeaders | void
172
+ ): HttpClient["raw"];
173
+ export declare function makeGetWithHeaders<TSetup = unknown>(
174
+ baseUrl: string,
175
+ routeHeaders?: RuntimeHeaders,
176
+ getHeaders?: (setupData?: TSetup | null) => RuntimeHeaders | void
177
+ ): HttpClient<TSetup>["getWithHeaders"];
178
+
179
+ declare global {
180
+ const __ENV: Record<string, string | undefined>;
181
+ }
182
+
183
+ export {};
package/package.json CHANGED
@@ -1,11 +1,18 @@
1
1
  {
2
2
  "name": "@elench/testkit",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "CLI for discovering and running local HTTP, DAL, and Playwright test suites",
5
5
  "type": "module",
6
+ "types": "./lib/index.d.ts",
6
7
  "exports": {
7
- ".": "./lib/index.mjs",
8
- "./runtime": "./lib/runtime/index.mjs",
8
+ ".": {
9
+ "types": "./lib/index.d.ts",
10
+ "default": "./lib/index.mjs"
11
+ },
12
+ "./runtime": {
13
+ "types": "./lib/runtime/index.d.ts",
14
+ "default": "./lib/runtime/index.mjs"
15
+ },
9
16
  "./package.json": "./package.json"
10
17
  },
11
18
  "bin": {