@reventlessdev/reventless-local 3.0.0-alpha.298 → 3.0.0-alpha.299
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 +29 -0
- package/package.json +10 -10
- package/src/EmitCapabilities.res +3 -3
- package/src/LocalPlatformRegistry.res +1 -1
- package/src/LocalSeedTarget.res +1 -1
- package/src/Platform.res +57 -3
- package/src/Platform.res.mjs +61 -6
- package/src/ShellConfig.res +11 -1
- package/src/ShellConfig.res.mjs +10 -4
- package/src/adapter/Auth/Auth_GraphqlContext.res +18 -2
- package/src/adapter/Auth/Auth_GraphqlContext.res.mjs +8 -1
- package/src/adapter/Auth/UserStore.res +102 -3
- package/src/adapter/Auth/UserStore.res.mjs +128 -2
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +33 -0
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res.mjs +26 -0
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +11 -3
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res.mjs +3 -0
- package/src/adapter/DomainGraphQL_Server.res +1 -1
- package/src/adapter/GraphQL_CallerError.res +4 -0
- package/src/adapter/GraphQL_CallerError.res.mjs +9 -0
- package/src/adapter/LocalAnonymousLimit.res +35 -0
- package/src/adapter/LocalAnonymousLimit.res.mjs +52 -0
- package/src/adapter/LocalCapabilities.res +14 -2
- package/src/adapter/LocalCapabilities.res.mjs +21 -5
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +22 -0
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res.mjs +7 -0
- package/src/components/InboundTranslationSlice_Builder.res +1 -0
- package/src/components/InboundTranslationSlice_Builder.res.mjs +4 -1
- package/tests/EmitCapabilitiesTest.res +4 -6
- package/tests/EmitCapabilitiesTest.res.mjs +1 -2
- package/tests/HostShellDistTest.res.mjs +1 -1
- package/tests/LocalPlatformRegistryTest.res +3 -5
- package/tests/LocalPlatformRegistryTest.res.mjs +2 -4
- package/tests/LocalPlatformStartTest.res +2 -2
- package/tests/LocalPlatformStartTest.res.mjs +1 -2
- package/tests/ShellConfigTest.res.mjs +27 -27
- package/tests/adapter/InboundTranslationMutationTest.res +13 -2
- package/tests/adapter/InboundTranslationMutationTest.res.mjs +7 -4
- package/tests/adapter/LocalAnonymousLimitTest.res +70 -0
- package/tests/adapter/LocalAnonymousLimitTest.res.mjs +85 -0
- package/tests/adapter/LocalAuthLoginTest.res +2 -2
- package/tests/adapter/LocalAuthUserStoreTest.res +88 -0
- package/tests/adapter/LocalAuthUserStoreTest.res.mjs +83 -0
- package/tests/capability/LocalCapabilitiesTest.res +2 -1
- package/tests/capability/LocalCapabilitiesTest.res.mjs +3 -2
- package/tests/components/inboundtranslationslice/InboundTranslationCredentialTest.res +136 -0
- package/tests/components/inboundtranslationslice/InboundTranslationCredentialTest.res.mjs +183 -0
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +143 -11
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res.mjs +129 -22
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +18 -5
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res.mjs +12 -3
- package/tests/components/inboundtranslationslice/InboundTranslationWebhookTest.res +26 -6
- package/tests/components/inboundtranslationslice/InboundTranslationWebhookTest.res.mjs +48 -13
- package/tests/plugin/Plugin_Fixtures.res +0 -4
- package/tests/plugin/Plugin_Fixtures.res.mjs +13 -33
|
@@ -32,7 +32,11 @@ describe("InboundTranslationSlice Callback", () => {
|
|
|
32
32
|
"status": "completed",
|
|
33
33
|
}->Obj.magic
|
|
34
34
|
|
|
35
|
-
let result = await Callback.receive(
|
|
35
|
+
let result = await Callback.receive(
|
|
36
|
+
mockPublish,
|
|
37
|
+
inputJson,
|
|
38
|
+
~capabilities=Reventless.Capabilities.none,
|
|
39
|
+
)
|
|
36
40
|
|
|
37
41
|
switch result {
|
|
38
42
|
| Ok({requestId, targetIds, commandCount}) =>
|
|
@@ -69,7 +73,11 @@ describe("InboundTranslationSlice Callback", () => {
|
|
|
69
73
|
"status": "pending",
|
|
70
74
|
}->Obj.magic
|
|
71
75
|
|
|
72
|
-
let result = await Callback.receive(
|
|
76
|
+
let result = await Callback.receive(
|
|
77
|
+
mockPublish,
|
|
78
|
+
inputJson,
|
|
79
|
+
~capabilities=Reventless.Capabilities.none,
|
|
80
|
+
)
|
|
73
81
|
|
|
74
82
|
switch result {
|
|
75
83
|
| Error({error}) => expect(error)->toBe("Unknown payment status: pending")
|
|
@@ -92,7 +100,11 @@ describe("InboundTranslationSlice Callback", () => {
|
|
|
92
100
|
// Missing required fields
|
|
93
101
|
let inputJson = {"unexpected": "data"}->Obj.magic
|
|
94
102
|
|
|
95
|
-
let result = await Callback.receive(
|
|
103
|
+
let result = await Callback.receive(
|
|
104
|
+
mockPublish,
|
|
105
|
+
inputJson,
|
|
106
|
+
~capabilities=Reventless.Capabilities.none,
|
|
107
|
+
)
|
|
96
108
|
|
|
97
109
|
switch result {
|
|
98
110
|
| Error(_) => expect(true)->toBe(true)
|
|
@@ -119,7 +131,11 @@ describe("InboundTranslationSlice Callback", () => {
|
|
|
119
131
|
"status": "completed",
|
|
120
132
|
}->Obj.magic
|
|
121
133
|
|
|
122
|
-
let result = await Callback.receive(
|
|
134
|
+
let result = await Callback.receive(
|
|
135
|
+
failingPublish,
|
|
136
|
+
inputJson,
|
|
137
|
+
~capabilities=Reventless.Capabilities.none,
|
|
138
|
+
)
|
|
123
139
|
|
|
124
140
|
switch result {
|
|
125
141
|
| Error({error}) => expect(error)->toBe("publish failed")
|
|
@@ -147,7 +163,11 @@ describe("InboundTranslationSlice Callback", () => {
|
|
|
147
163
|
let input = status =>
|
|
148
164
|
{"paymentId": "pay-1", "orderId": "ord-1", "status": status}->Obj.magic
|
|
149
165
|
|
|
150
|
-
let first = await Callback.receive(
|
|
166
|
+
let first = await Callback.receive(
|
|
167
|
+
mockPublish,
|
|
168
|
+
input("completed"),
|
|
169
|
+
~capabilities=Reventless.Capabilities.none,
|
|
170
|
+
)
|
|
151
171
|
let firstId = first->ReventlessCore.InboundTranslationSlice_Callback.requestIdOf
|
|
152
172
|
expect(
|
|
153
173
|
Callback.auditLog
|
|
@@ -158,7 +178,11 @@ describe("InboundTranslationSlice Callback", () => {
|
|
|
158
178
|
|
|
159
179
|
// A second request drains only its own row — the first one is gone, so it
|
|
160
180
|
// cannot be written a second time.
|
|
161
|
-
let second = await Callback.receive(
|
|
181
|
+
let second = await Callback.receive(
|
|
182
|
+
mockPublish,
|
|
183
|
+
input("completed"),
|
|
184
|
+
~capabilities=Reventless.Capabilities.none,
|
|
185
|
+
)
|
|
162
186
|
let secondId = second->ReventlessCore.InboundTranslationSlice_Callback.requestIdOf
|
|
163
187
|
expect(secondId == firstId)->toBe(false)
|
|
164
188
|
expect(Callback.auditLog->Dict.toArray->Array.length)->toBe(1)
|
|
@@ -182,6 +206,7 @@ describe("InboundTranslationSlice Callback", () => {
|
|
|
182
206
|
let result = await Callback.receive(
|
|
183
207
|
mockPublish,
|
|
184
208
|
{"paymentId": "pay-1", "orderId": "ord-1", "status": "pending"}->Obj.magic,
|
|
209
|
+
~capabilities=Reventless.Capabilities.none,
|
|
185
210
|
)
|
|
186
211
|
let id = result->ReventlessCore.InboundTranslationSlice_Callback.requestIdOf
|
|
187
212
|
switch Callback.auditLog->ReventlessCore.InboundTranslationSlice_Callback.takeAuditRow(id) {
|
|
@@ -215,9 +240,14 @@ describe("InboundTranslationSlice Callback", () => {
|
|
|
215
240
|
|
|
216
241
|
let targetName = "ConfirmPayment"
|
|
217
242
|
let externalSystem = None
|
|
243
|
+
let capabilityNeeds: array<Reventless.CapabilityNeed.t> = []
|
|
218
244
|
let endpoint = None
|
|
219
245
|
|
|
220
|
-
let translate =
|
|
246
|
+
let translate = async (
|
|
247
|
+
input: externalInput,
|
|
248
|
+
~capabilities as _: Reventless.Capabilities.t,
|
|
249
|
+
~caller as _: option<Reventless.Identity.t>,
|
|
250
|
+
) => Ok(
|
|
221
251
|
input.items->Array.map(
|
|
222
252
|
item => (input.orderId, ConfirmPayment({orderId: input.orderId, paymentId: item})),
|
|
223
253
|
),
|
|
@@ -248,7 +278,11 @@ describe("InboundTranslationSlice Callback", () => {
|
|
|
248
278
|
"items": ["pay-1", "pay-2", "pay-3"],
|
|
249
279
|
}->Obj.magic
|
|
250
280
|
|
|
251
|
-
let result = await MultiCallback.receive(
|
|
281
|
+
let result = await MultiCallback.receive(
|
|
282
|
+
mockPublish,
|
|
283
|
+
inputJson,
|
|
284
|
+
~capabilities=Reventless.Capabilities.none,
|
|
285
|
+
)
|
|
252
286
|
|
|
253
287
|
switch result {
|
|
254
288
|
| Ok({targetIds, commandCount}) =>
|
|
@@ -286,9 +320,14 @@ describe("InboundTranslationSlice Callback", () => {
|
|
|
286
320
|
|
|
287
321
|
let targetName = "ConfirmPayment"
|
|
288
322
|
let externalSystem = None
|
|
323
|
+
let capabilityNeeds: array<Reventless.CapabilityNeed.t> = []
|
|
289
324
|
let endpoint = None
|
|
290
325
|
|
|
291
|
-
let translate =
|
|
326
|
+
let translate = async (
|
|
327
|
+
_input: externalInput,
|
|
328
|
+
~capabilities as _: Reventless.Capabilities.t,
|
|
329
|
+
~caller as _: option<Reventless.Identity.t>,
|
|
330
|
+
) => Ok([])
|
|
292
331
|
|
|
293
332
|
type role = Reventless.Role.name
|
|
294
333
|
|
|
@@ -312,7 +351,11 @@ describe("InboundTranslationSlice Callback", () => {
|
|
|
312
351
|
|
|
313
352
|
let inputJson = {"orderId": "ord-1"}->Obj.magic
|
|
314
353
|
|
|
315
|
-
let result = await EmptyCallback.receive(
|
|
354
|
+
let result = await EmptyCallback.receive(
|
|
355
|
+
mockPublish,
|
|
356
|
+
inputJson,
|
|
357
|
+
~capabilities=Reventless.Capabilities.none,
|
|
358
|
+
)
|
|
316
359
|
|
|
317
360
|
switch result {
|
|
318
361
|
| Ok({targetIds, commandCount}) =>
|
|
@@ -360,6 +403,7 @@ describe("receive checks each command against its own rule", () => {
|
|
|
360
403
|
let result = await CatalogFeedCallback.receive(
|
|
361
404
|
publish,
|
|
362
405
|
catalogInput("both"),
|
|
406
|
+
~capabilities=Reventless.Capabilities.none,
|
|
363
407
|
~caller=caller(["Merchandiser"]),
|
|
364
408
|
)
|
|
365
409
|
switch result {
|
|
@@ -379,6 +423,7 @@ describe("receive checks each command against its own rule", () => {
|
|
|
379
423
|
let result = await CatalogFeedCallback.receive(
|
|
380
424
|
publish,
|
|
381
425
|
catalogInput("both"),
|
|
426
|
+
~capabilities=Reventless.Capabilities.none,
|
|
382
427
|
~caller=caller(["Merchandiser", "Admin"]),
|
|
383
428
|
)
|
|
384
429
|
expect(result->Result.isOk)->toBe(true)
|
|
@@ -389,6 +434,7 @@ describe("receive checks each command against its own rule", () => {
|
|
|
389
434
|
let result = await CatalogFeedCallback.receive(
|
|
390
435
|
publish,
|
|
391
436
|
catalogInput("product"),
|
|
437
|
+
~capabilities=Reventless.Capabilities.none,
|
|
392
438
|
~caller=caller(["Merchandiser"]),
|
|
393
439
|
)
|
|
394
440
|
expect(result->Result.isOk)->toBe(true)
|
|
@@ -396,7 +442,11 @@ describe("receive checks each command against its own rule", () => {
|
|
|
396
442
|
})
|
|
397
443
|
|
|
398
444
|
testPromise("no caller is the platform itself, and publishes", async () => {
|
|
399
|
-
let result = await CatalogFeedCallback.receive(
|
|
445
|
+
let result = await CatalogFeedCallback.receive(
|
|
446
|
+
publish,
|
|
447
|
+
catalogInput("both"),
|
|
448
|
+
~capabilities=Reventless.Capabilities.none,
|
|
449
|
+
)
|
|
400
450
|
expect(result->Result.isOk)->toBe(true)
|
|
401
451
|
expect(published.contents->Array.length)->toBe(2)
|
|
402
452
|
})
|
|
@@ -405,6 +455,7 @@ describe("receive checks each command against its own rule", () => {
|
|
|
405
455
|
let result = await CatalogFeedCallback.receive(
|
|
406
456
|
publish,
|
|
407
457
|
catalogInput("both"),
|
|
458
|
+
~capabilities=Reventless.Capabilities.none,
|
|
408
459
|
~caller=Reventless.Identity.anonymous,
|
|
409
460
|
)
|
|
410
461
|
switch result->ReventlessCore.InboundTranslationSlice_Callback.receiveResultToOutcome {
|
|
@@ -430,6 +481,7 @@ describe("receive encodes commands with their schema", () => {
|
|
|
430
481
|
let _ = await NoteFeedCallback.receive(
|
|
431
482
|
publish,
|
|
432
483
|
Dict.fromArray([("orderId", JSON.Encode.string("ord-1"))])->JSON.Encode.object,
|
|
484
|
+
~capabilities=Reventless.Capabilities.none,
|
|
433
485
|
)
|
|
434
486
|
|
|
435
487
|
let commandJson = (published.contents->Array.getUnsafe(0)).commandJson
|
|
@@ -439,3 +491,83 @@ describe("receive encodes commands with their schema", () => {
|
|
|
439
491
|
expect(commandJson->JSON.stringify->String.includes(`"note":null`))->toBe(true)
|
|
440
492
|
})
|
|
441
493
|
})
|
|
494
|
+
|
|
495
|
+
// A door that looks at its caller before it decides: only an administrator may
|
|
496
|
+
// send it. It refuses before reaching a capability, and the refusal is recorded
|
|
497
|
+
// like any other translation error, naming who was refused.
|
|
498
|
+
module AdminOnlyTranslation = {
|
|
499
|
+
let translate = async (input, ~capabilities, ~caller: option<Reventless.Identity.t>) =>
|
|
500
|
+
switch caller {
|
|
501
|
+
| None => Error("this door needs a caller")
|
|
502
|
+
| Some(identity) if !(identity->Reventless.Identity.hasGroup("Admin")) =>
|
|
503
|
+
Error("only an administrator may send this")
|
|
504
|
+
| Some(_) => await PaymentWebhookSpec.translate(input, ~capabilities, ~caller)
|
|
505
|
+
}
|
|
506
|
+
let moduleUrl = PaymentWebhookSpec.moduleUrl
|
|
507
|
+
}
|
|
508
|
+
module AdminOnlyCallback = ReventlessCore.InboundTranslationSlice_Callback.Make(
|
|
509
|
+
PaymentWebhookSpec,
|
|
510
|
+
AdminOnlyTranslation,
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
describe("receive hands the door its caller", () => {
|
|
514
|
+
let payment = {"paymentId": "pay-1", "orderId": "ord-1", "status": "completed"}->Obj.magic
|
|
515
|
+
let identity = (~userId, ~groups): Reventless.Identity.t => {
|
|
516
|
+
...Reventless.Identity.anonymous,
|
|
517
|
+
userId,
|
|
518
|
+
groups,
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
let receive = async (~caller=?) => {
|
|
522
|
+
AdminOnlyCallback.auditLog
|
|
523
|
+
->Dict.keysToArray
|
|
524
|
+
->Array.forEach(k => AdminOnlyCallback.auditLog->Dict.delete(k))
|
|
525
|
+
let published = ref([])
|
|
526
|
+
let publish: ReventlessInfra.CommandTopic.publishJsons = async cmds => published := cmds
|
|
527
|
+
let result = await AdminOnlyCallback.receive(
|
|
528
|
+
publish,
|
|
529
|
+
payment,
|
|
530
|
+
~capabilities=Reventless.Capabilities.none,
|
|
531
|
+
~caller?,
|
|
532
|
+
)
|
|
533
|
+
let row = AdminOnlyCallback.auditLog->Dict.valuesToArray->Array.getUnsafe(0)
|
|
534
|
+
(result, published.contents, row)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
testPromise("with no caller, the door refuses and nothing is published", async () => {
|
|
538
|
+
let (result, published, row) = await receive()
|
|
539
|
+
expect(result->Result.isError)->toBe(true)
|
|
540
|
+
expect(published)->toEqual([])
|
|
541
|
+
expect(row.status)->toBe(ReventlessCore.InboundTranslationSlice_Callback.Failure)
|
|
542
|
+
expect(row.error)->toEqual(Some("this door needs a caller"))
|
|
543
|
+
expect(row.caller)->toEqual(None)
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
testPromise(
|
|
547
|
+
"a caller lacking the role is refused, and the row names who was refused",
|
|
548
|
+
async () => {
|
|
549
|
+
let (result, published, row) = await receive(
|
|
550
|
+
~caller=identity(~userId="u_visitor", ~groups=["Shopper"]),
|
|
551
|
+
)
|
|
552
|
+
switch result {
|
|
553
|
+
| Error({error, ?errorCode}) =>
|
|
554
|
+
expect(error)->toBe("only an administrator may send this")
|
|
555
|
+
// The door's refusal, not the platform's per-command gate.
|
|
556
|
+
expect(errorCode)->toEqual(None)
|
|
557
|
+
| Ok(_) => expect(true)->toBe(false)
|
|
558
|
+
}
|
|
559
|
+
expect(published)->toEqual([])
|
|
560
|
+
expect(row.caller)->toEqual(Some("u_visitor"))
|
|
561
|
+
},
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
testPromise("a caller holding the role is let through, and named", async () => {
|
|
565
|
+
let (result, published, row) = await receive(
|
|
566
|
+
~caller=identity(~userId="u_admin", ~groups=["Admin"]),
|
|
567
|
+
)
|
|
568
|
+
expect(result->Result.isOk)->toBe(true)
|
|
569
|
+
expect(published->Array.length)->toBe(1)
|
|
570
|
+
expect(row.status)->toBe(ReventlessCore.InboundTranslationSlice_Callback.Success)
|
|
571
|
+
expect(row.caller)->toEqual(Some("u_admin"))
|
|
572
|
+
})
|
|
573
|
+
})
|
package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
|
|
|
7
7
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
8
8
|
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
9
9
|
import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
|
|
10
|
+
import * as Capabilities$Reventless from "@reventlessdev/reventless-spec/src/semantic/Capabilities.res.mjs";
|
|
10
11
|
import * as InboundTranslationSliceFixtures$ReventlessLocal from "./InboundTranslationSliceFixtures.res.mjs";
|
|
11
12
|
import * as InboundTranslationSlice_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs";
|
|
12
13
|
|
|
@@ -27,7 +28,8 @@ let Callback = InboundTranslationSlice_Callback$ReventlessCore.Make({
|
|
|
27
28
|
targetName: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.targetName,
|
|
28
29
|
externalSystem: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.externalSystem,
|
|
29
30
|
authorizationOf: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.authorizationOf,
|
|
30
|
-
endpoint: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.endpoint
|
|
31
|
+
endpoint: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.endpoint,
|
|
32
|
+
capabilityNeeds: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.capabilityNeeds
|
|
31
33
|
})(PaymentWebhookTranslation);
|
|
32
34
|
|
|
33
35
|
globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
@@ -47,7 +49,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
47
49
|
orderId: "ord-1",
|
|
48
50
|
status: "completed"
|
|
49
51
|
};
|
|
50
|
-
let result = await Callback.receive(mockPublish, inputJson, undefined, undefined);
|
|
52
|
+
let result = await Callback.receive(mockPublish, inputJson, Capabilities$Reventless.none, undefined, undefined);
|
|
51
53
|
if (result.TAG === "Ok") {
|
|
52
54
|
let match = result._0;
|
|
53
55
|
globalThis.expect(match.targetIds).toEqual(["ord-1"]);
|
|
@@ -78,7 +80,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
78
80
|
orderId: "ord-1",
|
|
79
81
|
status: "pending"
|
|
80
82
|
};
|
|
81
|
-
let result = await Callback.receive(mockPublish, inputJson, undefined, undefined);
|
|
83
|
+
let result = await Callback.receive(mockPublish, inputJson, Capabilities$Reventless.none, undefined, undefined);
|
|
82
84
|
if (result.TAG === "Ok") {
|
|
83
85
|
globalThis.expect(true).toBe(false);
|
|
84
86
|
} else {
|
|
@@ -95,7 +97,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
95
97
|
let inputJson = {
|
|
96
98
|
unexpected: "data"
|
|
97
99
|
};
|
|
98
|
-
let result = await Callback.receive(mockPublish, inputJson, undefined, undefined);
|
|
100
|
+
let result = await Callback.receive(mockPublish, inputJson, Capabilities$Reventless.none, undefined, undefined);
|
|
99
101
|
if (result.TAG === "Ok") {
|
|
100
102
|
globalThis.expect(true).toBe(false);
|
|
101
103
|
} else {
|
|
@@ -113,7 +115,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
113
115
|
orderId: "ord-1",
|
|
114
116
|
status: "completed"
|
|
115
117
|
};
|
|
116
|
-
let result = await Callback.receive(failingPublish, inputJson, undefined, undefined);
|
|
118
|
+
let result = await Callback.receive(failingPublish, inputJson, Capabilities$Reventless.none, undefined, undefined);
|
|
117
119
|
if (result.TAG === "Ok") {
|
|
118
120
|
globalThis.expect(true).toBe(false);
|
|
119
121
|
} else {
|
|
@@ -132,7 +134,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
132
134
|
paymentId: "pay-1",
|
|
133
135
|
orderId: "ord-1",
|
|
134
136
|
status: "completed"
|
|
135
|
-
}, undefined, undefined);
|
|
137
|
+
}, Capabilities$Reventless.none, undefined, undefined);
|
|
136
138
|
let firstId = InboundTranslationSlice_Callback$ReventlessCore.requestIdOf(first);
|
|
137
139
|
globalThis.expect(Stdlib_Option.isSome(InboundTranslationSlice_Callback$ReventlessCore.takeAuditRow(Callback.auditLog, firstId))).toBe(true);
|
|
138
140
|
globalThis.expect(Object.entries(Callback.auditLog).length).toBe(0);
|
|
@@ -140,7 +142,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
140
142
|
paymentId: "pay-1",
|
|
141
143
|
orderId: "ord-1",
|
|
142
144
|
status: "completed"
|
|
143
|
-
}, undefined, undefined);
|
|
145
|
+
}, Capabilities$Reventless.none, undefined, undefined);
|
|
144
146
|
let secondId = InboundTranslationSlice_Callback$ReventlessCore.requestIdOf(second);
|
|
145
147
|
globalThis.expect(secondId === firstId).toBe(false);
|
|
146
148
|
globalThis.expect(Object.entries(Callback.auditLog).length).toBe(1);
|
|
@@ -153,7 +155,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
153
155
|
paymentId: "pay-1",
|
|
154
156
|
orderId: "ord-1",
|
|
155
157
|
status: "pending"
|
|
156
|
-
}, undefined, undefined);
|
|
158
|
+
}, Capabilities$Reventless.none, undefined, undefined);
|
|
157
159
|
let id = InboundTranslationSlice_Callback$ReventlessCore.requestIdOf(result);
|
|
158
160
|
let row = InboundTranslationSlice_Callback$ReventlessCore.takeAuditRow(Callback.auditLog, id);
|
|
159
161
|
if (row !== undefined) {
|
|
@@ -176,7 +178,8 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
176
178
|
orderId: s.m(DcbTag$Reventless.string),
|
|
177
179
|
paymentId: s.m(Sury.string)
|
|
178
180
|
}));
|
|
179
|
-
let
|
|
181
|
+
let capabilityNeeds = [];
|
|
182
|
+
let translate = async (input, param, param$1) => ({
|
|
180
183
|
TAG: "Ok",
|
|
181
184
|
_0: input.items.map(item => [
|
|
182
185
|
input.orderId,
|
|
@@ -200,7 +203,8 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
200
203
|
targetName: "ConfirmPayment",
|
|
201
204
|
externalSystem: undefined,
|
|
202
205
|
authorizationOf: authorizationOf,
|
|
203
|
-
endpoint: undefined
|
|
206
|
+
endpoint: undefined,
|
|
207
|
+
capabilityNeeds: capabilityNeeds
|
|
204
208
|
})(MultiTranslation);
|
|
205
209
|
let publishedCommands = {
|
|
206
210
|
contents: []
|
|
@@ -216,7 +220,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
216
220
|
"pay-3"
|
|
217
221
|
]
|
|
218
222
|
};
|
|
219
|
-
let result = await MultiCallback.receive(mockPublish, inputJson, undefined, undefined);
|
|
223
|
+
let result = await MultiCallback.receive(mockPublish, inputJson, Capabilities$Reventless.none, undefined, undefined);
|
|
220
224
|
if (result.TAG === "Ok") {
|
|
221
225
|
let match = result._0;
|
|
222
226
|
globalThis.expect(match.targetIds).toEqual([
|
|
@@ -246,7 +250,8 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
246
250
|
orderId: s.m(DcbTag$Reventless.string),
|
|
247
251
|
paymentId: s.m(Sury.string)
|
|
248
252
|
}));
|
|
249
|
-
let
|
|
253
|
+
let capabilityNeeds = [];
|
|
254
|
+
let translate = async (_input, param, param$1) => ({
|
|
250
255
|
TAG: "Ok",
|
|
251
256
|
_0: []
|
|
252
257
|
});
|
|
@@ -263,7 +268,8 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
263
268
|
targetName: "ConfirmPayment",
|
|
264
269
|
externalSystem: undefined,
|
|
265
270
|
authorizationOf: authorizationOf,
|
|
266
|
-
endpoint: undefined
|
|
271
|
+
endpoint: undefined,
|
|
272
|
+
capabilityNeeds: capabilityNeeds
|
|
267
273
|
})(EmptyTranslation);
|
|
268
274
|
let publishedCommands = {
|
|
269
275
|
contents: []
|
|
@@ -274,7 +280,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
274
280
|
let inputJson = {
|
|
275
281
|
orderId: "ord-1"
|
|
276
282
|
};
|
|
277
|
-
let result = await EmptyCallback.receive(mockPublish, inputJson, undefined, undefined);
|
|
283
|
+
let result = await EmptyCallback.receive(mockPublish, inputJson, Capabilities$Reventless.none, undefined, undefined);
|
|
278
284
|
if (result.TAG === "Ok") {
|
|
279
285
|
let match = result._0;
|
|
280
286
|
globalThis.expect(match.targetIds).toEqual([]);
|
|
@@ -304,7 +310,8 @@ let CatalogFeedCallback = InboundTranslationSlice_Callback$ReventlessCore.Make({
|
|
|
304
310
|
targetName: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.targetName,
|
|
305
311
|
externalSystem: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.externalSystem,
|
|
306
312
|
authorizationOf: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.authorizationOf,
|
|
307
|
-
endpoint: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.endpoint
|
|
313
|
+
endpoint: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.endpoint,
|
|
314
|
+
capabilityNeeds: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.capabilityNeeds
|
|
308
315
|
})(CatalogFeedTranslation);
|
|
309
316
|
|
|
310
317
|
function caller(groups) {
|
|
@@ -340,7 +347,7 @@ globalThis.describe("receive checks each command against its own rule", () => {
|
|
|
340
347
|
published.contents = [];
|
|
341
348
|
});
|
|
342
349
|
globalThis.test("a caller lacking one command's role is refused, and nothing is published", async () => {
|
|
343
|
-
let result = await CatalogFeedCallback.receive(publish, catalogInput("both"), caller(["Merchandiser"]), undefined);
|
|
350
|
+
let result = await CatalogFeedCallback.receive(publish, catalogInput("both"), Capabilities$Reventless.none, caller(["Merchandiser"]), undefined);
|
|
344
351
|
if (result.TAG === "Ok") {
|
|
345
352
|
globalThis.expect(true).toBe(false);
|
|
346
353
|
} else {
|
|
@@ -352,7 +359,7 @@ globalThis.describe("receive checks each command against its own rule", () => {
|
|
|
352
359
|
globalThis.expect(published.contents.length).toBe(0);
|
|
353
360
|
});
|
|
354
361
|
globalThis.test("a caller holding every command's role publishes them all", async () => {
|
|
355
|
-
let result = await CatalogFeedCallback.receive(publish, catalogInput("both"), caller([
|
|
362
|
+
let result = await CatalogFeedCallback.receive(publish, catalogInput("both"), Capabilities$Reventless.none, caller([
|
|
356
363
|
"Merchandiser",
|
|
357
364
|
"Admin"
|
|
358
365
|
]), undefined);
|
|
@@ -360,17 +367,17 @@ globalThis.describe("receive checks each command against its own rule", () => {
|
|
|
360
367
|
globalThis.expect(published.contents.length).toBe(2);
|
|
361
368
|
});
|
|
362
369
|
globalThis.test("the role one command needs is enough when the input yields only it", async () => {
|
|
363
|
-
let result = await CatalogFeedCallback.receive(publish, catalogInput("product"), caller(["Merchandiser"]), undefined);
|
|
370
|
+
let result = await CatalogFeedCallback.receive(publish, catalogInput("product"), Capabilities$Reventless.none, caller(["Merchandiser"]), undefined);
|
|
364
371
|
globalThis.expect(Stdlib_Result.isOk(result)).toBe(true);
|
|
365
372
|
globalThis.expect(published.contents.length).toBe(1);
|
|
366
373
|
});
|
|
367
374
|
globalThis.test("no caller is the platform itself, and publishes", async () => {
|
|
368
|
-
let result = await CatalogFeedCallback.receive(publish, catalogInput("both"), undefined, undefined);
|
|
375
|
+
let result = await CatalogFeedCallback.receive(publish, catalogInput("both"), Capabilities$Reventless.none, undefined, undefined);
|
|
369
376
|
globalThis.expect(Stdlib_Result.isOk(result)).toBe(true);
|
|
370
377
|
globalThis.expect(published.contents.length).toBe(2);
|
|
371
378
|
});
|
|
372
379
|
globalThis.test("a refusal answers CommandRejected with Forbidden", async () => {
|
|
373
|
-
let result = await CatalogFeedCallback.receive(publish, catalogInput("both"), Identity$Reventless.anonymous, undefined);
|
|
380
|
+
let result = await CatalogFeedCallback.receive(publish, catalogInput("both"), Capabilities$Reventless.none, Identity$Reventless.anonymous, undefined);
|
|
374
381
|
let match = InboundTranslationSlice_Callback$ReventlessCore.receiveResultToOutcome(result);
|
|
375
382
|
switch (match.TAG) {
|
|
376
383
|
case "Rejected" :
|
|
@@ -401,7 +408,8 @@ let NoteFeedCallback = InboundTranslationSlice_Callback$ReventlessCore.Make({
|
|
|
401
408
|
targetName: InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.targetName,
|
|
402
409
|
externalSystem: InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.externalSystem,
|
|
403
410
|
authorizationOf: InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.authorizationOf,
|
|
404
|
-
endpoint: InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.endpoint
|
|
411
|
+
endpoint: InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.endpoint,
|
|
412
|
+
capabilityNeeds: InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.capabilityNeeds
|
|
405
413
|
})(NoteFeedTranslation);
|
|
406
414
|
|
|
407
415
|
globalThis.describe("receive encodes commands with their schema", () => {
|
|
@@ -415,7 +423,7 @@ globalThis.describe("receive encodes commands with their schema", () => {
|
|
|
415
423
|
await NoteFeedCallback.receive(publish, Object.fromEntries([[
|
|
416
424
|
"orderId",
|
|
417
425
|
"ord-1"
|
|
418
|
-
]]), undefined, undefined);
|
|
426
|
+
]]), Capabilities$Reventless.none, undefined, undefined);
|
|
419
427
|
let commandJson = published.contents[0].commandJson;
|
|
420
428
|
let decoded = Sury.parseOrThrow(commandJson, InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.commandSchema);
|
|
421
429
|
globalThis.expect(decoded).toEqual({
|
|
@@ -427,6 +435,103 @@ globalThis.describe("receive encodes commands with their schema", () => {
|
|
|
427
435
|
});
|
|
428
436
|
});
|
|
429
437
|
|
|
438
|
+
async function translate$3(input, capabilities, caller) {
|
|
439
|
+
if (caller !== undefined) {
|
|
440
|
+
if (Identity$Reventless.hasGroup(caller, "Admin")) {
|
|
441
|
+
return await InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.translate(input, capabilities, caller);
|
|
442
|
+
} else {
|
|
443
|
+
return {
|
|
444
|
+
TAG: "Error",
|
|
445
|
+
_0: "only an administrator may send this"
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
return {
|
|
450
|
+
TAG: "Error",
|
|
451
|
+
_0: "this door needs a caller"
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
let moduleUrl$3 = InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.moduleUrl;
|
|
457
|
+
|
|
458
|
+
let AdminOnlyTranslation = {
|
|
459
|
+
translate: translate$3,
|
|
460
|
+
moduleUrl: moduleUrl$3
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
let AdminOnlyCallback = InboundTranslationSlice_Callback$ReventlessCore.Make({
|
|
464
|
+
name: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.name,
|
|
465
|
+
moduleUrl: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.moduleUrl,
|
|
466
|
+
externalInputSchema: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.externalInputSchema,
|
|
467
|
+
commandSchema: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.commandSchema,
|
|
468
|
+
targetName: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.targetName,
|
|
469
|
+
externalSystem: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.externalSystem,
|
|
470
|
+
authorizationOf: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.authorizationOf,
|
|
471
|
+
endpoint: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.endpoint,
|
|
472
|
+
capabilityNeeds: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.capabilityNeeds
|
|
473
|
+
})(AdminOnlyTranslation);
|
|
474
|
+
|
|
475
|
+
globalThis.describe("receive hands the door its caller", () => {
|
|
476
|
+
let payment = {
|
|
477
|
+
paymentId: "pay-1",
|
|
478
|
+
orderId: "ord-1",
|
|
479
|
+
status: "completed"
|
|
480
|
+
};
|
|
481
|
+
let identity = (userId, groups) => {
|
|
482
|
+
let newrecord = {...Identity$Reventless.anonymous};
|
|
483
|
+
newrecord.groups = groups;
|
|
484
|
+
newrecord.userId = userId;
|
|
485
|
+
return newrecord;
|
|
486
|
+
};
|
|
487
|
+
let receive = async caller => {
|
|
488
|
+
Object.keys(AdminOnlyCallback.auditLog).forEach(k => Stdlib_Dict.$$delete(AdminOnlyCallback.auditLog, k));
|
|
489
|
+
let published = {
|
|
490
|
+
contents: []
|
|
491
|
+
};
|
|
492
|
+
let publish = async cmds => {
|
|
493
|
+
published.contents = cmds;
|
|
494
|
+
};
|
|
495
|
+
let result = await AdminOnlyCallback.receive(publish, payment, Capabilities$Reventless.none, caller, undefined);
|
|
496
|
+
let row = Object.values(AdminOnlyCallback.auditLog)[0];
|
|
497
|
+
return [
|
|
498
|
+
result,
|
|
499
|
+
published.contents,
|
|
500
|
+
row
|
|
501
|
+
];
|
|
502
|
+
};
|
|
503
|
+
globalThis.test("with no caller, the door refuses and nothing is published", async () => {
|
|
504
|
+
let match = await receive(undefined);
|
|
505
|
+
let row = match[2];
|
|
506
|
+
globalThis.expect(Stdlib_Result.isError(match[0])).toBe(true);
|
|
507
|
+
globalThis.expect(match[1]).toEqual([]);
|
|
508
|
+
globalThis.expect(row.status).toBe("Failure");
|
|
509
|
+
globalThis.expect(row.error).toEqual("this door needs a caller");
|
|
510
|
+
globalThis.expect(row.caller).toEqual(undefined);
|
|
511
|
+
});
|
|
512
|
+
globalThis.test("a caller lacking the role is refused, and the row names who was refused", async () => {
|
|
513
|
+
let match = await receive(identity("u_visitor", ["Shopper"]));
|
|
514
|
+
let result = match[0];
|
|
515
|
+
if (result.TAG === "Ok") {
|
|
516
|
+
globalThis.expect(true).toBe(false);
|
|
517
|
+
} else {
|
|
518
|
+
let match$1 = result._0;
|
|
519
|
+
globalThis.expect(match$1.error).toBe("only an administrator may send this");
|
|
520
|
+
globalThis.expect(match$1.errorCode).toEqual(undefined);
|
|
521
|
+
}
|
|
522
|
+
globalThis.expect(match[1]).toEqual([]);
|
|
523
|
+
globalThis.expect(match[2].caller).toEqual("u_visitor");
|
|
524
|
+
});
|
|
525
|
+
globalThis.test("a caller holding the role is let through, and named", async () => {
|
|
526
|
+
let match = await receive(identity("u_admin", ["Admin"]));
|
|
527
|
+
let row = match[2];
|
|
528
|
+
globalThis.expect(Stdlib_Result.isOk(match[0])).toBe(true);
|
|
529
|
+
globalThis.expect(match[1].length).toBe(1);
|
|
530
|
+
globalThis.expect(row.status).toBe("Success");
|
|
531
|
+
globalThis.expect(row.caller).toEqual("u_admin");
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
|
|
430
535
|
export {
|
|
431
536
|
PaymentWebhookTranslation,
|
|
432
537
|
Callback,
|
|
@@ -436,5 +541,7 @@ export {
|
|
|
436
541
|
catalogInput,
|
|
437
542
|
NoteFeedTranslation,
|
|
438
543
|
NoteFeedCallback,
|
|
544
|
+
AdminOnlyTranslation,
|
|
545
|
+
AdminOnlyCallback,
|
|
439
546
|
}
|
|
440
547
|
/* Callback Not a pure module */
|
|
@@ -28,9 +28,14 @@ module PaymentWebhookSpec = {
|
|
|
28
28
|
|
|
29
29
|
let targetName = "ConfirmPayment"
|
|
30
30
|
let externalSystem = None
|
|
31
|
+
let capabilityNeeds: array<Reventless.CapabilityNeed.t> = []
|
|
31
32
|
let endpoint = None
|
|
32
33
|
|
|
33
|
-
let translate = (
|
|
34
|
+
let translate = async (
|
|
35
|
+
input: externalInput,
|
|
36
|
+
~capabilities as _: Reventless.Capabilities.t,
|
|
37
|
+
~caller as _: option<Reventless.Identity.t>,
|
|
38
|
+
) =>
|
|
34
39
|
switch input.status {
|
|
35
40
|
| "completed" =>
|
|
36
41
|
Ok([(input.orderId, ConfirmPayment({orderId: input.orderId, paymentId: input.paymentId}))])
|
|
@@ -53,6 +58,7 @@ module CatalogFeedSpec = {
|
|
|
53
58
|
|
|
54
59
|
let targetName = "AddProduct"
|
|
55
60
|
let externalSystem = None
|
|
61
|
+
let capabilityNeeds: array<Reventless.CapabilityNeed.t> = []
|
|
56
62
|
let endpoint = None
|
|
57
63
|
|
|
58
64
|
type role = Reventless.Role.name
|
|
@@ -65,7 +71,11 @@ module CatalogFeedSpec = {
|
|
|
65
71
|
| _ => DenyAll
|
|
66
72
|
}
|
|
67
73
|
|
|
68
|
-
let translate = (
|
|
74
|
+
let translate = async (
|
|
75
|
+
input: externalInput,
|
|
76
|
+
~capabilities as _: Reventless.Capabilities.t,
|
|
77
|
+
~caller as _: option<Reventless.Identity.t>,
|
|
78
|
+
) =>
|
|
69
79
|
switch input.kind {
|
|
70
80
|
| "product" => Ok([(input.id, AddProduct({productId: input.id}))])
|
|
71
81
|
| "both" =>
|
|
@@ -91,9 +101,12 @@ module NoteFeedSpec = {
|
|
|
91
101
|
|
|
92
102
|
let targetName = "Annotate"
|
|
93
103
|
let externalSystem = None
|
|
104
|
+
let capabilityNeeds: array<Reventless.CapabilityNeed.t> = []
|
|
94
105
|
let endpoint = None
|
|
95
106
|
|
|
96
|
-
let translate =
|
|
97
|
-
|
|
98
|
-
|
|
107
|
+
let translate = async (
|
|
108
|
+
input: externalInput,
|
|
109
|
+
~capabilities as _: Reventless.Capabilities.t,
|
|
110
|
+
~caller as _: option<Reventless.Identity.t>,
|
|
111
|
+
) => Ok([(input.orderId, Annotate({orderId: input.orderId, note: None}))])
|
|
99
112
|
}
|