@reventlessdev/reventless-aws 3.0.0-alpha.239 → 3.0.0-alpha.241
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/package.json +7 -7
- package/src/Platform.res +292 -15
- package/src/Platform.res.mjs +362 -48
- package/src/adapter/Upload/Upload_Presign_S3.res +19 -3
- package/src/adapter/Upload/Upload_Presign_S3.res.mjs +9 -9
- package/src/capability/Capability_ObjectStore_S3.res +69 -7
- package/src/capability/Capability_ObjectStore_S3.res.mjs +32 -6
- package/src/plugin/stack/Plugin_Stack.res +209 -6
- package/src/plugin/stack/Plugin_Stack.res.mjs +122 -6
- package/src/util/Util_HostUiDomain.res +17 -0
- package/src/util/Util_HostUiDomain.res.mjs +8 -1
- package/src/util/Util_StoreLayout.res +136 -0
- package/src/util/Util_StoreLayout.res.mjs +55 -0
- package/tests/Util_StoreLayoutTest.res +155 -0
- package/tests/Util_StoreLayoutTest.res.mjs +87 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// How a stack lays out the object stores its plugins declare, and how hard it
|
|
2
|
+
// protects them. Pure functions so both decisions are decidable and unit-
|
|
3
|
+
// testable without touching Pulumi — the same shape `Util_HostUiDomain` uses
|
|
4
|
+
// for the host-shell FQDN.
|
|
5
|
+
//
|
|
6
|
+
// Two decisions, deliberately independent:
|
|
7
|
+
//
|
|
8
|
+
// layout — one bucket per store, or one bucket per stack with `{store}/…`
|
|
9
|
+
// prefixes inside it
|
|
10
|
+
// protection — whether an accidental removal may destroy the store
|
|
11
|
+
//
|
|
12
|
+
// It is tempting to make one switch decide both, and that is wrong. `alpha`
|
|
13
|
+
// shares a bucket *and* holds hand-entered data worth protecting; a `pr-*`
|
|
14
|
+
// stack shares a bucket and must be destroyable or teardown leaks exactly the
|
|
15
|
+
// buckets sharing exists to save. Layout answers "how many buckets"; protection
|
|
16
|
+
// answers "is this data disposable". Those are different questions about the
|
|
17
|
+
// same stack.
|
|
18
|
+
//
|
|
19
|
+
// Why production alone gets per-store buckets: the growth term is
|
|
20
|
+
// `plugins × stores × stacks`, and stacks is the factor that actually grows —
|
|
21
|
+
// the stack allowlist admits `pr-*`, so per-PR environments multiply every
|
|
22
|
+
// plugin's every store against a 100-bucket account default. Production does
|
|
23
|
+
// not multiply. It is also the only environment where a bucket boundary (rather
|
|
24
|
+
// than a prefix boundary) is worth its cost, since the honest price of sharing
|
|
25
|
+
// is that per-store least-privilege degrades from a bucket ARN to a prefix ARN.
|
|
26
|
+
|
|
27
|
+
/** How many buckets a stack's declared stores get. */
|
|
28
|
+
type t =
|
|
29
|
+
| PerStore
|
|
30
|
+
| SharedBucket
|
|
31
|
+
|
|
32
|
+
/** Whether a store may be destroyed by an accidental removal. */
|
|
33
|
+
type protection =
|
|
34
|
+
| Protected
|
|
35
|
+
| Unprotected
|
|
36
|
+
|
|
37
|
+
/** Which distribution fronts the declared stores. */
|
|
38
|
+
type serving =
|
|
39
|
+
/** No store is declared, so nothing is served. */
|
|
40
|
+
| NoStores
|
|
41
|
+
/** The host shell's own distribution serves them same-origin, so a minted
|
|
42
|
+
`/{prefix}/…` ref resolves relative and there is no base URL. */
|
|
43
|
+
| HostShell
|
|
44
|
+
/** The platform fronts them itself, because no host shell is deployed. */
|
|
45
|
+
| PlatformOwned
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
Stack-name prefixes whose stacks are disposable.
|
|
49
|
+
|
|
50
|
+
`pr-*` matches the stack allowlist's per-PR environments. A prefix rather than
|
|
51
|
+
an exact list because the whole point of these stacks is that nobody enumerates
|
|
52
|
+
them in advance.
|
|
53
|
+
*/
|
|
54
|
+
let defaultEphemeralPrefixes = ["pr-"]
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
Production gets a bucket per store; every other stack shares one.
|
|
58
|
+
|
|
59
|
+
`prodStacks` comes from `Util_HostUiDomain.resolveProdStacks` — the same notion
|
|
60
|
+
of "production" that names the host-shell domain, on purpose.
|
|
61
|
+
|
|
62
|
+
Note this polarity **fails open**: a new production stack whose name is not on
|
|
63
|
+
the list (`production`, `live`, `prod-eu`) silently gets the weaker layout and
|
|
64
|
+
nothing errors. That is the accepted cost of keying off a name allowlist, paid
|
|
65
|
+
down by the list being config-overridable and by the deploy logging the layout
|
|
66
|
+
it chose — a silent fail-open is only dangerous while it is silent.
|
|
67
|
+
*/
|
|
68
|
+
let layoutFor = (~stack: string, ~prodStacks: array<string>): t =>
|
|
69
|
+
prodStacks->Array.includes(stack) ? PerStore : SharedBucket
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
Destroy semantics follow disposability, **not** layout.
|
|
73
|
+
|
|
74
|
+
`alpha` shares a bucket and is still protected: it declares
|
|
75
|
+
`reventless:wipeable`, but that authorises the reset tool to empty stores
|
|
76
|
+
*deliberately*, after a scope prompt and a typed confirmation. It does not say a
|
|
77
|
+
field rename may destroy a bucket by accident. Only stacks that are routinely
|
|
78
|
+
torn down are unprotected.
|
|
79
|
+
*/
|
|
80
|
+
let protectionFor = (~stack: string, ~ephemeralPrefixes: array<string>=defaultEphemeralPrefixes): protection =>
|
|
81
|
+
ephemeralPrefixes->Array.some(p => stack->String.startsWith(p)) ? Unprotected : Protected
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
The bucket a store's objects live in.
|
|
85
|
+
|
|
86
|
+
Per-store: `{plugin}-{store}`, so the physical name traces back to the
|
|
87
|
+
declaration that required it — a store rename becomes a visible replace in
|
|
88
|
+
review rather than a silent one. Shared: one `{stack}-stores` bucket for every
|
|
89
|
+
declared store on the stack.
|
|
90
|
+
|
|
91
|
+
Pulumi appends its own suffix for global uniqueness, so neither form has to
|
|
92
|
+
carry an account or region discriminator.
|
|
93
|
+
*/
|
|
94
|
+
let bucketNameFor = (~layout: t, ~stack: string, ~plugin: string, ~store: string): string =>
|
|
95
|
+
switch layout {
|
|
96
|
+
| PerStore => `${plugin}-${store}`
|
|
97
|
+
| SharedBucket => `${stack}-stores`
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
The prefix a store's object keys are rooted at — the store name, in **both**
|
|
102
|
+
layouts.
|
|
103
|
+
|
|
104
|
+
This is the property that keeps the two layouts one model rather than a fork.
|
|
105
|
+
The presign service mints `{store}/{identity}{uuid}/{file}` either way, so the
|
|
106
|
+
stored ref is `/{store}/…` regardless of which bucket sits behind it, and only
|
|
107
|
+
the CDN origin differs. Refs live in an append-only event log: one that encoded
|
|
108
|
+
its bucket layout would be environment-specific and unrewritable, so a prod dump
|
|
109
|
+
restored into a PR stack would carry refs that cannot resolve.
|
|
110
|
+
|
|
111
|
+
The cost is a slightly redundant prefix inside a dedicated bucket
|
|
112
|
+
(`catalog-productImages/productImages/…`). Take the redundancy.
|
|
113
|
+
*/
|
|
114
|
+
let keyPrefixFor = (~store: string): string => store
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
Who serves the declared stores — and the answer is never "both".
|
|
118
|
+
|
|
119
|
+
A store's bucket blocks public policy and takes its read grant solely from a
|
|
120
|
+
distribution's `BucketPolicy`. **S3 permits exactly one bucket policy per
|
|
121
|
+
bucket**, so two distributions fronting one store would each write that single
|
|
122
|
+
policy and silently unpick the other's grant: green deploy, 404s afterwards.
|
|
123
|
+
Making this one function's return a three-way choice is what keeps "both" from
|
|
124
|
+
being expressible.
|
|
125
|
+
|
|
126
|
+
The polarity is the useful part. Provisioning a store and serving it are
|
|
127
|
+
separate; before this, serving happened only as a side car to a host-UI bundle,
|
|
128
|
+
so a platform whose UI shipped from its own stack provisioned stores that
|
|
129
|
+
nothing could read.
|
|
130
|
+
*/
|
|
131
|
+
let servingFor = (~hasHostUiBundle: bool, ~declaredBucketCount: int): serving =>
|
|
132
|
+
switch (hasHostUiBundle, declaredBucketCount) {
|
|
133
|
+
| (_, 0) => NoStores
|
|
134
|
+
| (true, _) => HostShell
|
|
135
|
+
| (false, _) => PlatformOwned
|
|
136
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
let defaultEphemeralPrefixes = ["pr-"];
|
|
5
|
+
|
|
6
|
+
function layoutFor(stack, prodStacks) {
|
|
7
|
+
if (prodStacks.includes(stack)) {
|
|
8
|
+
return "PerStore";
|
|
9
|
+
} else {
|
|
10
|
+
return "SharedBucket";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function protectionFor(stack, ephemeralPrefixesOpt) {
|
|
15
|
+
let ephemeralPrefixes = ephemeralPrefixesOpt !== undefined ? ephemeralPrefixesOpt : defaultEphemeralPrefixes;
|
|
16
|
+
if (ephemeralPrefixes.some(p => stack.startsWith(p))) {
|
|
17
|
+
return "Unprotected";
|
|
18
|
+
} else {
|
|
19
|
+
return "Protected";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function bucketNameFor(layout, stack, plugin, store) {
|
|
24
|
+
if (layout === "PerStore") {
|
|
25
|
+
return plugin + `-` + store;
|
|
26
|
+
} else {
|
|
27
|
+
return stack + `-stores`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function keyPrefixFor(store) {
|
|
32
|
+
return store;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function servingFor(hasHostUiBundle, declaredBucketCount) {
|
|
36
|
+
if (declaredBucketCount !== 0) {
|
|
37
|
+
if (hasHostUiBundle) {
|
|
38
|
+
return "HostShell";
|
|
39
|
+
} else {
|
|
40
|
+
return "PlatformOwned";
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
return "NoStores";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
defaultEphemeralPrefixes,
|
|
49
|
+
layoutFor,
|
|
50
|
+
protectionFor,
|
|
51
|
+
bucketNameFor,
|
|
52
|
+
keyPrefixFor,
|
|
53
|
+
servingFor,
|
|
54
|
+
}
|
|
55
|
+
/* No side effect */
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
open JestGlobals
|
|
2
|
+
|
|
3
|
+
let prodStacks = Util_HostUiDomain.defaultProdStacks
|
|
4
|
+
|
|
5
|
+
describe("Util_StoreLayout.layoutFor", () => {
|
|
6
|
+
testSync("a prod-named stack gets a bucket per store", () =>
|
|
7
|
+
expect(Util_StoreLayout.layoutFor(~stack="prod", ~prodStacks))->toEqual(Util_StoreLayout.PerStore)
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
testSync("'main' is production too, by the shared prod list", () =>
|
|
11
|
+
expect(Util_StoreLayout.layoutFor(~stack="main", ~prodStacks))->toEqual(Util_StoreLayout.PerStore)
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
testSync("alpha shares one bucket", () =>
|
|
15
|
+
expect(Util_StoreLayout.layoutFor(~stack="alpha", ~prodStacks))->toEqual(
|
|
16
|
+
Util_StoreLayout.SharedBucket,
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
testSync("a PR stack shares one bucket — the factor that multiplies", () =>
|
|
21
|
+
expect(Util_StoreLayout.layoutFor(~stack="pr-1234", ~prodStacks))->toEqual(
|
|
22
|
+
Util_StoreLayout.SharedBucket,
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
// The accepted fail-open: a production stack whose name is not on the list
|
|
27
|
+
// gets the weaker layout silently. Asserted so the behaviour is a decision on
|
|
28
|
+
// record rather than a surprise, and so the config override is the documented
|
|
29
|
+
// fix rather than a code change.
|
|
30
|
+
testSync("an unlisted production name falls open to the shared layout", () =>
|
|
31
|
+
expect(Util_StoreLayout.layoutFor(~stack="production", ~prodStacks))->toEqual(
|
|
32
|
+
Util_StoreLayout.SharedBucket,
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
testSync("adding it to the prod list is the fix", () =>
|
|
37
|
+
expect(Util_StoreLayout.layoutFor(~stack="production", ~prodStacks=["prod", "production"]))->toEqual(
|
|
38
|
+
Util_StoreLayout.PerStore,
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe("Util_StoreLayout.servingFor", () => {
|
|
44
|
+
testSync("no declared store means nothing to serve", () =>
|
|
45
|
+
expect(Util_StoreLayout.servingFor(~hasHostUiBundle=false, ~declaredBucketCount=0))->toEqual(
|
|
46
|
+
Util_StoreLayout.NoStores,
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
testSync("a host shell serves the stores from its own origin", () =>
|
|
51
|
+
expect(Util_StoreLayout.servingFor(~hasHostUiBundle=true, ~declaredBucketCount=1))->toEqual(
|
|
52
|
+
Util_StoreLayout.HostShell,
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
// The case that was previously unrepresentable and produced a write-only
|
|
57
|
+
// store: stores are provisioned unconditionally, but serving used to happen
|
|
58
|
+
// only as a side car to a host-UI bundle.
|
|
59
|
+
testSync("no host shell means the platform serves them itself", () =>
|
|
60
|
+
expect(Util_StoreLayout.servingFor(~hasHostUiBundle=false, ~declaredBucketCount=1))->toEqual(
|
|
61
|
+
Util_StoreLayout.PlatformOwned,
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
// A bucket carries exactly one policy, so two distributions fronting one
|
|
66
|
+
// store would unpick each other's read grant. The three-way return is what
|
|
67
|
+
// makes "both" unrepresentable — asserted so it stays that way.
|
|
68
|
+
testSync("a host shell wins even with several buckets — never both", () =>
|
|
69
|
+
expect(Util_StoreLayout.servingFor(~hasHostUiBundle=true, ~declaredBucketCount=3))->toEqual(
|
|
70
|
+
Util_StoreLayout.HostShell,
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
// Declaring nothing outranks having a shell: with no store there is no
|
|
75
|
+
// bucket, no policy and no origin, whoever is deployed.
|
|
76
|
+
testSync("no store outranks a host shell", () =>
|
|
77
|
+
expect(Util_StoreLayout.servingFor(~hasHostUiBundle=true, ~declaredBucketCount=0))->toEqual(
|
|
78
|
+
Util_StoreLayout.NoStores,
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
describe("Util_StoreLayout.protectionFor", () => {
|
|
84
|
+
// The pairing a single layout-driven switch would have got wrong: alpha
|
|
85
|
+
// shares a bucket and is still protected.
|
|
86
|
+
testSync("alpha shares a bucket and is still protected", () => {
|
|
87
|
+
expect(Util_StoreLayout.layoutFor(~stack="alpha", ~prodStacks))->toEqual(
|
|
88
|
+
Util_StoreLayout.SharedBucket,
|
|
89
|
+
)
|
|
90
|
+
expect(Util_StoreLayout.protectionFor(~stack="alpha"))->toEqual(Util_StoreLayout.Protected)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
testSync("prod is protected", () =>
|
|
94
|
+
expect(Util_StoreLayout.protectionFor(~stack="prod"))->toEqual(Util_StoreLayout.Protected)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
testSync("a PR stack is unprotected so teardown does not leak buckets", () =>
|
|
98
|
+
expect(Util_StoreLayout.protectionFor(~stack="pr-42"))->toEqual(Util_StoreLayout.Unprotected)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
testSync("'prepare' is not a PR stack — the prefix is 'pr-', not 'pr'", () =>
|
|
102
|
+
expect(Util_StoreLayout.protectionFor(~stack="prepare"))->toEqual(Util_StoreLayout.Protected)
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe("Util_StoreLayout.bucketNameFor", () => {
|
|
107
|
+
testSync("per-store names the bucket after the declaration", () =>
|
|
108
|
+
expect(
|
|
109
|
+
Util_StoreLayout.bucketNameFor(
|
|
110
|
+
~layout=PerStore,
|
|
111
|
+
~stack="prod",
|
|
112
|
+
~plugin="catalog",
|
|
113
|
+
~store="productImages",
|
|
114
|
+
),
|
|
115
|
+
)->toBe("catalog-productImages")
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
testSync("shared names one bucket per stack", () =>
|
|
119
|
+
expect(
|
|
120
|
+
Util_StoreLayout.bucketNameFor(
|
|
121
|
+
~layout=SharedBucket,
|
|
122
|
+
~stack="alpha",
|
|
123
|
+
~plugin="catalog",
|
|
124
|
+
~store="productImages",
|
|
125
|
+
),
|
|
126
|
+
)->toBe("alpha-stores")
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
testSync("every store on a shared stack lands in the same bucket", () =>
|
|
130
|
+
expect(
|
|
131
|
+
Util_StoreLayout.bucketNameFor(
|
|
132
|
+
~layout=SharedBucket,
|
|
133
|
+
~stack="alpha",
|
|
134
|
+
~plugin="ordering",
|
|
135
|
+
~store="invoices",
|
|
136
|
+
),
|
|
137
|
+
)->toBe(
|
|
138
|
+
Util_StoreLayout.bucketNameFor(
|
|
139
|
+
~layout=SharedBucket,
|
|
140
|
+
~stack="alpha",
|
|
141
|
+
~plugin="catalog",
|
|
142
|
+
~store="productImages",
|
|
143
|
+
),
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
describe("Util_StoreLayout.keyPrefixFor", () => {
|
|
149
|
+
// The assertion the whole dual-layout scheme rests on: a ref is
|
|
150
|
+
// layout-independent, so the same declaration produces the same stored string
|
|
151
|
+
// on a per-store stack and a shared one.
|
|
152
|
+
testSync("the key prefix is the store name in both layouts", () =>
|
|
153
|
+
expect(Util_StoreLayout.keyPrefixFor(~store="productImages"))->toBe("productImages")
|
|
154
|
+
)
|
|
155
|
+
})
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Util_StoreLayout$ReventlessAws from "../src/util/Util_StoreLayout.res.mjs";
|
|
4
|
+
import * as Util_HostUiDomain$ReventlessAws from "../src/util/Util_HostUiDomain.res.mjs";
|
|
5
|
+
|
|
6
|
+
globalThis.describe("Util_StoreLayout.layoutFor", () => {
|
|
7
|
+
globalThis.test("a prod-named stack gets a bucket per store", () => {
|
|
8
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.layoutFor("prod", Util_HostUiDomain$ReventlessAws.defaultProdStacks)).toEqual("PerStore");
|
|
9
|
+
});
|
|
10
|
+
globalThis.test("'main' is production too, by the shared prod list", () => {
|
|
11
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.layoutFor("main", Util_HostUiDomain$ReventlessAws.defaultProdStacks)).toEqual("PerStore");
|
|
12
|
+
});
|
|
13
|
+
globalThis.test("alpha shares one bucket", () => {
|
|
14
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.layoutFor("alpha", Util_HostUiDomain$ReventlessAws.defaultProdStacks)).toEqual("SharedBucket");
|
|
15
|
+
});
|
|
16
|
+
globalThis.test("a PR stack shares one bucket — the factor that multiplies", () => {
|
|
17
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.layoutFor("pr-1234", Util_HostUiDomain$ReventlessAws.defaultProdStacks)).toEqual("SharedBucket");
|
|
18
|
+
});
|
|
19
|
+
globalThis.test("an unlisted production name falls open to the shared layout", () => {
|
|
20
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.layoutFor("production", Util_HostUiDomain$ReventlessAws.defaultProdStacks)).toEqual("SharedBucket");
|
|
21
|
+
});
|
|
22
|
+
globalThis.test("adding it to the prod list is the fix", () => {
|
|
23
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.layoutFor("production", [
|
|
24
|
+
"prod",
|
|
25
|
+
"production"
|
|
26
|
+
])).toEqual("PerStore");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
globalThis.describe("Util_StoreLayout.servingFor", () => {
|
|
31
|
+
globalThis.test("no declared store means nothing to serve", () => {
|
|
32
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.servingFor(false, 0)).toEqual("NoStores");
|
|
33
|
+
});
|
|
34
|
+
globalThis.test("a host shell serves the stores from its own origin", () => {
|
|
35
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.servingFor(true, 1)).toEqual("HostShell");
|
|
36
|
+
});
|
|
37
|
+
globalThis.test("no host shell means the platform serves them itself", () => {
|
|
38
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.servingFor(false, 1)).toEqual("PlatformOwned");
|
|
39
|
+
});
|
|
40
|
+
globalThis.test("a host shell wins even with several buckets — never both", () => {
|
|
41
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.servingFor(true, 3)).toEqual("HostShell");
|
|
42
|
+
});
|
|
43
|
+
globalThis.test("no store outranks a host shell", () => {
|
|
44
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.servingFor(true, 0)).toEqual("NoStores");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
globalThis.describe("Util_StoreLayout.protectionFor", () => {
|
|
49
|
+
globalThis.test("alpha shares a bucket and is still protected", () => {
|
|
50
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.layoutFor("alpha", Util_HostUiDomain$ReventlessAws.defaultProdStacks)).toEqual("SharedBucket");
|
|
51
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.protectionFor("alpha", undefined)).toEqual("Protected");
|
|
52
|
+
});
|
|
53
|
+
globalThis.test("prod is protected", () => {
|
|
54
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.protectionFor("prod", undefined)).toEqual("Protected");
|
|
55
|
+
});
|
|
56
|
+
globalThis.test("a PR stack is unprotected so teardown does not leak buckets", () => {
|
|
57
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.protectionFor("pr-42", undefined)).toEqual("Unprotected");
|
|
58
|
+
});
|
|
59
|
+
globalThis.test("'prepare' is not a PR stack — the prefix is 'pr-', not 'pr'", () => {
|
|
60
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.protectionFor("prepare", undefined)).toEqual("Protected");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
globalThis.describe("Util_StoreLayout.bucketNameFor", () => {
|
|
65
|
+
globalThis.test("per-store names the bucket after the declaration", () => {
|
|
66
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.bucketNameFor("PerStore", "prod", "catalog", "productImages")).toBe("catalog-productImages");
|
|
67
|
+
});
|
|
68
|
+
globalThis.test("shared names one bucket per stack", () => {
|
|
69
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.bucketNameFor("SharedBucket", "alpha", "catalog", "productImages")).toBe("alpha-stores");
|
|
70
|
+
});
|
|
71
|
+
globalThis.test("every store on a shared stack lands in the same bucket", () => {
|
|
72
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.bucketNameFor("SharedBucket", "alpha", "ordering", "invoices")).toBe(Util_StoreLayout$ReventlessAws.bucketNameFor("SharedBucket", "alpha", "catalog", "productImages"));
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
globalThis.describe("Util_StoreLayout.keyPrefixFor", () => {
|
|
77
|
+
globalThis.test("the key prefix is the store name in both layouts", () => {
|
|
78
|
+
globalThis.expect(Util_StoreLayout$ReventlessAws.keyPrefixFor("productImages")).toBe("productImages");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
let prodStacks = Util_HostUiDomain$ReventlessAws.defaultProdStacks;
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
prodStacks,
|
|
86
|
+
}
|
|
87
|
+
/* Not a pure module */
|