@reventlessdev/reventless-seed-aws 1.0.0-alpha.6 → 1.0.0-alpha.8

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/lib/bs/.compiler.log +2 -2
  3. package/lib/bs/compiler-info.json +1 -1
  4. package/lib/bs/src/ReventlessSeedAws.ast +0 -0
  5. package/lib/bs/src/ReventlessSeedAws.cmi +0 -0
  6. package/lib/bs/src/ReventlessSeedAws.cmj +0 -0
  7. package/lib/bs/src/ReventlessSeedAws.cmt +0 -0
  8. package/lib/bs/src/ReventlessSeedAws.res +3 -47
  9. package/lib/bs/src/ReventlessSeedAws.res.mjs +1 -30
  10. package/lib/bs/src/ReventlessSeedAws_Reset.ast +0 -0
  11. package/lib/bs/src/ReventlessSeedAws_Reset.cmi +0 -0
  12. package/lib/bs/src/ReventlessSeedAws_Reset.cmj +0 -0
  13. package/lib/bs/src/ReventlessSeedAws_Reset.cmt +0 -0
  14. package/lib/bs/src/ReventlessSeedAws_Reset.res +261 -6
  15. package/lib/bs/src/ReventlessSeedAws_Reset.res.mjs +211 -25
  16. package/lib/bs/tests/EndpointResolutionTest.ast +0 -0
  17. package/lib/bs/tests/EndpointResolutionTest.cmi +0 -0
  18. package/lib/bs/tests/EndpointResolutionTest.cmj +0 -0
  19. package/lib/bs/tests/EndpointResolutionTest.cmt +0 -0
  20. package/lib/bs/tests/EndpointResolutionTest.res +12 -109
  21. package/lib/bs/tests/EndpointResolutionTest.res.mjs +4 -97
  22. package/lib/bs/tests/ObjectStoreResetTest.ast +0 -0
  23. package/lib/bs/tests/ObjectStoreResetTest.cmi +0 -0
  24. package/lib/bs/tests/ObjectStoreResetTest.cmj +0 -0
  25. package/lib/bs/tests/ObjectStoreResetTest.cmt +0 -0
  26. package/lib/bs/tests/ObjectStoreResetTest.res +170 -0
  27. package/lib/bs/tests/ObjectStoreResetTest.res.mjs +211 -0
  28. package/lib/ocaml/.compiler.log +2 -2
  29. package/lib/ocaml/EndpointResolutionTest.ast +0 -0
  30. package/lib/ocaml/EndpointResolutionTest.cmi +0 -0
  31. package/lib/ocaml/EndpointResolutionTest.cmj +0 -0
  32. package/lib/ocaml/EndpointResolutionTest.cmt +0 -0
  33. package/lib/ocaml/EndpointResolutionTest.res +12 -109
  34. package/lib/ocaml/ObjectStoreResetTest.ast +0 -0
  35. package/lib/ocaml/ObjectStoreResetTest.cmi +0 -0
  36. package/lib/ocaml/ObjectStoreResetTest.cmj +0 -0
  37. package/lib/ocaml/ObjectStoreResetTest.cmt +0 -0
  38. package/lib/ocaml/ObjectStoreResetTest.res +170 -0
  39. package/lib/ocaml/ReventlessSeedAws.ast +0 -0
  40. package/lib/ocaml/ReventlessSeedAws.cmi +0 -0
  41. package/lib/ocaml/ReventlessSeedAws.cmj +0 -0
  42. package/lib/ocaml/ReventlessSeedAws.cmt +0 -0
  43. package/lib/ocaml/ReventlessSeedAws.res +3 -47
  44. package/lib/ocaml/ReventlessSeedAws_Reset.ast +0 -0
  45. package/lib/ocaml/ReventlessSeedAws_Reset.cmi +0 -0
  46. package/lib/ocaml/ReventlessSeedAws_Reset.cmj +0 -0
  47. package/lib/ocaml/ReventlessSeedAws_Reset.cmt +0 -0
  48. package/lib/ocaml/ReventlessSeedAws_Reset.res +261 -6
  49. package/package.json +2 -2
  50. package/src/ReventlessSeedAws.res +3 -47
  51. package/src/ReventlessSeedAws.res.mjs +1 -30
  52. package/src/ReventlessSeedAws_Reset.res +261 -6
  53. package/src/ReventlessSeedAws_Reset.res.mjs +211 -25
  54. package/tests/EndpointResolutionTest.res +12 -109
  55. package/tests/EndpointResolutionTest.res.mjs +4 -97
  56. package/tests/ObjectStoreResetTest.res +170 -0
  57. package/tests/ObjectStoreResetTest.res.mjs +211 -0
