@reventlessdev/reventless-aws 3.0.0-alpha.295 → 3.0.0-alpha.297
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 +4 -4
- package/src/components/Api/AppSync_Adapter.res +28 -2
- package/src/components/Api/AppSync_Adapter.res.mjs +14 -4
- package/src/components/Api/AppSync_SdlDecorate.res +76 -1
- package/src/components/Api/AppSync_SdlDecorate.res.mjs +38 -1
- package/tests/AppSync_AdapterTest.res +68 -5
- package/tests/AppSync_AdapterTest.res.mjs +55 -5
- package/tests/AppSync_SdlDecorateTest.res +97 -0
- package/tests/AppSync_SdlDecorateTest.res.mjs +60 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.297 (2026-08-14)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **aws:** sweep undirectived fields at the schema assembly choke point ([f0ed736](https://github.com/ReventlessDev/reventless-core/commit/f0ed7369ec4dddfb697a3c93115b12f310a491b0))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.296 (2026-08-14)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **aws:** give every field and type an explicit Cognito directive ([37c1063](https://github.com/ReventlessDev/reventless-core/commit/37c1063560aaa6ee200e74d58b9de4d6881441fd))
|
|
18
|
+
|
|
19
|
+
|
|
6
20
|
# 3.0.0-alpha.295 (2026-08-14)
|
|
7
21
|
|
|
8
22
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.297",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
"@reventlessdev/rescript-node": "2.0.0-alpha.6",
|
|
19
19
|
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.75",
|
|
20
20
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
|
|
21
|
-
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
22
21
|
"@reventlessdev/reventless-core": "3.0.0-alpha.233",
|
|
22
|
+
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
23
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.140",
|
|
23
24
|
"@reventlessdev/reventless-postgres": "3.0.0-alpha.97",
|
|
24
|
-
"@reventlessdev/reventless-interop": "3.0.0-alpha.31",
|
|
25
25
|
"@reventlessdev/reventless-spec": "3.0.0-alpha.113",
|
|
26
|
-
"@reventlessdev/reventless-
|
|
26
|
+
"@reventlessdev/reventless-interop": "3.0.0-alpha.31"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"rescript": "12.3.0",
|
|
@@ -355,6 +355,12 @@ let injectAwsAuth = (
|
|
|
355
355
|
}
|
|
356
356
|
})
|
|
357
357
|
|
|
358
|
+
// A field with no group restriction gets the group-less Cognito directive
|
|
359
|
+
// rather than no directive at all. Same reachability under `defaultAction:
|
|
360
|
+
// ALLOW`; the difference only shows once the default is DENY, where an
|
|
361
|
+
// unstamped field is refused. See AppSync_SdlDecorate.cognitoOpenDirective.
|
|
362
|
+
let openDirective = AppSync_SdlDecorate.cognitoOpenDirective
|
|
363
|
+
|
|
358
364
|
let augmentedMutations = parts.mutations->Array.map(field => {
|
|
359
365
|
let fieldName = ReventlessCore.GraphQL_Stitcher.extractLeadingName(field)
|
|
360
366
|
let groups = mutationAuthMap->Dict.get(fieldName)
|
|
@@ -363,7 +369,7 @@ let injectAwsAuth = (
|
|
|
363
369
|
} else {
|
|
364
370
|
switch groups {
|
|
365
371
|
| Some(groups) => `${field}\n ${_formatGroupsDirective(groups)}`
|
|
366
|
-
| None => field
|
|
372
|
+
| None => `${field}\n ${openDirective}`
|
|
367
373
|
}
|
|
368
374
|
}
|
|
369
375
|
})
|
|
@@ -379,11 +385,23 @@ let injectAwsAuth = (
|
|
|
379
385
|
} else {
|
|
380
386
|
switch groups {
|
|
381
387
|
| Some(groups) => `${field} ${_formatGroupsDirective(groups)}`
|
|
382
|
-
| None => field
|
|
388
|
+
| None => `${field} ${openDirective}`
|
|
383
389
|
}
|
|
384
390
|
}
|
|
385
391
|
})
|
|
386
392
|
|
|
393
|
+
// Subscriptions carried no directive at all on this path — they are reads and
|
|
394
|
+
// need one for the same reason queries do. A subscription is never IAM-marked
|
|
395
|
+
// (the deploy caller does not subscribe), so it takes the Cognito arm only:
|
|
396
|
+
// the mutation's groups where the subscribed field has them, otherwise open.
|
|
397
|
+
let augmentedSubscriptions = parts.subscriptions->Array.map(field => {
|
|
398
|
+
let fieldName = ReventlessCore.GraphQL_Stitcher.extractLeadingName(field)
|
|
399
|
+
switch mutationAuthMap->Dict.get(fieldName) {
|
|
400
|
+
| Some(groups) => `${field}\n ${_formatGroupsDirective(groups)}`
|
|
401
|
+
| None => `${field}\n ${openDirective}`
|
|
402
|
+
}
|
|
403
|
+
})
|
|
404
|
+
|
|
387
405
|
// Type-level dual-auth for every type the callable entries' responses reach —
|
|
388
406
|
// the node type, its Connection/Edge wrappers, and nested state types all
|
|
389
407
|
// share the entry's returnTypeName prefix.
|
|
@@ -400,6 +418,7 @@ let injectAwsAuth = (
|
|
|
400
418
|
types: augmentedTypes,
|
|
401
419
|
mutations: augmentedMutations,
|
|
402
420
|
queries: augmentedQueries,
|
|
421
|
+
subscriptions: augmentedSubscriptions,
|
|
403
422
|
})
|
|
404
423
|
}
|
|
405
424
|
|
|
@@ -441,9 +460,16 @@ let stitchStandaloneWithAwsDirectives = (
|
|
|
441
460
|
~baseFragment=fragment,
|
|
442
461
|
~pluginFragments=[],
|
|
443
462
|
)
|
|
463
|
+
// Every AWS source-API document assembles through here, so this is the one
|
|
464
|
+
// place that can guarantee no field or type reaches a deployed schema without
|
|
465
|
+
// an auth directive — whichever path injected it.
|
|
466
|
+
let fragment = AppSync_SdlDecorate.stampUndirectivedFields(fragment)
|
|
444
467
|
ReventlessCore.GraphQL_Stitcher.stitchStandalone(~fragment)
|
|
445
468
|
->AppSync_SdlDecorate.injectAwsSubscribe(~sources)
|
|
446
469
|
->stampSharedIamTypes
|
|
470
|
+
// Last: every remaining undirectived type takes the group-less Cognito arm.
|
|
471
|
+
// After stampSharedIamTypes so the shared traversal types keep `@aws_iam`.
|
|
472
|
+
->AppSync_SdlDecorate.stampAllTypesCognito
|
|
447
473
|
}
|
|
448
474
|
|
|
449
475
|
// ── Provider implementation ────────────────────────────────────────────────
|
|
@@ -216,7 +216,7 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
|
|
|
216
216
|
} else if (groups !== undefined) {
|
|
217
217
|
return field + `\n ` + AppSync_SdlDecorate$ReventlessAws.formatCognitoGroupsDirective(groups);
|
|
218
218
|
} else {
|
|
219
|
-
return field;
|
|
219
|
+
return field + `\n ` + AppSync_SdlDecorate$ReventlessAws.cognitoOpenDirective;
|
|
220
220
|
}
|
|
221
221
|
});
|
|
222
222
|
let augmentedQueries = parts.queries.map(field => {
|
|
@@ -228,7 +228,16 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
|
|
|
228
228
|
} else if (groups !== undefined) {
|
|
229
229
|
return field + ` ` + AppSync_SdlDecorate$ReventlessAws.formatCognitoGroupsDirective(groups);
|
|
230
230
|
} else {
|
|
231
|
-
return field;
|
|
231
|
+
return field + ` ` + AppSync_SdlDecorate$ReventlessAws.cognitoOpenDirective;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
let augmentedSubscriptions = parts.subscriptions.map(field => {
|
|
235
|
+
let fieldName = GraphQL_Stitcher$ReventlessCore.extractLeadingName(field);
|
|
236
|
+
let groups = mutationAuthMap[fieldName];
|
|
237
|
+
if (groups !== undefined) {
|
|
238
|
+
return field + `\n ` + AppSync_SdlDecorate$ReventlessAws.formatCognitoGroupsDirective(groups);
|
|
239
|
+
} else {
|
|
240
|
+
return field + `\n ` + AppSync_SdlDecorate$ReventlessAws.cognitoOpenDirective;
|
|
232
241
|
}
|
|
233
242
|
});
|
|
234
243
|
let augmentedTypes = parts.types.map(decl => {
|
|
@@ -243,7 +252,7 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
|
|
|
243
252
|
types: augmentedTypes,
|
|
244
253
|
mutations: augmentedMutations,
|
|
245
254
|
queries: augmentedQueries,
|
|
246
|
-
subscriptions:
|
|
255
|
+
subscriptions: augmentedSubscriptions,
|
|
247
256
|
subscriptionSources: parts.subscriptionSources
|
|
248
257
|
});
|
|
249
258
|
}
|
|
@@ -255,7 +264,8 @@ function injectAwsAuthAll(fragment, group, iamFieldNamesOpt) {
|
|
|
255
264
|
|
|
256
265
|
function stitchStandaloneWithAwsDirectives(fragment) {
|
|
257
266
|
let sources = GraphQL_Stitcher$ReventlessCore.collectSubscriptionSources(fragment, []);
|
|
258
|
-
|
|
267
|
+
let fragment$1 = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(fragment);
|
|
268
|
+
return AppSync_SdlDecorate$ReventlessAws.stampAllTypesCognito(AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes(AppSync_SdlDecorate$ReventlessAws.injectAwsSubscribe(GraphQL_Stitcher$ReventlessCore.stitchStandalone(fragment$1), sources)));
|
|
259
269
|
}
|
|
260
270
|
|
|
261
271
|
function _makeApiResourceWith(name, schema, userPoolConfig, opts) {
|
|
@@ -87,6 +87,18 @@ let formatCognitoGroupsDirective = (groups: array<string>): string => {
|
|
|
87
87
|
`@aws_cognito_user_pools(cognito_groups: [${quoted}])`
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
// The group-less Cognito directive: reachable by any authenticated Cognito
|
|
91
|
+
// caller. Emitted where a field or type is deliberately open — `AllowAuthenticated`,
|
|
92
|
+
// `AllowAnonymous`, a field carrying no permission at all, and every object type.
|
|
93
|
+
//
|
|
94
|
+
// Under `userPoolConfig.defaultAction: ALLOW` this is a no-op: an undirectived
|
|
95
|
+
// field is already reachable by any authenticated caller, so stamping it changes
|
|
96
|
+
// nothing. It exists so the default can be flipped to DENY, where "no directive"
|
|
97
|
+
// stops meaning "open" and starts meaning "refused" — at which point anything
|
|
98
|
+
// left unstamped breaks. Stamping first and flipping second is what keeps that
|
|
99
|
+
// flip from being a guess.
|
|
100
|
+
let cognitoOpenDirective = "@aws_cognito_user_pools"
|
|
101
|
+
|
|
90
102
|
// Multi-auth directive for a field/type that must accept BOTH Cognito and IAM.
|
|
91
103
|
// `groups=Some([...])` preserves Cognito group gating; `groups=None` keeps it
|
|
92
104
|
// open to any authenticated Cognito user. `@aws_iam` admits the deploy-time
|
|
@@ -94,7 +106,7 @@ let formatCognitoGroupsDirective = (groups: array<string>): string => {
|
|
|
94
106
|
let formatDualAuthDirective = (groups: option<array<string>>): string => {
|
|
95
107
|
let cognito = switch groups {
|
|
96
108
|
| Some(g) => formatCognitoGroupsDirective(g)
|
|
97
|
-
| None =>
|
|
109
|
+
| None => cognitoOpenDirective
|
|
98
110
|
}
|
|
99
111
|
`${cognito} @aws_iam`
|
|
100
112
|
}
|
|
@@ -150,6 +162,69 @@ let stampSharedIamTypes = (sdl: string): string =>
|
|
|
150
162
|
acc->String.replace(`type ${name} {`, `type ${name} @aws_cognito_user_pools @aws_iam {`)
|
|
151
163
|
)
|
|
152
164
|
|
|
165
|
+
// Final field sweep, run at the assembly choke point on the FRAGMENT (where each
|
|
166
|
+
// element is one whole field, so "does it already carry a directive?" is a
|
|
167
|
+
// reliable question — on raw SDL text a directive placed on the following line
|
|
168
|
+
// would read as absent).
|
|
169
|
+
//
|
|
170
|
+
// `injectAwsAuth` only decorates fields it can pair with a schema entry, and
|
|
171
|
+
// several surfaces never reach it at all: the domain base document
|
|
172
|
+
// (`Platform_ping`), event-history queries, event-log `…_eventAppended`
|
|
173
|
+
// subscriptions, the upload presign/release mutations, `Platform_SetActiveRole`,
|
|
174
|
+
// and `geocode`. Those deployed with no directive — invisible under
|
|
175
|
+
// `defaultAction: ALLOW`, refused under DENY. Sweeping here rather than at each
|
|
176
|
+
// emission site means a future injected field cannot silently miss the net.
|
|
177
|
+
//
|
|
178
|
+
// Idempotent, and deliberately does not second-guess gating: it only fills in
|
|
179
|
+
// "open to any authenticated Cognito caller", which is what an undirectived
|
|
180
|
+
// field already means today.
|
|
181
|
+
let stampUndirectivedFields = (
|
|
182
|
+
fragment: Reventless.Plugin.apiSchemaFragment,
|
|
183
|
+
): Reventless.Plugin.apiSchemaFragment => {
|
|
184
|
+
let parts = ReventlessCore.GraphQL_Stitcher.decode(fragment)
|
|
185
|
+
let stamp = (field: string): string =>
|
|
186
|
+
field->String.includes("@aws_") ? field : `${field}\n ${cognitoOpenDirective}`
|
|
187
|
+
|
|
188
|
+
ReventlessCore.GraphQL_Stitcher.encode({
|
|
189
|
+
...parts,
|
|
190
|
+
mutations: parts.mutations->Array.map(stamp),
|
|
191
|
+
queries: parts.queries->Array.map(stamp),
|
|
192
|
+
subscriptions: parts.subscriptions->Array.map(stamp),
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Stamp every object `type` declaration that does not already carry an auth
|
|
197
|
+
// directive with the group-less Cognito arm. Run on the ASSEMBLED SDL, AFTER
|
|
198
|
+
// stampSharedIamTypes so the shared traversal types keep their `@aws_iam` arm
|
|
199
|
+
// (this pass skips anything already carrying an `@aws_` directive).
|
|
200
|
+
//
|
|
201
|
+
// Types, not just fields: on a multi-auth API with `defaultAction: DENY`,
|
|
202
|
+
// AppSync refuses an undirectived TYPE even when the caller passed the field's
|
|
203
|
+
// own gate — response shaping walks `…Connection` → `…Edge` → node → nested
|
|
204
|
+
// state types and dies one level in. Entry gating stays on the fields; a type
|
|
205
|
+
// stamp only restores the accessibility an undirectived type has today.
|
|
206
|
+
//
|
|
207
|
+
// Only `type` declarations take auth directives — `input`, `enum`, `union` and
|
|
208
|
+
// `interface` do not, and stamping them is a schema error.
|
|
209
|
+
let stampAllTypesCognito = (sdl: string): string =>
|
|
210
|
+
sdl
|
|
211
|
+
->String.split("\n")
|
|
212
|
+
->Array.map(line =>
|
|
213
|
+
if line->String.startsWith("type ") && !(line->String.includes("@aws_")) {
|
|
214
|
+
switch line->String.indexOfOpt("{") {
|
|
215
|
+
| Some(i) =>
|
|
216
|
+
line->String.slice(~start=0, ~end=i) ++
|
|
217
|
+
cognitoOpenDirective ++
|
|
218
|
+
" " ++
|
|
219
|
+
line->String.slice(~start=i, ~end=line->String.length)
|
|
220
|
+
| None => line
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
line
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
->Array.join("\n")
|
|
227
|
+
|
|
153
228
|
// ── Merged-API canonical stamping ─────────────────────────────────────────────
|
|
154
229
|
// Under AppSync Merged APIs the admin source API owns the shared traversal
|
|
155
230
|
// types; `@canonical` makes its definition win over every plugin source's copy
|
|
@@ -42,8 +42,10 @@ function formatCognitoGroupsDirective(groups) {
|
|
|
42
42
|
return `@aws_cognito_user_pools(cognito_groups: [` + quoted + `])`;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
let cognitoOpenDirective = "@aws_cognito_user_pools";
|
|
46
|
+
|
|
45
47
|
function formatDualAuthDirective(groups) {
|
|
46
|
-
let cognito = groups !== undefined ? formatCognitoGroupsDirective(groups) :
|
|
48
|
+
let cognito = groups !== undefined ? formatCognitoGroupsDirective(groups) : cognitoOpenDirective;
|
|
47
49
|
return cognito + ` @aws_iam`;
|
|
48
50
|
}
|
|
49
51
|
|
|
@@ -86,6 +88,38 @@ function stampSharedIamTypes(sdl) {
|
|
|
86
88
|
return Stdlib_Array.reduce(sharedIamTypeNames, sdl, (acc, name) => acc.replace(`type ` + name + ` {`, `type ` + name + ` @aws_cognito_user_pools @aws_iam {`));
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
function stampUndirectivedFields(fragment) {
|
|
92
|
+
let parts = GraphQL_Stitcher$ReventlessCore.decode(fragment);
|
|
93
|
+
let stamp = field => {
|
|
94
|
+
if (field.includes("@aws_")) {
|
|
95
|
+
return field;
|
|
96
|
+
} else {
|
|
97
|
+
return field + `\n ` + cognitoOpenDirective;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
return GraphQL_Stitcher$ReventlessCore.encode({
|
|
101
|
+
types: parts.types,
|
|
102
|
+
mutations: parts.mutations.map(stamp),
|
|
103
|
+
queries: parts.queries.map(stamp),
|
|
104
|
+
subscriptions: parts.subscriptions.map(stamp),
|
|
105
|
+
subscriptionSources: parts.subscriptionSources
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function stampAllTypesCognito(sdl) {
|
|
110
|
+
return sdl.split("\n").map(line => {
|
|
111
|
+
if (!(line.startsWith("type ") && !line.includes("@aws_"))) {
|
|
112
|
+
return line;
|
|
113
|
+
}
|
|
114
|
+
let i = Stdlib_String.indexOfOpt(line, "{");
|
|
115
|
+
if (i !== undefined) {
|
|
116
|
+
return line.slice(0, i) + cognitoOpenDirective + " " + line.slice(i, line.length);
|
|
117
|
+
} else {
|
|
118
|
+
return line;
|
|
119
|
+
}
|
|
120
|
+
}).join("\n");
|
|
121
|
+
}
|
|
122
|
+
|
|
89
123
|
let canonicalTypeNames = [
|
|
90
124
|
"PageInfo",
|
|
91
125
|
"CommandAccepted",
|
|
@@ -126,10 +160,13 @@ function stampCanonicalTypes(sdl) {
|
|
|
126
160
|
export {
|
|
127
161
|
injectAwsSubscribe,
|
|
128
162
|
formatCognitoGroupsDirective,
|
|
163
|
+
cognitoOpenDirective,
|
|
129
164
|
formatDualAuthDirective,
|
|
130
165
|
injectAwsAuthAll,
|
|
131
166
|
sharedIamTypeNames,
|
|
132
167
|
stampSharedIamTypes,
|
|
168
|
+
stampUndirectivedFields,
|
|
169
|
+
stampAllTypesCognito,
|
|
133
170
|
canonicalTypeNames,
|
|
134
171
|
stampCanonicalTypes,
|
|
135
172
|
}
|
|
@@ -218,7 +218,7 @@ describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifting", () =>
|
|
|
218
218
|
)
|
|
219
219
|
})
|
|
220
220
|
|
|
221
|
-
testSync("AllowAuthenticated emits
|
|
221
|
+
testSync("AllowAuthenticated emits the group-less Cognito directive", () => {
|
|
222
222
|
let fp = Dict.fromArray([(
|
|
223
223
|
"p_Add",
|
|
224
224
|
Reventless.Authorization.AllowAuthenticated,
|
|
@@ -231,7 +231,11 @@ describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifting", () =>
|
|
|
231
231
|
~queryEntries=[],
|
|
232
232
|
)
|
|
233
233
|
let parts = decodeFragment(aug)
|
|
234
|
-
|
|
234
|
+
// Open, not ungated: the field carries the group-less directive so it stays
|
|
235
|
+
// reachable once defaultAction flips to DENY, but names no group.
|
|
236
|
+
let m = parts.mutations->Array.getUnsafe(0)
|
|
237
|
+
expect(m)->toContain("@aws_cognito_user_pools")
|
|
238
|
+
expect(m)->not_->toContain("cognito_groups")
|
|
235
239
|
})
|
|
236
240
|
|
|
237
241
|
testSync("DenyAll emits sentinel __deny_all__ group", () => {
|
|
@@ -273,7 +277,9 @@ describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifting", () =>
|
|
|
273
277
|
| None => JsError.throwWithMessage("missing archive field")
|
|
274
278
|
}
|
|
275
279
|
switch add {
|
|
276
|
-
| Some(f) =>
|
|
280
|
+
| Some(f) =>
|
|
281
|
+
expect(f)->toContain("@aws_cognito_user_pools")
|
|
282
|
+
expect(f)->not_->toContain("cognito_groups")
|
|
277
283
|
| None => JsError.throwWithMessage("missing add field")
|
|
278
284
|
}
|
|
279
285
|
})
|
|
@@ -338,7 +344,7 @@ describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifting", () =>
|
|
|
338
344
|
expect(m)->not_->toContain(`"Admin"`)
|
|
339
345
|
})
|
|
340
346
|
|
|
341
|
-
testSync("AllowAuthenticated on a field overrides the legacy authorization (
|
|
347
|
+
testSync("AllowAuthenticated on a field overrides the legacy authorization (drops the group)", () => {
|
|
342
348
|
let fp = Dict.fromArray([(
|
|
343
349
|
"p_X",
|
|
344
350
|
Reventless.Authorization.AllowAuthenticated,
|
|
@@ -359,7 +365,11 @@ describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifting", () =>
|
|
|
359
365
|
~queryEntries=[],
|
|
360
366
|
)
|
|
361
367
|
let parts = decodeFragment(aug)
|
|
362
|
-
|
|
368
|
+
// The spec-level AllowAuthenticated wins over the legacy `authorization.group`:
|
|
369
|
+
// the field keeps a directive, but no longer names a group.
|
|
370
|
+
let m = parts.mutations->Array.getUnsafe(0)
|
|
371
|
+
expect(m)->toContain("@aws_cognito_user_pools")
|
|
372
|
+
expect(m)->not_->toContain("cognito_groups")
|
|
363
373
|
})
|
|
364
374
|
})
|
|
365
375
|
|
|
@@ -851,3 +861,56 @@ describe("AppSync_Adapter.waitForMergeSuccess", () => {
|
|
|
851
861
|
expect(message.contents)->toContain("MERGE_IN_PROGRESS")
|
|
852
862
|
})
|
|
853
863
|
})
|
|
864
|
+
|
|
865
|
+
// ── Subscription stamping ───────────────────────────────────────────────────
|
|
866
|
+
//
|
|
867
|
+
// `injectAwsAuth` re-encoded `subscriptions` untouched, so every plugin
|
|
868
|
+
// subscription reached the deployed SDL with no auth directive at all — 22 of
|
|
869
|
+
// them on the hybrid domain API. Harmless under `defaultAction: ALLOW`, refused
|
|
870
|
+
// under DENY. A subscription is a read and takes the Cognito arm; it is never
|
|
871
|
+
// IAM-marked, because the deploy-time caller does not subscribe.
|
|
872
|
+
|
|
873
|
+
describe("AppSync_Adapter.injectAwsAuth — subscriptions", () => {
|
|
874
|
+
let makeFragment = subscriptionFields =>
|
|
875
|
+
ReventlessCore.GraphQL_Stitcher.encode({
|
|
876
|
+
types: [],
|
|
877
|
+
mutations: ["p_Ship(id: ID!): CommandResult!"],
|
|
878
|
+
queries: [],
|
|
879
|
+
subscriptions: subscriptionFields,
|
|
880
|
+
subscriptionSources: [],
|
|
881
|
+
})
|
|
882
|
+
|
|
883
|
+
let entry = (~fieldNames, ~fieldPermissions): ReventlessInfra.Api.mutationSchemaEntry => {
|
|
884
|
+
fieldNames,
|
|
885
|
+
commandSchema: S.unknown,
|
|
886
|
+
fieldPermissions,
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
testSync("an unannotated subscription gets the group-less Cognito directive", () => {
|
|
890
|
+
let frag = makeFragment(["onSomething: CommandResult"])
|
|
891
|
+
let aug = AppSync_Adapter.injectAwsAuth(frag, ~mutationEntries=[], ~queryEntries=[])
|
|
892
|
+
let s = decodeFragment(aug).subscriptions->Array.getUnsafe(0)
|
|
893
|
+
expect(s)->toContain("@aws_cognito_user_pools")
|
|
894
|
+
expect(s)->not_->toContain("cognito_groups")
|
|
895
|
+
expect(s)->not_->toContain("@aws_iam")
|
|
896
|
+
})
|
|
897
|
+
|
|
898
|
+
testSync("a subscription named like a gated mutation inherits its groups", () => {
|
|
899
|
+
let fp = Dict.fromArray([("p_Ship", Reventless.Authorization.AllowGroups(["Fulfilment"]))])
|
|
900
|
+
let frag = makeFragment(["p_Ship: CommandResult"])
|
|
901
|
+
let aug = AppSync_Adapter.injectAwsAuth(
|
|
902
|
+
frag,
|
|
903
|
+
~mutationEntries=[entry(~fieldNames=["p_Ship"], ~fieldPermissions=fp)],
|
|
904
|
+
~queryEntries=[],
|
|
905
|
+
)
|
|
906
|
+
let s = decodeFragment(aug).subscriptions->Array.getUnsafe(0)
|
|
907
|
+
expect(s)->toContain(`@aws_cognito_user_pools(cognito_groups: ["Fulfilment"])`)
|
|
908
|
+
expect(s)->not_->toContain("@aws_iam")
|
|
909
|
+
})
|
|
910
|
+
|
|
911
|
+
testSync("subscription fields survive the round-trip", () => {
|
|
912
|
+
let frag = makeFragment(["onA: CommandResult", "onB: CommandResult"])
|
|
913
|
+
let aug = AppSync_Adapter.injectAwsAuth(frag, ~mutationEntries=[], ~queryEntries=[])
|
|
914
|
+
expect(decodeFragment(aug).subscriptions->Array.length)->toBe(2)
|
|
915
|
+
})
|
|
916
|
+
})
|
|
@@ -161,7 +161,7 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifti
|
|
|
161
161
|
let parts = GraphQL_Stitcher$ReventlessCore.decode(aug);
|
|
162
162
|
globalThis.expect(parts.mutations[0]).toContain(`@aws_cognito_user_pools(cognito_groups: ["Admin", "Editor"])`);
|
|
163
163
|
});
|
|
164
|
-
globalThis.test("AllowAuthenticated emits
|
|
164
|
+
globalThis.test("AllowAuthenticated emits the group-less Cognito directive", () => {
|
|
165
165
|
let fp = Object.fromEntries([[
|
|
166
166
|
"p_Add",
|
|
167
167
|
"AllowAuthenticated"
|
|
@@ -170,7 +170,9 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifti
|
|
|
170
170
|
let frag = makeFragment(["p_Add(id: ID!): String"], []);
|
|
171
171
|
let aug = AppSync_Adapter$ReventlessAws.injectAwsAuth(frag, [entry], []);
|
|
172
172
|
let parts = GraphQL_Stitcher$ReventlessCore.decode(aug);
|
|
173
|
-
|
|
173
|
+
let m = parts.mutations[0];
|
|
174
|
+
globalThis.expect(m).toContain("@aws_cognito_user_pools");
|
|
175
|
+
globalThis.expect(m).not.toContain("cognito_groups");
|
|
174
176
|
});
|
|
175
177
|
globalThis.test("DenyAll emits sentinel __deny_all__ group", () => {
|
|
176
178
|
let fp = Object.fromEntries([[
|
|
@@ -215,7 +217,8 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifti
|
|
|
215
217
|
Stdlib_JsError.throwWithMessage("missing archive field");
|
|
216
218
|
}
|
|
217
219
|
if (add !== undefined) {
|
|
218
|
-
globalThis.expect(add).
|
|
220
|
+
globalThis.expect(add).toContain("@aws_cognito_user_pools");
|
|
221
|
+
globalThis.expect(add).not.toContain("cognito_groups");
|
|
219
222
|
return;
|
|
220
223
|
} else {
|
|
221
224
|
return Stdlib_JsError.throwWithMessage("missing add field");
|
|
@@ -269,7 +272,7 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifti
|
|
|
269
272
|
globalThis.expect(m).toContain(`cognito_groups: ["Manager"]`);
|
|
270
273
|
globalThis.expect(m).not.toContain(`"Admin"`);
|
|
271
274
|
});
|
|
272
|
-
globalThis.test("AllowAuthenticated on a field overrides the legacy authorization (
|
|
275
|
+
globalThis.test("AllowAuthenticated on a field overrides the legacy authorization (drops the group)", () => {
|
|
273
276
|
let fp = Object.fromEntries([[
|
|
274
277
|
"p_X",
|
|
275
278
|
"AllowAuthenticated"
|
|
@@ -289,7 +292,9 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifti
|
|
|
289
292
|
let frag = makeFragment(["p_X(id: ID!): String"], []);
|
|
290
293
|
let aug = AppSync_Adapter$ReventlessAws.injectAwsAuth(frag, [entry], []);
|
|
291
294
|
let parts = GraphQL_Stitcher$ReventlessCore.decode(aug);
|
|
292
|
-
|
|
295
|
+
let m = parts.mutations[0];
|
|
296
|
+
globalThis.expect(m).toContain("@aws_cognito_user_pools");
|
|
297
|
+
globalThis.expect(m).not.toContain("cognito_groups");
|
|
293
298
|
});
|
|
294
299
|
});
|
|
295
300
|
|
|
@@ -746,6 +751,51 @@ globalThis.describe("AppSync_Adapter.waitForMergeSuccess", () => {
|
|
|
746
751
|
});
|
|
747
752
|
});
|
|
748
753
|
|
|
754
|
+
globalThis.describe("AppSync_Adapter.injectAwsAuth — subscriptions", () => {
|
|
755
|
+
let makeFragment = subscriptionFields => GraphQL_Stitcher$ReventlessCore.encode({
|
|
756
|
+
types: [],
|
|
757
|
+
mutations: ["p_Ship(id: ID!): CommandResult!"],
|
|
758
|
+
queries: [],
|
|
759
|
+
subscriptions: subscriptionFields,
|
|
760
|
+
subscriptionSources: []
|
|
761
|
+
});
|
|
762
|
+
let entry = (fieldNames, fieldPermissions) => ({
|
|
763
|
+
fieldNames: fieldNames,
|
|
764
|
+
commandSchema: S.unknown,
|
|
765
|
+
fieldPermissions: fieldPermissions
|
|
766
|
+
});
|
|
767
|
+
globalThis.test("an unannotated subscription gets the group-less Cognito directive", () => {
|
|
768
|
+
let frag = makeFragment(["onSomething: CommandResult"]);
|
|
769
|
+
let aug = AppSync_Adapter$ReventlessAws.injectAwsAuth(frag, [], []);
|
|
770
|
+
let s = GraphQL_Stitcher$ReventlessCore.decode(aug).subscriptions[0];
|
|
771
|
+
globalThis.expect(s).toContain("@aws_cognito_user_pools");
|
|
772
|
+
globalThis.expect(s).not.toContain("cognito_groups");
|
|
773
|
+
globalThis.expect(s).not.toContain("@aws_iam");
|
|
774
|
+
});
|
|
775
|
+
globalThis.test("a subscription named like a gated mutation inherits its groups", () => {
|
|
776
|
+
let fp = Object.fromEntries([[
|
|
777
|
+
"p_Ship",
|
|
778
|
+
{
|
|
779
|
+
TAG: "AllowGroups",
|
|
780
|
+
_0: ["Fulfilment"]
|
|
781
|
+
}
|
|
782
|
+
]]);
|
|
783
|
+
let frag = makeFragment(["p_Ship: CommandResult"]);
|
|
784
|
+
let aug = AppSync_Adapter$ReventlessAws.injectAwsAuth(frag, [entry(["p_Ship"], fp)], []);
|
|
785
|
+
let s = GraphQL_Stitcher$ReventlessCore.decode(aug).subscriptions[0];
|
|
786
|
+
globalThis.expect(s).toContain(`@aws_cognito_user_pools(cognito_groups: ["Fulfilment"])`);
|
|
787
|
+
globalThis.expect(s).not.toContain("@aws_iam");
|
|
788
|
+
});
|
|
789
|
+
globalThis.test("subscription fields survive the round-trip", () => {
|
|
790
|
+
let frag = makeFragment([
|
|
791
|
+
"onA: CommandResult",
|
|
792
|
+
"onB: CommandResult"
|
|
793
|
+
]);
|
|
794
|
+
let aug = AppSync_Adapter$ReventlessAws.injectAwsAuth(frag, [], []);
|
|
795
|
+
globalThis.expect(GraphQL_Stitcher$ReventlessCore.decode(aug).subscriptions.length).toBe(2);
|
|
796
|
+
});
|
|
797
|
+
});
|
|
798
|
+
|
|
749
799
|
export {
|
|
750
800
|
decodeFragment,
|
|
751
801
|
}
|
|
@@ -152,3 +152,100 @@ describe("AppSync_SdlDecorate.stampCanonicalTypes", () => {
|
|
|
152
152
|
expect(AppSync_SdlDecorate.stampCanonicalTypes(once))->toBe(once)
|
|
153
153
|
})
|
|
154
154
|
})
|
|
155
|
+
|
|
156
|
+
// ── Type-level Cognito stamping (defaultAction: DENY groundwork) ─────────────
|
|
157
|
+
//
|
|
158
|
+
// With `defaultAction: ALLOW` an undirectived type is reachable by any
|
|
159
|
+
// authenticated Cognito caller, so this pass changes nothing observable. It
|
|
160
|
+
// exists so the default can be flipped to DENY, where an undirectived type is
|
|
161
|
+
// refused mid-traversal even for a caller who passed the field's own gate.
|
|
162
|
+
|
|
163
|
+
describe("AppSync_SdlDecorate.stampAllTypesCognito", () => {
|
|
164
|
+
testSync("stamps an undirectived object type", () => {
|
|
165
|
+
let sdl = AppSync_SdlDecorate.stampAllTypesCognito("type Product {\n id: ID!\n}")
|
|
166
|
+
expect(sdl)->toContain("type Product @aws_cognito_user_pools {")
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
testSync("leaves an already-directived type alone, keeping its @aws_iam arm", () => {
|
|
170
|
+
let sdl =
|
|
171
|
+
AppSync_SdlDecorate.stampSharedIamTypes(
|
|
172
|
+
"type PageInfo {\n hasNextPage: Boolean!\n}",
|
|
173
|
+
)->AppSync_SdlDecorate.stampAllTypesCognito
|
|
174
|
+
expect(sdl)->toContain("type PageInfo @aws_cognito_user_pools @aws_iam {")
|
|
175
|
+
// Not double-stamped.
|
|
176
|
+
expect(sdl->String.includes("@aws_cognito_user_pools @aws_cognito_user_pools"))->toBe(false)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
testSync("does not stamp input, enum, union or interface declarations", () => {
|
|
180
|
+
let sdl = AppSync_SdlDecorate.stampAllTypesCognito(
|
|
181
|
+
"input ProductFilter {\n id: ID\n}\n\nenum Status {\n Active\n}\n\nunion CommandResult = CommandAccepted | CommandRejected\n\ninterface Node {\n id: ID!\n}",
|
|
182
|
+
)
|
|
183
|
+
expect(sdl->String.includes("@aws_cognito_user_pools"))->toBe(false)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
testSync("is idempotent", () => {
|
|
187
|
+
let once = AppSync_SdlDecorate.stampAllTypesCognito("type Product {\n id: ID!\n}")
|
|
188
|
+
expect(AppSync_SdlDecorate.stampAllTypesCognito(once))->toBe(once)
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
// ── Final field sweep ───────────────────────────────────────────────────────
|
|
193
|
+
//
|
|
194
|
+
// `injectAwsAuth` only decorates fields it can pair with a schema entry. Several
|
|
195
|
+
// surfaces never reach it: the domain base (`Platform_ping`), event-history
|
|
196
|
+
// queries, `…_eventAppended` subscriptions, upload mutations,
|
|
197
|
+
// `Platform_SetActiveRole`, `geocode`. A deployed-SDL count after the first
|
|
198
|
+
// sweep found 11 such fields still bare on the domain API. This pass is the net.
|
|
199
|
+
|
|
200
|
+
describe("AppSync_SdlDecorate.stampUndirectivedFields", () => {
|
|
201
|
+
let frag = (~mutations=[], ~queries=[], ~subscriptions=[], ()) =>
|
|
202
|
+
ReventlessCore.GraphQL_Stitcher.encode({
|
|
203
|
+
types: [],
|
|
204
|
+
mutations,
|
|
205
|
+
queries,
|
|
206
|
+
subscriptions,
|
|
207
|
+
subscriptionSources: [],
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
testSync("stamps a query no schema entry ever paired with", () => {
|
|
211
|
+
let out = AppSync_SdlDecorate.stampUndirectivedFields(
|
|
212
|
+
frag(~queries=[" Platform_ping: String"], ()),
|
|
213
|
+
)
|
|
214
|
+
let q = ReventlessCore.GraphQL_Stitcher.decode(out).queries->Array.getUnsafe(0)
|
|
215
|
+
expect(q)->toContain("@aws_cognito_user_pools")
|
|
216
|
+
expect(q)->not_->toContain("cognito_groups")
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
testSync("stamps an event-log subscription", () => {
|
|
220
|
+
let out = AppSync_SdlDecorate.stampUndirectivedFields(
|
|
221
|
+
frag(~subscriptions=[" onFoo_eventAppended: FooEvent"], ()),
|
|
222
|
+
)
|
|
223
|
+
expect(
|
|
224
|
+
ReventlessCore.GraphQL_Stitcher.decode(out).subscriptions->Array.getUnsafe(0),
|
|
225
|
+
)->toContain("@aws_cognito_user_pools")
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
testSync("leaves an already-gated field untouched, group and all", () => {
|
|
229
|
+
let gated = ` Secret_Read: String\n @aws_cognito_user_pools(cognito_groups: ["Admin"])`
|
|
230
|
+
let out = AppSync_SdlDecorate.stampUndirectivedFields(frag(~queries=[gated], ()))
|
|
231
|
+
let q = ReventlessCore.GraphQL_Stitcher.decode(out).queries->Array.getUnsafe(0)
|
|
232
|
+
expect(q)->toContain(`cognito_groups: ["Admin"]`)
|
|
233
|
+
expect(q->String.includes("@aws_cognito_user_pools @aws_cognito_user_pools"))->toBe(false)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
testSync("does not strip an @aws_iam arm", () => {
|
|
237
|
+
let dual = " Sys_Sync: String\n @aws_cognito_user_pools @aws_iam"
|
|
238
|
+
let out = AppSync_SdlDecorate.stampUndirectivedFields(frag(~mutations=[dual], ()))
|
|
239
|
+
expect(
|
|
240
|
+
ReventlessCore.GraphQL_Stitcher.decode(out).mutations->Array.getUnsafe(0),
|
|
241
|
+
)->toContain("@aws_iam")
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
testSync("is idempotent", () => {
|
|
245
|
+
let once = AppSync_SdlDecorate.stampUndirectivedFields(frag(~queries=[" a: String"], ()))
|
|
246
|
+
let twice = AppSync_SdlDecorate.stampUndirectivedFields(once)
|
|
247
|
+
expect(ReventlessCore.GraphQL_Stitcher.decode(twice).queries->Array.getUnsafe(0))->toBe(
|
|
248
|
+
ReventlessCore.GraphQL_Stitcher.decode(once).queries->Array.getUnsafe(0),
|
|
249
|
+
)
|
|
250
|
+
})
|
|
251
|
+
})
|
|
@@ -132,6 +132,66 @@ globalThis.describe("AppSync_SdlDecorate.stampCanonicalTypes", () => {
|
|
|
132
132
|
});
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
+
globalThis.describe("AppSync_SdlDecorate.stampAllTypesCognito", () => {
|
|
136
|
+
globalThis.test("stamps an undirectived object type", () => {
|
|
137
|
+
let sdl = AppSync_SdlDecorate$ReventlessAws.stampAllTypesCognito("type Product {\n id: ID!\n}");
|
|
138
|
+
globalThis.expect(sdl).toContain("type Product @aws_cognito_user_pools {");
|
|
139
|
+
});
|
|
140
|
+
globalThis.test("leaves an already-directived type alone, keeping its @aws_iam arm", () => {
|
|
141
|
+
let sdl = AppSync_SdlDecorate$ReventlessAws.stampAllTypesCognito(AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes("type PageInfo {\n hasNextPage: Boolean!\n}"));
|
|
142
|
+
globalThis.expect(sdl).toContain("type PageInfo @aws_cognito_user_pools @aws_iam {");
|
|
143
|
+
globalThis.expect(sdl.includes("@aws_cognito_user_pools @aws_cognito_user_pools")).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
globalThis.test("does not stamp input, enum, union or interface declarations", () => {
|
|
146
|
+
let sdl = AppSync_SdlDecorate$ReventlessAws.stampAllTypesCognito("input ProductFilter {\n id: ID\n}\n\nenum Status {\n Active\n}\n\nunion CommandResult = CommandAccepted | CommandRejected\n\ninterface Node {\n id: ID!\n}");
|
|
147
|
+
globalThis.expect(sdl.includes("@aws_cognito_user_pools")).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
globalThis.test("is idempotent", () => {
|
|
150
|
+
let once = AppSync_SdlDecorate$ReventlessAws.stampAllTypesCognito("type Product {\n id: ID!\n}");
|
|
151
|
+
globalThis.expect(AppSync_SdlDecorate$ReventlessAws.stampAllTypesCognito(once)).toBe(once);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
globalThis.describe("AppSync_SdlDecorate.stampUndirectivedFields", () => {
|
|
156
|
+
let frag = (mutationsOpt, queriesOpt, subscriptionsOpt, param) => {
|
|
157
|
+
let mutations = mutationsOpt !== undefined ? mutationsOpt : [];
|
|
158
|
+
let queries = queriesOpt !== undefined ? queriesOpt : [];
|
|
159
|
+
let subscriptions = subscriptionsOpt !== undefined ? subscriptionsOpt : [];
|
|
160
|
+
return GraphQL_Stitcher$ReventlessCore.encode({
|
|
161
|
+
types: [],
|
|
162
|
+
mutations: mutations,
|
|
163
|
+
queries: queries,
|
|
164
|
+
subscriptions: subscriptions,
|
|
165
|
+
subscriptionSources: []
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
globalThis.test("stamps a query no schema entry ever paired with", () => {
|
|
169
|
+
let out = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag(undefined, [" Platform_ping: String"], undefined, undefined));
|
|
170
|
+
let q = GraphQL_Stitcher$ReventlessCore.decode(out).queries[0];
|
|
171
|
+
globalThis.expect(q).toContain("@aws_cognito_user_pools");
|
|
172
|
+
globalThis.expect(q).not.toContain("cognito_groups");
|
|
173
|
+
});
|
|
174
|
+
globalThis.test("stamps an event-log subscription", () => {
|
|
175
|
+
let out = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag(undefined, undefined, [" onFoo_eventAppended: FooEvent"], undefined));
|
|
176
|
+
globalThis.expect(GraphQL_Stitcher$ReventlessCore.decode(out).subscriptions[0]).toContain("@aws_cognito_user_pools");
|
|
177
|
+
});
|
|
178
|
+
globalThis.test("leaves an already-gated field untouched, group and all", () => {
|
|
179
|
+
let out = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag(undefined, [` Secret_Read: String\n @aws_cognito_user_pools(cognito_groups: ["Admin"])`], undefined, undefined));
|
|
180
|
+
let q = GraphQL_Stitcher$ReventlessCore.decode(out).queries[0];
|
|
181
|
+
globalThis.expect(q).toContain(`cognito_groups: ["Admin"]`);
|
|
182
|
+
globalThis.expect(q.includes("@aws_cognito_user_pools @aws_cognito_user_pools")).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
globalThis.test("does not strip an @aws_iam arm", () => {
|
|
185
|
+
let out = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag([" Sys_Sync: String\n @aws_cognito_user_pools @aws_iam"], undefined, undefined, undefined));
|
|
186
|
+
globalThis.expect(GraphQL_Stitcher$ReventlessCore.decode(out).mutations[0]).toContain("@aws_iam");
|
|
187
|
+
});
|
|
188
|
+
globalThis.test("is idempotent", () => {
|
|
189
|
+
let once = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag(undefined, [" a: String"], undefined, undefined));
|
|
190
|
+
let twice = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(once);
|
|
191
|
+
globalThis.expect(GraphQL_Stitcher$ReventlessCore.decode(twice).queries[0]).toBe(GraphQL_Stitcher$ReventlessCore.decode(once).queries[0]);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
135
195
|
export {
|
|
136
196
|
sources,
|
|
137
197
|
stitchedSdl,
|