@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.
- package/README.md +206 -172
- package/dist/assert.d.ts +1 -1
- package/dist/assert.js +4 -4
- package/dist/{builder-c7tXey03.d.ts → builder-dqXTFZ4j.d.ts} +29 -29
- package/dist/{chunk-3IL457A7.js → chunk-P4ZI7G5M.js} +22 -2
- package/dist/chunk-TO3T2D2Y.js +84 -0
- package/dist/compiler.d.ts +2 -2
- package/dist/engine-CphCJ1ZS.d.ts +47 -0
- package/dist/express.d.ts +5 -4
- package/dist/express.js +4 -7
- package/dist/fastify.d.ts +5 -4
- package/dist/fastify.js +4 -7
- package/dist/index.d.ts +5 -28
- package/dist/index.js +522 -270
- package/dist/lockfile.d.ts +2 -2
- package/dist/nextjs.d.ts +5 -4
- package/dist/nextjs.js +6 -9
- package/dist/{types-oYS_Yv4G.d.ts → types-yIhY8cwG.d.ts} +1 -1
- package/package.json +1 -1
- package/dist/chunk-CD3M7H5A.js +0 -332
package/dist/lockfile.d.ts
CHANGED
|
@@ -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-
|
|
5
|
-
import './types-
|
|
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 {
|
|
2
|
-
import './
|
|
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
|
|
10
|
+
readonly engine: PreconditionEngine;
|
|
10
11
|
}
|
|
11
12
|
type NextHandler = (req: Request) => Promise<Response>;
|
|
12
|
-
declare function createNextHandler({ enabled,
|
|
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-
|
|
10
|
-
import "./chunk-3IL457A7.js";
|
|
8
|
+
} from "./chunk-TO3T2D2Y.js";
|
|
11
9
|
|
|
12
10
|
// src/adapters/nextjs.ts
|
|
13
|
-
function createNextHandler({ enabled,
|
|
11
|
+
function createNextHandler({ enabled, engine }) {
|
|
14
12
|
if (!enabled) {
|
|
15
13
|
return async () => jsonResponse({ error: "Not found" }, 404);
|
|
16
14
|
}
|
|
17
|
-
const
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
package/dist/chunk-CD3M7H5A.js
DELETED
|
@@ -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
|
-
};
|