@@ -0,0 +1,170 @@
1
+ // How the reset learns which uploaded objects belong to which plugin, and what
2
+ // it refuses.
3
+ //
4
+ // A declared store's bucket is built by the PLATFORM deploy, so its tags say
5
+ // which project built it, not whose data is in it. Ownership therefore comes
6
+ // from the platform's `objectStores` stack output, and these pin the pure half
7
+ // of that: parsing the output, and refusing any store set a prefix-scoped wipe
8
+ // could not separate. The impure half is a `pulumi stack output` subprocess and
9
+ // the S3 calls, and the decisions under test are in neither.
10
+
11
+ open JestGlobals
12
+
13
+ module Reset = ReventlessSeedAws_Reset
14
+
15
+ let obj = entries => JSON.Encode.object(entries->Dict.fromArray)
16
+ let str = JSON.Encode.string
17
+
18
+ let storeEntry = (~bucket, ~prefix) =>
19
+ obj([("bucketName", str(bucket)), ("keyPrefix", str(prefix))])
20
+
21
+ let output = entries => Some(obj(entries))
22
+
23
+ let store = (~qualified, ~bucket, ~prefix): Reset.objectStore => {
24
+ let (plugin, name) = Reset.splitQualified(qualified)->Option.getOr(("", qualified))
25
+ {qualified, plugin, store: name, bucketName: bucket, keyPrefix: prefix}
26
+ }
27
+
28
+ describe("parseObjectStores", () => {
29
+ testSync("splits the qualified key into plugin and store", () =>
30
+ switch Reset.parseObjectStores(
31
+ output([("Catalog.productImages", storeEntry(~bucket="alpha-stores", ~prefix="productImages"))]),
32
+ ) {
33
+ | Ok([s]) =>
34
+ expect((s.plugin, s.store, s.bucketName, s.keyPrefix))->toEqual((
35
+ "Catalog",
36
+ "productImages",
37
+ "alpha-stores",
38
+ "productImages",
39
+ ))
40
+ | other => fail(`expected one parsed store, got ${other->JSON.stringifyAny->Option.getOr("?")}`)
41
+ }
42
+ )
43
+
44
+ // A store name may contain a dot; a registered plugin name may not — so the
45
+ // split is at the first one, matching how the key is composed.
46
+ testSync("splits at the first dot only", () =>
47
+ switch Reset.splitQualified("Catalog.product.images") {
48
+ | Some((plugin, name)) => expect((plugin, name))->toEqual(("Catalog", "product.images"))
49
+ | None => fail("expected a split")
50
+ }
51
+ )
52
+
53
+ // A platform that declares no stores is ordinary, not an error.
54
+ testSync("absent output yields no stores", () =>
55
+ switch Reset.parseObjectStores(None) {
56
+ | Ok(stores) => expect(stores->Array.length)->toBe(0)
57
+ | Error(message) => fail(message)
58
+ }
59
+ )
60
+
61
+ // A store the reset cannot read is a store it would silently leave behind, so
62
+ // a malformed entry fails the run rather than being skipped.
63
+ testSync("a malformed entry is an error, not a skip", () =>
64
+ switch Reset.parseObjectStores(output([("Catalog.productImages", obj([("keyPrefix", str("x"))]))])) {
65
+ | Ok(_) => fail("expected a malformed entry to be refused")
66
+ | Error(message) => expect(message->String.includes("Catalog.productImages"))->toBe(true)
67
+ }
68
+ )
69
+
70
+ testSync("an unqualified key is an error", () =>
71
+ switch Reset.parseObjectStores(
72
+ output([("productImages", storeEntry(~bucket="alpha-stores", ~prefix="productImages"))]),
73
+ ) {
74
+ | Ok(_) => fail("expected an unqualified key to be refused")
75
+ | Error(message) => expect(message->String.includes("productImages"))->toBe(true)
76
+ }
77
+ )
78
+ })
79
+
80
+ describe("validateStores", () => {
81
+ testSync("distinct prefixes in one bucket are fine", () =>
82
+ expect(
83
+ Reset.validateStores([
84
+ store(~qualified="Catalog.productImages", ~bucket="alpha-stores", ~prefix="productImages"),
85
+ store(~qualified="Ordering.labels", ~bucket="alpha-stores", ~prefix="labels"),
86
+ ]),
87
+ )->toEqual(Ok())
88
+ )
89
+
90
+ // The cross-plugin collision: one prefix, two owners, nothing to tell their
91
+ // objects apart.
92
+ testSync("two plugins on one prefix are refused", () =>
93
+ switch Reset.validateStores([
94
+ store(~qualified="Catalog.productImages", ~bucket="alpha-stores", ~prefix="productImages"),
95
+ store(~qualified="Ordering.productImages", ~bucket="alpha-stores", ~prefix="productImages"),
96
+ ]) {
97
+ | Ok() => fail("expected a collision to be refused")
98
+ | Error(message) =>
99
+ expect(
100
+ message->String.includes("Catalog.productImages") &&
101
+ message->String.includes("Ordering.productImages"),
102
+ )->toBe(true)
103
+ }
104
+ )
105
+
106
+ // The same shape one level up: `Catalog` encloses `Catalog/productImages`, so
107
+ // wiping the first would delete the second's objects.
108
+ testSync("an enclosing prefix is refused", () =>
109
+ switch Reset.validateStores([
110
+ store(~qualified="Legacy.catalog", ~bucket="alpha-stores", ~prefix="Catalog"),
111
+ store(~qualified="Catalog.productImages", ~bucket="alpha-stores", ~prefix="Catalog/productImages"),
112
+ ]) {
113
+ | Ok() => fail("expected an enclosing prefix to be refused")
114
+ | Error(message) => expect(message->String.includes("encloses"))->toBe(true)
115
+ }
116
+ )
117
+
118
+ // Different buckets cannot reach each other, so an equal prefix there is not a
119
+ // collision — which is what the per-store layout produces in production.
120
+ testSync("one prefix in two different buckets is fine", () =>
121
+ expect(
122
+ Reset.validateStores([
123
+ store(~qualified="Catalog.productImages", ~bucket="catalog-productImages", ~prefix="productImages"),
124
+ store(~qualified="Ordering.productImages", ~bucket="ordering-productImages", ~prefix="productImages"),
125
+ ]),
126
+ )->toEqual(Ok())
127
+ )
128
+
129
+ // A shared prefix that merely starts with another's characters is not nesting:
130
+ // the delete prefix carries a trailing slash, so `images` cannot reach
131
+ // `images-archive`.
132
+ testSync("a sibling prefix sharing a character run is fine", () =>
133
+ expect(
134
+ Reset.validateStores([
135
+ store(~qualified="Catalog.images", ~bucket="alpha-stores", ~prefix="images"),
136
+ store(~qualified="Catalog.imagesArchive", ~bucket="alpha-stores", ~prefix="images-archive"),
137
+ ]),
138
+ )->toEqual(Ok())
139
+ )
140
+
141
+ testSync("an empty or slash-bearing store name is refused", () => {
142
+ switch Reset.validateStores([
143
+ store(~qualified="Catalog.productImages", ~bucket="alpha-stores", ~prefix=""),
144
+ ]) {
145
+ | Ok() => fail("expected an empty prefix to be refused")
146
+ | Error(_) => ()
147
+ }
148
+ switch Reset.validateStores([
149
+ store(~qualified="Catalog.a/b", ~bucket="alpha-stores", ~prefix="a/b"),
150
+ ]) {
151
+ | Ok() => fail("expected a store name containing a slash to be refused")
152
+ | Error(_) => ()
153
+ }
154
+ })
155
+ })
156
+
157
+ describe("pluginOf", () => {
158
+ // The `objectStores` keys carry the REGISTERED plugin name; the menu carries
159
+ // the operator-facing label. Attribution uses the declared name so the reset
160
+ // never has to guess the relation between the two.
161
+ testSync("prefers the declared plugin name over the label", () =>
162
+ expect(
163
+ Reset.pluginOf({projectDir: "../catalog-aws", label: "catalog", group: Domain, plugin: "Catalog"}),
164
+ )->toBe("Catalog")
165
+ )
166
+
167
+ testSync("falls back to the label when no plugin name is declared", () =>
168
+ expect(Reset.pluginOf({projectDir: ".", label: "platform", group: Platform}))->toBe("platform")
169
+ )
170
+ })
@@ -0,0 +1,211 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as JestGlobals from "@reventlessdev/rescript-jest/src/JestGlobals.res.mjs";
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as ReventlessSeedAws_Reset from "../src/ReventlessSeedAws_Reset.res.mjs";
6
+
7
+ function obj(entries) {
8
+ return Object.fromEntries(entries);
9
+ }
10
+
11
+ function str(prim) {
12
+ return prim;
13
+ }
14
+
15
+ function storeEntry(bucket, prefix) {
16
+ return Object.fromEntries([
17
+ [
18
+ "bucketName",
19
+ bucket
20
+ ],
21
+ [
22
+ "keyPrefix",
23
+ prefix
24
+ ]
25
+ ]);
26
+ }
27
+
28
+ function output(entries) {
29
+ return Object.fromEntries(entries);
30
+ }
31
+
32
+ function store(qualified, bucket, prefix) {
33
+ let match = Stdlib_Option.getOr(ReventlessSeedAws_Reset.splitQualified(qualified), [
34
+ "",
35
+ qualified
36
+ ]);
37
+ return {
38
+ qualified: qualified,
39
+ plugin: match[0],
40
+ store: match[1],
41
+ bucketName: bucket,
42
+ keyPrefix: prefix
43
+ };
44
+ }
45
+
46
+ globalThis.describe("parseObjectStores", () => {
47
+ globalThis.test("splits the qualified key into plugin and store", () => {
48
+ let entries = [[
49
+ "Catalog.productImages",
50
+ storeEntry("alpha-stores", "productImages")
51
+ ]];
52
+ let other = ReventlessSeedAws_Reset.parseObjectStores(Object.fromEntries(entries));
53
+ if (other.TAG === "Ok") {
54
+ let match = other._0;
55
+ if (match.length === 1) {
56
+ let s = match[0];
57
+ globalThis.expect([
58
+ s.plugin,
59
+ s.store,
60
+ s.bucketName,
61
+ s.keyPrefix
62
+ ]).toEqual([
63
+ "Catalog",
64
+ "productImages",
65
+ "alpha-stores",
66
+ "productImages"
67
+ ]);
68
+ return;
69
+ }
70
+ }
71
+ JestGlobals.fail(`expected one parsed store, got ` + Stdlib_Option.getOr(JSON.stringify(other), "?"));
72
+ });
73
+ globalThis.test("splits at the first dot only", () => {
74
+ let match = ReventlessSeedAws_Reset.splitQualified("Catalog.product.images");
75
+ if (match !== undefined) {
76
+ globalThis.expect([
77
+ match[0],
78
+ match[1]
79
+ ]).toEqual([
80
+ "Catalog",
81
+ "product.images"
82
+ ]);
83
+ return;
84
+ } else {
85
+ return JestGlobals.fail("expected a split");
86
+ }
87
+ });
88
+ globalThis.test("absent output yields no stores", () => {
89
+ let stores = ReventlessSeedAws_Reset.parseObjectStores(undefined);
90
+ if (stores.TAG !== "Ok") {
91
+ return JestGlobals.fail(stores._0);
92
+ }
93
+ globalThis.expect(stores._0.length).toBe(0);
94
+ });
95
+ globalThis.test("a malformed entry is an error, not a skip", () => {
96
+ let entries = [[
97
+ "Catalog.productImages",
98
+ Object.fromEntries([[
99
+ "keyPrefix",
100
+ "x"
101
+ ]])
102
+ ]];
103
+ let message = ReventlessSeedAws_Reset.parseObjectStores(Object.fromEntries(entries));
104
+ if (message.TAG === "Ok") {
105
+ return JestGlobals.fail("expected a malformed entry to be refused");
106
+ }
107
+ globalThis.expect(message._0.includes("Catalog.productImages")).toBe(true);
108
+ });
109
+ globalThis.test("an unqualified key is an error", () => {
110
+ let entries = [[
111
+ "productImages",
112
+ storeEntry("alpha-stores", "productImages")
113
+ ]];
114
+ let message = ReventlessSeedAws_Reset.parseObjectStores(Object.fromEntries(entries));
115
+ if (message.TAG === "Ok") {
116
+ return JestGlobals.fail("expected an unqualified key to be refused");
117
+ }
118
+ globalThis.expect(message._0.includes("productImages")).toBe(true);
119
+ });
120
+ });
121
+
122
+ globalThis.describe("validateStores", () => {
123
+ globalThis.test("distinct prefixes in one bucket are fine", () => {
124
+ globalThis.expect(ReventlessSeedAws_Reset.validateStores([
125
+ store("Catalog.productImages", "alpha-stores", "productImages"),
126
+ store("Ordering.labels", "alpha-stores", "labels")
127
+ ])).toEqual({
128
+ TAG: "Ok",
129
+ _0: undefined
130
+ });
131
+ });
132
+ globalThis.test("two plugins on one prefix are refused", () => {
133
+ let message = ReventlessSeedAws_Reset.validateStores([
134
+ store("Catalog.productImages", "alpha-stores", "productImages"),
135
+ store("Ordering.productImages", "alpha-stores", "productImages")
136
+ ]);
137
+ if (message.TAG === "Ok") {
138
+ return JestGlobals.fail("expected a collision to be refused");
139
+ }
140
+ let message$1 = message._0;
141
+ globalThis.expect(message$1.includes("Catalog.productImages") && message$1.includes("Ordering.productImages")).toBe(true);
142
+ });
143
+ globalThis.test("an enclosing prefix is refused", () => {
144
+ let message = ReventlessSeedAws_Reset.validateStores([
145
+ store("Legacy.catalog", "alpha-stores", "Catalog"),
146
+ store("Catalog.productImages", "alpha-stores", "Catalog/productImages")
147
+ ]);
148
+ if (message.TAG === "Ok") {
149
+ return JestGlobals.fail("expected an enclosing prefix to be refused");
150
+ }
151
+ globalThis.expect(message._0.includes("encloses")).toBe(true);
152
+ });
153
+ globalThis.test("one prefix in two different buckets is fine", () => {
154
+ globalThis.expect(ReventlessSeedAws_Reset.validateStores([
155
+ store("Catalog.productImages", "catalog-productImages", "productImages"),
156
+ store("Ordering.productImages", "ordering-productImages", "productImages")
157
+ ])).toEqual({
158
+ TAG: "Ok",
159
+ _0: undefined
160
+ });
161
+ });
162
+ globalThis.test("a sibling prefix sharing a character run is fine", () => {
163
+ globalThis.expect(ReventlessSeedAws_Reset.validateStores([
164
+ store("Catalog.images", "alpha-stores", "images"),
165
+ store("Catalog.imagesArchive", "alpha-stores", "images-archive")
166
+ ])).toEqual({
167
+ TAG: "Ok",
168
+ _0: undefined
169
+ });
170
+ });
171
+ globalThis.test("an empty or slash-bearing store name is refused", () => {
172
+ let match = ReventlessSeedAws_Reset.validateStores([store("Catalog.productImages", "alpha-stores", "")]);
173
+ if (match.TAG === "Ok") {
174
+ JestGlobals.fail("expected an empty prefix to be refused");
175
+ }
176
+ let match$1 = ReventlessSeedAws_Reset.validateStores([store("Catalog.a/b", "alpha-stores", "a/b")]);
177
+ if (match$1.TAG === "Ok") {
178
+ return JestGlobals.fail("expected a store name containing a slash to be refused");
179
+ }
180
+ });
181
+ });
182
+
183
+ globalThis.describe("pluginOf", () => {
184
+ globalThis.test("prefers the declared plugin name over the label", () => {
185
+ globalThis.expect(ReventlessSeedAws_Reset.pluginOf({
186
+ projectDir: "../catalog-aws",
187
+ label: "catalog",
188
+ group: "Domain",
189
+ plugin: "Catalog"
190
+ })).toBe("Catalog");
191
+ });
192
+ globalThis.test("falls back to the label when no plugin name is declared", () => {
193
+ globalThis.expect(ReventlessSeedAws_Reset.pluginOf({
194
+ projectDir: ".",
195
+ label: "platform",
196
+ group: "Platform"
197
+ })).toBe("platform");
198
+ });
199
+ });
200
+
201
+ let Reset;
202
+
203
+ export {
204
+ Reset,
205
+ obj,
206
+ str,
207
+ storeEntry,
208
+ output,
209
+ store,
210
+ }
211
+ /* Not a pure module */
@@ -1,2 +1,2 @@
1
- #Start(1785535392961)
2
- #Done(1785535392990)
1
+ #Start(1785624003215)
2
+ #Done(1785624003246)
@@ -1,25 +1,22 @@
1
- // Which document `resolveEndpoints` reads, and what each arm takes from it.
1
+ // Which document `resolveEndpoints` reads, and which GraphQL endpoint each arm takes
2
+ // from it.
2
3
  //
