@ripplo/testing 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  import { Codec } from '@ripplo/spec';
2
2
  import { z } from 'zod';
3
3
  import { CompileResult } from './compiler.js';
4
- import './builder-c7tXey03.js';
5
- import './types-oYS_Yv4G.js';
4
+ import './builder-dqXTFZ4j.js';
5
+ import './types-yIhY8cwG.js';
6
6
  import './step-De52hTLd.js';
7
7
 
8
8
  declare const LOCKFILE_RELATIVE_PATH = ".ripplo/ripplo.lock";
package/dist/nextjs.d.ts CHANGED
@@ -1,14 +1,15 @@
1
- import { R as RipploBuilder } from './builder-c7tXey03.js';
2
- import './types-oYS_Yv4G.js';
1
+ import { P as PreconditionEngine } from './engine-CphCJ1ZS.js';
2
+ import './builder-dqXTFZ4j.js';
3
+ import './types-yIhY8cwG.js';
3
4
  import 'zod';
4
5
  import './step-De52hTLd.js';
5
6
  import '@ripplo/spec';
6
7
 
7
8
  interface CreateNextHandlerParams {
8
9
  readonly enabled: boolean;
9
- readonly ripplo: RipploBuilder;
10
+ readonly engine: PreconditionEngine;
10
11
  }
11
12
  type NextHandler = (req: Request) => Promise<Response>;
12
- declare function createNextHandler({ enabled, ripplo }: CreateNextHandlerParams): NextHandler;
13
+ declare function createNextHandler({ enabled, engine }: CreateNextHandlerParams): NextHandler;
13
14
 
14
15
  export { type CreateNextHandlerParams, createNextHandler };
package/dist/nextjs.js CHANGED
@@ -1,21 +1,18 @@
1
1
  import {
2
2
  batchRequestSchema,
3
3
  buildSetCookieHeader,
4
- createEngine,
5
4
  observerRequestSchema,
6
5
  serializeCookie,
7
6
  teardownRequestSchema,
8
7
  verifyWebhookSignature
9
- } from "./chunk-CD3M7H5A.js";
10
- import "./chunk-3IL457A7.js";
8
+ } from "./chunk-TO3T2D2Y.js";
11
9
 
12
10
  // src/adapters/nextjs.ts
