@reventlessdev/reventless-aws 3.0.0-alpha.344 → 3.0.0-alpha.346

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 (263) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/package.json +15 -12
  3. package/run-provision-accounts.mjs +3 -0
  4. package/run-provision-admin.mjs +3 -0
  5. package/scripts/ProvisionAccounts.res +325 -0
  6. package/scripts/ProvisionAccounts.res.mjs +337 -0
  7. package/scripts/ProvisionAdmin.res +275 -0
  8. package/scripts/ProvisionAdmin.res.mjs +289 -0
  9. package/scripts/ProvisionCognito.res +149 -0
  10. package/scripts/ProvisionCognito.res.mjs +98 -0
  11. package/scripts/ProvisionIdentity.res +84 -14
  12. package/scripts/ProvisionIdentity.res.mjs +72 -7
  13. package/scripts/ProvisionProvider.res +104 -0
  14. package/scripts/ProvisionProvider.res.mjs +123 -0
  15. package/src/Platform.res +358 -329
  16. package/src/Platform.res.mjs +24 -12
  17. package/src/Platform_Casts.res +13 -4
  18. package/src/Platform_Stack.res +119 -9
  19. package/src/Platform_Stack.res.mjs +42 -4
  20. package/src/adapter/Api/AppSyncEventsSigner_Ops.res +8 -4
  21. package/src/adapter/Api/AppSync_EventsApi.res +12 -14
  22. package/src/adapter/Api/AppSync_Resolver_Native.res +3 -4
  23. package/src/adapter/Api/AppSync_Resolver_Retrying.res +78 -70
  24. package/src/adapter/Api/AppSync_SourceApiAssociation_Retrying.res +11 -8
  25. package/src/adapter/Api/Platform_AdminScan_Ops.res +15 -14
  26. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +10 -2
  27. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res +40 -50
  28. package/src/adapter/Api/Platform_UIFragments_Lambda.res +39 -32
  29. package/src/adapter/Api/Platform_UIFragments_Lambda_Ops.res +5 -2
  30. package/src/adapter/Auth/Auth_ActiveRolePoolAttachment.res +0 -1
  31. package/src/adapter/Auth/Auth_ActiveRoleStore.res +1 -2
  32. package/src/adapter/Auth/Auth_ActiveRoleStore_Schema.res +1 -4
  33. package/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res +5 -2
  34. package/src/adapter/Auth/Auth_Cognito.res +3 -9
  35. package/src/adapter/Auth/Auth_LoginIdentifier.res +66 -0
  36. package/src/adapter/Auth/Auth_LoginIdentifier.res.mjs +70 -0
  37. package/src/adapter/Auth/Auth_SignUpMode.res +78 -0
  38. package/src/adapter/Auth/Auth_SignUpMode.res.mjs +56 -0
  39. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync.res +1 -4
  40. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync_NoOp.res +3 -1
  41. package/src/adapter/CommandGenerator/InboundTranslationResolvers_AppSync.res +1 -4
  42. package/src/adapter/CommandTopic/CommandTopicChannel_SQS.res +7 -4
  43. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Async.res +6 -1
  44. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_FIFO.res +7 -4
  45. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Runtime.res +24 -18
  46. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Sync.res +9 -4
  47. package/src/adapter/CommandTopic/CommandTopicRegistry.res +1 -4
  48. package/src/adapter/Counter/CounterHandler_DynamoDbStream.res +15 -15
  49. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb.res +9 -5
  50. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res +179 -213
  51. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res +5 -5
  52. package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res +6 -2
  53. package/src/adapter/EventCollector/EventCollectorChannel_SQS.res +14 -7
  54. package/src/adapter/EventCollector/EventCollectorChannel_SQS_FIFO.res +14 -7
  55. package/src/adapter/EventCollector/EventCollectorChannel_SQS_Runtime.res +6 -3
  56. package/src/adapter/EventLog/EventLogStorage_DynamoDbStream.res +6 -1
  57. package/src/adapter/EventLog/EventLogStorage_DynamoDb_Runtime.res +13 -17
  58. package/src/adapter/EventLog/EventLogStorage_Postgres.res +7 -4
  59. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +12 -7
  60. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync_Ops.res +4 -2
  61. package/src/adapter/EventTopic/EventTopicPublisher_DynamoDbStream.res +3 -2
  62. package/src/adapter/EventTopic/EventTopicPublisher_SNS.res +20 -12
  63. package/src/adapter/EventTopic/EventTopicPublisher_SNS_FIFO.res +21 -9
  64. package/src/adapter/EventTopic/EventTopicPublisher_SNS_Runtime.res +5 -3
  65. package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res +6 -3
  66. package/src/adapter/Mcp/MCP_Lambda.res +31 -46
  67. package/src/adapter/Messaging/Messaging_Ses_Backend.res +11 -7
  68. package/src/adapter/Messaging/Messaging_Ses_Backend.res.mjs +2 -4
  69. package/src/adapter/Monitoring/Monitoring_CloudWatch.res +16 -16
  70. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +5 -10
  71. package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +4 -2
  72. package/src/adapter/Postgres/PgConnection.res +6 -5
  73. package/src/adapter/Postgres/PgMigration_Builder.res +4 -8
  74. package/src/adapter/Postgres/PgRuntime.res +4 -7
  75. package/src/adapter/QueryDb/PgQueryResolver_Builder.res +9 -11
  76. package/src/adapter/QueryDb/PgQueryResolver_Lambda.res +22 -21
  77. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +114 -111
  78. package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res +1 -7
  79. package/src/adapter/QueryDb/QueryDbStorage.res +4 -2
  80. package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res +23 -24
  81. package/src/adapter/QueryDb/QueryDbStorage_DynamoDbStream.res +8 -2
  82. package/src/adapter/QueryDb/QueryDbStorage_DynamoDb_Runtime.res +71 -73
  83. package/src/adapter/QueryDb/QueryDbStorage_Postgres.res +5 -2
  84. package/src/adapter/QueryDb/QueryInterceptor_Provisioning.res +1 -4
  85. package/src/adapter/QueryEngine/QueryEngine_DynamoDb.res +8 -2
  86. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro.res +73 -81
  87. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro_Async.res +72 -78
  88. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate.res +44 -67
  89. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate_Async.res +44 -67
  90. package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res +53 -82
  91. package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res +47 -77
  92. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +23 -11
  93. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +5 -1
  94. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +80 -97
  95. package/src/adapter/Runtime/CounterEntryPoint_Ops.res +2 -7
  96. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +16 -10
  97. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +24 -15
  98. package/src/adapter/Runtime/EventCollectorRuntime_Builder_PerEventCollector.res +17 -37
  99. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +31 -57
  100. package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res +18 -24
  101. package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res +1 -5
  102. package/src/adapter/Runtime/ExtensionPointRuntime_Builder_PerExtensionPoint.res +4 -3
  103. package/src/adapter/Runtime/HeartbeatEntryPoint.res +0 -1
  104. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res +4 -2
  105. package/src/adapter/Runtime/PgMigrationEntryPoint.res +0 -1
  106. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +3 -2
  107. package/src/adapter/Runtime/PluginExtensionPointEntryPoint.res +1 -2
  108. package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res +4 -5
  109. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +22 -10
  110. package/src/adapter/Runtime/RuntimeExtensionEntryPoint_Ops.res +8 -2
  111. package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res +14 -31
  112. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +16 -17
  113. package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res +10 -9
  114. package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res +46 -52
  115. package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res +36 -41
  116. package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res +31 -37
  117. package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res +6 -7
  118. package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents.res +4 -6
  119. package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents_Runtime.res +2 -6
  120. package/src/adapter/StateTopic/StateTopic_AppSync.res +21 -16
  121. package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res +36 -32
  122. package/src/adapter/Task/TaskBucket_S3.res +6 -1
  123. package/src/adapter/Upload/Upload_Claim_S3.res +17 -20
  124. package/src/adapter/Upload/Upload_Claim_S3_Ops.res +5 -2
  125. package/src/adapter/Upload/Upload_Presign_S3.res +51 -49
  126. package/src/capability/Capability_Geocoding_AwsLocation.res +4 -1
  127. package/src/capability/Capability_Messaging.res +4 -5
  128. package/src/capability/Capability_Messaging_Ses.res +3 -1
  129. package/src/capability/Capability_ObjectStore_S3.res +1 -7
  130. package/src/components/Api/AppSync_Adapter.res +80 -109
  131. package/src/components/Api/AppSync_Adapter.res.mjs +26 -60
  132. package/src/components/Api/AppSync_SdlDecorate.res +118 -31
  133. package/src/components/Api/AppSync_SdlDecorate.res.mjs +45 -0
  134. package/src/components/AutomationSlice_Builder.res +6 -4
  135. package/src/components/ExtensionPoint_Builder.res +9 -10
  136. package/src/components/InboundTranslationSlice_Builder.res +7 -8
  137. package/src/components/OutboundTranslationSlice_Builder.res +14 -8
  138. package/src/components/OutboundTranslationSlice_Builder.res.mjs +1 -1
  139. package/src/components/SideEffectHandler_Single.res +4 -4
  140. package/src/components/StateViewSlice_Builder.res +6 -4
  141. package/src/components/StateViewSlice_Builder_Stream.res +6 -4
  142. package/src/components/Task_Builder_PerBucket.res +10 -11
  143. package/src/plugin/cloner/ClonerRunner_Fargate.res +3 -10
  144. package/src/plugin/cloner/ClonerRunner_Fargate_Runtime.res +1 -4
  145. package/src/plugin/heartbeat/HeartbeatRunner_CloudWatchEvents.res +5 -5
  146. package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res +19 -21
  147. package/src/plugin/runtime/PluginRuntime_Builder.res +167 -188
  148. package/src/plugin/runtime/Util_PluginMessage_Runtime.res +6 -5
  149. package/src/plugin/stack/Plugin_Stack.res +104 -103
  150. package/src/util/Util_AlarmSpec.res +2 -1
  151. package/src/util/Util_AppSync.res +8 -2
  152. package/src/util/Util_AppSync_Caller.res +15 -14
  153. package/src/util/Util_Bundle.res +46 -42
  154. package/src/util/Util_CognitoGroupUser_Runtime.res +32 -34
  155. package/src/util/Util_Cognito_Runtime.res +11 -7
  156. package/src/util/Util_DeadLetterQueue.res +12 -2
  157. package/src/util/Util_DynamoDb.res +5 -3
  158. package/src/util/Util_DynamoDbStream.res +7 -3
  159. package/src/util/Util_DynamoDbStream_Runtime.res +1 -2
  160. package/src/util/Util_DynamoDb_Runtime.res +23 -41
  161. package/src/util/Util_HostUiDomain.res +6 -1
  162. package/src/util/Util_Kinesis_Runtime.res +10 -11
  163. package/src/util/Util_Lambda.res +12 -5
  164. package/src/util/Util_LambdaEnvBudget.res +5 -5
  165. package/src/util/Util_LambdaLogging.res +1 -2
  166. package/src/util/Util_LogGroup_Adopting.res +1 -4
  167. package/src/util/Util_ResourceNaming.res +1 -2
  168. package/src/util/Util_SNS.res +8 -3
  169. package/src/util/Util_SNS_FIFO.res +1 -1
  170. package/src/util/Util_SNS_Runtime.res +11 -10
  171. package/src/util/Util_SQS.res +4 -2
  172. package/src/util/Util_SQS_FIFO.res +5 -2
  173. package/src/util/Util_SQS_Runtime.res +37 -36
  174. package/src/util/Util_ShellConfig.res +3 -2
  175. package/src/util/Util_StaticBundle.res +1 -3
  176. package/src/util/Util_StoreLayout.res +4 -1
  177. package/tests/AWS_TagsTest.res +16 -5
  178. package/tests/AppSyncEventsSigner_OpsTest.res +11 -2
  179. package/tests/AppSync_AdapterTest.res +286 -224
  180. package/tests/AppSync_AdapterTest.res.mjs +69 -0
  181. package/tests/AppSync_Resolver_RetryingTest.res +64 -44
  182. package/tests/AppSync_RetirementNarrowingTest.res +59 -46
  183. package/tests/AppSync_SdlDecorateTest.res +22 -20
  184. package/tests/AppSync_SourceApiAssociation_RetryingTest.res +40 -22
  185. package/tests/Auth_ActiveRolePoolAttachmentTest.res +114 -107
  186. package/tests/Auth_ActiveRoleStoreTest.res +12 -9
  187. package/tests/Auth_ActiveRoleStore_SchemaTest.res +2 -1
  188. package/tests/Auth_ActiveRoleTrigger_OpsTest.res +20 -16
  189. package/tests/Auth_CognitoTest.res +31 -46
  190. package/tests/Auth_LoginIdentifierTest.res +51 -0
  191. package/tests/Auth_LoginIdentifierTest.res.mjs +51 -0
  192. package/tests/Auth_SignUpModeTest.res +56 -0
  193. package/tests/Auth_SignUpModeTest.res.mjs +53 -0
  194. package/tests/AutomationSliceEntryPoint_OpsTest.res +69 -67
  195. package/tests/BakeConvergenceTest.res +22 -25
  196. package/tests/Capability_MessagingTest.res +28 -12
  197. package/tests/CommandTopicQueuePolicyTest.res +1 -2
  198. package/tests/CounterEntryPoint_OpsTest.res +3 -7
  199. package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res +306 -210
  200. package/tests/EventCollectorConnectLambdaPreviewTest.res +14 -7
  201. package/tests/EventCollectorEntryPoint_OpsTest.res +12 -4
  202. package/tests/EventCollectorPluginDefinitionAssetTest.res +18 -11
  203. package/tests/EventCollectorReadyGateTest.res +5 -4
  204. package/tests/EventLogStorage_DynamoDb_RuntimeTest.res +1 -2
  205. package/tests/EventMapperEntryPoint_OpsTest.res +1 -3
  206. package/tests/ExtensionPointEntryPoint_OpsTest.res +1 -4
  207. package/tests/HandlerFactoryDispatchTest.res +97 -70
  208. package/tests/MCP_LambdaTest.res +16 -27
  209. package/tests/PgChangeFeedRelay_IntegrationTest.res +123 -93
  210. package/tests/PgChangeFeedRelay_RuntimeTest.res +13 -10
  211. package/tests/PgMigrationEntryPoint_IntegrationTest.res +0 -1
  212. package/tests/PgPipeline_IntegrationTest.res +80 -66
  213. package/tests/PgQueryDbUnion_IntegrationTest.res +1 -2
  214. package/tests/PgQueryResolverEntryPoint_OpsTest.res +2 -6
  215. package/tests/PgQueryResolver_LambdaTest.res +41 -24
  216. package/tests/Platform_AdminScan_OpsTest.res +4 -1
  217. package/tests/Platform_ComponentDefinitions_Lambda_OpsTest.res +137 -129
  218. package/tests/ProjectionEntryPoint_DisplayNameTest.res +7 -3
  219. package/tests/ProjectionEntryPoint_UnionStampTest.res +3 -2
  220. package/tests/ProvisionAccountsTest.res +142 -0
  221. package/tests/ProvisionAccountsTest.res.mjs +153 -0
  222. package/tests/ProvisionAdminTest.res +55 -0
  223. package/tests/ProvisionAdminTest.res.mjs +66 -0
  224. package/tests/ProvisionIdentityTest.res +91 -9
  225. package/tests/ProvisionIdentityTest.res.mjs +66 -1
  226. package/tests/QueryDbOwnerIndexTableTest.res +6 -4
  227. package/tests/QueryDbResolvers_AppSyncTest.res +23 -16
  228. package/tests/QueryInterceptor_LambdaTest.res +11 -13
  229. package/tests/ReadModelEntryPoint_OpsTest.res +43 -41
  230. package/tests/RuntimeExtensionEntryPoint_OpsTest.res +4 -3
  231. package/tests/SideEffectEntryPoint_OpsTest.res +23 -18
  232. package/tests/StateChangeDescriptorParityTest.res +28 -26
  233. package/tests/StateTopicKeySchemaTest.res +18 -15
  234. package/tests/StateViewSliceEntryPoint_OpsTest.res +55 -44
  235. package/tests/TaskBucketEntryPoint_OpsTest.res +13 -13
  236. package/tests/Upload_ClaimTest.res +3 -3
  237. package/tests/Util_AwsErrorTest.res +4 -7
  238. package/tests/Util_BundleDependencyClosureTest.res +15 -3
  239. package/tests/Util_BundleEsmLoaderTest.res +3 -3
  240. package/tests/Util_BundleResolveTest.res +4 -6
  241. package/tests/Util_BundleRuntimeExtensionTest.res +17 -18
  242. package/tests/Util_DcbMetricsTest.res +6 -6
  243. package/tests/Util_DynamoDbStreamTest.res +4 -5
  244. package/tests/Util_DynamoDbTest.res +3 -2
  245. package/tests/Util_LambdaEnvBudgetTest.res +22 -18
  246. package/tests/Util_LambdaLoggingTest.res +9 -8
  247. package/tests/Util_LogGroup_AdoptingTest.res +10 -5
  248. package/tests/Util_LogRetentionTest.res +6 -2
  249. package/tests/Util_OwnerScopeEnvTest.res +35 -8
  250. package/tests/Util_OwnerScopeEnvTest.res.mjs +13 -0
  251. package/tests/Util_SQS_RuntimeTest.res +5 -7
  252. package/tests/Util_ShellConfigTest.res +7 -17
  253. package/tests/Util_StaticBundleTest.res +3 -8
  254. package/tests/Util_StoreLayoutTest.res +17 -12
  255. package/tests/integration/AggregateEntryPoint_IntegrationTest.res +1 -4
  256. package/tests/integration/DcbCommandTopicEntryPoint_IntegrationTest.res +15 -15
  257. package/tests/integration/DcbEventLogStorage_DynamoDb_IntegrationTest.res +296 -232
  258. package/tests/integration/DcbIntegrationHarness.res +8 -8
  259. package/tests/integration/EpCompositeSlice.res +16 -8
  260. package/tests/integration/QueryDbUnion_DynamoDb_IntegrationTest.res +8 -10
  261. package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res +10 -10
  262. package/tests/integration/SvsTestSlice_Projection.res +1 -3
  263. package/tests/util/Util_AlarmSpecTest.res +62 -49