3
4
  // `endpointsFrom` is the pure half of `resolveEndpoints`; the impure half is a
4
- // `pulumi stack output` subprocess and a `config.json` fetch, and the decision
5
- // these tests pin is in neither. The defect they lock out: **neither arm read
6
- // `uploadEndpoints`**, so a platform that publishes a presign endpoint per
7
- // declared store resolved "" and the seeder reported it as serving no uploads.
8
- // The stack-outputs arm is the one that matters a platform deploying no host
9
- // shell publishes no `hostShellUrl` by construction, so it takes that arm, and
10
- // the per-store endpoint is its only way to reach the store.
5
+ // `pulumi stack output` subprocess and a `config.json` fetch, and the decision these
6
+ // tests pin is in neither. Upload endpoints are no longer resolved here — under route B
7
+ // the seed mints through the domain API's `Upload_Presign` mutation on the GraphQL
8
+ // endpoint below so the only decision left is which GraphQL endpoint each source
9
+ // publishes, and the merged-over-per-plugin precedence within the stack-outputs arm.
11
10
 
12
11
  open JestGlobals
13
12
  open ReventlessSeed
14
13
 
15
-
16
14
  // Every value read here has an env override, and an ambient one in the runner's
17
- // environment (AWS_REGION is routinely set) would silently stand in for the
18
- // document under test. An empty string reads as unset.
15
+ // environment (AWS_REGION is routinely set) would silently stand in for the document
16
+ // under test. An empty string reads as unset.
19
17
  let clearOverrides = () =>
