@reventlessdev/reventless-aws 3.0.0-alpha.337 → 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.
@@ -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 */