@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.
- package/CHANGELOG.md +14 -0
- package/lib/bs/.compiler.log +2 -2
- package/lib/bs/compiler-info.json +1 -1
- package/lib/bs/src/ReventlessSeedAws.ast +0 -0
- package/lib/bs/src/ReventlessSeedAws.cmi +0 -0
- package/lib/bs/src/ReventlessSeedAws.cmj +0 -0
- package/lib/bs/src/ReventlessSeedAws.cmt +0 -0
- package/lib/bs/src/ReventlessSeedAws.res +3 -47
- package/lib/bs/src/ReventlessSeedAws.res.mjs +1 -30
- package/lib/bs/src/ReventlessSeedAws_Reset.ast +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reset.cmi +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reset.cmj +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reset.cmt +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reset.res +261 -6
- package/lib/bs/src/ReventlessSeedAws_Reset.res.mjs +211 -25
- package/lib/bs/tests/EndpointResolutionTest.ast +0 -0
- package/lib/bs/tests/EndpointResolutionTest.cmi +0 -0
- package/lib/bs/tests/EndpointResolutionTest.cmj +0 -0
- package/lib/bs/tests/EndpointResolutionTest.cmt +0 -0
- package/lib/bs/tests/EndpointResolutionTest.res +12 -109
- package/lib/bs/tests/EndpointResolutionTest.res.mjs +4 -97
- package/lib/bs/tests/ObjectStoreResetTest.ast +0 -0
- package/lib/bs/tests/ObjectStoreResetTest.cmi +0 -0
- package/lib/bs/tests/ObjectStoreResetTest.cmj +0 -0
- package/lib/bs/tests/ObjectStoreResetTest.cmt +0 -0
- package/lib/bs/tests/ObjectStoreResetTest.res +170 -0
- package/lib/bs/tests/ObjectStoreResetTest.res.mjs +211 -0
- package/lib/ocaml/.compiler.log +2 -2
- package/lib/ocaml/EndpointResolutionTest.ast +0 -0
- package/lib/ocaml/EndpointResolutionTest.cmi +0 -0
- package/lib/ocaml/EndpointResolutionTest.cmj +0 -0
- package/lib/ocaml/EndpointResolutionTest.cmt +0 -0
- package/lib/ocaml/EndpointResolutionTest.res +12 -109
- package/lib/ocaml/ObjectStoreResetTest.ast +0 -0
- package/lib/ocaml/ObjectStoreResetTest.cmi +0 -0
- package/lib/ocaml/ObjectStoreResetTest.cmj +0 -0
- package/lib/ocaml/ObjectStoreResetTest.cmt +0 -0
- package/lib/ocaml/ObjectStoreResetTest.res +170 -0
- package/lib/ocaml/ReventlessSeedAws.ast +0 -0
- package/lib/ocaml/ReventlessSeedAws.cmi +0 -0
- package/lib/ocaml/ReventlessSeedAws.cmj +0 -0
- package/lib/ocaml/ReventlessSeedAws.cmt +0 -0
- package/lib/ocaml/ReventlessSeedAws.res +3 -47
- package/lib/ocaml/ReventlessSeedAws_Reset.ast +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reset.cmi +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reset.cmj +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reset.cmt +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reset.res +261 -6
- package/package.json +2 -2
- package/src/ReventlessSeedAws.res +3 -47
- package/src/ReventlessSeedAws.res.mjs +1 -30
- package/src/ReventlessSeedAws_Reset.res +261 -6
- package/src/ReventlessSeedAws_Reset.res.mjs +211 -25
- package/tests/EndpointResolutionTest.res +12 -109
- package/tests/EndpointResolutionTest.res.mjs +4 -97
- package/tests/ObjectStoreResetTest.res +170 -0
- 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 */
|
package/lib/ocaml/.compiler.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#Start(
|
|
2
|
-
#Done(
|
|
1
|
+
#Start(1785624003215)
|
|
2
|
+
#Done(1785624003246)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,25 +1,22 @@
|
|
|
1
|
-
// Which document `resolveEndpoints` reads, and
|
|
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
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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
|
-
//
|
|
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
|
|
64
|
-
let eps = ReventlessSeedAws.endpointsFrom(
|
|
65
|
-
|
|
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
|
-
})
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|