@reventlessdev/reventless-core 3.0.0-alpha.284 → 3.0.0-alpha.285
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 +8 -0
- package/package.json +6 -6
- package/src/Message.res.mjs +3 -0
- package/src/components/Aggregate/Aggregate_Callback.res +116 -39
- package/src/components/Aggregate/Aggregate_Callback.res.mjs +96 -37
- package/src/components/CommandGenerator/CommandGenerator_Callback.res +9 -8
- package/src/components/CommandGenerator/CommandGenerator_Callback.res.mjs +10 -8
- package/src/components/CommandTopic/CommandTopic.res.mjs +3 -0
- package/src/components/CommandTopic/CommandTopic_Helpers.res +37 -1
- package/src/components/CommandTopic/CommandTopic_Helpers.res.mjs +29 -0
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +191 -141
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +66 -12
- package/src/plugin/component/Plugin_Helpers.res +33 -5
- package/src/plugin/component/Plugin_Helpers.res.mjs +45 -4
- package/tests/commandgenerator/OwnerActingTest.res +601 -0
- package/tests/commandgenerator/OwnerActingTest.res.mjs +1018 -0
- package/tests/commandgenerator/OwnerStampingTest.res +90 -0
- package/tests/commandgenerator/OwnerStampingTest.res.mjs +87 -0
- package/tests/commandtopic/CommandOutcomeHookTest.res +1 -0
- package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +11 -1
|
@@ -256,3 +256,93 @@ describe("the schema a generator is built with must answer for its commands:", (
|
|
|
256
256
|
},
|
|
257
257
|
)
|
|
258
258
|
})
|
|
259
|
+
|
|
260
|
+
// The claim a handler checks ownership against. It is written from the same
|
|
261
|
+
// classification the stamp uses, on every command, whether or not the command
|
|
262
|
+
// itself records an owner: the command that needs it most (`CancelOrder`) names
|
|
263
|
+
// no owner of its own.
|
|
264
|
+
describe("the caller claim on the envelope:", () => {
|
|
265
|
+
let claimOf = (cmd: Message.commandJson) => cmd.meta.callerClaim
|
|
266
|
+
|
|
267
|
+
let cancel = (~identity) =>
|
|
268
|
+
payload(
|
|
269
|
+
~command="ImportProducts",
|
|
270
|
+
~args=Dict.fromArray([("batchId", str("b-1")), ("source", str("acme"))]),
|
|
271
|
+
~identity,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
testPromise("an owned caller is claimed as that owner", async () => {
|
|
275
|
+
let _ = await generate(
|
|
276
|
+
cancel(~identity=cognito(~userId="cust-A", ~groups=["User"])),
|
|
277
|
+
)->Effect.runPromise
|
|
278
|
+
expect(lastPublished()->claimOf)->toEqual(Some(Message.CallerClaim.Owned({userId: "cust-A"})))
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
testPromise("an elevated caller and a system caller are claimed exempt", async () => {
|
|
282
|
+
let _ = await generate(
|
|
283
|
+
cancel(~identity=cognito(~userId="ops-1", ~groups=["Admin"])),
|
|
284
|
+
)->Effect.runPromise
|
|
285
|
+
let elevated = lastPublished()->claimOf
|
|
286
|
+
let _ = await generate(cancel(~identity=iam))->Effect.runPromise
|
|
287
|
+
expect((elevated, lastPublished()->claimOf))->toEqual((
|
|
288
|
+
Some(Message.CallerClaim.Exempt),
|
|
289
|
+
Some(Message.CallerClaim.Exempt),
|
|
290
|
+
))
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
// Not refused here — the command records no owner, so `AllowAnonymous` still
|
|
294
|
+
// admits it. The claim is what lets the handler refuse it on an owned partition.
|
|
295
|
+
testPromise("an anonymous caller of an unowned command is claimed unidentified", async () => {
|
|
296
|
+
let _ = await generate(cancel(~identity=Reventless.Identity.anonymous))->Effect.runPromise
|
|
297
|
+
expect(lastPublished()->claimOf)->toEqual(Some(Message.CallerClaim.Unidentified))
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
// A client controls the arguments and nothing else. Naming the claim among them
|
|
301
|
+
// must neither reach the envelope nor survive into the command.
|
|
302
|
+
testPromise("a claim sent among the arguments is ignored", async () => {
|
|
303
|
+
let _ = await generate(
|
|
304
|
+
payload(
|
|
305
|
+
~command="ImportProducts",
|
|
306
|
+
~args=Dict.fromArray([
|
|
307
|
+
("batchId", str("b-1")),
|
|
308
|
+
("source", str("acme")),
|
|
309
|
+
("callerClaim", str("Exempt")),
|
|
310
|
+
("meta", JSON.Encode.object(Dict.fromArray([("callerClaim", str("Exempt"))]))),
|
|
311
|
+
]),
|
|
312
|
+
~identity=cognito(~userId="cust-A", ~groups=["User"]),
|
|
313
|
+
),
|
|
314
|
+
)->Effect.runPromise
|
|
315
|
+
expect(lastPublished()->claimOf)->toEqual(Some(Message.CallerClaim.Owned({userId: "cust-A"})))
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
// Unlike the stamp, the claim needs no schema: the permissive generator that
|
|
319
|
+
// stamps nothing still classifies the caller.
|
|
320
|
+
testPromise("a permissive-schema generator still writes the claim", async () => {
|
|
321
|
+
let permissive = CommandGenerator_Callback.makeGenerateCommand(
|
|
322
|
+
~publishJsons=async cmds => published := published.contents->Array.concat(cmds),
|
|
323
|
+
~serviceName="Ordering",
|
|
324
|
+
~commandSchema=S.json->S.castToUnknown,
|
|
325
|
+
~componentKind=CommandGenerator_Callback.StateChangeSlice,
|
|
326
|
+
~stripIdFromParams=false,
|
|
327
|
+
)
|
|
328
|
+
let _ = await permissive(
|
|
329
|
+
cancel(~identity=cognito(~userId="cust-A", ~groups=["User"])),
|
|
330
|
+
)->Effect.runPromise
|
|
331
|
+
expect(lastPublished()->claimOf)->toEqual(Some(Message.CallerClaim.Owned({userId: "cust-A"})))
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
// The claim belongs to the command alone. An event the command produces, and a
|
|
335
|
+
// command an automation issues in reaction to it, derive their envelope from
|
|
336
|
+
// it and must not inherit the caller.
|
|
337
|
+
testSync("a derived envelope does not inherit the claim", () => {
|
|
338
|
+
let parent: Message.meta = {
|
|
339
|
+
service: "Ordering",
|
|
340
|
+
time: "2024-01-01T00:00:00Z",
|
|
341
|
+
msgId: "m-1",
|
|
342
|
+
correlationId: "m-1",
|
|
343
|
+
user: "cust-A",
|
|
344
|
+
callerClaim: Message.CallerClaim.Owned({userId: "cust-A"}),
|
|
345
|
+
}
|
|
346
|
+
expect(Message.deriveMeta(~parent).callerClaim)->toEqual(None)
|
|
347
|
+
})
|
|
348
|
+
})
|
|
@@ -9,6 +9,7 @@ import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/component
|
|
|
9
9
|
import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
|
|
10
10
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
11
11
|
import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types/OwnerScope.res.mjs";
|
|
12
|
+
import * as Message$ReventlessCore from "../../src/Message.res.mjs";
|
|
12
13
|
import * as Plugin_ResolverError$ReventlessCore from "../../src/plugin/component/Plugin_ResolverError.res.mjs";
|
|
13
14
|
import * as CommandGenerator_Callback$ReventlessCore from "../../src/components/CommandGenerator/CommandGenerator_Callback.res.mjs";
|
|
14
15
|
|
|
@@ -209,6 +210,92 @@ globalThis.describe("the schema a generator is built with must answer for its co
|
|
|
209
210
|
});
|
|
210
211
|
});
|
|
211
212
|
|
|
213
|
+
globalThis.describe("the caller claim on the envelope:", () => {
|
|
214
|
+
let cancel = identity => payload("ImportProducts", Object.fromEntries([
|
|
215
|
+
[
|
|
216
|
+
"batchId",
|
|
217
|
+
"b-1"
|
|
218
|
+
],
|
|
219
|
+
[
|
|
220
|
+
"source",
|
|
221
|
+
"acme"
|
|
222
|
+
]
|
|
223
|
+
]), identity);
|
|
224
|
+
globalThis.test("an owned caller is claimed as that owner", async () => {
|
|
225
|
+
await Effect.runPromise(generate(cancel(cognito("cust-A", ["User"]))));
|
|
226
|
+
globalThis.expect(published.contents[published.contents.length - 1 | 0].meta.callerClaim).toEqual({
|
|
227
|
+
TAG: "Owned",
|
|
228
|
+
userId: "cust-A"
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
globalThis.test("an elevated caller and a system caller are claimed exempt", async () => {
|
|
232
|
+
await Effect.runPromise(generate(cancel(cognito("ops-1", ["Admin"]))));
|
|
233
|
+
let elevated = published.contents[published.contents.length - 1 | 0].meta.callerClaim;
|
|
234
|
+
await Effect.runPromise(generate(cancel(iam)));
|
|
235
|
+
globalThis.expect([
|
|
236
|
+
elevated,
|
|
237
|
+
published.contents[published.contents.length - 1 | 0].meta.callerClaim
|
|
238
|
+
]).toEqual([
|
|
239
|
+
"Exempt",
|
|
240
|
+
"Exempt"
|
|
241
|
+
]);
|
|
242
|
+
});
|
|
243
|
+
globalThis.test("an anonymous caller of an unowned command is claimed unidentified", async () => {
|
|
244
|
+
await Effect.runPromise(generate(cancel(Identity$Reventless.anonymous)));
|
|
245
|
+
globalThis.expect(published.contents[published.contents.length - 1 | 0].meta.callerClaim).toEqual("Unidentified");
|
|
246
|
+
});
|
|
247
|
+
globalThis.test("a claim sent among the arguments is ignored", async () => {
|
|
248
|
+
await Effect.runPromise(generate(payload("ImportProducts", Object.fromEntries([
|
|
249
|
+
[
|
|
250
|
+
"batchId",
|
|
251
|
+
"b-1"
|
|
252
|
+
],
|
|
253
|
+
[
|
|
254
|
+
"source",
|
|
255
|
+
"acme"
|
|
256
|
+
],
|
|
257
|
+
[
|
|
258
|
+
"callerClaim",
|
|
259
|
+
"Exempt"
|
|
260
|
+
],
|
|
261
|
+
[
|
|
262
|
+
"meta",
|
|
263
|
+
Object.fromEntries([[
|
|
264
|
+
"callerClaim",
|
|
265
|
+
"Exempt"
|
|
266
|
+
]])
|
|
267
|
+
]
|
|
268
|
+
]), cognito("cust-A", ["User"]))));
|
|
269
|
+
globalThis.expect(published.contents[published.contents.length - 1 | 0].meta.callerClaim).toEqual({
|
|
270
|
+
TAG: "Owned",
|
|
271
|
+
userId: "cust-A"
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
globalThis.test("a permissive-schema generator still writes the claim", async () => {
|
|
275
|
+
let permissive = CommandGenerator_Callback$ReventlessCore.makeGenerateCommand(async cmds => {
|
|
276
|
+
published.contents = published.contents.concat(cmds);
|
|
277
|
+
}, undefined, "Ordering", Sury.json, "StateChangeSlice", false, undefined);
|
|
278
|
+
await Effect.runPromise(permissive(cancel(cognito("cust-A", ["User"]))));
|
|
279
|
+
globalThis.expect(published.contents[published.contents.length - 1 | 0].meta.callerClaim).toEqual({
|
|
280
|
+
TAG: "Owned",
|
|
281
|
+
userId: "cust-A"
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
globalThis.test("a derived envelope does not inherit the claim", () => {
|
|
285
|
+
globalThis.expect(Message$ReventlessCore.deriveMeta({
|
|
286
|
+
service: "Ordering",
|
|
287
|
+
time: "2024-01-01T00:00:00Z",
|
|
288
|
+
user: "cust-A",
|
|
289
|
+
msgId: "m-1",
|
|
290
|
+
correlationId: "m-1",
|
|
291
|
+
callerClaim: {
|
|
292
|
+
TAG: "Owned",
|
|
293
|
+
userId: "cust-A"
|
|
294
|
+
}
|
|
295
|
+
}, undefined).callerClaim).toEqual(undefined);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
212
299
|
export {
|
|
213
300
|
commandSchema,
|
|
214
301
|
published,
|
|
@@ -106,7 +106,17 @@ function seen() {
|
|
|
106
106
|
];
|
|
107
107
|
}
|
|
108
108
|
let tmp;
|
|
109
|
-
|
|
109
|
+
switch (match.cause) {
|
|
110
|
+
case "DomainRejection" :
|
|
111
|
+
tmp = "rejected/domain";
|
|
112
|
+
break;
|
|
113
|
+
case "InfrastructureFailure" :
|
|
114
|
+
tmp = "rejected/infrastructure";
|
|
115
|
+
break;
|
|
116
|
+
case "AccessRefusal" :
|
|
117
|
+
tmp = "rejected/access";
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
110
120
|
return [
|
|
111
121
|
r.component,
|
|
112
122
|
r.reference,
|