@@ -0,0 +1,142 @@
1
+ open JestGlobals
2
+
3
+ // What can be decided before an AWS call is made: which pool and which manifest
4
+ // the run is about, and which usernames a pool could never authenticate.
5
+ //
6
+ // The applying itself is not covered here — it is four SDK calls in a loop. What
7
+ // is covered is the preflight, because its whole purpose is to fail *before*
8
+ // half a cast exists, and the argument parsing, because getting it wrong
9
+ // provisions the right accounts into the wrong deployment.
10
+ //
11
+ // The manifest's own rules — what a second run does to a password already in the
12
+ // file — live with the format, in `spec`'s `AccountsManifestTest`.
13
+
14
+ module Provision = ProvisionAccounts
15
+
16
+ describe("ProvisionAccounts.parseArgs", () => {
17
+ testSync("no arguments leaves every choice unanswered", () =>
18
+ expect(Provision.parseArgs([]))->toEqual(
19
+ Ok({Provision.providerId: None, stack: None, file: None, help: false}),
20
+ )
21
+ )
22
+
23
+ testSync("a pool and a manifest parse", () =>
24
+ expect(
25
+ Provision.parseArgs(["--provider-id", "eu-west-1_AbCdEfGhI", "--file", "cast.yaml"]),
26
+ )->toEqual(
27
+ Ok({
28
+ Provision.providerId: Some("eu-west-1_AbCdEfGhI"),
29
+ stack: None,
30
+ file: Some("cast.yaml"),
31
+ help: false,
32
+ }),
33
+ )
34
+ )
35
+
36
+ testSync("--stack picks the deployment to read the pool id from", () =>
37
+ expect(Provision.parseArgs(["--stack", "beta"]))->toEqual(
38
+ Ok({Provision.providerId: None, stack: Some("beta"), file: None, help: false}),
39
+ )
40
+ )
41
+
42
+ // The reason unknown flags refuse rather than being skipped, and it lands
43
+ // harder here than on the first administrator: a typo'd `--provider-id` falls
44
+ // through to the environment and creates the whole cast in a different pool
45
+ // than the operator named.
46
+ testSync("an unknown flag refuses rather than being ignored", () =>
47
+ expect(Provision.parseArgs(["--pool", "eu-west-1_AbCdEfGhI"]))->toEqual(
48
+ Error(`unknown argument "--pool"`),
49
+ )
50
+ )
51
+
52
+ testSync("a flag with no value refuses", () =>
53
+ expect(Provision.parseArgs(["--provider-id"]))->toEqual(Error("--provider-id needs a value"))
54
+ )
55
+
56
+ testSync("-h asks for the usage", () =>
57
+ expect(Provision.parseArgs(["-h"]))->toEqual(
58
+ Ok({Provision.providerId: None, stack: None, file: None, help: true}),
59
+ )
60
+ )
61
+ })
62
+
63
+ // Which pool a run writes to, which is the one decision that is expensive to get
64
+ // wrong: provisioning a cast into the wrong deployment succeeds, and leaves
65
+ // working accounts somewhere nobody is looking.
66
+ //
67
+ // The stack fallback is not exercised here — it shells out to `pulumi`, and a
68
+ // test that did would depend on a logged-in CLI and a deployed stack. What is
69
+ // covered is the precedence above it, and that every answer says where it came
70
+ // from, which is what makes a wrong-pool run visible rather than silent.
71
+ describe("ProvisionProvider.resolve", () => {
72
+ let withoutEnv = fn => {
73
+ let saved = NodeProcess.env->Dict.get(ProvisionProvider.envKey)
74
+ NodeProcess.env->Dict.delete(ProvisionProvider.envKey)
75
+ let outcome = fn()
76
+ switch saved {
77
+ | Some(value) => NodeProcess.env->Dict.set(ProvisionProvider.envKey, value)
78
+ | None => ()
79
+ }
80
+ outcome
81
+ }
82
+
83
+ testSync("an explicit id wins, and says so", () =>
84
+ expect(
85
+ withoutEnv(
86
+ () => ProvisionProvider.resolve(~given=Some("eu-west-1_Explicit"), ~stack=Some("alpha")),
87
+ ),
88
+ )->toEqual(Ok(("eu-west-1_Explicit", ProvisionProvider.Flag)))
89
+ )
90
+
91
+ // The flag beats the variable rather than the other way round: a CI shell that
92
+ // exports one for every stack must still be overridable for a single run.
93
+ testSync("an explicit id beats the environment", () => {
94
+ NodeProcess.env->Dict.set(ProvisionProvider.envKey, "eu-west-1_FromEnv")
95
+ let resolved = ProvisionProvider.resolve(~given=Some("eu-west-1_Explicit"), ~stack=None)
96
+ NodeProcess.env->Dict.delete(ProvisionProvider.envKey)
97
+ expect(resolved)->toEqual(Ok(("eu-west-1_Explicit", ProvisionProvider.Flag)))
98
+ })
99
+
100
+ testSync("the environment answers when no flag does", () => {
101
+ NodeProcess.env->Dict.set(ProvisionProvider.envKey, "eu-west-1_FromEnv")
102
+ let resolved = ProvisionProvider.resolve(~given=None, ~stack=None)
103
+ NodeProcess.env->Dict.delete(ProvisionProvider.envKey)
104
+ expect(resolved)->toEqual(Ok(("eu-west-1_FromEnv", ProvisionProvider.Environment)))
105
+ })
106
+
107
+ // An empty variable is not an answer. Exporting it unset is the ordinary shape
108
+ // of a CI script whose earlier step did not run, and reading "" as the pool id
109
+ // would fail much later with a message about Cognito rather than about setup.
110
+ testSync("an empty environment variable is not an answer", () =>
111
+ expect({
112
+ NodeProcess.env->Dict.set(ProvisionProvider.envKey, " ")
113
+ let resolved = ProvisionProvider.resolve(~given=None, ~stack=Some("no-such-stack-here"))
114
+ NodeProcess.env->Dict.delete(ProvisionProvider.envKey)
115
+ resolved->Result.isError
116
+ })->toBe(true)
117
+ )
118
+
119
+ testSync("each source describes itself", () => {
120
+ expect(ProvisionProvider.describe(Flag))->toBe("--provider-id")
121
+ expect(ProvisionProvider.describe(Environment))->toBe("REVENTLESS_IDENTITY_PROVIDER_ID")
122
+ expect(ProvisionProvider.describe(Stack("beta")))->toBe("stack beta")
123
+ })
124
+ })
125
+
126
+ // A manifest usually names plain usernames — `shopper`, `merch` — and a pool
127
+ // created with `UsernameAttributes: [email]` will not take them. The accounts it
128
+ // makes instead are correctly created and cannot authenticate, which is a symptom
129
+ // that arrives at a login screen far from the run that caused it.
130
+ describe("ProvisionAccounts.looksLikeEmail", () => {
131
+ testSync("an address is one", () =>
132
+ expect(Provision.looksLikeEmail("me@example.com"))->toBe(true)
133
+ )
134
+
135
+ testSync("a demo username is not", () => expect(Provision.looksLikeEmail("shopper"))->toBe(false))
136
+
137
+ // Not a validator — Cognito owns that. It answers one question: would this pool
138
+ // accept this string as a sign-in name. A local part with no domain would not.
139
+ testSync("an @ with no domain dot is not", () =>
140
+ expect(Provision.looksLikeEmail("admin@localhost"))->toBe(false)
141
+ )
142
+ })
@@ -0,0 +1,153 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
4
+ import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
5
+ import * as ProvisionAccounts$ReventlessAws from "../scripts/ProvisionAccounts.res.mjs";
6
+ import * as ProvisionProvider$ReventlessAws from "../scripts/ProvisionProvider.res.mjs";
7
+
8
+ globalThis.describe("ProvisionAccounts.parseArgs", () => {
9
+ globalThis.test("no arguments leaves every choice unanswered", () => {
10
+ globalThis.expect(ProvisionAccounts$ReventlessAws.parseArgs([])).toEqual({
11
+ TAG: "Ok",
12
+ _0: {
13
+ providerId: undefined,
14
+ stack: undefined,
15
+ file: undefined,
16
+ help: false
17
+ }
18
+ });
19
+ });
20
+ globalThis.test("a pool and a manifest parse", () => {
21
+ globalThis.expect(ProvisionAccounts$ReventlessAws.parseArgs([
22
+ "--provider-id",
23
+ "eu-west-1_AbCdEfGhI",
24
+ "--file",
25
+ "cast.yaml"
26
+ ])).toEqual({
27
+ TAG: "Ok",
28
+ _0: {
29
+ providerId: "eu-west-1_AbCdEfGhI",
30
+ stack: undefined,
31
+ file: "cast.yaml",
32
+ help: false
33
+ }
34
+ });
35
+ });
36
+ globalThis.test("--stack picks the deployment to read the pool id from", () => {
37
+ globalThis.expect(ProvisionAccounts$ReventlessAws.parseArgs([
38
+ "--stack",
39
+ "beta"
40
+ ])).toEqual({
41
+ TAG: "Ok",
42
+ _0: {
43
+ providerId: undefined,
44
+ stack: "beta",
45
+ file: undefined,
46
+ help: false
47
+ }
48
+ });
49
+ });
50
+ globalThis.test("an unknown flag refuses rather than being ignored", () => {
51
+ globalThis.expect(ProvisionAccounts$ReventlessAws.parseArgs([
52
+ "--pool",
53
+ "eu-west-1_AbCdEfGhI"
54
+ ])).toEqual({
55
+ TAG: "Error",
56
+ _0: `unknown argument "--pool"`
57
+ });
58
+ });
59
+ globalThis.test("a flag with no value refuses", () => {
60
+ globalThis.expect(ProvisionAccounts$ReventlessAws.parseArgs(["--provider-id"])).toEqual({
61
+ TAG: "Error",
62
+ _0: "--provider-id needs a value"
63
+ });
64
+ });
65
+ globalThis.test("-h asks for the usage", () => {
66
+ globalThis.expect(ProvisionAccounts$ReventlessAws.parseArgs(["-h"])).toEqual({
67
+ TAG: "Ok",
68
+ _0: {
69
+ providerId: undefined,
70
+ stack: undefined,
71
+ file: undefined,
72
+ help: true
73
+ }
74
+ });
75
+ });
76
+ });
77
+
78
+ globalThis.describe("ProvisionProvider.resolve", () => {
79
+ let withoutEnv = fn => {
80
+ let saved = process.env[ProvisionProvider$ReventlessAws.envKey];
81
+ Stdlib_Dict.$$delete(process.env, ProvisionProvider$ReventlessAws.envKey);
82
+ let outcome = fn();
83
+ if (saved !== undefined) {
84
+ process.env[ProvisionProvider$ReventlessAws.envKey] = saved;
85
+ }
86
+ return outcome;
87
+ };
88
+ globalThis.test("an explicit id wins, and says so", () => {
89
+ globalThis.expect(withoutEnv(() => ProvisionProvider$ReventlessAws.resolve("eu-west-1_Explicit", "alpha"))).toEqual({
90
+ TAG: "Ok",
91
+ _0: [
92
+ "eu-west-1_Explicit",
93
+ "Flag"
94
+ ]
95
+ });
96
+ });
97
+ globalThis.test("an explicit id beats the environment", () => {
98
+ process.env[ProvisionProvider$ReventlessAws.envKey] = "eu-west-1_FromEnv";
99
+ let resolved = ProvisionProvider$ReventlessAws.resolve("eu-west-1_Explicit", undefined);
100
+ Stdlib_Dict.$$delete(process.env, ProvisionProvider$ReventlessAws.envKey);
101
+ globalThis.expect(resolved).toEqual({
102
+ TAG: "Ok",
103
+ _0: [
104
+ "eu-west-1_Explicit",
105
+ "Flag"
106
+ ]
107
+ });
108
+ });
109
+ globalThis.test("the environment answers when no flag does", () => {
110
+ process.env[ProvisionProvider$ReventlessAws.envKey] = "eu-west-1_FromEnv";
111
+ let resolved = ProvisionProvider$ReventlessAws.resolve(undefined, undefined);
112
+ Stdlib_Dict.$$delete(process.env, ProvisionProvider$ReventlessAws.envKey);
113
+ globalThis.expect(resolved).toEqual({
114
+ TAG: "Ok",
115
+ _0: [
116
+ "eu-west-1_FromEnv",
117
+ "Environment"
118
+ ]
119
+ });
120
+ });
121
+ globalThis.test("an empty environment variable is not an answer", () => {
122
+ process.env[ProvisionProvider$ReventlessAws.envKey] = " ";
123
+ let resolved = ProvisionProvider$ReventlessAws.resolve(undefined, "no-such-stack-here");
124
+ globalThis.expect((Stdlib_Dict.$$delete(process.env, ProvisionProvider$ReventlessAws.envKey), Stdlib_Result.isError(resolved))).toBe(true);
125
+ });
126
+ globalThis.test("each source describes itself", () => {
127
+ globalThis.expect(ProvisionProvider$ReventlessAws.describe("Flag")).toBe("--provider-id");
128
+ globalThis.expect(ProvisionProvider$ReventlessAws.describe("Environment")).toBe("REVENTLESS_IDENTITY_PROVIDER_ID");
129
+ globalThis.expect(ProvisionProvider$ReventlessAws.describe({
130
+ TAG: "Stack",
131
+ _0: "beta"
132
+ })).toBe("stack beta");
133
+ });
134
+ });
135
+
136
+ globalThis.describe("ProvisionAccounts.looksLikeEmail", () => {
137
+ globalThis.test("an address is one", () => {
138
+ globalThis.expect(ProvisionAccounts$ReventlessAws.looksLikeEmail("me@example.com")).toBe(true);
139
+ });
140
+ globalThis.test("a demo username is not", () => {
141
+ globalThis.expect(ProvisionAccounts$ReventlessAws.looksLikeEmail("shopper")).toBe(false);
142
+ });
143
+ globalThis.test("an @ with no domain dot is not", () => {
144
+ globalThis.expect(ProvisionAccounts$ReventlessAws.looksLikeEmail("admin@localhost")).toBe(false);
145
+ });
146
+ });
147
+
148
+ let Provision;
149
+
150
+ export {
151
+ Provision,
152
+ }
153
+ /* Not a pure module */
@@ -0,0 +1,55 @@
1
+ open JestGlobals
2
+
3
+ // The bootstrap script's decidable part: which pool and which person the run is
4
+ // about. Reachable at all only because the module defines `main` without calling
5
+ // it — `run-provision-admin.mjs` does that — which is the same reason its sibling
6
+ // is testable.
7
+ //
8
+ // The AWS calls themselves are not covered here; what is covered is everything
9
+ // that can be wrong *before* one is made. The credential this script hands out is
10
+ // no longer its own — the generator moved to `spec` so the manifest's
11
+ // platform-neutral half could reach it, and `Util_PasswordTest` holds the policy
12
+ // it has to satisfy.
13
+
14
+ module Provision = ProvisionAdmin
15
+
16
+ describe("ProvisionAdmin.parseArgs", () => {
17
+ testSync("both arguments parse", () =>
18
+ expect(
19
+ Provision.parseArgs(["--provider-id", "eu-west-1_AbCdEfGhI", "--email", "me@example.com"]),
20
+ )->toEqual(
21
+ Ok({
22
+ Provision.providerId: Some("eu-west-1_AbCdEfGhI"),
23
+ stack: None,
24
+ email: Some("me@example.com"),
25
+ help: false,
26
+ }),
27
+ )
28
+ )
29
+
30
+ testSync("no arguments leaves both unanswered", () =>
31
+ expect(Provision.parseArgs([]))->toEqual(
32
+ Ok({Provision.providerId: None, stack: None, email: None, help: false}),
33
+ )
34
+ )
35
+
36
+ // The reason unknown flags refuse rather than being skipped: a typo'd
37
+ // `--provider-id` would fall through to the environment and make the
38
+ // administrator in a different pool than the operator named — a successful run
39
+ // against the wrong deployment, which is worse than a failed one.
40
+ testSync("an unknown flag refuses rather than being ignored", () =>
41
+ expect(Provision.parseArgs(["--provider", "eu-west-1_AbCdEfGhI"]))->toEqual(
42
+ Error(`unknown argument "--provider"`),
43
+ )
44
+ )
45
+
46
+ testSync("a flag with no value refuses", () =>
47
+ expect(Provision.parseArgs(["--email"]))->toEqual(Error("--email needs a value"))
48
+ )
49
+
50
+ testSync("-h asks for the usage", () =>
51
+ expect(Provision.parseArgs(["-h"]))->toEqual(
52
+ Ok({Provision.providerId: None, stack: None, email: None, help: true}),
53
+ )
54
+ )
55
+ })
@@ -0,0 +1,66 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as ProvisionAdmin$ReventlessAws from "../scripts/ProvisionAdmin.res.mjs";
4
+
5
+ globalThis.describe("ProvisionAdmin.parseArgs", () => {
6
+ globalThis.test("both arguments parse", () => {
7
+ globalThis.expect(ProvisionAdmin$ReventlessAws.parseArgs([
8
+ "--provider-id",
9
+ "eu-west-1_AbCdEfGhI",
10
+ "--email",
11
+ "me@example.com"
12
+ ])).toEqual({
13
+ TAG: "Ok",
14
+ _0: {
15
+ providerId: "eu-west-1_AbCdEfGhI",
16
+ stack: undefined,
17
+ email: "me@example.com",
18
+ help: false
19
+ }
20
+ });
21
+ });
22
+ globalThis.test("no arguments leaves both unanswered", () => {
23
+ globalThis.expect(ProvisionAdmin$ReventlessAws.parseArgs([])).toEqual({
24
+ TAG: "Ok",
25
+ _0: {
26
+ providerId: undefined,
27
+ stack: undefined,
28
+ email: undefined,
29
+ help: false
30
+ }
31
+ });
32
+ });
33
+ globalThis.test("an unknown flag refuses rather than being ignored", () => {
34
+ globalThis.expect(ProvisionAdmin$ReventlessAws.parseArgs([
35
+ "--provider",
36
+ "eu-west-1_AbCdEfGhI"
37
+ ])).toEqual({
38
+ TAG: "Error",
39
+ _0: `unknown argument "--provider"`
40
+ });
41
+ });
42
+ globalThis.test("a flag with no value refuses", () => {
43
+ globalThis.expect(ProvisionAdmin$ReventlessAws.parseArgs(["--email"])).toEqual({
44
+ TAG: "Error",
45
+ _0: "--email needs a value"
46
+ });
47
+ });
48
+ globalThis.test("-h asks for the usage", () => {
49
+ globalThis.expect(ProvisionAdmin$ReventlessAws.parseArgs(["-h"])).toEqual({
50
+ TAG: "Ok",
51
+ _0: {
52
+ providerId: undefined,
53
+ stack: undefined,
54
+ email: undefined,
55
+ help: true
56
+ }
57
+ });
58
+ });
59
+ });
60
+
61
+ let Provision;
62
+
63
+ export {
64
+ Provision,
65
+ }
66
+ /* Not a pure module */
@@ -16,6 +16,8 @@ describe("ProvisionIdentity.parseArgs", () => {
16
16
  Ok({
17
17
  Provision.poolName: Provision.defaultPoolName,
18
18
  providerId: None,
19
+ loginIdentifier: Auth_LoginIdentifier.Email,
20
+ signUpMode: Auth_SignUpMode.AdminOnly,
19
21
  help: false,
20
22
  }),
21
23
  )
@@ -26,6 +28,8 @@ describe("ProvisionIdentity.parseArgs", () => {
26
28
  Ok({
27
29
  Provision.poolName: Provision.defaultPoolName,
28
30
  providerId: Some("eu-west-1_AbCdEfGhI"),
31
+ loginIdentifier: Auth_LoginIdentifier.Email,
32
+ signUpMode: Auth_SignUpMode.AdminOnly,
29
33
  help: false,
30
34
  }),
31
35
  )
@@ -33,13 +37,60 @@ describe("ProvisionIdentity.parseArgs", () => {
33
37
 
34
38
  testSync("--name selects which pool to adopt by name", () =>
35
39
  expect(Provision.parseArgs(["--name", "examples-dev"]))->toEqual(
36
- Ok({Provision.poolName: "examples-dev", providerId: None, help: false}),
40
+ Ok({
41
+ Provision.poolName: "examples-dev",
42
+ providerId: None,
43
+ loginIdentifier: Auth_LoginIdentifier.Email,
44
+ signUpMode: Auth_SignUpMode.AdminOnly,
45
+ help: false,
46
+ }),
37
47
  )
38
48
  )
39
49
 
40
50
  testSync("both together are accepted, and the id wins at resolve time", () =>
41
51
  expect(Provision.parseArgs(["--name", "ignored", "--provider-id", "eu-west-1_x"]))->toEqual(
42
- Ok({Provision.poolName: "ignored", providerId: Some("eu-west-1_x"), help: false}),
52
+ Ok({
53
+ Provision.poolName: "ignored",
54
+ providerId: Some("eu-west-1_x"),
55
+ loginIdentifier: Auth_LoginIdentifier.Email,
56
+ signUpMode: Auth_SignUpMode.AdminOnly,
57
+ help: false,
58
+ }),
59
+ )
60
+ )
61
+
62
+ testSync("--login-identifier picks the sign-in attribute", () =>
63
+ expect(
64
+ Provision.parseArgs(["--login-identifier", "emailOrPhone"])->Result.map(
65
+ a => a.loginIdentifier,
66
+ ),
67
+ )->toEqual(Ok(Auth_LoginIdentifier.EmailOrPhone))
68
+ )
69
+
70
+ // 🚨 The sign-in attribute cannot be changed once the pool exists, so a typo
71
+ // that fell back to the default would create a pool the operator has to
72
+ // rebuild — and re-register every account in — to correct.
73
+ testSync("an unrecognised sign-in attribute is refused, never defaulted", () =>
74
+ expect(Provision.parseArgs(["--login-identifier", "e-mail"])->Result.isError)->toBe(true)
75
+ )
76
+
77
+ testSync("--sign-up-mode opens the pool to self-registration", () =>
78
+ expect(
79
+ Provision.parseArgs(["--sign-up-mode", "selfService"])->Result.map(a => a.signUpMode),
80
+ )->toEqual(Ok(Auth_SignUpMode.SelfService))
81
+ )
82
+
83
+ // This one IS correctable later, unlike the sign-in attribute — it is refused
84
+ // because the mistake is silent, not because it is permanent. Defaulting past
85
+ // a typo leaves a pool that refuses every registration while the command line
86
+ // reads as though it allows them.
87
+ testSync("an unrecognised sign-up mode is refused, never defaulted", () =>
88
+ expect(Provision.parseArgs(["--sign-up-mode", "self-service"])->Result.isError)->toBe(true)
89
+ )
90
+
91
+ testSync("sign-up mode defaults to admin-only, so an existing pool is unchanged", () =>
92
+ expect(Provision.parseArgs([])->Result.map(a => a.signUpMode))->toEqual(
93
+ Ok(Auth_SignUpMode.AdminOnly),
43
94
  )
44
95
  )
45
96
 
@@ -55,15 +106,19 @@ describe("ProvisionIdentity.parseArgs", () => {
55
106
  // Same hazard from the other direction: a flag whose value was left off must
56
107
  // not read as "no id given".
57
108
  testSync("a flag with no value is refused", () =>
58
- expect(Provision.parseArgs(["--provider-id"]))->toEqual(
59
- Error("--provider-id needs a value"),
60
- )
109
+ expect(Provision.parseArgs(["--provider-id"]))->toEqual(Error("--provider-id needs a value"))
61
110
  )
62
111
 
63
112
  testSync("--name with no value is refused too", () =>
64
113
  expect(Provision.parseArgs(["--name"]))->toEqual(Error("--name needs a value"))
65
114
  )
66
115
 
116
+ testSync("--login-identifier with no value is refused too", () =>
117
+ expect(Provision.parseArgs(["--login-identifier"]))->toEqual(
118
+ Error("--login-identifier needs a value"),
119
+ )
120
+ )
121
+
67
122
  testSync("--help and -h both ask for the usage text", () =>
68
123
  expect((
69
124
  Provision.parseArgs(["--help"])->Result.map(a => a.help),
@@ -85,12 +140,14 @@ describe("ProvisionIdentity.parseArgs", () => {
85
140
  // touches it — so its settings are not a detail an operator can revise cheaply
86
141
  // once accounts exist in it.
87
142
  describe("ProvisionIdentity.poolSettings", () => {
88
- let settings = Provision.poolSettings(~poolName="MyIdentity")
89
-
90
- testSync("carries the name it was asked for", () =>
91
- expect(settings.poolName)->toBe("MyIdentity")
143
+ let settings = Provision.poolSettings(
144
+ ~poolName="MyIdentity",
145
+ ~loginIdentifier=Auth_LoginIdentifier.default,
146
+ ~signUpMode=Auth_SignUpMode.default,
92
147
  )
93
148
 
149
+ testSync("carries the name it was asked for", () => expect(settings.poolName)->toBe("MyIdentity"))
150
+
94
151
  testSync("matches what auto mode declares: email sign-in, no MFA, admin-only", () =>
95
152
  expect((
96
153
  settings.usernameAttributes,
@@ -99,6 +156,31 @@ describe("ProvisionIdentity.poolSettings", () => {
99
156
  ))->toEqual((Some(["email"]), Some("OFF"), Some(true)))
100
157
  )
101
158
 
159
+ testSync("a phone pool is created signing in on phone_number", () =>
160
+ expect(
161
+ Provision.poolSettings(
162
+ ~poolName="MyIdentity",
163
+ ~loginIdentifier=Auth_LoginIdentifier.Phone,
164
+ ~signUpMode=Auth_SignUpMode.default,
165
+ ).usernameAttributes,
166
+ )->toEqual(Some(["phone_number"]))
167
+ )
168
+
169
+ // The negation is the part worth pinning: this type asks who may sign up, and
170
+ // Cognito's field asks the opposite question. Reading it backwards would open a
171
+ // pool that was meant to stay closed.
172
+ testSync("selfService clears allowAdminCreateUserOnly, adminOnly sets it", () => {
173
+ let openPool = Provision.poolSettings(
174
+ ~poolName="MyIdentity",
175
+ ~loginIdentifier=Auth_LoginIdentifier.default,
176
+ ~signUpMode=Auth_SignUpMode.SelfService,
177
+ )
178
+ expect((
179
+ openPool.adminCreateUserConfig->Option.flatMap(c => c.allowAdminCreateUserOnly),
180
+ settings.adminCreateUserConfig->Option.flatMap(c => c.allowAdminCreateUserOnly),
181
+ ))->toEqual((Some(false), Some(true)))
182
+ })
183
+
102
184
  testSync("keeps the 12-character password policy", () =>
103
185
  expect(
104
186
  settings.policies