@glubean/sdk 0.2.0 → 0.2.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/dist/configure.d.ts +4 -21
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +19 -145
- package/dist/configure.js.map +1 -1
- package/dist/contract-core.d.ts +3 -1
- package/dist/contract-core.d.ts.map +1 -1
- package/dist/contract-core.js +4 -2
- package/dist/contract-core.js.map +1 -1
- package/dist/contract-http/index.d.ts +4 -3
- package/dist/contract-http/index.d.ts.map +1 -1
- package/dist/contract-http/index.js +4 -3
- package/dist/contract-http/index.js.map +1 -1
- package/dist/each-builder.d.ts +244 -0
- package/dist/each-builder.d.ts.map +1 -0
- package/dist/each-builder.js +268 -0
- package/dist/each-builder.js.map +1 -0
- package/dist/index.d.ts +14 -516
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -834
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -0
- package/dist/internal.js.map +1 -1
- package/dist/runtime-carrier.d.ts +142 -0
- package/dist/runtime-carrier.d.ts.map +1 -0
- package/dist/runtime-carrier.js +148 -0
- package/dist/runtime-carrier.js.map +1 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +2 -1
- package/dist/session.js.map +1 -1
- package/dist/test-builder.d.ts +249 -0
- package/dist/test-builder.d.ts.map +1 -0
- package/dist/test-builder.js +265 -0
- package/dist/test-builder.js.map +1 -0
- package/dist/test-extend.d.ts +59 -0
- package/dist/test-extend.d.ts.map +1 -0
- package/dist/test-extend.js +111 -0
- package/dist/test-extend.js.map +1 -0
- package/dist/test-utils.d.ts +39 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +91 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/types.d.ts +19 -112
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { registerTest } from "./internal.js";
|
|
2
2
|
import { toArray } from "./data.js";
|
|
3
|
+
import { TestBuilder } from "./test-builder.js";
|
|
4
|
+
import { EachBuilder } from "./each-builder.js";
|
|
5
|
+
import { createExtendedTest } from "./test-extend.js";
|
|
6
|
+
import { normalizeEachTable, resolveBaseMeta, interpolateTemplate, selectPickExamples } from "./test-utils.js";
|
|
3
7
|
/**
|
|
4
8
|
* Glubean SDK spec version.
|
|
5
9
|
*
|
|
@@ -14,292 +18,19 @@ import { toArray } from "./data.js";
|
|
|
14
18
|
* ```
|
|
15
19
|
*/
|
|
16
20
|
export const SPEC_VERSION = "2.0";
|
|
17
|
-
// =============================================================================
|
|
18
|
-
// Note: Registry functions (getRegistry, clearRegistry) have been moved to
|
|
19
|
-
// internal.ts to keep the public API clean. Import from "@glubean/sdk/internal"
|
|
20
|
-
// if you need them (for scanner or testing purposes only).
|
|
21
|
-
// =============================================================================
|
|
22
|
-
// =============================================================================
|
|
23
|
-
// New Builder API
|
|
24
|
-
// =============================================================================
|
|
25
|
-
/**
|
|
26
|
-
* Builder class for creating tests with a fluent API.
|
|
27
|
-
*
|
|
28
|
-
* @template S The state type for multi-step tests
|
|
29
|
-
* @template Ctx The context type (defaults to TestContext; augmented by test.extend())
|
|
30
|
-
*
|
|
31
|
-
* @example Simple test (quick mode)
|
|
32
|
-
* ```ts
|
|
33
|
-
* export const login = test("login", async (ctx) => {
|
|
34
|
-
* ctx.assert(true, "works");
|
|
35
|
-
* });
|
|
36
|
-
* ```
|
|
37
|
-
*
|
|
38
|
-
* @example Multi-step test (builder mode)
|
|
39
|
-
* ```ts
|
|
40
|
-
* export const checkout = test("checkout")
|
|
41
|
-
* .meta({ tags: ["e2e"] })
|
|
42
|
-
* .setup(async (ctx) => ({ cart: await createCart() }))
|
|
43
|
-
* .step("Add to cart", async (ctx, state) => {
|
|
44
|
-
* await addItem(state.cart, "item-1");
|
|
45
|
-
* return state;
|
|
46
|
-
* })
|
|
47
|
-
* .step("Checkout", async (ctx, state) => {
|
|
48
|
-
* await checkout(state.cart);
|
|
49
|
-
* return state;
|
|
50
|
-
* })
|
|
51
|
-
* .teardown(async (ctx, state) => {
|
|
52
|
-
* await cleanup(state.cart);
|
|
53
|
-
* })
|
|
54
|
-
* .build();
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
export class TestBuilder {
|
|
58
|
-
_meta;
|
|
59
|
-
_setup;
|
|
60
|
-
_teardown;
|
|
61
|
-
_steps = [];
|
|
62
|
-
_built = false;
|
|
63
|
-
_fixtures;
|
|
64
|
-
/**
|
|
65
|
-
* Marker property so the runner can detect un-built TestBuilder exports
|
|
66
|
-
* without importing the SDK. The runner checks this string to auto-build.
|
|
67
|
-
*/
|
|
68
|
-
__glubean_type = "builder";
|
|
69
|
-
constructor(id, fixtures) {
|
|
70
|
-
this._meta = { id, name: id };
|
|
71
|
-
this._fixtures = fixtures;
|
|
72
|
-
// Auto-finalize (register) after all synchronous chaining completes.
|
|
73
|
-
// Module top-level code is synchronous, so by the time the microtask
|
|
74
|
-
// fires, all .step() / .meta() / .setup() / .teardown() calls are done.
|
|
75
|
-
queueMicrotask(() => this._finalize());
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Set additional metadata for the test.
|
|
79
|
-
*
|
|
80
|
-
* @example
|
|
81
|
-
* ```ts
|
|
82
|
-
* test("my-test")
|
|
83
|
-
* .meta({ tags: ["smoke"], description: "A smoke test" })
|
|
84
|
-
* .step(...)
|
|
85
|
-
* ```
|
|
86
|
-
*/
|
|
87
|
-
meta(meta) {
|
|
88
|
-
this._meta = { ...this._meta, ...meta };
|
|
89
|
-
return this;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Mark this test as focused.
|
|
93
|
-
*
|
|
94
|
-
* Focused tests are intended for local debugging sessions. When any tests in
|
|
95
|
-
* a run are marked as `only`, non-focused tests may be excluded by discovery
|
|
96
|
-
* tooling/orchestrators. If `skip` is also set on the same test, `skip`
|
|
97
|
-
* still wins during run selection.
|
|
98
|
-
*/
|
|
99
|
-
only() {
|
|
100
|
-
this._meta = { ...this._meta, only: true };
|
|
101
|
-
return this;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Mark this test as skipped.
|
|
105
|
-
*
|
|
106
|
-
* Skip takes precedence over `only` when both are present.
|
|
107
|
-
*/
|
|
108
|
-
skip() {
|
|
109
|
-
this._meta = { ...this._meta, skip: true };
|
|
110
|
-
return this;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Set the setup function that runs before all steps.
|
|
114
|
-
* The returned state is passed to all steps and teardown.
|
|
115
|
-
*
|
|
116
|
-
* @example
|
|
117
|
-
* ```ts
|
|
118
|
-
* test("auth")
|
|
119
|
-
* .setup(async (ctx) => {
|
|
120
|
-
* const baseUrl = ctx.vars.require("BASE_URL");
|
|
121
|
-
* const apiKey = ctx.secrets.require("API_KEY");
|
|
122
|
-
* const { token } = await ctx.http.post(`${baseUrl}/auth/token`, {
|
|
123
|
-
* headers: { "X-API-Key": apiKey },
|
|
124
|
-
* }).json();
|
|
125
|
-
* return { token };
|
|
126
|
-
* })
|
|
127
|
-
* .step("verify", async (ctx, { token }) => { ... })
|
|
128
|
-
* ```
|
|
129
|
-
*/
|
|
130
|
-
setup(fn) {
|
|
131
|
-
this._setup = fn;
|
|
132
|
-
return this;
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Set the teardown function that runs after all steps (even on failure).
|
|
136
|
-
*
|
|
137
|
-
* @example
|
|
138
|
-
* ```ts
|
|
139
|
-
* test("db-test")
|
|
140
|
-
* .setup(async (ctx) => ({ conn: await connect() }))
|
|
141
|
-
* .step(...)
|
|
142
|
-
* .teardown(async (ctx, { conn }) => {
|
|
143
|
-
* await conn.close();
|
|
144
|
-
* })
|
|
145
|
-
* ```
|
|
146
|
-
*/
|
|
147
|
-
teardown(fn) {
|
|
148
|
-
this._teardown = fn;
|
|
149
|
-
return this;
|
|
150
|
-
}
|
|
151
|
-
step(name, optionsOrFn, maybeFn) {
|
|
152
|
-
const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
|
|
153
|
-
const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
|
|
154
|
-
this._steps.push({
|
|
155
|
-
meta: { name, ...options },
|
|
156
|
-
fn: fn,
|
|
157
|
-
});
|
|
158
|
-
return this;
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Apply a builder transform function for step composition.
|
|
162
|
-
*
|
|
163
|
-
* Reusable step sequences are just plain functions that take a builder
|
|
164
|
-
* and return a builder. `.use()` applies such a function to the current
|
|
165
|
-
* chain, preserving state flow.
|
|
166
|
-
*
|
|
167
|
-
* @param fn Transform function that receives this builder and returns a (possibly re-typed) builder
|
|
168
|
-
*
|
|
169
|
-
* @example Reusable step sequence
|
|
170
|
-
* ```ts
|
|
171
|
-
* // Define once — just a function
|
|
172
|
-
* const withAuth = (b: TestBuilder<unknown>) => b
|
|
173
|
-
* .step("login", async (ctx) => {
|
|
174
|
-
* const data = await ctx.http.post("/login", { json: creds }).json<{ token: string }>();
|
|
175
|
-
* return { token: data.token };
|
|
176
|
-
* });
|
|
177
|
-
*
|
|
178
|
-
* // Reuse across tests
|
|
179
|
-
* export const testA = test("test-a").use(withAuth).step("act", async (ctx, { token }) => { ... });
|
|
180
|
-
* export const testB = test("test-b").use(withAuth).step("verify", async (ctx, { token }) => { ... });
|
|
181
|
-
* ```
|
|
182
|
-
*/
|
|
183
|
-
use(fn) {
|
|
184
|
-
return fn(this);
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* Apply a builder transform and tag all newly added steps with a group ID.
|
|
188
|
-
*
|
|
189
|
-
* Works exactly like `.use()`, but every step added by `fn` is marked with
|
|
190
|
-
* `group` metadata for visual grouping in reports and dashboards.
|
|
191
|
-
*
|
|
192
|
-
* @param id Group identifier (displayed in reports as a section header)
|
|
193
|
-
* @param fn Transform function that adds steps to the builder
|
|
194
|
-
*
|
|
195
|
-
* @example Reusable steps with grouping
|
|
196
|
-
* ```ts
|
|
197
|
-
* const withAuth = (b: TestBuilder<unknown>) => b
|
|
198
|
-
* .step("login", async (ctx) => ({ token: "..." }))
|
|
199
|
-
* .step("verify", async (ctx, { token }) => ({ token, verified: true }));
|
|
200
|
-
*
|
|
201
|
-
* export const checkout = test("checkout")
|
|
202
|
-
* .group("auth", withAuth)
|
|
203
|
-
* .step("pay", async (ctx, { token }) => { ... });
|
|
204
|
-
*
|
|
205
|
-
* // Report output:
|
|
206
|
-
* // checkout
|
|
207
|
-
* // ├─ [auth]
|
|
208
|
-
* // │ ├─ login ✓
|
|
209
|
-
* // │ └─ verify ✓
|
|
210
|
-
* // └─ pay ✓
|
|
211
|
-
* ```
|
|
212
|
-
*
|
|
213
|
-
* @example Inline grouping (no reuse, just organization)
|
|
214
|
-
* ```ts
|
|
215
|
-
* export const e2e = test("e2e")
|
|
216
|
-
* .group("setup", b => b
|
|
217
|
-
* .step("seed db", async (ctx) => ({ dbId: "..." }))
|
|
218
|
-
* .step("create user", async (ctx, { dbId }) => ({ dbId, userId: "..." }))
|
|
219
|
-
* )
|
|
220
|
-
* .step("verify", async (ctx, { dbId, userId }) => { ... });
|
|
221
|
-
* ```
|
|
222
|
-
*/
|
|
223
|
-
group(id, fn) {
|
|
224
|
-
const before = this._steps.length;
|
|
225
|
-
const result = fn(this);
|
|
226
|
-
for (let i = before; i < this._steps.length; i++) {
|
|
227
|
-
this._steps[i].meta.group = id;
|
|
228
|
-
}
|
|
229
|
-
return result;
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Finalize and register the test in the global registry.
|
|
233
|
-
* Called automatically via microtask if not explicitly invoked via build().
|
|
234
|
-
* Idempotent — safe to call multiple times.
|
|
235
|
-
* @internal
|
|
236
|
-
*/
|
|
237
|
-
_finalize() {
|
|
238
|
-
if (this._built)
|
|
239
|
-
return;
|
|
240
|
-
this._built = true;
|
|
241
|
-
registerTest({
|
|
242
|
-
id: this._meta.id,
|
|
243
|
-
name: this._meta.name || this._meta.id,
|
|
244
|
-
type: "steps",
|
|
245
|
-
tags: toArray(this._meta.tags),
|
|
246
|
-
description: this._meta.description,
|
|
247
|
-
steps: this._steps.map((s) => ({
|
|
248
|
-
name: s.meta.name,
|
|
249
|
-
...(s.meta.group ? { group: s.meta.group } : {}),
|
|
250
|
-
})),
|
|
251
|
-
hasSetup: !!this._setup,
|
|
252
|
-
hasTeardown: !!this._teardown,
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Build and register the test. Returns a plain `Test<S>` object.
|
|
257
|
-
*
|
|
258
|
-
* **Optional** — if omitted, the builder auto-finalizes via microtask
|
|
259
|
-
* after all synchronous chaining completes, and the runner will
|
|
260
|
-
* auto-detect the builder export. Calling `.build()` explicitly is
|
|
261
|
-
* still supported for backward compatibility.
|
|
262
|
-
*
|
|
263
|
-
* @example
|
|
264
|
-
* ```ts
|
|
265
|
-
* // With .build() (explicit — backward compatible)
|
|
266
|
-
* export const myTest = test("my-test")
|
|
267
|
-
* .step("step-1", async (ctx) => { ... })
|
|
268
|
-
* .build();
|
|
269
|
-
*
|
|
270
|
-
* // Without .build() (auto-finalized — recommended)
|
|
271
|
-
* export const myTest = test("my-test")
|
|
272
|
-
* .step("step-1", async (ctx) => { ... });
|
|
273
|
-
* ```
|
|
274
|
-
*/
|
|
275
|
-
build() {
|
|
276
|
-
this._finalize();
|
|
277
|
-
return {
|
|
278
|
-
meta: this._meta,
|
|
279
|
-
type: "steps",
|
|
280
|
-
setup: this._setup,
|
|
281
|
-
teardown: this._teardown,
|
|
282
|
-
steps: this._steps,
|
|
283
|
-
...(this._fixtures ? { fixtures: this._fixtures } : {}),
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
21
|
export function test(idOrMeta, fn) {
|
|
288
22
|
const meta = typeof idOrMeta === "string"
|
|
289
23
|
? { id: idOrMeta, name: idOrMeta }
|
|
290
24
|
: { name: idOrMeta.id, ...idOrMeta };
|
|
291
|
-
// Normalize tags to string[]
|
|
292
25
|
if (meta.tags) {
|
|
293
26
|
meta.tags = toArray(meta.tags);
|
|
294
27
|
}
|
|
295
|
-
// Quick mode: test("id", fn) -> returns Test directly
|
|
296
28
|
if (fn) {
|
|
297
29
|
const testDef = {
|
|
298
30
|
meta,
|
|
299
31
|
type: "simple",
|
|
300
32
|
fn,
|
|
301
33
|
};
|
|
302
|
-
// Register to global registry
|
|
303
34
|
registerTest({
|
|
304
35
|
id: meta.id,
|
|
305
36
|
name: meta.name || meta.id,
|
|
@@ -309,541 +40,12 @@ export function test(idOrMeta, fn) {
|
|
|
309
40
|
});
|
|
310
41
|
return testDef;
|
|
311
42
|
}
|
|
312
|
-
// Builder mode: test("id") -> returns TestBuilder
|
|
313
43
|
const builder = new TestBuilder(meta.id);
|
|
314
44
|
if (typeof idOrMeta !== "string") {
|
|
315
45
|
builder.meta(idOrMeta);
|
|
316
46
|
}
|
|
317
47
|
return builder;
|
|
318
48
|
}
|
|
319
|
-
// =============================================================================
|
|
320
|
-
// Data-Driven API (test.each)
|
|
321
|
-
// =============================================================================
|
|
322
|
-
/**
|
|
323
|
-
* Interpolate `$key` placeholders in a template string with data values.
|
|
324
|
-
* Supports `$index` for the row index and `$key` for any key in the data object.
|
|
325
|
-
*
|
|
326
|
-
* @internal
|
|
327
|
-
*/
|
|
328
|
-
function interpolateTemplate(template, data, index) {
|
|
329
|
-
let result = template.replace(/\$index/g, String(index));
|
|
330
|
-
for (const [key, value] of Object.entries(data)) {
|
|
331
|
-
result = result.replaceAll(`$${key}`, String(value));
|
|
332
|
-
}
|
|
333
|
-
return result;
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* Resolve baseMeta from string or TestMeta input.
|
|
337
|
-
* @internal
|
|
338
|
-
*/
|
|
339
|
-
function resolveBaseMeta(idOrMeta) {
|
|
340
|
-
return typeof idOrMeta === "string" ? { id: idOrMeta, name: idOrMeta } : { name: idOrMeta.id, ...idOrMeta };
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Normalize table input for test.each: accepts array or plain object (map).
|
|
344
|
-
*
|
|
345
|
-
* - Array: returned as-is
|
|
346
|
-
* - Plain object: converted to array with `_pick` key injected per entry
|
|
347
|
-
*
|
|
348
|
-
* @internal
|
|
349
|
-
*/
|
|
350
|
-
function isPlainObject(value) {
|
|
351
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
352
|
-
return false;
|
|
353
|
-
const proto = Object.getPrototypeOf(value);
|
|
354
|
-
return proto === Object.prototype || proto === null;
|
|
355
|
-
}
|
|
356
|
-
function normalizeEachTable(table) {
|
|
357
|
-
if (Array.isArray(table))
|
|
358
|
-
return table;
|
|
359
|
-
if (!isPlainObject(table)) {
|
|
360
|
-
throw new Error("test.each() expects an array or a plain object (map).");
|
|
361
|
-
}
|
|
362
|
-
return Object.entries(table).map(([key, val]) => ({ ...val, _pick: key }));
|
|
363
|
-
}
|
|
364
|
-
/**
|
|
365
|
-
* Builder for data-driven tests with multi-step workflow support.
|
|
366
|
-
*
|
|
367
|
-
* Created by `test.each(table)(idTemplate)` (without a callback).
|
|
368
|
-
* Provides the same fluent `.step()` / `.setup()` / `.teardown()` API
|
|
369
|
-
* as `TestBuilder`, but each step/setup/teardown also receives the
|
|
370
|
-
* data row for the current test.
|
|
371
|
-
*
|
|
372
|
-
* On finalization, creates one `Test` per row in the table, each with
|
|
373
|
-
* full step definitions visible in `glubean scan` metadata and dashboards.
|
|
374
|
-
*
|
|
375
|
-
* @template S The state type for multi-step tests
|
|
376
|
-
* @template T The data row type
|
|
377
|
-
*
|
|
378
|
-
* @example
|
|
379
|
-
* ```ts
|
|
380
|
-
* export const userFlows = test.each([
|
|
381
|
-
* { userId: 1 },
|
|
382
|
-
* { userId: 2 },
|
|
383
|
-
* ])("user-flow-$userId")
|
|
384
|
-
* .step("fetch user", async (ctx, state, { userId }) => {
|
|
385
|
-
* const res = await ctx.http.get(`/users/${userId}`);
|
|
386
|
-
* ctx.assert(res.ok, "user exists");
|
|
387
|
-
* return { user: await res.json() };
|
|
388
|
-
* })
|
|
389
|
-
* .step("verify posts", async (ctx, { user }) => {
|
|
390
|
-
* const res = await ctx.http.get(`/users/${user.id}/posts`);
|
|
391
|
-
* ctx.assert(res.ok, "posts accessible");
|
|
392
|
-
* });
|
|
393
|
-
* ```
|
|
394
|
-
*/
|
|
395
|
-
export class EachBuilder {
|
|
396
|
-
_baseMeta;
|
|
397
|
-
_table;
|
|
398
|
-
_setup;
|
|
399
|
-
_teardown;
|
|
400
|
-
_steps = [];
|
|
401
|
-
_built = false;
|
|
402
|
-
_parallel;
|
|
403
|
-
_fixtures;
|
|
404
|
-
/**
|
|
405
|
-
* Marker property so the runner and scanner can detect EachBuilder exports.
|
|
406
|
-
*/
|
|
407
|
-
__glubean_type = "each-builder";
|
|
408
|
-
constructor(baseMeta, table, fixtures, parallel = false) {
|
|
409
|
-
this._baseMeta = baseMeta;
|
|
410
|
-
this._table = table;
|
|
411
|
-
this._fixtures = fixtures;
|
|
412
|
-
this._parallel = parallel;
|
|
413
|
-
// Auto-finalize after all synchronous chaining completes.
|
|
414
|
-
queueMicrotask(() => this._finalize());
|
|
415
|
-
}
|
|
416
|
-
/**
|
|
417
|
-
* Set additional metadata for all generated tests.
|
|
418
|
-
*
|
|
419
|
-
* @example
|
|
420
|
-
* ```ts
|
|
421
|
-
* test.each(table)("user-$userId")
|
|
422
|
-
* .meta({ tags: ["smoke"], timeout: 10000 })
|
|
423
|
-
* .step("fetch", async (ctx, state, row) => { ... });
|
|
424
|
-
* ```
|
|
425
|
-
*/
|
|
426
|
-
meta(meta) {
|
|
427
|
-
this._baseMeta = { ...this._baseMeta, ...meta };
|
|
428
|
-
return this;
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* Mark all generated tests from this data set as focused.
|
|
432
|
-
* If `skip` is also set, skipped tests are still excluded.
|
|
433
|
-
*/
|
|
434
|
-
only() {
|
|
435
|
-
this._baseMeta = { ...this._baseMeta, only: true };
|
|
436
|
-
return this;
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* Mark all generated tests from this data set as skipped.
|
|
440
|
-
* Skip takes precedence over `only` when both are present.
|
|
441
|
-
*/
|
|
442
|
-
skip() {
|
|
443
|
-
this._baseMeta = { ...this._baseMeta, skip: true };
|
|
444
|
-
return this;
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Set the setup function. Receives context and data row, returns state.
|
|
448
|
-
*
|
|
449
|
-
* @example
|
|
450
|
-
* ```ts
|
|
451
|
-
* test.each(table)("id-$key")
|
|
452
|
-
* .setup(async (ctx, row) => {
|
|
453
|
-
* const api = ctx.http.extend({ headers: { "X-User": row.userId } });
|
|
454
|
-
* return { api };
|
|
455
|
-
* })
|
|
456
|
-
* .step("use api", async (ctx, { api }) => { ... });
|
|
457
|
-
* ```
|
|
458
|
-
*/
|
|
459
|
-
setup(fn) {
|
|
460
|
-
this._setup = fn;
|
|
461
|
-
return this;
|
|
462
|
-
}
|
|
463
|
-
/**
|
|
464
|
-
* Set the teardown function. Runs after all steps (even on failure).
|
|
465
|
-
*
|
|
466
|
-
* @example
|
|
467
|
-
* ```ts
|
|
468
|
-
* test.each(table)("user-$userId")
|
|
469
|
-
* .setup(async (ctx, row) => ({ token: await login(ctx, row) }))
|
|
470
|
-
* .step("test", async (ctx, { token }) => { ... })
|
|
471
|
-
* .teardown(async (ctx, state, row) => {
|
|
472
|
-
* await ctx.http.post("/logout", { body: { token: state.token } });
|
|
473
|
-
* });
|
|
474
|
-
* ```
|
|
475
|
-
*/
|
|
476
|
-
teardown(fn) {
|
|
477
|
-
this._teardown = fn;
|
|
478
|
-
return this;
|
|
479
|
-
}
|
|
480
|
-
step(name, optionsOrFn, maybeFn) {
|
|
481
|
-
const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
|
|
482
|
-
const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
|
|
483
|
-
this._steps.push({
|
|
484
|
-
meta: { name, ...options },
|
|
485
|
-
fn,
|
|
486
|
-
});
|
|
487
|
-
return this;
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* Apply a builder transform function for step composition.
|
|
491
|
-
*
|
|
492
|
-
* Works the same as `TestBuilder.use()` — reusable step sequences
|
|
493
|
-
* are plain functions that take a builder and return a builder.
|
|
494
|
-
*
|
|
495
|
-
* @param fn Transform function that receives this builder and returns a (possibly re-typed) builder
|
|
496
|
-
*
|
|
497
|
-
* @example
|
|
498
|
-
* ```ts
|
|
499
|
-
* const withVerify = (b: EachBuilder<{ id: string }, { userId: number }>) => b
|
|
500
|
-
* .step("verify", async (ctx, { id }, row) => {
|
|
501
|
-
* ctx.expect(id).toBeTruthy();
|
|
502
|
-
* });
|
|
503
|
-
*
|
|
504
|
-
* export const users = test.each(table)("user-$userId")
|
|
505
|
-
* .setup(async (ctx, row) => ({ id: String(row.userId) }))
|
|
506
|
-
* .use(withVerify);
|
|
507
|
-
* ```
|
|
508
|
-
*/
|
|
509
|
-
use(fn) {
|
|
510
|
-
return fn(this);
|
|
511
|
-
}
|
|
512
|
-
/**
|
|
513
|
-
* Apply a builder transform and tag all newly added steps with a group ID.
|
|
514
|
-
*
|
|
515
|
-
* Works the same as `TestBuilder.group()` — steps added by `fn` are marked
|
|
516
|
-
* with `group` metadata for visual grouping in reports.
|
|
517
|
-
*
|
|
518
|
-
* @param id Group identifier (displayed in reports as a section header)
|
|
519
|
-
* @param fn Transform function that adds steps to the builder
|
|
520
|
-
*
|
|
521
|
-
* @example
|
|
522
|
-
* ```ts
|
|
523
|
-
* export const users = test.each(table)("user-$userId")
|
|
524
|
-
* .group("setup", b => b
|
|
525
|
-
* .step("init", async (ctx, state, row) => ({ id: String(row.userId) }))
|
|
526
|
-
* )
|
|
527
|
-
* .step("verify", async (ctx, { id }) => { ... });
|
|
528
|
-
* ```
|
|
529
|
-
*/
|
|
530
|
-
group(id, fn) {
|
|
531
|
-
const before = this._steps.length;
|
|
532
|
-
const result = fn(this);
|
|
533
|
-
for (let i = before; i < this._steps.length; i++) {
|
|
534
|
-
this._steps[i].meta.group = id;
|
|
535
|
-
}
|
|
536
|
-
return result;
|
|
537
|
-
}
|
|
538
|
-
/**
|
|
539
|
-
* Get the filtered table (apply filter callback if present).
|
|
540
|
-
* @internal
|
|
541
|
-
*/
|
|
542
|
-
_filteredTable() {
|
|
543
|
-
const filter = this._baseMeta.filter;
|
|
544
|
-
if (!filter)
|
|
545
|
-
return this._table;
|
|
546
|
-
return this._table.filter((row, index) => filter(row, index));
|
|
547
|
-
}
|
|
548
|
-
/**
|
|
549
|
-
* Compute tags for a specific row (static tags + tagFields).
|
|
550
|
-
* @internal
|
|
551
|
-
*/
|
|
552
|
-
_tagsForRow(row) {
|
|
553
|
-
const staticTags = toArray(this._baseMeta.tags);
|
|
554
|
-
const tagFieldNames = toArray(this._baseMeta.tagFields);
|
|
555
|
-
const dynamicTags = tagFieldNames
|
|
556
|
-
.map((field) => {
|
|
557
|
-
const value = row[field];
|
|
558
|
-
return value != null ? `${field}:${value}` : null;
|
|
559
|
-
})
|
|
560
|
-
.filter((t) => t !== null);
|
|
561
|
-
return [...staticTags, ...dynamicTags];
|
|
562
|
-
}
|
|
563
|
-
/**
|
|
564
|
-
* Finalize and register all tests in the global registry.
|
|
565
|
-
* Called automatically via microtask if not explicitly invoked via build().
|
|
566
|
-
* Idempotent — safe to call multiple times.
|
|
567
|
-
* @internal
|
|
568
|
-
*/
|
|
569
|
-
_finalize() {
|
|
570
|
-
if (this._built)
|
|
571
|
-
return;
|
|
572
|
-
this._built = true;
|
|
573
|
-
const stepMetas = this._steps.map((s) => ({
|
|
574
|
-
name: s.meta.name,
|
|
575
|
-
...(s.meta.group ? { group: s.meta.group } : {}),
|
|
576
|
-
}));
|
|
577
|
-
const table = this._filteredTable();
|
|
578
|
-
const isPick = table.length > 0 && "_pick" in table[0];
|
|
579
|
-
const hasGroup = isPick || this._parallel;
|
|
580
|
-
for (let i = 0; i < table.length; i++) {
|
|
581
|
-
const row = table[i];
|
|
582
|
-
const id = interpolateTemplate(this._baseMeta.id, row, i);
|
|
583
|
-
const name = this._baseMeta.name ? interpolateTemplate(this._baseMeta.name, row, i) : id;
|
|
584
|
-
registerTest({
|
|
585
|
-
id,
|
|
586
|
-
name,
|
|
587
|
-
type: "steps",
|
|
588
|
-
tags: this._tagsForRow(row),
|
|
589
|
-
description: this._baseMeta.description,
|
|
590
|
-
steps: stepMetas,
|
|
591
|
-
hasSetup: !!this._setup,
|
|
592
|
-
hasTeardown: !!this._teardown,
|
|
593
|
-
...(hasGroup ? { groupId: this._baseMeta.id } : {}),
|
|
594
|
-
...(this._parallel ? { parallel: true } : {}),
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
/**
|
|
599
|
-
* Build and register all tests. Returns a `Test[]` array.
|
|
600
|
-
*
|
|
601
|
-
* **Optional** — if omitted, the builder auto-finalizes via microtask
|
|
602
|
-
* and the runner will auto-detect the EachBuilder export.
|
|
603
|
-
*/
|
|
604
|
-
build() {
|
|
605
|
-
this._finalize();
|
|
606
|
-
const table = this._filteredTable();
|
|
607
|
-
return table.map((row, index) => {
|
|
608
|
-
const id = interpolateTemplate(this._baseMeta.id, row, index);
|
|
609
|
-
const name = this._baseMeta.name ? interpolateTemplate(this._baseMeta.name, row, index) : id;
|
|
610
|
-
const meta = {
|
|
611
|
-
...this._baseMeta,
|
|
612
|
-
id,
|
|
613
|
-
name,
|
|
614
|
-
tags: this._tagsForRow(row),
|
|
615
|
-
};
|
|
616
|
-
const setup = this._setup;
|
|
617
|
-
const teardown = this._teardown;
|
|
618
|
-
return {
|
|
619
|
-
meta,
|
|
620
|
-
type: "steps",
|
|
621
|
-
setup: setup ? ((ctx) => setup(ctx, row)) : undefined,
|
|
622
|
-
teardown: teardown
|
|
623
|
-
? ((ctx, state) => teardown(ctx, state, row))
|
|
624
|
-
: undefined,
|
|
625
|
-
steps: this._steps.map((s) => ({
|
|
626
|
-
meta: s.meta,
|
|
627
|
-
fn: ((ctx, state) => s.fn(ctx, state, row)),
|
|
628
|
-
})),
|
|
629
|
-
...(this._fixtures ? { fixtures: this._fixtures } : {}),
|
|
630
|
-
};
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
/**
|
|
635
|
-
* Data-driven test generation.
|
|
636
|
-
*
|
|
637
|
-
* Creates one independent test per row in the data table.
|
|
638
|
-
* Each test gets its own ID (from template interpolation), runs independently,
|
|
639
|
-
* and reports its own pass/fail status.
|
|
640
|
-
*
|
|
641
|
-
* Use `$key` in the ID/name template to interpolate values from the data row.
|
|
642
|
-
* Use `$index` for the row index (0-based).
|
|
643
|
-
*
|
|
644
|
-
* Supports two modes:
|
|
645
|
-
*
|
|
646
|
-
* 1. **Simple mode** — pass a callback to get `Test[]` (single-function tests).
|
|
647
|
-
* 2. **Builder mode** — omit the callback to get an `EachBuilder` with
|
|
648
|
-
* `.step()` / `.setup()` / `.teardown()` support for multi-step workflows.
|
|
649
|
-
*
|
|
650
|
-
* @example Simple mode (backward compatible)
|
|
651
|
-
* ```ts
|
|
652
|
-
* import { test } from "@glubean/sdk";
|
|
653
|
-
*
|
|
654
|
-
* export const statusTests = test.each([
|
|
655
|
-
* { id: 1, expected: 200 },
|
|
656
|
-
* { id: 999, expected: 404 },
|
|
657
|
-
* ])("get-user-$id", async (ctx, { id, expected }) => {
|
|
658
|
-
* const res = await ctx.http.get(`${ctx.vars.require("BASE_URL")}/users/${id}`, {
|
|
659
|
-
* throwHttpErrors: false,
|
|
660
|
-
* });
|
|
661
|
-
* ctx.expect(res.status).toBe(expected);
|
|
662
|
-
* });
|
|
663
|
-
* ```
|
|
664
|
-
*
|
|
665
|
-
* @example Builder mode (multi-step per data row)
|
|
666
|
-
* ```ts
|
|
667
|
-
* export const userFlows = test.each([
|
|
668
|
-
* { userId: 1 },
|
|
669
|
-
* { userId: 2 },
|
|
670
|
-
* ])("user-flow-$userId")
|
|
671
|
-
* .step("fetch user", async (ctx, _state, { userId }) => {
|
|
672
|
-
* const res = await ctx.http.get(`/users/${userId}`);
|
|
673
|
-
* ctx.assert(res.ok, "user exists");
|
|
674
|
-
* return { user: await res.json() };
|
|
675
|
-
* })
|
|
676
|
-
* .step("verify posts", async (ctx, { user }) => {
|
|
677
|
-
* const res = await ctx.http.get(`/users/${user.id}/posts`);
|
|
678
|
-
* ctx.assert(res.ok, "posts accessible");
|
|
679
|
-
* });
|
|
680
|
-
* ```
|
|
681
|
-
*
|
|
682
|
-
* @param table Array of data rows. Each row produces one test.
|
|
683
|
-
* @returns A function that accepts an ID template and optional test function
|
|
684
|
-
*/
|
|
685
|
-
// =============================================================================
|
|
686
|
-
// Extended Test (test.extend)
|
|
687
|
-
// =============================================================================
|
|
688
|
-
/** Keys that cannot be used as extension names (they shadow core TestContext). */
|
|
689
|
-
const EXTEND_RESERVED_KEYS = new Set(["vars", "secrets", "http"]);
|
|
690
|
-
/**
|
|
691
|
-
* Select examples from a named map based on the GLUBEAN_PICK env var
|
|
692
|
-
* or random selection. Shared between `test.pick` and extended test `.pick()`.
|
|
693
|
-
*
|
|
694
|
-
* @internal
|
|
695
|
-
*/
|
|
696
|
-
function selectPickExamples(examples, count) {
|
|
697
|
-
const keys = Object.keys(examples);
|
|
698
|
-
if (keys.length === 0) {
|
|
699
|
-
throw new Error("test.pick requires at least one example");
|
|
700
|
-
}
|
|
701
|
-
let pickedEnv;
|
|
702
|
-
try {
|
|
703
|
-
pickedEnv = typeof process !== "undefined" ? process.env["GLUBEAN_PICK"] : undefined;
|
|
704
|
-
}
|
|
705
|
-
catch {
|
|
706
|
-
pickedEnv = undefined;
|
|
707
|
-
}
|
|
708
|
-
if (pickedEnv) {
|
|
709
|
-
const trimmed = pickedEnv.trim();
|
|
710
|
-
if (trimmed === "all" || trimmed === "*") {
|
|
711
|
-
return keys.map((k) => ({ ...examples[k], _pick: k }));
|
|
712
|
-
}
|
|
713
|
-
const pickedKeys = trimmed
|
|
714
|
-
.split(",")
|
|
715
|
-
.map((k) => k.trim())
|
|
716
|
-
.filter((k) => k.length > 0);
|
|
717
|
-
const hasGlob = pickedKeys.some((k) => k.includes("*"));
|
|
718
|
-
let validKeys;
|
|
719
|
-
if (hasGlob) {
|
|
720
|
-
const patterns = pickedKeys.map((p) => globToRegExp(p));
|
|
721
|
-
validKeys = keys.filter((k) => patterns.some((re) => re.test(k)));
|
|
722
|
-
}
|
|
723
|
-
else {
|
|
724
|
-
validKeys = pickedKeys.filter((k) => k in examples);
|
|
725
|
-
}
|
|
726
|
-
if (validKeys.length > 0) {
|
|
727
|
-
return validKeys.map((k) => ({ ...examples[k], _pick: k }));
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
// Random selection fallback
|
|
731
|
-
const shuffled = [...keys].sort(() => Math.random() - 0.5);
|
|
732
|
-
const picked = shuffled.slice(0, Math.min(count, keys.length));
|
|
733
|
-
return picked.map((k) => ({ ...examples[k], _pick: k }));
|
|
734
|
-
}
|
|
735
|
-
/**
|
|
736
|
-
* Create an extended test function with fixture definitions.
|
|
737
|
-
*
|
|
738
|
-
* @internal
|
|
739
|
-
*/
|
|
740
|
-
function createExtendedTest(allFixtures) {
|
|
741
|
-
// Validate no reserved keys
|
|
742
|
-
for (const key of Object.keys(allFixtures)) {
|
|
743
|
-
if (EXTEND_RESERVED_KEYS.has(key)) {
|
|
744
|
-
throw new Error(`Cannot extend test context with reserved key "${key}". ` +
|
|
745
|
-
`Reserved keys: ${[...EXTEND_RESERVED_KEYS].join(", ")}.`);
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
// The callable part — quick mode and builder mode
|
|
749
|
-
function extTest(idOrMeta, fn) {
|
|
750
|
-
if (fn) {
|
|
751
|
-
// Quick mode
|
|
752
|
-
const meta = typeof idOrMeta === "string"
|
|
753
|
-
? { id: idOrMeta, name: idOrMeta }
|
|
754
|
-
: { name: idOrMeta.id, ...idOrMeta };
|
|
755
|
-
if (meta.tags)
|
|
756
|
-
meta.tags = toArray(meta.tags);
|
|
757
|
-
const testDef = {
|
|
758
|
-
meta,
|
|
759
|
-
type: "simple",
|
|
760
|
-
fn: fn,
|
|
761
|
-
fixtures: allFixtures,
|
|
762
|
-
};
|
|
763
|
-
registerTest({
|
|
764
|
-
id: meta.id,
|
|
765
|
-
name: meta.name || meta.id,
|
|
766
|
-
type: "simple",
|
|
767
|
-
tags: toArray(meta.tags),
|
|
768
|
-
description: meta.description,
|
|
769
|
-
});
|
|
770
|
-
return testDef;
|
|
771
|
-
}
|
|
772
|
-
// Builder mode
|
|
773
|
-
const id = typeof idOrMeta === "string" ? idOrMeta : idOrMeta.id;
|
|
774
|
-
const builder = new TestBuilder(id, allFixtures);
|
|
775
|
-
if (typeof idOrMeta !== "string") {
|
|
776
|
-
builder.meta(idOrMeta);
|
|
777
|
-
}
|
|
778
|
-
return builder;
|
|
779
|
-
}
|
|
780
|
-
// .extend() — chained extension
|
|
781
|
-
extTest.extend = (extensions) => {
|
|
782
|
-
return createExtendedTest({
|
|
783
|
-
...allFixtures,
|
|
784
|
-
...extensions,
|
|
785
|
-
});
|
|
786
|
-
};
|
|
787
|
-
// .each() — data-driven with fixtures
|
|
788
|
-
extTest.each = (table, options) => {
|
|
789
|
-
const rows = normalizeEachTable(table);
|
|
790
|
-
const legacyParallel = options?.parallel ?? false;
|
|
791
|
-
return ((idOrMeta, fn) => {
|
|
792
|
-
const baseMeta = resolveBaseMeta(idOrMeta);
|
|
793
|
-
const parallel = baseMeta.parallel ?? legacyParallel;
|
|
794
|
-
if (!fn) {
|
|
795
|
-
return new EachBuilder(baseMeta, rows, allFixtures, parallel);
|
|
796
|
-
}
|
|
797
|
-
// Simple mode with fixtures
|
|
798
|
-
const filteredTable = baseMeta.filter
|
|
799
|
-
? rows.filter((row, i) => baseMeta.filter(row, i))
|
|
800
|
-
: rows;
|
|
801
|
-
const tagFieldNames = toArray(baseMeta.tagFields);
|
|
802
|
-
const staticTags = toArray(baseMeta.tags);
|
|
803
|
-
const isPick = filteredTable.length > 0 && "_pick" in filteredTable[0];
|
|
804
|
-
const hasGroup = isPick || parallel;
|
|
805
|
-
return filteredTable.map((row, index) => {
|
|
806
|
-
const id = interpolateTemplate(baseMeta.id, row, index);
|
|
807
|
-
const name = baseMeta.name ? interpolateTemplate(baseMeta.name, row, index) : id;
|
|
808
|
-
const dynamicTags = tagFieldNames
|
|
809
|
-
.map((field) => {
|
|
810
|
-
const value = row[field];
|
|
811
|
-
return value != null ? `${field}:${value}` : null;
|
|
812
|
-
})
|
|
813
|
-
.filter((t) => t !== null);
|
|
814
|
-
const allTags = [...staticTags, ...dynamicTags];
|
|
815
|
-
const meta = {
|
|
816
|
-
...baseMeta,
|
|
817
|
-
id,
|
|
818
|
-
name,
|
|
819
|
-
tags: allTags.length > 0 ? allTags : undefined,
|
|
820
|
-
};
|
|
821
|
-
const testDef = {
|
|
822
|
-
meta,
|
|
823
|
-
type: "simple",
|
|
824
|
-
fn: (async (ctx) => await fn(ctx, row)),
|
|
825
|
-
fixtures: allFixtures,
|
|
826
|
-
};
|
|
827
|
-
registerTest({
|
|
828
|
-
id: meta.id,
|
|
829
|
-
name: meta.name || meta.id,
|
|
830
|
-
type: "simple",
|
|
831
|
-
tags: allTags.length > 0 ? allTags : undefined,
|
|
832
|
-
description: meta.description,
|
|
833
|
-
...(hasGroup ? { groupId: baseMeta.id } : {}),
|
|
834
|
-
...(parallel ? { parallel: true } : {}),
|
|
835
|
-
});
|
|
836
|
-
return testDef;
|
|
837
|
-
});
|
|
838
|
-
});
|
|
839
|
-
};
|
|
840
|
-
// .pick() — example selection with fixtures
|
|
841
|
-
extTest.pick = (examples, count = 1) => {
|
|
842
|
-
const selected = selectPickExamples(examples, count);
|
|
843
|
-
return extTest.each(selected);
|
|
844
|
-
};
|
|
845
|
-
return extTest;
|
|
846
|
-
}
|
|
847
49
|
(function (test) {
|
|
848
50
|
function only(idOrMeta, fn) {
|
|
849
51
|
const baseMeta = typeof idOrMeta === "string" ? { id: idOrMeta, name: idOrMeta } : idOrMeta;
|
|
@@ -865,11 +67,9 @@ function createExtendedTest(allFixtures) {
|
|
|
865
67
|
return ((idOrMeta, fn) => {
|
|
866
68
|
const baseMeta = resolveBaseMeta(idOrMeta);
|
|
867
69
|
const parallel = baseMeta.parallel ?? legacyParallel;
|
|
868
|
-
// Builder mode: no callback → return EachBuilder
|
|
869
70
|
if (!fn) {
|
|
870
71
|
return new EachBuilder(baseMeta, rows, undefined, parallel);
|
|
871
72
|
}
|
|
872
|
-
// Apply filter if present
|
|
873
73
|
const filteredTable = baseMeta.filter
|
|
874
74
|
? rows.filter((row, index) => baseMeta.filter(row, index))
|
|
875
75
|
: rows;
|
|
@@ -877,11 +77,9 @@ function createExtendedTest(allFixtures) {
|
|
|
877
77
|
const staticTags = toArray(baseMeta.tags);
|
|
878
78
|
const isPick = filteredTable.length > 0 && "_pick" in filteredTable[0];
|
|
879
79
|
const hasGroup = isPick || parallel;
|
|
880
|
-
// Simple mode: with callback → return Test[]
|
|
881
80
|
return filteredTable.map((row, index) => {
|
|
882
81
|
const id = interpolateTemplate(baseMeta.id, row, index);
|
|
883
82
|
const name = baseMeta.name ? interpolateTemplate(baseMeta.name, row, index) : id;
|
|
884
|
-
// Compute tags: static tags + dynamic tagFields
|
|
885
83
|
const dynamicTags = tagFieldNames
|
|
886
84
|
.map((field) => {
|
|
887
85
|
const value = row[field];
|
|
@@ -1026,37 +224,20 @@ function createExtendedTest(allFixtures) {
|
|
|
1026
224
|
}
|
|
1027
225
|
test.extend = extend;
|
|
1028
226
|
})(test || (test = {}));
|
|
1029
|
-
//
|
|
1030
|
-
//
|
|
1031
|
-
//
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
function globToRegExp(pattern) {
|
|
1038
|
-
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
1039
|
-
const regexStr = "^" + escaped.replace(/\*/g, ".*") + "$";
|
|
1040
|
-
return new RegExp(regexStr);
|
|
1041
|
-
}
|
|
1042
|
-
// Re-export all types for user convenience
|
|
1043
|
-
export * from "./types.js";
|
|
1044
|
-
// Re-export data loaders for convenience
|
|
1045
|
-
// Users can also import from "@glubean/sdk/data" directly
|
|
1046
|
-
export { fromCsv, fromDir, fromJson, fromJsonl, fromYaml, toArray } from "./data.js";
|
|
1047
|
-
// Re-export configure API
|
|
1048
|
-
export { configure, resolveTemplate } from "./configure.js";
|
|
1049
|
-
// Re-export plugin utilities
|
|
1050
|
-
export { definePlugin } from "./plugin.js";
|
|
1051
|
-
// Contract API — generic core (HTTP adapter self-registers via ./contract-http)
|
|
227
|
+
// =============================================================================
|
|
228
|
+
// Builder + data-driven re-exports
|
|
229
|
+
// =============================================================================
|
|
230
|
+
export { TestBuilder } from "./test-builder.js";
|
|
231
|
+
export { EachBuilder } from "./each-builder.js";
|
|
232
|
+
// =============================================================================
|
|
233
|
+
// Contract API
|
|
234
|
+
// =============================================================================
|
|
1052
235
|
export { runFlow, normalizeFlow, extractMappings, extractMappingsOut, traceComputeFn, getAdapter, LensPurityError, } from "./contract-core.js";
|
|
1053
236
|
// HTTP adapter — built-in, registers itself at SDK load time
|
|
1054
237
|
import { contract as _contract } from "./contract-core.js";
|
|
1055
238
|
import { httpAdapter } from "./contract-http/adapter.js";
|
|
1056
239
|
import { createHttpRoot } from "./contract-http/factory.js";
|
|
1057
240
|
_contract.register("http", httpAdapter);
|
|
1058
|
-
// After register(), contract.http is the generic dispatcher. Wrap it in the
|
|
1059
|
-
// factory so users get `contract.http.with("name", {...})("id", spec)` UX.
|
|
1060
241
|
{
|
|
1061
242
|
const dispatcher = _contract.http;
|
|
1062
243
|
_contract.http = createHttpRoot(dispatcher);
|
|
@@ -1070,10 +251,14 @@ _contract.register("http", httpAdapter);
|
|
|
1070
251
|
* - `contract[protocol](id, spec)` — attached by `register()`
|
|
1071
252
|
*/
|
|
1072
253
|
export const contract = _contract;
|
|
1073
|
-
// Re-export HTTP-specific types + value exports
|
|
1074
254
|
export { createHttpFactory, createHttpRoot, } from "./contract-http/factory.js";
|
|
1075
|
-
//
|
|
255
|
+
// =============================================================================
|
|
256
|
+
// Utility + plugin re-exports
|
|
257
|
+
// =============================================================================
|
|
258
|
+
export * from "./types.js";
|
|
259
|
+
export { fromCsv, fromDir, fromJson, fromJsonl, fromYaml, toArray } from "./data.js";
|
|
260
|
+
export { configure, resolveTemplate } from "./configure.js";
|
|
261
|
+
export { definePlugin } from "./plugin.js";
|
|
1076
262
|
export { defineSession, session } from "./session.js";
|
|
1077
|
-
// Re-export assertion utilities
|
|
1078
263
|
export { Expectation, ExpectFailError } from "./expect.js";
|
|
1079
264
|
//# sourceMappingURL=index.js.map
|