@reventlessdev/reventless-local 3.0.0-alpha.288 → 3.0.0-alpha.290
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/CHANGELOG.md +20 -0
- package/package.json +10 -10
- package/src/adapter/Webhook/LocalWebhookRegistry.res +64 -0
- package/src/adapter/Webhook/LocalWebhookRegistry.res.mjs +52 -0
- package/src/components/InboundTranslationSlice_Builder.res +10 -1
- package/src/components/InboundTranslationSlice_Builder.res.mjs +9 -1
- package/tests/adapter/InboundTranslationMutationTest.res.mjs +6 -4
- package/tests/components/automationslice/AutomationRescheduleFixtures.res +0 -1
- package/tests/components/automationslice/AutomationRescheduleFixtures.res.mjs +0 -1
- package/tests/components/automationslice/AutomationScheduleFixtures.res +0 -1
- package/tests/components/automationslice/AutomationScheduleFixtures.res.mjs +0 -5
- package/tests/components/automationslice/AutomationSliceFixtures.res +0 -2
- package/tests/components/automationslice/AutomationSliceFixtures.res.mjs +0 -2
- package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +0 -1
- package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res.mjs +0 -1
- package/tests/components/automationslice/AutomationSweepFixtures.res +0 -1
- package/tests/components/automationslice/AutomationSweepFixtures.res.mjs +0 -1
- package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res +0 -1
- package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res.mjs +0 -1
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +2 -0
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res.mjs +25 -20
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +3 -0
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res.mjs +3 -0
- package/tests/components/inboundtranslationslice/InboundTranslationWebhookTest.res +305 -0
- package/tests/components/inboundtranslationslice/InboundTranslationWebhookTest.res.mjs +473 -0
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res.mjs +0 -2
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res +0 -2
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res.mjs +0 -2
- package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res +0 -1
- package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res.mjs +0 -1
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Sury from "sury";
|
|
4
|
+
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
5
|
+
import * as Nodecrypto from "node:crypto";
|
|
6
|
+
import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
|
|
7
|
+
import * as Pulumi from "@pulumi/pulumi";
|
|
8
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
9
|
+
import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.res.mjs";
|
|
10
|
+
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
11
|
+
import * as Api$ReventlessInfra from "@reventlessdev/reventless-infra/src/components/Api.res.mjs";
|
|
12
|
+
import * as TestRunner$ReventlessLocal from "../../../src/test/TestRunner.res.mjs";
|
|
13
|
+
import * as LocalWebhookRegistry$ReventlessLocal from "../../../src/adapter/Webhook/LocalWebhookRegistry.res.mjs";
|
|
14
|
+
import * as InboundTranslationSlice_Webhook$ReventlessCore from "@reventlessdev/reventless-core/src/components/InboundTranslationSlice/InboundTranslationSlice_Webhook.res.mjs";
|
|
15
|
+
import * as InboundTranslationSlice_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs";
|
|
16
|
+
|
|
17
|
+
TestRunner$ReventlessLocal.setup();
|
|
18
|
+
|
|
19
|
+
let merchandiser = "Merchandiser";
|
|
20
|
+
|
|
21
|
+
let moduleUrl = "signed-feed://spec";
|
|
22
|
+
|
|
23
|
+
let externalInputSchema = Sury.$schema(s => ({
|
|
24
|
+
kind: s.m(Sury.string),
|
|
25
|
+
id: s.m(Sury.string)
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
let commandSchema = Sury.union([
|
|
29
|
+
Sury.$schema(s => ({
|
|
30
|
+
TAG: "AddProduct",
|
|
31
|
+
productId: s.m(DcbTag$Reventless.string)
|
|
32
|
+
})),
|
|
33
|
+
Sury.$schema(s => ({
|
|
34
|
+
TAG: "AddCategory",
|
|
35
|
+
categoryId: s.m(DcbTag$Reventless.string)
|
|
36
|
+
}))
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
function authorizationOf(name) {
|
|
40
|
+
if (name === "AddProduct") {
|
|
41
|
+
return {
|
|
42
|
+
TAG: "AllowRoles",
|
|
43
|
+
_0: [merchandiser]
|
|
44
|
+
};
|
|
45
|
+
} else {
|
|
46
|
+
return {
|
|
47
|
+
TAG: "AllowRoles",
|
|
48
|
+
_0: [Role$Reventless.admin]
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let endpoint = {
|
|
54
|
+
path: "/feeds/signed/",
|
|
55
|
+
auth: {
|
|
56
|
+
TAG: "SignedBody",
|
|
57
|
+
_0: {
|
|
58
|
+
header: "X-Signature",
|
|
59
|
+
prefix: "sha256=",
|
|
60
|
+
timestampHeader: "X-Timestamp",
|
|
61
|
+
secret: "feed"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
role: merchandiser
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
let traits = [];
|
|
68
|
+
|
|
69
|
+
let FeedSpec = {
|
|
70
|
+
name: "SignedFeed",
|
|
71
|
+
moduleUrl: moduleUrl,
|
|
72
|
+
externalInputSchema: externalInputSchema,
|
|
73
|
+
commandSchema: commandSchema,
|
|
74
|
+
targetName: "AddProduct",
|
|
75
|
+
externalSystem: undefined,
|
|
76
|
+
authorizationOf: authorizationOf,
|
|
77
|
+
endpoint: endpoint,
|
|
78
|
+
traits: traits
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function translateFeed(input) {
|
|
82
|
+
let kind = input.kind;
|
|
83
|
+
switch (kind) {
|
|
84
|
+
case "category" :
|
|
85
|
+
return {
|
|
86
|
+
TAG: "Ok",
|
|
87
|
+
_0: [[
|
|
88
|
+
input.id,
|
|
89
|
+
{
|
|
90
|
+
TAG: "AddCategory",
|
|
91
|
+
categoryId: input.id
|
|
92
|
+
}
|
|
93
|
+
]]
|
|
94
|
+
};
|
|
95
|
+
case "product" :
|
|
96
|
+
return {
|
|
97
|
+
TAG: "Ok",
|
|
98
|
+
_0: [[
|
|
99
|
+
input.id,
|
|
100
|
+
{
|
|
101
|
+
TAG: "AddProduct",
|
|
102
|
+
productId: input.id
|
|
103
|
+
}
|
|
104
|
+
]]
|
|
105
|
+
};
|
|
106
|
+
default:
|
|
107
|
+
return {
|
|
108
|
+
TAG: "Error",
|
|
109
|
+
_0: `unknown kind ` + kind
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let FeedCallback = InboundTranslationSlice_Callback$ReventlessCore.Make(FeedSpec)({
|
|
115
|
+
translate: translateFeed,
|
|
116
|
+
moduleUrl: moduleUrl
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
let moduleUrl$1 = "open-feed://spec";
|
|
120
|
+
|
|
121
|
+
let externalInputSchema$1 = Sury.$schema(s => ({
|
|
122
|
+
kind: s.m(Sury.string),
|
|
123
|
+
id: s.m(Sury.string)
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
let commandSchema$1 = Sury.$schema(s => ({
|
|
127
|
+
TAG: "AddProduct",
|
|
128
|
+
productId: s.m(DcbTag$Reventless.string)
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
function authorizationOf$1(param) {
|
|
132
|
+
return "AllowAuthenticated";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let traits$1 = [];
|
|
136
|
+
|
|
137
|
+
let OpenSpec_endpoint = {
|
|
138
|
+
path: "open",
|
|
139
|
+
auth: "Open"
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
let OpenSpec = {
|
|
143
|
+
name: "OpenFeed",
|
|
144
|
+
moduleUrl: moduleUrl$1,
|
|
145
|
+
externalInputSchema: externalInputSchema$1,
|
|
146
|
+
commandSchema: commandSchema$1,
|
|
147
|
+
targetName: "AddProduct",
|
|
148
|
+
externalSystem: undefined,
|
|
149
|
+
authorizationOf: authorizationOf$1,
|
|
150
|
+
endpoint: OpenSpec_endpoint,
|
|
151
|
+
traits: traits$1
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
function translate(input) {
|
|
155
|
+
return {
|
|
156
|
+
TAG: "Ok",
|
|
157
|
+
_0: [[
|
|
158
|
+
input.id,
|
|
159
|
+
{
|
|
160
|
+
TAG: "AddProduct",
|
|
161
|
+
productId: input.id
|
|
162
|
+
}
|
|
163
|
+
]]
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let OpenCallback = InboundTranslationSlice_Callback$ReventlessCore.Make(OpenSpec)({
|
|
168
|
+
translate: translate,
|
|
169
|
+
moduleUrl: moduleUrl$1
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
let inputSchema = Sury.$schema(s => ({
|
|
173
|
+
id: s.m(Sury.string)
|
|
174
|
+
}));
|
|
175
|
+
|
|
176
|
+
let published = {
|
|
177
|
+
contents: []
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
async function publishJsons(cmds) {
|
|
181
|
+
published.contents = published.contents.concat(cmds);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function feedOps_receive(input, caller, door) {
|
|
185
|
+
return FeedCallback.receive(publishJsons, input, caller, door);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function feedOps_refuse(body, errorCode, error) {
|
|
189
|
+
return FeedCallback.refuse(body, errorCode, error);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let feedOps = {
|
|
193
|
+
receive: feedOps_receive,
|
|
194
|
+
refuse: feedOps_refuse
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
function openOps_receive(input, caller, door) {
|
|
198
|
+
return OpenCallback.receive(publishJsons, input, caller, door);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function openOps_refuse(body, errorCode, error) {
|
|
202
|
+
return OpenCallback.refuse(body, errorCode, error);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let openOps = {
|
|
206
|
+
receive: openOps_receive,
|
|
207
|
+
refuse: openOps_refuse
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
let viaWebhook = "Webhook";
|
|
211
|
+
|
|
212
|
+
let viaMutation = "Mutation";
|
|
213
|
+
|
|
214
|
+
let secret = "s3cret";
|
|
215
|
+
|
|
216
|
+
function secretOf(name) {
|
|
217
|
+
if (name === "feed") {
|
|
218
|
+
return secret;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let signature = {
|
|
223
|
+
header: "X-Signature",
|
|
224
|
+
prefix: "sha256=",
|
|
225
|
+
timestampHeader: "X-Timestamp",
|
|
226
|
+
secret: "feed"
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
function hmac(secret, text) {
|
|
230
|
+
return Nodecrypto.createHmac("sha256", secret).update(text).digest("hex");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function signedHeaders(secretOpt, atOpt, body) {
|
|
234
|
+
let secret$1 = secretOpt !== undefined ? secretOpt : secret;
|
|
235
|
+
let at = atOpt !== undefined ? atOpt : 1700000000;
|
|
236
|
+
let at$1 = at.toString();
|
|
237
|
+
return Object.fromEntries([
|
|
238
|
+
[
|
|
239
|
+
"x-timestamp",
|
|
240
|
+
at$1
|
|
241
|
+
],
|
|
242
|
+
[
|
|
243
|
+
"x-signature",
|
|
244
|
+
"sha256=" + hmac(secret$1, at$1 + `.` + body)
|
|
245
|
+
]
|
|
246
|
+
]);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function body(kindOpt, idOpt) {
|
|
250
|
+
let kind = kindOpt !== undefined ? kindOpt : "product";
|
|
251
|
+
let id = idOpt !== undefined ? idOpt : "p-1";
|
|
252
|
+
return `{"kind":"` + kind + `","id":"` + id + `"}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function post(pathOpt, method, headers, body) {
|
|
256
|
+
let path = pathOpt !== undefined ? pathOpt : "feeds/signed";
|
|
257
|
+
return LocalWebhookRegistry$ReventlessLocal.post(path, method, headers, secretOf, 1700000000, body);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function throws(run) {
|
|
261
|
+
try {
|
|
262
|
+
run();
|
|
263
|
+
return false;
|
|
264
|
+
} catch (exn) {
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function rows(log) {
|
|
270
|
+
return Object.values(log);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function clear(log) {
|
|
274
|
+
Object.keys(log).forEach(key => Stdlib_Dict.$$delete(log, key));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
globalThis.describe("webhook signature", () => {
|
|
278
|
+
let check = (secretsOpt, headers, text) => {
|
|
279
|
+
let secrets = secretsOpt !== undefined ? secretsOpt : [secret];
|
|
280
|
+
return InboundTranslationSlice_Webhook$ReventlessCore.verify(signature, secrets, headers, text, 1700000000);
|
|
281
|
+
};
|
|
282
|
+
globalThis.test("a signature by the shared secret passes", () => {
|
|
283
|
+
globalThis.expect(check(undefined, signedHeaders(undefined, undefined, body(undefined, undefined)), body(undefined, undefined))).toEqual({
|
|
284
|
+
TAG: "Ok",
|
|
285
|
+
_0: undefined
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
globalThis.test("a signature by another secret is refused", () => {
|
|
289
|
+
globalThis.expect(Stdlib_Result.isError(check(undefined, signedHeaders("other", undefined, body(undefined, undefined)), body(undefined, undefined)))).toBe(true);
|
|
290
|
+
});
|
|
291
|
+
globalThis.test("a body changed after signing is refused", () => {
|
|
292
|
+
globalThis.expect(Stdlib_Result.isError(check(undefined, signedHeaders(undefined, undefined, body(undefined, undefined)), body(undefined, "p-2")))).toBe(true);
|
|
293
|
+
});
|
|
294
|
+
globalThis.test("a timestamp outside the window is refused, so a captured request cannot be replayed", () => {
|
|
295
|
+
globalThis.expect(Stdlib_Result.isError(check(undefined, signedHeaders(undefined, 1700000000 - 301, body(undefined, undefined)), body(undefined, undefined)))).toBe(true);
|
|
296
|
+
});
|
|
297
|
+
globalThis.test("a request without the signature header is refused", () => {
|
|
298
|
+
let headers = signedHeaders(undefined, undefined, body(undefined, undefined));
|
|
299
|
+
Stdlib_Dict.$$delete(headers, "x-signature");
|
|
300
|
+
globalThis.expect(Stdlib_Result.isError(check(undefined, headers, body(undefined, undefined)))).toBe(true);
|
|
301
|
+
});
|
|
302
|
+
globalThis.test("while a secret is rotated, either one passes", () => {
|
|
303
|
+
globalThis.expect(check(InboundTranslationSlice_Webhook$ReventlessCore.secretsOf("new, old"), signedHeaders("old", undefined, body(undefined, undefined)), body(undefined, undefined))).toEqual({
|
|
304
|
+
TAG: "Ok",
|
|
305
|
+
_0: undefined
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
globalThis.test("a secret is read from a variable named after it", () => {
|
|
309
|
+
globalThis.expect(InboundTranslationSlice_Webhook$ReventlessCore.secretEnvKey("supplier-feed")).toBe("REVENTLESS_WEBHOOK_SECRET_SUPPLIER_FEED");
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
globalThis.describe("webhook door", () => {
|
|
314
|
+
globalThis.beforeAll(() => {
|
|
315
|
+
LocalWebhookRegistry$ReventlessLocal.reset();
|
|
316
|
+
let match = FeedCallback.webhook;
|
|
317
|
+
let match$1 = OpenCallback.webhook;
|
|
318
|
+
if (match !== undefined && match$1 !== undefined) {
|
|
319
|
+
LocalWebhookRegistry$ReventlessLocal.register(match, Pulumi.output(feedOps));
|
|
320
|
+
return LocalWebhookRegistry$ReventlessLocal.register(match$1, Pulumi.output(openOps));
|
|
321
|
+
} else {
|
|
322
|
+
return Stdlib_JsError.throwWithMessage("fixture specs declare endpoints");
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
globalThis.afterAll(() => LocalWebhookRegistry$ReventlessLocal.reset());
|
|
326
|
+
globalThis.beforeEach(() => {
|
|
327
|
+
published.contents = [];
|
|
328
|
+
clear(FeedCallback.auditLog);
|
|
329
|
+
clear(OpenCallback.auditLog);
|
|
330
|
+
});
|
|
331
|
+
globalThis.test("a signed request publishes its commands as the endpoint's role", async () => {
|
|
332
|
+
let response = await post(undefined, undefined, signedHeaders(undefined, undefined, body(undefined, undefined)), body(undefined, undefined));
|
|
333
|
+
globalThis.expect(response.status).toBe(200);
|
|
334
|
+
globalThis.expect(published.contents.map(c => c.id)).toEqual(["p-1"]);
|
|
335
|
+
globalThis.expect(Object.values(FeedCallback.auditLog).map(row => row.door)).toEqual([viaWebhook]);
|
|
336
|
+
});
|
|
337
|
+
globalThis.test("a command the endpoint's role may not send is refused", async () => {
|
|
338
|
+
let text = body("category", undefined);
|
|
339
|
+
let response = await post(undefined, undefined, signedHeaders(undefined, undefined, text), text);
|
|
340
|
+
globalThis.expect(response.status).toBe(403);
|
|
341
|
+
globalThis.expect(published.contents).toEqual([]);
|
|
342
|
+
});
|
|
343
|
+
globalThis.test("a bad signature is refused before the body is read, with an audit row", async () => {
|
|
344
|
+
let response = await post(undefined, undefined, signedHeaders("other", undefined, body(undefined, undefined)), body(undefined, undefined));
|
|
345
|
+
globalThis.expect(response.status).toBe(401);
|
|
346
|
+
globalThis.expect(published.contents).toEqual([]);
|
|
347
|
+
globalThis.expect(Object.values(FeedCallback.auditLog).map(row => [
|
|
348
|
+
row.status,
|
|
349
|
+
row.door,
|
|
350
|
+
row.input
|
|
351
|
+
])).toEqual([[
|
|
352
|
+
"Failure",
|
|
353
|
+
viaWebhook,
|
|
354
|
+
body(undefined, undefined)
|
|
355
|
+
]]);
|
|
356
|
+
});
|
|
357
|
+
globalThis.test("a signed body that is not JSON is refused", async () => {
|
|
358
|
+
let response = await post(undefined, undefined, signedHeaders(undefined, undefined, "not json"), "not json");
|
|
359
|
+
globalThis.expect(response.status).toBe(400);
|
|
360
|
+
});
|
|
361
|
+
globalThis.test("an unknown path and a wrong method are refused", async () => {
|
|
362
|
+
let unknown = await post("nowhere", undefined, undefined, body(undefined, undefined));
|
|
363
|
+
let wrongMethod = await post(undefined, "PUT", signedHeaders(undefined, undefined, body(undefined, undefined)), body(undefined, undefined));
|
|
364
|
+
globalThis.expect([
|
|
365
|
+
unknown.status,
|
|
366
|
+
wrongMethod.status
|
|
367
|
+
]).toEqual([
|
|
368
|
+
404,
|
|
369
|
+
405
|
|
370
|
+
]);
|
|
371
|
+
});
|
|
372
|
+
globalThis.test("a secret the platform lacks fails closed, without an audit row", async () => {
|
|
373
|
+
let response = await LocalWebhookRegistry$ReventlessLocal.post("feeds/signed", undefined, signedHeaders(undefined, undefined, body(undefined, undefined)), param => {}, 1700000000, body(undefined, undefined));
|
|
374
|
+
globalThis.expect(response.status).toBe(500);
|
|
375
|
+
globalThis.expect(published.contents).toEqual([]);
|
|
376
|
+
globalThis.expect(Object.values(FeedCallback.auditLog)).toEqual([]);
|
|
377
|
+
});
|
|
378
|
+
globalThis.test("an open URL identifies nobody, so a signed-in rule refuses it", async () => {
|
|
379
|
+
let response = await post("open", undefined, undefined, body(undefined, undefined));
|
|
380
|
+
globalThis.expect(response.status).toBe(403);
|
|
381
|
+
});
|
|
382
|
+
globalThis.test("both doors publish the same commands and are told apart in the audit", async () => {
|
|
383
|
+
let caller_groups = [merchandiser];
|
|
384
|
+
let caller = {
|
|
385
|
+
userId: "u-1",
|
|
386
|
+
username: "alice",
|
|
387
|
+
groups: caller_groups,
|
|
388
|
+
provider: "InMemory"
|
|
389
|
+
};
|
|
390
|
+
let mutationResult = await feedOps_receive(JSON.parse(body(undefined, undefined)), caller, undefined);
|
|
391
|
+
await post(undefined, undefined, signedHeaders(undefined, undefined, body(undefined, undefined)), body(undefined, undefined));
|
|
392
|
+
globalThis.expect(Stdlib_Result.isOk(mutationResult)).toBe(true);
|
|
393
|
+
let commands = published.contents.map(c => c.commandJson);
|
|
394
|
+
if (commands.length !== 2) {
|
|
395
|
+
globalThis.expect(commands.length).toBe(2);
|
|
396
|
+
} else {
|
|
397
|
+
let viaMutationCommand = commands[0];
|
|
398
|
+
let viaWebhookCommand = commands[1];
|
|
399
|
+
globalThis.expect(viaWebhookCommand).toEqual(viaMutationCommand);
|
|
400
|
+
}
|
|
401
|
+
globalThis.expect(Object.values(FeedCallback.auditLog).map(row => row.door)).toEqual([
|
|
402
|
+
viaMutation,
|
|
403
|
+
viaWebhook
|
|
404
|
+
]);
|
|
405
|
+
});
|
|
406
|
+
globalThis.test("two slices may not share a path", () => {
|
|
407
|
+
let route = FeedCallback.webhook;
|
|
408
|
+
if (route !== undefined) {
|
|
409
|
+
globalThis.expect(throws(() => LocalWebhookRegistry$ReventlessLocal.register(route, Pulumi.output(feedOps)))).toBe(true);
|
|
410
|
+
return;
|
|
411
|
+
} else {
|
|
412
|
+
return Stdlib_JsError.throwWithMessage("fixture spec declares an endpoint");
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
globalThis.describe("which doors a slice has", () => {
|
|
418
|
+
globalThis.test("@noApi on externalInput withholds the mutation", () => {
|
|
419
|
+
globalThis.expect([
|
|
420
|
+
InboundTranslationSlice_Callback$ReventlessCore.exposesMutation(Api$ReventlessInfra.markNoApi(inputSchema)),
|
|
421
|
+
InboundTranslationSlice_Callback$ReventlessCore.exposesMutation(inputSchema)
|
|
422
|
+
]).toEqual([
|
|
423
|
+
false,
|
|
424
|
+
true
|
|
425
|
+
]);
|
|
426
|
+
});
|
|
427
|
+
globalThis.test("a slice with neither door is refused", () => {
|
|
428
|
+
let check = webhook => throws(() => InboundTranslationSlice_Callback$ReventlessCore.checkReachable("P", "S", false, webhook));
|
|
429
|
+
globalThis.expect([
|
|
430
|
+
check(false),
|
|
431
|
+
check(true)
|
|
432
|
+
]).toEqual([
|
|
433
|
+
true,
|
|
434
|
+
false
|
|
435
|
+
]);
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
let Webhook;
|
|
440
|
+
|
|
441
|
+
let Callback;
|
|
442
|
+
|
|
443
|
+
let now = 1700000000;
|
|
444
|
+
|
|
445
|
+
export {
|
|
446
|
+
Webhook,
|
|
447
|
+
Callback,
|
|
448
|
+
merchandiser,
|
|
449
|
+
FeedSpec,
|
|
450
|
+
translateFeed,
|
|
451
|
+
FeedCallback,
|
|
452
|
+
OpenSpec,
|
|
453
|
+
OpenCallback,
|
|
454
|
+
inputSchema,
|
|
455
|
+
published,
|
|
456
|
+
publishJsons,
|
|
457
|
+
feedOps,
|
|
458
|
+
openOps,
|
|
459
|
+
viaWebhook,
|
|
460
|
+
viaMutation,
|
|
461
|
+
secret,
|
|
462
|
+
now,
|
|
463
|
+
secretOf,
|
|
464
|
+
signature,
|
|
465
|
+
hmac,
|
|
466
|
+
signedHeaders,
|
|
467
|
+
body,
|
|
468
|
+
post,
|
|
469
|
+
throws,
|
|
470
|
+
rows,
|
|
471
|
+
clear,
|
|
472
|
+
}
|
|
473
|
+
/* Not a pure module */
|
package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res.mjs
CHANGED
|
@@ -44,7 +44,6 @@ let FireForgetCallback = OutboundTranslationSlice_Callback$ReventlessCore.Make({
|
|
|
44
44
|
outboundItemSchema: OutboundTranslationSliceFixtures$ReventlessLocal.SendTrackingEmailSpec.outboundItemSchema,
|
|
45
45
|
inboundCommandSchema: OutboundTranslationSliceFixtures$ReventlessLocal.SendTrackingEmailSpec.inboundCommandSchema,
|
|
46
46
|
maxRetries: OutboundTranslationSliceFixtures$ReventlessLocal.SendTrackingEmailSpec.maxRetries,
|
|
47
|
-
heartbeatInterval: OutboundTranslationSliceFixtures$ReventlessLocal.SendTrackingEmailSpec.heartbeatInterval,
|
|
48
47
|
targetName: OutboundTranslationSliceFixtures$ReventlessLocal.SendTrackingEmailSpec.targetName,
|
|
49
48
|
sourceNames: OutboundTranslationSliceFixtures$ReventlessLocal.SendTrackingEmailSpec.sourceNames,
|
|
50
49
|
externalSystem: OutboundTranslationSliceFixtures$ReventlessLocal.SendTrackingEmailSpec.externalSystem,
|
|
@@ -59,7 +58,6 @@ let CommandBackCallback = OutboundTranslationSlice_Callback$ReventlessCore.Make(
|
|
|
59
58
|
outboundItemSchema: OutboundTranslationSliceFixtures$ReventlessLocal.ProcessPaymentSpec.outboundItemSchema,
|
|
60
59
|
inboundCommandSchema: OutboundTranslationSliceFixtures$ReventlessLocal.ProcessPaymentSpec.inboundCommandSchema,
|
|
61
60
|
maxRetries: OutboundTranslationSliceFixtures$ReventlessLocal.ProcessPaymentSpec.maxRetries,
|
|
62
|
-
heartbeatInterval: OutboundTranslationSliceFixtures$ReventlessLocal.ProcessPaymentSpec.heartbeatInterval,
|
|
63
61
|
targetName: OutboundTranslationSliceFixtures$ReventlessLocal.ProcessPaymentSpec.targetName,
|
|
64
62
|
sourceNames: OutboundTranslationSliceFixtures$ReventlessLocal.ProcessPaymentSpec.sourceNames,
|
|
65
63
|
externalSystem: OutboundTranslationSliceFixtures$ReventlessLocal.ProcessPaymentSpec.externalSystem,
|
|
@@ -38,7 +38,6 @@ module SendTrackingEmailSpec = {
|
|
|
38
38
|
let onExhausted = (_id, _item, ~lastError as _) => None
|
|
39
39
|
|
|
40
40
|
let maxRetries = 3
|
|
41
|
-
let heartbeatInterval = 60
|
|
42
41
|
let targetName = None
|
|
43
42
|
let sourceNames: array<string> = []
|
|
44
43
|
let externalSystem = None
|
|
@@ -86,7 +85,6 @@ module ProcessPaymentSpec = {
|
|
|
86
85
|
let onExhausted = (id, item, ~lastError) => onExhaustedFn.contents(id, item, lastError)
|
|
87
86
|
|
|
88
87
|
let maxRetries = 2
|
|
89
|
-
let heartbeatInterval = 30
|
|
90
88
|
let targetName = Some("ConfirmPayment")
|
|
91
89
|
let sourceNames: array<string> = []
|
|
92
90
|
let externalSystem = None
|
|
@@ -76,7 +76,6 @@ let SendTrackingEmailSpec = {
|
|
|
76
76
|
translate: translate,
|
|
77
77
|
onExhausted: onExhausted,
|
|
78
78
|
maxRetries: 3,
|
|
79
|
-
heartbeatInterval: 60,
|
|
80
79
|
targetName: undefined,
|
|
81
80
|
sourceNames: sourceNames,
|
|
82
81
|
externalSystem: undefined,
|
|
@@ -158,7 +157,6 @@ let ProcessPaymentSpec = {
|
|
|
158
157
|
onExhaustedFn: onExhaustedFn,
|
|
159
158
|
onExhausted: onExhausted$1,
|
|
160
159
|
maxRetries: 2,
|
|
161
|
-
heartbeatInterval: 30,
|
|
162
160
|
targetName: ProcessPaymentSpec_targetName,
|
|
163
161
|
sourceNames: sourceNames$1,
|
|
164
162
|
externalSystem: undefined,
|
package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res.mjs
CHANGED
|
@@ -121,7 +121,6 @@ let SendConfirmSpec = {
|
|
|
121
121
|
outboundItemSchema: outboundItemSchema,
|
|
122
122
|
inboundCommandSchema: inboundCommandSchema,
|
|
123
123
|
maxRetries: 3,
|
|
124
|
-
heartbeatInterval: 60,
|
|
125
124
|
targetName: undefined,
|
|
126
125
|
sourceNames: sourceNames,
|
|
127
126
|
externalSystem: SendConfirmSpec_externalSystem,
|