@reventlessdev/reventless-aws 3.0.0-alpha.336 → 3.0.0-alpha.338
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 +52 -0
- package/package.json +9 -9
- package/src/Platform.res +59 -5
- package/src/Platform.res.mjs +27 -4
- package/src/adapter/Monitoring/Monitoring_CloudWatch.res +197 -0
- package/src/adapter/Monitoring/Monitoring_CloudWatch.res.mjs +144 -0
- package/src/adapter/QueryEngine/QueryEngine_DynamoDb.res +10 -2
- package/src/adapter/QueryEngine/QueryEngine_DynamoDb.res.mjs +3 -2
- package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +10 -11
- package/src/util/Util_AlarmSpec.res +155 -0
- package/src/util/Util_AlarmSpec.res.mjs +108 -0
- package/src/util/Util_DeadLetterQueue.res +70 -13
- package/src/util/Util_DeadLetterQueue.res.mjs +27 -6
- package/src/util/Util_ShellConfig.res +14 -0
- package/src/util/Util_ShellConfig.res.mjs +5 -1
- package/src/util/Util_StaticBundle.res +21 -0
- package/src/util/Util_StaticBundle.res.mjs +8 -0
- package/tests/Util_ShellConfigTest.res +42 -0
- package/tests/Util_ShellConfigTest.res.mjs +53 -15
- package/tests/util/Util_AlarmSpecTest.res +109 -0
- package/tests/util/Util_AlarmSpecTest.res.mjs +78 -0
|
@@ -81,6 +81,48 @@ describe("Util_ShellConfig.fields — bakedManifest", () => {
|
|
|
81
81
|
})
|
|
82
82
|
})
|
|
83
83
|
|
|
84
|
+
// The seam's two halves meet here. The deploy writes the module as an object and
|
|
85
|
+
// names it with this key, and the shell imports nothing when the key is absent —
|
|
86
|
+
// so a deploy that wrote the file without naming it would ship a module nothing
|
|
87
|
+
// fetches, and the surface would draw its own regions exactly as it does when no
|
|
88
|
+
// slots were declared. There is no symptom to notice, which is why it is pinned.
|
|
89
|
+
describe("Util_ShellConfig.fields — uiSlotsFile", () => {
|
|
90
|
+
testSync("unset ⇒ no uiSlotsUrl, and the shell imports nothing", () => {
|
|
91
|
+
let out = Util_ShellConfig.fields(~computed)
|
|
92
|
+
expect(out->Dict.get("uiSlotsUrl")->Option.isNone)->toBe(true)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
testSync("declared ⇒ the URL the deploy writes the module to", () => {
|
|
96
|
+
let out = Util_ShellConfig.fields(~computed, ~uiSlotsFile="/src/storefront-slots.js")
|
|
97
|
+
expect(out->get("uiSlotsUrl"))->toEqual(JSON.Encode.string("/ui-slots.js"))
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// The declared path is where the file is *authored*; the URL is where it is
|
|
101
|
+
// *served*. Publishing the author's path would name a file no browser can
|
|
102
|
+
// reach, and on a laptop it would name someone's home directory.
|
|
103
|
+
testSync("names where the file is served, never where it was authored", () => {
|
|
104
|
+
let out = Util_ShellConfig.fields(~computed, ~uiSlotsFile="/home/me/prj/slots.js")
|
|
105
|
+
expect(out->get("uiSlotsUrl"))->toEqual(JSON.Encode.string("/ui-slots.js"))
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
testSync("a shellConfig uiSlotsUrl fails the deploy rather than redirecting it", () => {
|
|
109
|
+
let failure = try {
|
|
110
|
+
let _ = Util_ShellConfig.fields(
|
|
111
|
+
~computed,
|
|
112
|
+
~uiSlotsFile="/src/storefront-slots.js",
|
|
113
|
+
~shellConfig=Dict.fromArray([("uiSlotsUrl", JSON.Encode.string("/elsewhere.js"))]),
|
|
114
|
+
)
|
|
115
|
+
None
|
|
116
|
+
} catch {
|
|
117
|
+
| Failure(message) => Some(message)
|
|
118
|
+
}
|
|
119
|
+
switch failure {
|
|
120
|
+
| Some(message) => expect(message->String.includes("uiSlotsUrl"))->toBe(true)
|
|
121
|
+
| None => fail("a shellConfig key redirecting the computed slots module must fail the deploy")
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
|
|
84
126
|
// ── Journeys ──────────────────────────────────────────────────────────────
|
|
85
127
|
//
|
|
86
128
|
// One curated surface per audience, beside the default one. The property under
|
|
@@ -27,7 +27,7 @@ function get(out, key) {
|
|
|
27
27
|
|
|
28
28
|
globalThis.describe("Util_ShellConfig.fields — viewModes", () => {
|
|
29
29
|
globalThis.test("unset ⇒ no viewModes key and the computed set is untouched", () => {
|
|
30
|
-
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, undefined);
|
|
30
|
+
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, undefined, undefined);
|
|
31
31
|
globalThis.expect(Object.keys(out)).toEqual([
|
|
32
32
|
"apiEndpoint",
|
|
33
33
|
"region"
|
|
@@ -38,7 +38,7 @@ globalThis.describe("Util_ShellConfig.fields — viewModes", () => {
|
|
|
38
38
|
let out = Util_ShellConfig$ReventlessAws.fields(computed, [{
|
|
39
39
|
TAG: "Map",
|
|
40
40
|
_0: {}
|
|
41
|
-
}], undefined, undefined);
|
|
41
|
+
}], undefined, undefined, undefined);
|
|
42
42
|
globalThis.expect(get(out, "viewModes")).toEqual(["map"]);
|
|
43
43
|
globalThis.expect(Stdlib_Option.isNone(out["mapStyle"])).toBe(true);
|
|
44
44
|
});
|
|
@@ -56,7 +56,7 @@ globalThis.describe("Util_ShellConfig.fields — viewModes", () => {
|
|
|
56
56
|
layout: "dagre"
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
], undefined, undefined);
|
|
59
|
+
], undefined, undefined, undefined);
|
|
60
60
|
globalThis.expect(get(out, "viewModes")).toEqual([
|
|
61
61
|
"map",
|
|
62
62
|
"graph"
|
|
@@ -76,21 +76,21 @@ globalThis.describe("Util_ShellConfig.fields — bakedManifest", () => {
|
|
|
76
76
|
key: key
|
|
77
77
|
});
|
|
78
78
|
globalThis.test("unset ⇒ no manifestUrl, so every caller keeps the admin path", () => {
|
|
79
|
-
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, undefined);
|
|
79
|
+
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, undefined, undefined);
|
|
80
80
|
globalThis.expect(Stdlib_Option.isNone(out["manifestUrl"])).toBe(true);
|
|
81
81
|
});
|
|
82
82
|
globalThis.test("declared ⇒ the URL the deploy writes the file to", () => {
|
|
83
|
-
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, bake(undefined), undefined);
|
|
83
|
+
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, bake(undefined), undefined, undefined);
|
|
84
84
|
globalThis.expect(get(out, "manifestUrl")).toEqual("/component-manifest.json");
|
|
85
85
|
});
|
|
86
86
|
globalThis.test("follows a renamed bake", () => {
|
|
87
|
-
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, bake("storefront.json"), undefined);
|
|
87
|
+
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, bake("storefront.json"), undefined, undefined);
|
|
88
88
|
globalThis.expect(get(out, "manifestUrl")).toEqual("/storefront.json");
|
|
89
89
|
});
|
|
90
90
|
globalThis.test("a shellConfig manifestUrl fails the deploy rather than redirecting it", () => {
|
|
91
91
|
let failure;
|
|
92
92
|
try {
|
|
93
|
-
Util_ShellConfig$ReventlessAws.fields(computed, undefined, bake(undefined), Object.fromEntries([[
|
|
93
|
+
Util_ShellConfig$ReventlessAws.fields(computed, undefined, bake(undefined), undefined, Object.fromEntries([[
|
|
94
94
|
"manifestUrl",
|
|
95
95
|
"/elsewhere.json"
|
|
96
96
|
]]));
|
|
@@ -112,6 +112,44 @@ globalThis.describe("Util_ShellConfig.fields — bakedManifest", () => {
|
|
|
112
112
|
});
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
+
globalThis.describe("Util_ShellConfig.fields — uiSlotsFile", () => {
|
|
116
|
+
globalThis.test("unset ⇒ no uiSlotsUrl, and the shell imports nothing", () => {
|
|
117
|
+
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, undefined, undefined);
|
|
118
|
+
globalThis.expect(Stdlib_Option.isNone(out["uiSlotsUrl"])).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
globalThis.test("declared ⇒ the URL the deploy writes the module to", () => {
|
|
121
|
+
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, "/src/storefront-slots.js", undefined);
|
|
122
|
+
globalThis.expect(get(out, "uiSlotsUrl")).toEqual("/ui-slots.js");
|
|
123
|
+
});
|
|
124
|
+
globalThis.test("names where the file is served, never where it was authored", () => {
|
|
125
|
+
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, "/home/me/prj/slots.js", undefined);
|
|
126
|
+
globalThis.expect(get(out, "uiSlotsUrl")).toEqual("/ui-slots.js");
|
|
127
|
+
});
|
|
128
|
+
globalThis.test("a shellConfig uiSlotsUrl fails the deploy rather than redirecting it", () => {
|
|
129
|
+
let failure;
|
|
130
|
+
try {
|
|
131
|
+
Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, "/src/storefront-slots.js", Object.fromEntries([[
|
|
132
|
+
"uiSlotsUrl",
|
|
133
|
+
"/elsewhere.js"
|
|
134
|
+
]]));
|
|
135
|
+
failure = undefined;
|
|
136
|
+
} catch (raw_message) {
|
|
137
|
+
let message = Primitive_exceptions.internalToException(raw_message);
|
|
138
|
+
if (message.RE_EXN_ID === "Failure") {
|
|
139
|
+
failure = message._1;
|
|
140
|
+
} else {
|
|
141
|
+
throw message;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (failure !== undefined) {
|
|
145
|
+
globalThis.expect(failure.includes("uiSlotsUrl")).toBe(true);
|
|
146
|
+
return;
|
|
147
|
+
} else {
|
|
148
|
+
return JestGlobals.fail("a shellConfig key redirecting the computed slots module must fail the deploy");
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
115
153
|
globalThis.describe("Util_ShellConfig.fields — journeys", () => {
|
|
116
154
|
let withJourneys = journeys => ({
|
|
117
155
|
components: [{
|
|
@@ -148,22 +186,22 @@ globalThis.describe("Util_ShellConfig.fields — journeys", () => {
|
|
|
148
186
|
views: ["Products"],
|
|
149
187
|
commands: []
|
|
150
188
|
}]
|
|
151
|
-
}, undefined);
|
|
189
|
+
}, undefined, undefined);
|
|
152
190
|
globalThis.expect(Stdlib_Option.isNone(out["journeyManifestUrls"])).toBe(true);
|
|
153
191
|
});
|
|
154
192
|
globalThis.test("an empty journeys array is the same as none", () => {
|
|
155
|
-
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, withJourneys([]), undefined);
|
|
193
|
+
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, withJourneys([]), undefined, undefined);
|
|
156
194
|
globalThis.expect(Stdlib_Option.isNone(out["journeyManifestUrls"])).toBe(true);
|
|
157
195
|
});
|
|
158
196
|
globalThis.test("keeps manifestUrl as the default journey", () => {
|
|
159
|
-
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, withJourneys([shopper]), undefined);
|
|
197
|
+
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, withJourneys([shopper]), undefined, undefined);
|
|
160
198
|
globalThis.expect(get(out, "manifestUrl")).toEqual("/component-manifest.json");
|
|
161
199
|
});
|
|
162
200
|
globalThis.test("maps each declared group to its own file", () => {
|
|
163
201
|
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, withJourneys([
|
|
164
202
|
shopper,
|
|
165
203
|
fulfilment
|
|
166
|
-
]), undefined);
|
|
204
|
+
]), undefined, undefined);
|
|
167
205
|
globalThis.expect(get(out, "journeyManifestUrls")).toEqual(Object.fromEntries([
|
|
168
206
|
[
|
|
169
207
|
"Shopper",
|
|
@@ -180,14 +218,14 @@ globalThis.describe("Util_ShellConfig.fields — journeys", () => {
|
|
|
180
218
|
shopper,
|
|
181
219
|
fulfilment
|
|
182
220
|
]);
|
|
183
|
-
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, bake, undefined);
|
|
221
|
+
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, bake, undefined, undefined);
|
|
184
222
|
let published = Stdlib_Option.mapOr(Stdlib_JSON.Decode.string(get(out, "manifestUrl")), [], url => [url]).concat(Stdlib_Array.filterMap(Object.values(Stdlib_Option.getOr(Stdlib_JSON.Decode.object(get(out, "journeyManifestUrls")), {})), Stdlib_JSON.Decode.string));
|
|
185
223
|
globalThis.expect(published).toEqual(Platform_BakedManifest$ReventlessCore.files(bake).map(param => "/" + param[0]));
|
|
186
224
|
});
|
|
187
225
|
globalThis.test("a shellConfig journey map fails the deploy rather than redirecting it", () => {
|
|
188
226
|
let failure;
|
|
189
227
|
try {
|
|
190
|
-
Util_ShellConfig$ReventlessAws.fields(computed, undefined, withJourneys([shopper]), Object.fromEntries([[
|
|
228
|
+
Util_ShellConfig$ReventlessAws.fields(computed, undefined, withJourneys([shopper]), undefined, Object.fromEntries([[
|
|
191
229
|
"journeyManifestUrls",
|
|
192
230
|
{}
|
|
193
231
|
]]));
|
|
@@ -211,7 +249,7 @@ globalThis.describe("Util_ShellConfig.fields — journeys", () => {
|
|
|
211
249
|
|
|
212
250
|
globalThis.describe("Util_ShellConfig.fields — shellConfig passthrough", () => {
|
|
213
251
|
globalThis.test("shell-owned keys land verbatim, under the computed ones", () => {
|
|
214
|
-
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, Object.fromEntries([
|
|
252
|
+
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, undefined, Object.fromEntries([
|
|
215
253
|
[
|
|
216
254
|
"platformName",
|
|
217
255
|
"Online Shop"
|
|
@@ -232,7 +270,7 @@ globalThis.describe("Util_ShellConfig.fields — shellConfig passthrough", () =>
|
|
|
232
270
|
globalThis.test("a key the deploy computes fails the deploy, naming it", () => {
|
|
233
271
|
let failure;
|
|
234
272
|
try {
|
|
235
|
-
Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, Object.fromEntries([[
|
|
273
|
+
Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, undefined, Object.fromEntries([[
|
|
236
274
|
"apiEndpoint",
|
|
237
275
|
"https://wrong.example/graphql"
|
|
238
276
|
]]));
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
open JestGlobals
|
|
2
|
+
|
|
3
|
+
module M = ReventlessCore.Monitoring
|
|
4
|
+
module A = Util_AlarmSpec
|
|
5
|
+
|
|
6
|
+
describe("Util_AlarmSpec", () => {
|
|
7
|
+
testSync("every kind alarms on Errors by default", () => {
|
|
8
|
+
[M.CommandHandler, Projection, Reactor, EventCollector, Task, Other("Counter")]->Array.forEach(
|
|
9
|
+
kind => {
|
|
10
|
+
let specs = A.forKind(~kind)
|
|
11
|
+
expect(specs->Array.length)->toBe(1)
|
|
12
|
+
let spec = specs->Array.getUnsafe(0)
|
|
13
|
+
expect(spec.metricName)->toBe("Errors")
|
|
14
|
+
expect(spec.comparisonOperator)->toBe("GreaterThanOrEqualToThreshold")
|
|
15
|
+
// An idle stack publishes no datapoints, and "no traffic" must not read as
|
|
16
|
+
// a failure — an alarm permanently in ALARM teaches everyone to ignore it.
|
|
17
|
+
expect(spec.treatMissingData)->toBe("notBreaching")
|
|
18
|
+
},
|
|
19
|
+
)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// The handler fails on every delivery on purpose, so Errors says only that it is
|
|
23
|
+
// doing its job. That it ran at all is the incident.
|
|
24
|
+
testSync("a dead-letter sink alarms on Invocations, not Errors", () => {
|
|
25
|
+
let specs = A.forKind(~kind=DeadLetterSink)
|
|
26
|
+
expect(specs->Array.length)->toBe(1)
|
|
27
|
+
expect((specs->Array.getUnsafe(0)).metricName)->toBe("Invocations")
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// The case that went unnoticed on a deployed estate: a heartbeat that stops
|
|
31
|
+
// emits no errors AND no invocations, so nothing an Errors alarm can see.
|
|
32
|
+
testSync("a scheduler also alarms on its own silence, and that alarm treats missing data as breaching", () => {
|
|
33
|
+
let specs = A.forKind(~kind=Scheduler, ~silenceWindowSeconds=1800)
|
|
34
|
+
expect(specs->Array.length)->toBe(2)
|
|
35
|
+
|
|
36
|
+
let silence = specs->Array.getUnsafe(1)
|
|
37
|
+
expect(silence.metricName)->toBe("Invocations")
|
|
38
|
+
expect(silence.comparisonOperator)->toBe("LessThanThreshold")
|
|
39
|
+
expect(silence.period)->toBe(1800)
|
|
40
|
+
expect(silence.treatMissingData)->toBe("breaching")
|
|
41
|
+
// Distinct suffix, or the two alarms would collide on one resource name.
|
|
42
|
+
expect(silence.suffix)->toBe("Silent")
|
|
43
|
+
expect(silence.meaning)->toBe("has not run for 30 minutes")
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
testSync("the silence window defaults to an hour — twelve missed heartbeats", () => {
|
|
47
|
+
let silence = A.forKind(~kind=Scheduler)->Array.getUnsafe(1)
|
|
48
|
+
expect(silence.period)->toBe(3600)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
testSync("resource names separate a unit's alarms, and like-named components of different plugins", () => {
|
|
52
|
+
let specs = A.forKind(~kind=Scheduler)
|
|
53
|
+
let nameFor = (spec: A.t, ~plugin) =>
|
|
54
|
+
A.resourceName(~kind=Scheduler, ~name="Heartbeat", ~plugin, ~suffix=spec.suffix)
|
|
55
|
+
|
|
56
|
+
expect(nameFor(specs->Array.getUnsafe(0), ~plugin=Some("Catalog")))->toBe(
|
|
57
|
+
"alarm-scheduler-Catalog-Heartbeat",
|
|
58
|
+
)
|
|
59
|
+
expect(nameFor(specs->Array.getUnsafe(1), ~plugin=Some("Catalog")))->toBe(
|
|
60
|
+
"alarm-scheduler-Catalog-HeartbeatSilent",
|
|
61
|
+
)
|
|
62
|
+
// Two plugins can own like-named components; without the plugin these collide.
|
|
63
|
+
expect(nameFor(specs->Array.getUnsafe(0), ~plugin=Some("Ordering")))->toBe(
|
|
64
|
+
"alarm-scheduler-Ordering-Heartbeat",
|
|
65
|
+
)
|
|
66
|
+
// Platform substrate belongs to no plugin.
|
|
67
|
+
expect(
|
|
68
|
+
A.resourceName(~kind=DeadLetterSink, ~name="DeadLetterQueue", ~plugin=None, ~suffix=""),
|
|
69
|
+
)->toBe("alarm-deadlettersink-DeadLetterQueue")
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// A state-change message carries the description and neither the tags nor the
|
|
73
|
+
// name, so everything a reader or a parser needs has to be in this one string.
|
|
74
|
+
testSync("the description names the unit for a person and carries a machine token", () => {
|
|
75
|
+
let spec = A.forKind(~kind=CommandHandler)->Array.getUnsafe(0)
|
|
76
|
+
let desc = A.description(
|
|
77
|
+
~kind=CommandHandler,
|
|
78
|
+
~name="AllAggregatesCmdHandler",
|
|
79
|
+
~plugin=Some("Ordering"),
|
|
80
|
+
~platform=Some("online-shop"),
|
|
81
|
+
~spec,
|
|
82
|
+
~logs=Some("/aws/lambda/online-shop-AllAggregatesCmdHandler"),
|
|
83
|
+
)
|
|
84
|
+
expect(desc->String.includes("'AllAggregatesCmdHandler' (plugin Ordering, platform online-shop)"))->toBe(true)
|
|
85
|
+
expect(desc->String.includes("Logs: /aws/lambda/online-shop-AllAggregatesCmdHandler."))->toBe(true)
|
|
86
|
+
expect(
|
|
87
|
+
desc->String.includes(
|
|
88
|
+
"[reventless plugin=Ordering platform=online-shop component=AllAggregatesCmdHandler kind=commandhandler]",
|
|
89
|
+
),
|
|
90
|
+
)->toBe(true)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
testSync("a unit owned by no plugin claims none, and one with no logs promises no address", () => {
|
|
94
|
+
let spec = A.forKind(~kind=DeadLetterSink)->Array.getUnsafe(0)
|
|
95
|
+
let desc = A.description(
|
|
96
|
+
~kind=DeadLetterSink,
|
|
97
|
+
~name="DeadLetterQueue",
|
|
98
|
+
~plugin=None,
|
|
99
|
+
~platform=None,
|
|
100
|
+
~spec,
|
|
101
|
+
~logs=None,
|
|
102
|
+
)
|
|
103
|
+
// A dead-letter queue is shared by every plugin in the estate — naming one
|
|
104
|
+
// would be a lie, and an empty parenthesis is noise.
|
|
105
|
+
expect(desc->String.includes("(plugin"))->toBe(false)
|
|
106
|
+
expect(desc->String.includes("Logs:"))->toBe(false)
|
|
107
|
+
expect(desc->String.includes("kind=deadlettersink"))->toBe(true)
|
|
108
|
+
})
|
|
109
|
+
})
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Util_AlarmSpec$ReventlessAws from "../../src/util/Util_AlarmSpec.res.mjs";
|
|
4
|
+
|
|
5
|
+
globalThis.describe("Util_AlarmSpec", () => {
|
|
6
|
+
globalThis.test("every kind alarms on Errors by default", () => {
|
|
7
|
+
[
|
|
8
|
+
"CommandHandler",
|
|
9
|
+
"Projection",
|
|
10
|
+
"Reactor",
|
|
11
|
+
"EventCollector",
|
|
12
|
+
"Task",
|
|
13
|
+
{
|
|
14
|
+
TAG: "Other",
|
|
15
|
+
_0: "Counter"
|
|
16
|
+
}
|
|
17
|
+
].forEach(kind => {
|
|
18
|
+
let specs = Util_AlarmSpec$ReventlessAws.forKind(kind, undefined);
|
|
19
|
+
globalThis.expect(specs.length).toBe(1);
|
|
20
|
+
let spec = specs[0];
|
|
21
|
+
globalThis.expect(spec.metricName).toBe("Errors");
|
|
22
|
+
globalThis.expect(spec.comparisonOperator).toBe("GreaterThanOrEqualToThreshold");
|
|
23
|
+
globalThis.expect(spec.treatMissingData).toBe("notBreaching");
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
globalThis.test("a dead-letter sink alarms on Invocations, not Errors", () => {
|
|
27
|
+
let specs = Util_AlarmSpec$ReventlessAws.forKind("DeadLetterSink", undefined);
|
|
28
|
+
globalThis.expect(specs.length).toBe(1);
|
|
29
|
+
globalThis.expect(specs[0].metricName).toBe("Invocations");
|
|
30
|
+
});
|
|
31
|
+
globalThis.test("a scheduler also alarms on its own silence, and that alarm treats missing data as breaching", () => {
|
|
32
|
+
let specs = Util_AlarmSpec$ReventlessAws.forKind("Scheduler", 1800);
|
|
33
|
+
globalThis.expect(specs.length).toBe(2);
|
|
34
|
+
let silence = specs[1];
|
|
35
|
+
globalThis.expect(silence.metricName).toBe("Invocations");
|
|
36
|
+
globalThis.expect(silence.comparisonOperator).toBe("LessThanThreshold");
|
|
37
|
+
globalThis.expect(silence.period).toBe(1800);
|
|
38
|
+
globalThis.expect(silence.treatMissingData).toBe("breaching");
|
|
39
|
+
globalThis.expect(silence.suffix).toBe("Silent");
|
|
40
|
+
globalThis.expect(silence.meaning).toBe("has not run for 30 minutes");
|
|
41
|
+
});
|
|
42
|
+
globalThis.test("the silence window defaults to an hour — twelve missed heartbeats", () => {
|
|
43
|
+
let silence = Util_AlarmSpec$ReventlessAws.forKind("Scheduler", undefined)[1];
|
|
44
|
+
globalThis.expect(silence.period).toBe(3600);
|
|
45
|
+
});
|
|
46
|
+
globalThis.test("resource names separate a unit's alarms, and like-named components of different plugins", () => {
|
|
47
|
+
let specs = Util_AlarmSpec$ReventlessAws.forKind("Scheduler", undefined);
|
|
48
|
+
let nameFor = (spec, plugin) => Util_AlarmSpec$ReventlessAws.resourceName("Scheduler", "Heartbeat", plugin, spec.suffix);
|
|
49
|
+
globalThis.expect(nameFor(specs[0], "Catalog")).toBe("alarm-scheduler-Catalog-Heartbeat");
|
|
50
|
+
globalThis.expect(nameFor(specs[1], "Catalog")).toBe("alarm-scheduler-Catalog-HeartbeatSilent");
|
|
51
|
+
globalThis.expect(nameFor(specs[0], "Ordering")).toBe("alarm-scheduler-Ordering-Heartbeat");
|
|
52
|
+
globalThis.expect(Util_AlarmSpec$ReventlessAws.resourceName("DeadLetterSink", "DeadLetterQueue", undefined, "")).toBe("alarm-deadlettersink-DeadLetterQueue");
|
|
53
|
+
});
|
|
54
|
+
globalThis.test("the description names the unit for a person and carries a machine token", () => {
|
|
55
|
+
let spec = Util_AlarmSpec$ReventlessAws.forKind("CommandHandler", undefined)[0];
|
|
56
|
+
let desc = Util_AlarmSpec$ReventlessAws.description("CommandHandler", "AllAggregatesCmdHandler", "Ordering", "online-shop", spec, "/aws/lambda/online-shop-AllAggregatesCmdHandler");
|
|
57
|
+
globalThis.expect(desc.includes("'AllAggregatesCmdHandler' (plugin Ordering, platform online-shop)")).toBe(true);
|
|
58
|
+
globalThis.expect(desc.includes("Logs: /aws/lambda/online-shop-AllAggregatesCmdHandler.")).toBe(true);
|
|
59
|
+
globalThis.expect(desc.includes("[reventless plugin=Ordering platform=online-shop component=AllAggregatesCmdHandler kind=commandhandler]")).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
globalThis.test("a unit owned by no plugin claims none, and one with no logs promises no address", () => {
|
|
62
|
+
let spec = Util_AlarmSpec$ReventlessAws.forKind("DeadLetterSink", undefined)[0];
|
|
63
|
+
let desc = Util_AlarmSpec$ReventlessAws.description("DeadLetterSink", "DeadLetterQueue", undefined, undefined, spec, undefined);
|
|
64
|
+
globalThis.expect(desc.includes("(plugin")).toBe(false);
|
|
65
|
+
globalThis.expect(desc.includes("Logs:")).toBe(false);
|
|
66
|
+
globalThis.expect(desc.includes("kind=deadlettersink")).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
let M;
|
|
71
|
+
|
|
72
|
+
let A;
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
M,
|
|
76
|
+
A,
|
|
77
|
+
}
|
|
78
|
+
/* Not a pure module */
|