@reventlessdev/reventless-local 3.0.0-alpha.311 → 3.0.0-alpha.313
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 +14 -0
- package/package.json +10 -10
- package/src/components/AutomationSlice_Builder.res +1 -0
- package/src/components/AutomationSlice_Builder.res.mjs +3 -0
- package/tests/components/automationslice/AutomationSliceCallbackTest.res +45 -22
- package/tests/components/automationslice/AutomationSliceCallbackTest.res.mjs +34 -33
- package/tests/components/automationslice/AutomationSlicePiiTest.res +178 -0
- package/tests/components/automationslice/AutomationSlicePiiTest.res.mjs +352 -0
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +82 -33
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res.mjs +38 -38
- package/tests/components/outboundtranslationslice/OutboundTranslationSlicePiiTest.res +160 -0
- package/tests/components/outboundtranslationslice/OutboundTranslationSlicePiiTest.res.mjs +300 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// An automation's work item that carries personal data is stored as an event is:
|
|
2
|
+
// its marked fields sealed, its row keyed by a fingerprint of the key the mapping
|
|
3
|
+
// chose, that key sealed inside the item for `process` and `resolve`, and an item
|
|
4
|
+
// whose subject was erased completed without processing.
|
|
5
|
+
|
|
6
|
+
open JestGlobals
|
|
7
|
+
|
|
8
|
+
module Source = {
|
|
9
|
+
module Id = Reventless.Id.String
|
|
10
|
+
let name = "GreetDcbEventLog"
|
|
11
|
+
@schema
|
|
12
|
+
type event =
|
|
13
|
+
| MemberJoined({memberId: @s.matches(Reventless.DcbTag.string) string, email: string})
|
|
14
|
+
| MemberGreeted({memberId: @s.matches(Reventless.DcbTag.string) string, email: string})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module Spec = {
|
|
18
|
+
let name = "GreetMember"
|
|
19
|
+
let moduleUrl: string = %raw(`import.meta.url`)
|
|
20
|
+
|
|
21
|
+
@schema
|
|
22
|
+
type todoItem = {
|
|
23
|
+
memberId: string,
|
|
24
|
+
email: @s.matches(Reventless.Pii.mark(~subject="memberId", S.string)) string,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@schema
|
|
28
|
+
type command = SendGreeting({memberId: @s.matches(Reventless.DcbTag.string) string, key: string})
|
|
29
|
+
|
|
30
|
+
let maxRetries = 3
|
|
31
|
+
let targetName = "SendGreeting"
|
|
32
|
+
let schedule = None
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Keyed by the address, as a translation keyed by contact would be.
|
|
36
|
+
module Mapping = Reventless.AutomationSlice.Mapping.Make(
|
|
37
|
+
Source,
|
|
38
|
+
Spec,
|
|
39
|
+
{
|
|
40
|
+
let collect = (event: Source.event, ~sourceId as _, _ctx) =>
|
|
41
|
+
switch event {
|
|
42
|
+
| MemberJoined({memberId, email}) => [
|
|
43
|
+
(`${memberId}:${email}`, ({memberId, email}: Spec.todoItem)),
|
|
44
|
+
]
|
|
45
|
+
| MemberGreeted(_) => []
|
|
46
|
+
}
|
|
47
|
+
let resolve = (event: Source.event) =>
|
|
48
|
+
switch event {
|
|
49
|
+
| MemberGreeted({memberId, email}) => Some(`${memberId}:${email}`)
|
|
50
|
+
| MemberJoined(_) => None
|
|
51
|
+
}
|
|
52
|
+
let reschedule = _ => []
|
|
53
|
+
},
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
let processed: ref<array<(string, string)>> = ref([])
|
|
57
|
+
|
|
58
|
+
module Automation: Reventless.AutomationSlice.Automation with module Spec := Spec = {
|
|
59
|
+
let process = (key, item: Spec.todoItem, ~now as _) => {
|
|
60
|
+
processed := processed.contents->Array.concat([(key, item.email)])
|
|
61
|
+
Some((item.memberId, Spec.SendGreeting({memberId: item.memberId, key})))
|
|
62
|
+
}
|
|
63
|
+
let onExhausted = (_id, _item: Spec.todoItem) => None
|
|
64
|
+
let moduleUrl: string = %raw(`import.meta.url`)
|
|
65
|
+
module M = Reventless.AutomationSlice.Mappings.Make(Spec)
|
|
66
|
+
module type Mapping = M.Mapping
|
|
67
|
+
let mappings: array<module(Mapping)> = [module(Mapping)]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module Callback = ReventlessCore.AutomationSlice_Callback.Make(Spec, Automation)
|
|
71
|
+
|
|
72
|
+
let context: Reventless.AutomationSlice.context = {
|
|
73
|
+
environment: "test",
|
|
74
|
+
platformName: "local",
|
|
75
|
+
pluginName: "TestPlugin",
|
|
76
|
+
sliceName: "GreetMember",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let envelope = (event: Source.event) =>
|
|
80
|
+
JSON.Encode.object(
|
|
81
|
+
Dict.fromArray([
|
|
82
|
+
("id", JSON.Encode.string("m-1")),
|
|
83
|
+
(
|
|
84
|
+
"meta",
|
|
85
|
+
ReventlessCore.Message.generateMeta(~service=Source.name)->Reventless.Util_Sury.toJson(
|
|
86
|
+
Reventless.Message.metaSchema,
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
("event", event->Reventless.Util_Sury.toJson(Source.eventSchema)),
|
|
90
|
+
]),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
let toBase64 = (s: string) =>
|
|
94
|
+
NodeCrypto.bufferFromString(s, "utf8")->NodeCrypto.bufferToString("base64")
|
|
95
|
+
let fromBase64 = (s: string) =>
|
|
96
|
+
NodeCrypto.bufferFromString(s, "base64")->NodeCrypto.bufferToString("utf8")
|
|
97
|
+
|
|
98
|
+
let erased = ref(false)
|
|
99
|
+
let provider: Reventless.Pii.provider = {
|
|
100
|
+
name: "fake",
|
|
101
|
+
encrypts: true,
|
|
102
|
+
seal: async (~subject, ~field as _, ~forgettable as _, json) => Ok(
|
|
103
|
+
Reventless.Pii.wrap(`enc:v1:${subject}:fake:${json->JSON.stringify->toBase64}`),
|
|
104
|
+
),
|
|
105
|
+
prefetch: async _ => Ok(),
|
|
106
|
+
openNow: (~field, json) =>
|
|
107
|
+
switch json->Reventless.Pii.unwrap->Option.flatMap(Reventless.Pii.parseStored) {
|
|
108
|
+
| Some(Cipher(_)) if erased.contents => Ok(Reventless.Pii.wrap(Reventless.Pii.erasedMarker))
|
|
109
|
+
| Some(Cipher({bytes})) => Ok(bytes->fromBase64->JSON.parseOrThrow)
|
|
110
|
+
| _ => Error(Reventless.Pii.UnknownForm({field: field}))
|
|
111
|
+
},
|
|
112
|
+
release: async (~subject as _, ~ref as _) => Ok(),
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let keyed: Reventless.Capabilities.t = {
|
|
116
|
+
...Reventless.Capabilities.none,
|
|
117
|
+
secrets: Reventless.Secrets.fixed(
|
|
118
|
+
~token="t",
|
|
119
|
+
~hash=s => s,
|
|
120
|
+
~fingerprint=(~purpose, input) =>
|
|
121
|
+
`fp(${purpose})#${input->toBase64->String.length->Int.toString}`,
|
|
122
|
+
),
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let joined = Source.MemberJoined({memberId: "m-1", email: "ada@example.org"})
|
|
126
|
+
let greeted = Source.MemberGreeted({memberId: "m-1", email: "ada@example.org"})
|
|
127
|
+
let rows = () => Callback.todoItems->Dict.toArray
|
|
128
|
+
let published: ref<array<Reventless.Message.commandJson>> = ref([])
|
|
129
|
+
let publish: ReventlessInfra.CommandTopic.publishJsons = async msgs => {
|
|
130
|
+
published := published.contents->Array.concat(msgs)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
describe("an automation's work item with personal data", () => {
|
|
134
|
+
beforeEach(() => {
|
|
135
|
+
Callback.todoItems->Dict.keysToArray->Array.forEach(k => Callback.todoItems->Dict.delete(k))
|
|
136
|
+
processed := []
|
|
137
|
+
published := []
|
|
138
|
+
erased := false
|
|
139
|
+
Reventless.Pii.Provider.install(provider)
|
|
140
|
+
})
|
|
141
|
+
afterEach(() => Reventless.Pii.Provider.install(Reventless.Pii.plain))
|
|
142
|
+
|
|
143
|
+
testPromise("is stored sealed, under a key that holds no address", async () => {
|
|
144
|
+
await Callback.phase1([envelope(joined)], context, ~capabilities=keyed)
|
|
145
|
+
expect(rows()->JSON.stringifyAny->Option.getOr("")->String.includes("ada@example.org"))->toBe(
|
|
146
|
+
false,
|
|
147
|
+
)
|
|
148
|
+
expect(
|
|
149
|
+
rows()->Array.every(((id, _)) => id->String.startsWith("fp(automationWorkItem:GreetMember)")),
|
|
150
|
+
)->toBe(true)
|
|
151
|
+
expect(rows()->Array.length)->toBe(1)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
testPromise("is processed with the key and the value it was collected with", async () => {
|
|
155
|
+
await Callback.phase1([envelope(joined)], context, ~capabilities=keyed)
|
|
156
|
+
await Callback.phase2(publish)
|
|
157
|
+
expect(processed.contents)->toEqual([("m-1:ada@example.org", "ada@example.org")])
|
|
158
|
+
expect(published.contents->Array.length)->toBe(1)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
testPromise("is resolved by the key the mapping chose", async () => {
|
|
162
|
+
await Callback.phase1([envelope(joined)], context, ~capabilities=keyed)
|
|
163
|
+
await Callback.phase1([envelope(greeted)], context, ~capabilities=keyed)
|
|
164
|
+
expect(rows()->Array.map(((_, row)) => row.status))->toEqual([
|
|
165
|
+
ReventlessCore.AutomationSlice_Callback.Completed,
|
|
166
|
+
])
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
testPromise("is completed without processing once its subject is erased", async () => {
|
|
170
|
+
await Callback.phase1([envelope(joined)], context, ~capabilities=keyed)
|
|
171
|
+
erased := true
|
|
172
|
+
await Callback.phase2(publish)
|
|
173
|
+
expect(processed.contents)->toEqual([])
|
|
174
|
+
expect(rows()->Array.map(((_, row)) => row.status))->toEqual([
|
|
175
|
+
ReventlessCore.AutomationSlice_Callback.Completed,
|
|
176
|
+
])
|
|
177
|
+
})
|
|
178
|
+
})
|
|
@@ -0,0 +1,352 @@
|
|
|
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 Nodebuffer from "node:buffer";
|
|
6
|
+
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
7
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
8
|
+
import * as Pii$Reventless from "@reventlessdev/reventless-spec/src/components/Pii.res.mjs";
|
|
9
|
+
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
10
|
+
import * as Message$Reventless from "@reventlessdev/reventless-spec/src/types/Message.res.mjs";
|
|
11
|
+
import * as Secrets$Reventless from "@reventlessdev/reventless-spec/src/semantic/Secrets.res.mjs";
|
|
12
|
+
import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
|
|
13
|
+
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
14
|
+
import * as Capabilities$Reventless from "@reventlessdev/reventless-spec/src/semantic/Capabilities.res.mjs";
|
|
15
|
+
import * as AutomationSlice$Reventless from "@reventlessdev/reventless-spec/src/components/AutomationSlice.res.mjs";
|
|
16
|
+
import * as AutomationSlice_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs";
|
|
17
|
+
|
|
18
|
+
let name = "GreetDcbEventLog";
|
|
19
|
+
|
|
20
|
+
let eventSchema = Sury.union([
|
|
21
|
+
Sury.$schema(s => ({
|
|
22
|
+
TAG: "MemberJoined",
|
|
23
|
+
memberId: s.m(DcbTag$Reventless.string),
|
|
24
|
+
email: s.m(Sury.string)
|
|
25
|
+
})),
|
|
26
|
+
Sury.$schema(s => ({
|
|
27
|
+
TAG: "MemberGreeted",
|
|
28
|
+
memberId: s.m(DcbTag$Reventless.string),
|
|
29
|
+
email: s.m(Sury.string)
|
|
30
|
+
}))
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
let Source = {
|
|
34
|
+
Id: undefined,
|
|
35
|
+
name: name,
|
|
36
|
+
eventSchema: eventSchema
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
let moduleUrl = import.meta.url;
|
|
40
|
+
|
|
41
|
+
let todoItemSchema = Sury.$schema(s => ({
|
|
42
|
+
memberId: s.m(Sury.string),
|
|
43
|
+
email: s.m(Pii$Reventless.mark("memberId", undefined, Sury.string))
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
let commandSchema = Sury.$schema(s => ({
|
|
47
|
+
TAG: "SendGreeting",
|
|
48
|
+
memberId: s.m(DcbTag$Reventless.string),
|
|
49
|
+
key: s.m(Sury.string)
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
function authorizationOf(param) {
|
|
53
|
+
return "AllowAuthenticated";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function commandTransition(param) {
|
|
57
|
+
return "Undeclared";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let traits = [];
|
|
61
|
+
|
|
62
|
+
let Spec = {
|
|
63
|
+
name: "GreetMember",
|
|
64
|
+
moduleUrl: moduleUrl,
|
|
65
|
+
todoItemSchema: todoItemSchema,
|
|
66
|
+
commandSchema: commandSchema,
|
|
67
|
+
maxRetries: 3,
|
|
68
|
+
targetName: "SendGreeting",
|
|
69
|
+
schedule: undefined,
|
|
70
|
+
authorizationOf: authorizationOf,
|
|
71
|
+
commandTransition: commandTransition,
|
|
72
|
+
traits: traits
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function collect(event, param, _ctx) {
|
|
76
|
+
if (event.TAG !== "MemberJoined") {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
let email = event.email;
|
|
80
|
+
let memberId = event.memberId;
|
|
81
|
+
return [[
|
|
82
|
+
memberId + `:` + email,
|
|
83
|
+
{
|
|
84
|
+
memberId: memberId,
|
|
85
|
+
email: email
|
|
86
|
+
}
|
|
87
|
+
]];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function resolve(event) {
|
|
91
|
+
if (event.TAG === "MemberJoined") {
|
|
92
|
+
return;
|
|
93
|
+
} else {
|
|
94
|
+
return event.memberId + `:` + event.email;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function reschedule(param) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let Mapping = AutomationSlice$Reventless.Mapping.Make({
|
|
103
|
+
Id: Id$Reventless.$$String,
|
|
104
|
+
name: name,
|
|
105
|
+
eventSchema: eventSchema
|
|
106
|
+
})(Spec)({
|
|
107
|
+
collect: collect,
|
|
108
|
+
resolve: resolve,
|
|
109
|
+
reschedule: reschedule
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
let processed = {
|
|
113
|
+
contents: []
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
function process(key, item, param) {
|
|
117
|
+
processed.contents = processed.contents.concat([[
|
|
118
|
+
key,
|
|
119
|
+
item.email
|
|
120
|
+
]]);
|
|
121
|
+
return [
|
|
122
|
+
item.memberId,
|
|
123
|
+
{
|
|
124
|
+
TAG: "SendGreeting",
|
|
125
|
+
memberId: item.memberId,
|
|
126
|
+
key: key
|
|
127
|
+
}
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function onExhausted(_id, _item) {
|
|
132
|
+
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let moduleUrl$1 = import.meta.url;
|
|
136
|
+
|
|
137
|
+
AutomationSlice$Reventless.Mappings.Make(Spec);
|
|
138
|
+
|
|
139
|
+
let mappings = [Mapping];
|
|
140
|
+
|
|
141
|
+
let Automation = {
|
|
142
|
+
process: process,
|
|
143
|
+
onExhausted: onExhausted,
|
|
144
|
+
moduleUrl: moduleUrl$1,
|
|
145
|
+
mappings: mappings
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
let Callback = AutomationSlice_Callback$ReventlessCore.Make(Spec)(Automation);
|
|
149
|
+
|
|
150
|
+
let context = {
|
|
151
|
+
environment: "test",
|
|
152
|
+
platformName: "local",
|
|
153
|
+
pluginName: "TestPlugin",
|
|
154
|
+
sliceName: "GreetMember"
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
function envelope(event) {
|
|
158
|
+
return Object.fromEntries([
|
|
159
|
+
[
|
|
160
|
+
"id",
|
|
161
|
+
"m-1"
|
|
162
|
+
],
|
|
163
|
+
[
|
|
164
|
+
"meta",
|
|
165
|
+
Util_Sury$Reventless.toJson(Message$ReventlessCore.generateMeta(name, undefined, undefined, undefined, undefined, undefined, undefined), Message$Reventless.metaSchema)
|
|
166
|
+
],
|
|
167
|
+
[
|
|
168
|
+
"event",
|
|
169
|
+
Util_Sury$Reventless.toJson(event, eventSchema)
|
|
170
|
+
]
|
|
171
|
+
]);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function toBase64(s) {
|
|
175
|
+
return Nodebuffer.Buffer.from(s, "utf8").toString("base64");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function fromBase64(s) {
|
|
179
|
+
return Nodebuffer.Buffer.from(s, "base64").toString("utf8");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let erased = {
|
|
183
|
+
contents: false
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
async function provider_seal(subject, param, param$1, json) {
|
|
187
|
+
return {
|
|
188
|
+
TAG: "Ok",
|
|
189
|
+
_0: Pii$Reventless.wrap(`enc:v1:` + subject + `:fake:` + toBase64(JSON.stringify(json)))
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function provider_prefetch(param) {
|
|
194
|
+
return {
|
|
195
|
+
TAG: "Ok",
|
|
196
|
+
_0: undefined
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function provider_openNow(field, json) {
|
|
201
|
+
let match = Stdlib_Option.flatMap(Pii$Reventless.unwrap(json), Pii$Reventless.parseStored);
|
|
202
|
+
if (match === undefined) {
|
|
203
|
+
return {
|
|
204
|
+
TAG: "Error",
|
|
205
|
+
_0: {
|
|
206
|
+
TAG: "UnknownForm",
|
|
207
|
+
field: field
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
switch (match.TAG) {
|
|
212
|
+
case "Cipher" :
|
|
213
|
+
if (erased.contents) {
|
|
214
|
+
return {
|
|
215
|
+
TAG: "Ok",
|
|
216
|
+
_0: Pii$Reventless.wrap(Pii$Reventless.erasedMarker)
|
|
217
|
+
};
|
|
218
|
+
} else {
|
|
219
|
+
return {
|
|
220
|
+
TAG: "Ok",
|
|
221
|
+
_0: JSON.parse(fromBase64(match.bytes))
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
case "ErasedForm" :
|
|
225
|
+
case "ForgottenForm" :
|
|
226
|
+
return {
|
|
227
|
+
TAG: "Error",
|
|
228
|
+
_0: {
|
|
229
|
+
TAG: "UnknownForm",
|
|
230
|
+
field: field
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function provider_release(param, param$1) {
|
|
237
|
+
return {
|
|
238
|
+
TAG: "Ok",
|
|
239
|
+
_0: undefined
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let provider = {
|
|
244
|
+
name: "fake",
|
|
245
|
+
encrypts: true,
|
|
246
|
+
seal: provider_seal,
|
|
247
|
+
prefetch: provider_prefetch,
|
|
248
|
+
openNow: provider_openNow,
|
|
249
|
+
release: provider_release
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
let keyed_geocode = Capabilities$Reventless.none.geocode;
|
|
253
|
+
|
|
254
|
+
let keyed_messaging = Capabilities$Reventless.none.messaging;
|
|
255
|
+
|
|
256
|
+
let keyed_identityProvider = Capabilities$Reventless.none.identityProvider;
|
|
257
|
+
|
|
258
|
+
let keyed_secrets = Secrets$Reventless.fixed("t", s => s, (purpose, input) => `fp(` + purpose + `)#` + toBase64(input).length.toString());
|
|
259
|
+
|
|
260
|
+
let keyed_stash = Capabilities$Reventless.none.stash;
|
|
261
|
+
|
|
262
|
+
let keyed = {
|
|
263
|
+
geocode: keyed_geocode,
|
|
264
|
+
messaging: keyed_messaging,
|
|
265
|
+
identityProvider: keyed_identityProvider,
|
|
266
|
+
secrets: keyed_secrets,
|
|
267
|
+
stash: keyed_stash
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
let joined = {
|
|
271
|
+
TAG: "MemberJoined",
|
|
272
|
+
memberId: "m-1",
|
|
273
|
+
email: "ada@example.org"
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
let greeted = {
|
|
277
|
+
TAG: "MemberGreeted",
|
|
278
|
+
memberId: "m-1",
|
|
279
|
+
email: "ada@example.org"
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
function rows() {
|
|
283
|
+
return Object.entries(Callback.todoItems);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
let published = {
|
|
287
|
+
contents: []
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
async function publish(msgs) {
|
|
291
|
+
published.contents = published.contents.concat(msgs);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
globalThis.describe("an automation's work item with personal data", () => {
|
|
295
|
+
globalThis.beforeEach(() => {
|
|
296
|
+
Object.keys(Callback.todoItems).forEach(k => Stdlib_Dict.$$delete(Callback.todoItems, k));
|
|
297
|
+
processed.contents = [];
|
|
298
|
+
published.contents = [];
|
|
299
|
+
erased.contents = false;
|
|
300
|
+
Pii$Reventless.Provider.install(provider);
|
|
301
|
+
});
|
|
302
|
+
globalThis.afterEach(() => Pii$Reventless.Provider.install(Pii$Reventless.plain));
|
|
303
|
+
globalThis.test("is stored sealed, under a key that holds no address", async () => {
|
|
304
|
+
await Callback.phase1([envelope(joined)], context, keyed);
|
|
305
|
+
globalThis.expect(Stdlib_Option.getOr(JSON.stringify(Object.entries(Callback.todoItems)), "").includes("ada@example.org")).toBe(false);
|
|
306
|
+
globalThis.expect(Object.entries(Callback.todoItems).every(param => param[0].startsWith("fp(automationWorkItem:GreetMember)"))).toBe(true);
|
|
307
|
+
globalThis.expect(Object.entries(Callback.todoItems).length).toBe(1);
|
|
308
|
+
});
|
|
309
|
+
globalThis.test("is processed with the key and the value it was collected with", async () => {
|
|
310
|
+
await Callback.phase1([envelope(joined)], context, keyed);
|
|
311
|
+
await Callback.phase2(publish);
|
|
312
|
+
globalThis.expect(processed.contents).toEqual([[
|
|
313
|
+
"m-1:ada@example.org",
|
|
314
|
+
"ada@example.org"
|
|
315
|
+
]]);
|
|
316
|
+
globalThis.expect(published.contents.length).toBe(1);
|
|
317
|
+
});
|
|
318
|
+
globalThis.test("is resolved by the key the mapping chose", async () => {
|
|
319
|
+
await Callback.phase1([envelope(joined)], context, keyed);
|
|
320
|
+
await Callback.phase1([envelope(greeted)], context, keyed);
|
|
321
|
+
globalThis.expect(Object.entries(Callback.todoItems).map(param => param[1].status)).toEqual(["Completed"]);
|
|
322
|
+
});
|
|
323
|
+
globalThis.test("is completed without processing once its subject is erased", async () => {
|
|
324
|
+
await Callback.phase1([envelope(joined)], context, keyed);
|
|
325
|
+
erased.contents = true;
|
|
326
|
+
await Callback.phase2(publish);
|
|
327
|
+
globalThis.expect(processed.contents).toEqual([]);
|
|
328
|
+
globalThis.expect(Object.entries(Callback.todoItems).map(param => param[1].status)).toEqual(["Completed"]);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
export {
|
|
333
|
+
Source,
|
|
334
|
+
Spec,
|
|
335
|
+
Mapping,
|
|
336
|
+
processed,
|
|
337
|
+
Automation,
|
|
338
|
+
Callback,
|
|
339
|
+
context,
|
|
340
|
+
envelope,
|
|
341
|
+
toBase64,
|
|
342
|
+
fromBase64,
|
|
343
|
+
erased,
|
|
344
|
+
provider,
|
|
345
|
+
keyed,
|
|
346
|
+
joined,
|
|
347
|
+
greeted,
|
|
348
|
+
rows,
|
|
349
|
+
published,
|
|
350
|
+
publish,
|
|
351
|
+
}
|
|
352
|
+
/* eventSchema Not a pure module */
|