20
18
  [
21
19
  "REVENTLESS_GRAPHQL_ENDPOINT",
22
- "REVENTLESS_UPLOAD_ENDPOINT",
23
20
  "AWS_REGION",
24
21
  "COGNITO_CLIENT_ID",
25
22
  "SEED_SKIP_UPLOADS",
@@ -28,9 +25,6 @@ let clearOverrides = () =>
28
25
  let obj = entries => JSON.Encode.object(entries->Dict.fromArray)
29
26
  let str = JSON.Encode.string
30
27
 
31
- // A host shell's config.json and the stack outputs of a platform that serves no
32
- // shell — the two documents the two arms read, minus the upload keys each test
33
- // supplies.
34
28
  let config = (~extra: array<(string, JSON.t)>) =>
35
29
  obj(
36
30
  Array.concat(
@@ -55,84 +49,18 @@ let outputs = (~extra: array<(string, JSON.t)>) =>
55
49
  ),
56
50
  )
57
51
 
58
- let storeEndpoints = obj([("Catalog.productImages", str("https://presign.example/"))])
59
-
60
52
  describe("endpointsFrom — host shell arm", () => {
61
53
  beforeEach(clearOverrides)
62
54
 
63
- testSync("reads the legacy single service and the per-store map together", () => {
64
- let eps = ReventlessSeedAws.endpointsFrom(
65
- ~stack="alpha",
66
- HostShellConfig(
67
- config(
68
- ~extra=[
69
- ("uploadEndpoint", str("https://legacy.example/")),
70
- ("uploadEndpoints", storeEndpoints),
71
- ],
72
- ),
73
- ),
74
- )
75
- expect(eps.uploadEndpoint)->toBe("https://legacy.example/")
76
- expect(eps.uploadEndpoints)->toEqual(
77
- Dict.fromArray([("Catalog.productImages", "https://presign.example/")]),
78
- )
79
- })
80
-
81
- // A shell fronting only declared stores writes no singular key. That used to
82
- // fail the whole run before a command was sent ("deployment is missing
83
- // uploadEndpoint"), which is a harder failure than the one this plan fixes.
84
- testSync("a shell publishing only per-store endpoints resolves an empty legacy service", () => {
85
- let eps = ReventlessSeedAws.endpointsFrom(
86
- ~stack="alpha",
87
- HostShellConfig(config(~extra=[("uploadEndpoints", storeEndpoints)])),
88
- )
89
- expect(eps.uploadEndpoint)->toBe("")
90
- expect(eps.uploadEndpoints->Dict.keysToArray)->toEqual(["Catalog.productImages"])
91
- })
92
-
93
- testSync("REVENTLESS_UPLOAD_ENDPOINT overrides the legacy service and leaves the map alone", () => {
94
- NodeProcess.env->Dict.set("REVENTLESS_UPLOAD_ENDPOINT", "https://override.example/")
95
- let eps = ReventlessSeedAws.endpointsFrom(
96
- ~stack="alpha",
97
- HostShellConfig(
98
- config(
99
- ~extra=[
100
- ("uploadEndpoint", str("https://legacy.example/")),
101
- ("uploadEndpoints", storeEndpoints),
102
- ],
103
- ),
104
- ),
105
- )
106
- expect(eps.uploadEndpoint)->toBe("https://override.example/")
107
- expect(eps.uploadEndpoints->Dict.get("Catalog.productImages"))->toEqual(
108
- Some("https://presign.example/"),
109
- )
55
+ testSync("reads the GraphQL endpoint from the shell config", () => {
56
+ let eps = ReventlessSeedAws.endpointsFrom(~stack="alpha", HostShellConfig(config(~extra=[])))
57
+ expect(eps.graphql)->toBe("https://api.example/graphql")
110
58
  })
111
59
  })
112
60
 
113
61
  describe("endpointsFrom — stack outputs arm", () => {
114
62
  beforeEach(clearOverrides)
115
63
 
116
- // The lookup that was missing entirely: this arm had no upload source but the
117
- // env var, so a `PlatformOwned` deployment resolved "" whatever it published.
118
- testSync("reads the uploadEndpoints stack output", () => {
119
- let eps = ReventlessSeedAws.endpointsFrom(
120
- ~stack="pr-verify",
121
- StackOutputs(outputs(~extra=[("uploadEndpoints", storeEndpoints)])),
122
- )
123
- expect(eps.uploadEndpoints)->toEqual(
124
- Dict.fromArray([("Catalog.productImages", "https://presign.example/")]),
125
- )
126
- })
127
-
128
- // Declaring no store is a legitimate deployment, not a broken one — it stays a
129
- // no-op at the data set rather than failing the run.
130
- testSync("a deployment declaring no store resolves an empty map without failing", () => {
131
- let eps = ReventlessSeedAws.endpointsFrom(~stack="pr-verify", StackOutputs(outputs(~extra=[])))
132
- expect(eps.uploadEndpoint)->toBe("")
133
- expect(eps.uploadEndpoints->Dict.keysToArray)->toEqual([])
134
- })
135
-
136
64
  testSync("prefers the merged API endpoint over the per-plugin one", () => {
137
65
  let eps = ReventlessSeedAws.endpointsFrom(
138
66
  ~stack="pr-verify",
@@ -153,28 +81,3 @@ describe("endpointsFrom — stack outputs arm", () => {
153
81
  }
154
82
  )
155
83
  })
156
-
157
- describe("endpointsFrom — malformed uploadEndpoints", () => {
158
- beforeEach(clearOverrides)
159
-
160
- // One bad member should not cost the run every endpoint it could have used.
161
- testSync("drops a non-string member rather than the whole map", () => {
162
- let eps = ReventlessSeedAws.endpointsFrom(
163
- ~stack="pr-verify",
164
- StackOutputs(
165
- outputs(
166
- ~extra=[
167
- (
168
- "uploadEndpoints",
169
- obj([
170
- ("Catalog.productImages", str("https://presign.example/")),
171
- ("Catalog.broken", JSON.Encode.int(7)),
172
- ]),
173
- ),
174
- ],
175
- ),
176
- ),
177
- )
178
- expect(eps.uploadEndpoints->Dict.keysToArray)->toEqual(["Catalog.productImages"])
179
- })
180
- })