13
- function createNextHandler({ enabled, ripplo }) {
11
+ function createNextHandler({ enabled, engine }) {
14
12
  if (!enabled) {
15
13
  return async () => jsonResponse({ error: "Not found" }, 404);
16
14
  }
17
- const engine = createEngine(ripplo);
18
- const webhookSecret = ripplo.getConfig().webhookSecret;
15
+ const webhookSecret = engine.getConfig().webhookSecret;
19
16
  return async function handler(req) {
20
17
  const action = lastPathSegment(req.url);
21
18
  if (action !== "execute-preconditions" && action !== "teardown-preconditions" && action !== "execute-observer") {
@@ -30,7 +27,7 @@ function createNextHandler({ enabled, ripplo }) {
30
27
  }
31
28
  function dispatchAction({ action, body, engine, req }) {
32
29
  if (action === "execute-preconditions") {
33
- return handleExecuteBatch({ body, engine, req });
30
+ return handleExecutePreconditions({ body, engine, req });
34
31
  }
35
32
  if (action === "execute-observer") {
36
33
  return handleExecuteObserver({ body, engine });
@@ -52,7 +49,7 @@ async function handleExecuteObserver({
52
49
  200
53
50
  );
54
51
  }
55
- async function handleExecuteBatch({
52
+ async function handleExecutePreconditions({
56
53
  body,
57
54
  engine,
58
55
  req
@@ -68,7 +65,7 @@ async function handleExecuteBatch({
68
65
  }
69
66
  const proto = req.headers.get("x-forwarded-proto") ?? "http";
70
67
  const appUrl = `${proto}://${host}`;
71
- const result = await engine.executeBatch(parsed.data.preconditions, { appUrl });
68
+ const result = await engine.executePreconditions(parsed.data.preconditions, { appUrl });
72
69
  const headers = new Headers({ "content-type": "application/json" });
73
70
  result.cookies.forEach((cookie) => {
74
71
  headers.append("Set-Cookie", buildSetCookieHeader(serializeCookie(cookie)));
@@ -112,4 +112,4 @@ interface ObserverDefinition {
112
112
  readonly run: (ctx: ObserverContext, params: Record<string, string>) => Promise<ObserverOutcome>;
113
113
  }
114
114
 
115
- export { type CookieEntry as C, DEFAULT_IGNORE_PATHS as D, type ObserverHandle as O, type Precondition as P, type SetupContext as S, type TeardownContext as T, type UnimplementedItems as U, type ObserverInput as a, type ObserverBudgetTier as b, type ObserverOutcome as c, type CookieOptions as d, DEFAULT_WATCH_PATHS as e, type DslConfig as f, type ObserverContext as g, type ObserverDefinition as h, type PreconditionDeps as i, type PreconditionData as j, type TestValue as k, type PreconditionDefinition as l, type TestDefinition as m };
115
+ export { type CookieEntry as C, DEFAULT_IGNORE_PATHS as D, type ObserverHandle as O, type Precondition as P, type SetupContext as S, type TeardownContext as T, type UnimplementedItems as U, type ObserverInput as a, type ObserverBudgetTier as b, type CookieOptions as c, DEFAULT_WATCH_PATHS as d, type DslConfig as e, type ObserverContext as f, type ObserverDefinition as g, type ObserverOutcome as h, type PreconditionDeps as i, type TestDefinition as j, type PreconditionDefinition as k, type PreconditionData as l, type TestValue as m };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ripplo/testing",
3
3
  "description": "TypeScript DSL for defining and running Ripplo e2e workflow tests",
4
- "version": "0.1.1",
4
+ "version": "0.2.0",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist"
@@ -1,332 +0,0 @@
1
- import {
2
- createTestValue,
3
- makeObserverHandle,
4
- readTestValue
5
- } from "./chunk-3IL457A7.js";
6
-
7
- // src/observer.ts
8
- function createPassOutcome() {
9
- return { kind: "pass" };
10
- }
11
- function createRetryOutcome(reason) {
12
- return { kind: "retry", reason };
13
- }
14
- function createFailOutcome(reason) {
15
- return { kind: "fail", reason };
16
- }
17
- function buildObserver({ name, observers }) {
18
- let description = "";
19
- let budget = "fast";
20
- const self = {
21
- budget(tier) {
22
- budget = tier;
23
- return self;
24
- },
25
- description(text) {
26
- description = text;
27
- return self;
28
- },
29
- input() {
30
- return buildObserverReady({
31
- name,
32
- observers,
33
- getBudget: () => budget,
34
- getDescription: () => description
35
- });
36
- }
37
- };
38
- return self;
39
- }
40
- function buildObserverReady({
41
- getBudget,
42
- getDescription,
43
- name,
44
- observers
45
- }) {
46
- return {
47
- contract() {
48
- pushObserverStub({ budget: getBudget(), description: getDescription(), name, observers });
49
- return makeObserverHandle(name, getBudget());
50
- },
51
- notImplemented() {
52
- pushObserverStub({ budget: getBudget(), description: getDescription(), name, observers });
53
- return makeObserverHandle(name, getBudget());
54
- }
55
- };
56
- }
57
- function pushObserverStub({ budget, description, name, observers }) {
58
- observers.push({
59
- budget,
60
- description,
61
- implemented: false,
62
- name,
63
- run: () => Promise.resolve(createFailOutcome(`observer "${name}" not implemented`))
64
- });
65
- }
66
-
67
- // src/engine.ts
68
- function createEngine(ripplo) {
69
- return {
70
- executeBatch: (names, options) => executeBatch(ripplo, names, options),
71
- executeObserver: (name, params) => executeObserver(ripplo, name, params),
72
- teardown: (names, data) => teardown(ripplo, names, data)
73
- };
74
- }
75
- async function executeObserver(ripplo, name, params) {
76
- const lookup = findObserver(ripplo.getObservers(), name);
77
- if (lookup.error != null) {
78
- return { error: lookup.error, outcome: void 0, success: false };
79
- }
80
- const ctx = createObserverContext(crypto.randomUUID().slice(0, 12));
81
- return invokeRun(lookup.def, ctx, params);
82
- }
83
- function findObserver(defs, name) {
84
- const def = defs.find((o) => o.name === name);
85
- if (def == null) {
86
- return { def: STUB_OBSERVER, error: `Unknown observer: "${name}"` };
87
- }
88
- if (!def.implemented) {
89
- return { def, error: `Observer "${name}" is not implemented` };
90
- }
91
- return { def, error: void 0 };
92
- }
93
- var STUB_OBSERVER = {
94
- budget: "fast",
95
- description: "",
96
- implemented: false,
97
- name: "",
98
- run: () => Promise.resolve(createFailOutcome("stub"))
99
- };
100
- async function invokeRun(def, ctx, params) {
101
- try {
102
- const outcome = await def.run(ctx, params);
103
- return { error: void 0, outcome, success: true };
104
- } catch (error) {
105
- const message = error instanceof Error ? error.message : String(error);
106
- return { error: void 0, outcome: createFailOutcome(message), success: true };
107
- }
108
- }
109
- function createObserverContext(runId) {
110
- return {
111
- runId,
112
- fail: (reason) => createFailOutcome(reason),
113
- pass: () => createPassOutcome(),
114
- retry: (reason) => createRetryOutcome(reason)
115
- };
116
- }
117
- async function executeBatch(ripplo, names, options) {
118
- const runId = crypto.randomUUID().slice(0, 12);
119
- const cookies = [];
120
- const defaultDomain = deriveDefaultDomain(options?.appUrl);
121
- const state = {
122
- cookies,
123
- ctx: createSetupContext({ cookies, defaultDomain, runId }),
124
- data: {},
125
- defsByName: buildDefMap(ripplo.getPreconditions()),
126
- executed: [],
127
- runId
128
- };
129
- return runBatchSequence(state, names);
130
- }
131
- async function runBatchSequence(state, names) {
132
- let index = 0;
133
- while (index < names.length) {
134
- const name = names[index];
135
- if (name == null) {
136
- break;
137
- }
138
- const error = validatePrecondition(state.defsByName, name);
139
- if (error != null) {
140
- return fail(state, error);
141
- }
142
- const stepError = await executeOnePrecondition(state, name);
143
- if (stepError != null) {
144
- return fail(state, stepError);
145
- }
146
- index += 1;
147
- }
148
- return {
149
- cookies: state.cookies,
150
- data: state.data,
151
- error: void 0,
152
- executed: state.executed,
153
- runId: state.runId,
154
- success: true
155
- };
156
- }
157
- function validatePrecondition(defsByName, name) {
158
- const def = defsByName.get(name);
159
- if (def == null) {
160
- return `Unknown precondition: "${name}"`;
161
- }
162
- if (!def.implemented) {
163
- return `Precondition "${name}" is not implemented`;
164
- }
165
- return void 0;
166
- }
167
- async function executeOnePrecondition(state, name) {
168
- const def = state.defsByName.get(name);
169
- if (def == null) {
170
- return `Unknown precondition: "${name}"`;
171
- }
172
- try {
173
- const result = await def.setup(state.ctx, state.data);
174
- const resolved = {};
175
- Object.entries(result).forEach(([key, value]) => {
176
- resolved[key] = readTestValue(value);
177
- });
178
- state.data[name] = resolved;
179
- state.executed.push(name);
180
- return void 0;
181
- } catch (error) {
182
- return error instanceof Error ? error.message : String(error);
183
- }
184
- }
185
- function fail(state, error) {
186
- return {
187
- cookies: state.cookies,
188
- data: state.data,
189
- error,
190
- executed: state.executed,
191
- runId: state.runId,
192
- success: false
193
- };
194
- }
195
- async function teardown(ripplo, names, data) {
196
- const defsByName = buildDefMap(ripplo.getPreconditions());
197
- const reversed = [...names].toReversed();
198
- let index = 0;
199
- while (index < reversed.length) {
200
- const name = reversed[index];
201
- if (name != null) {
202
- await teardownOne(defsByName, name, data);
203
- }
204
- index += 1;
205
- }
206
- }
207
- async function teardownOne(defsByName, name, data) {
208
- const def = defsByName.get(name);
209
- if (def?.teardown == null) {
210
- return;
211
- }
212
- try {
213
- await def.teardown({ data: data[name] ?? {} });
214
- } catch {
215
- }
216
- }
217
- function buildDefMap(defs) {
218
- return new Map(defs.map((d) => [d.name, d]));
219
- }
220
- function createSetupContext({
221
- cookies,
222
- defaultDomain,
223
- runId
224
- }) {
225
- return {
226
- runId,
227
- fixed: (value) => createTestValue(value),
228
- setCookie: (name, value, options) => {
229
- const resolvedOptions = options != null && options.domain == null && defaultDomain != null ? { ...options, domain: defaultDomain } : options ?? void 0;
230
- cookies.push({ name, options: resolvedOptions, value });
231
- },
232
- uniqueEmail: () => createTestValue(`ripplo-test-${runId}@test.ripplo.ai`),
233
- uniqueId: (prefix) => createTestValue(`ripplo-test-${prefix}-${runId}`)
234
- };
235
- }
236
- function deriveDefaultDomain(baseUrl) {
237
- if (baseUrl == null) {
238
- return void 0;
239
- }
240
- try {
241
- return new URL(baseUrl).hostname;
242
- } catch {
243
- return void 0;
244
- }
245
- }
246
-
247
- // src/adapters/shared.ts
248
- import { Webhook, WebhookVerificationError } from "standardwebhooks";
249
- import { z } from "zod";
250
- var batchRequestSchema = z.object({
251
- preconditions: z.array(z.string().min(1))
252
- });
253
- var teardownRequestSchema = z.object({
254
- data: z.record(z.string(), z.record(z.string(), z.string())),
255
- preconditions: z.array(z.string().min(1))
256
- });
257
- var observerRequestSchema = z.object({
258
- observer: z.string().min(1).max(200),
259
- params: z.record(z.string().max(200), z.string())
260
- });
261
- var observerOutcomeSchema = z.discriminatedUnion("kind", [
262
- z.object({ kind: z.literal("pass") }),
263
- z.object({ kind: z.literal("retry"), reason: z.string() }),
264
- z.object({ kind: z.literal("fail"), reason: z.string() })
265
- ]);
266
- var observerResponseSchema = z.object({
267
- error: z.string().optional(),
268
- outcome: observerOutcomeSchema.optional(),
269
- success: z.boolean()
270
- });
271
- function verifyWebhookSignature(payload, headers, secret) {
272
- try {
273
- const wh = new Webhook(secret);
274
- wh.verify(payload, {
275
- "webhook-id": headers["webhook-id"] ?? "",
276
- "webhook-signature": headers["webhook-signature"] ?? "",
277
- "webhook-timestamp": headers["webhook-timestamp"] ?? ""
278
- });
279
- return true;
280
- } catch (error) {
281
- if (error instanceof WebhookVerificationError) {
282
- return false;
283
- }
284
- throw error;
285
- }
286
- }
287
- function serializeCookie(cookie) {
288
- return {
289
- domain: cookie.options?.domain,
290
- expires: cookie.options?.expires == null ? void 0 : new Date(cookie.options.expires * 1e3),
291
- httpOnly: cookie.options?.httpOnly,
292
- name: cookie.name,
293
- path: cookie.options?.path,
294
- sameSite: cookie.options?.sameSite,
295
- secure: cookie.options?.secure,
296
- value: cookie.value
297
- };
298
- }
299
- function buildSetCookieHeader(cookie) {
300
- const parts = [`${cookie.name}=${cookie.value}`];
301
- if (cookie.domain != null) {
302
- parts.push(`Domain=${cookie.domain}`);
303
- }
304
- if (cookie.path != null) {
305
- parts.push(`Path=${cookie.path}`);
306
- }
307
- if (cookie.expires != null) {
308
- parts.push(`Expires=${cookie.expires.toUTCString()}`);
309
- }
310
- if (cookie.httpOnly === true) {
311
- parts.push("HttpOnly");
312
- }
313
- if (cookie.secure === true) {
314
- parts.push("Secure");
315
- }
316
- if (cookie.sameSite != null) {
317
- const capitalized = cookie.sameSite.charAt(0).toUpperCase() + cookie.sameSite.slice(1);
318
- parts.push(`SameSite=${capitalized}`);
319
- }
320
- return parts.join("; ");
321
- }
322
-
323
- export {
324
- buildObserver,
325
- createEngine,
326
- batchRequestSchema,
327
- teardownRequestSchema,
328
- observerRequestSchema,
329
- verifyWebhookSignature,
330
- serializeCookie,
331
- buildSetCookieHeader
332
- };