@stacksjs/ts-cloud-core 0.1.7 → 0.1.9

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 (225) hide show
  1. package/package.json +7 -6
  2. package/src/advanced-features.test.ts +465 -0
  3. package/src/aws/cloudformation.ts +421 -0
  4. package/src/aws/cloudfront.ts +158 -0
  5. package/src/aws/credentials.test.ts +132 -0
  6. package/src/aws/credentials.ts +545 -0
  7. package/src/aws/index.ts +87 -0
  8. package/src/aws/s3.test.ts +188 -0
  9. package/src/aws/s3.ts +1088 -0
  10. package/src/aws/signature.test.ts +670 -0
  11. package/src/aws/signature.ts +1155 -0
  12. package/src/backup/disaster-recovery.test.ts +726 -0
  13. package/src/backup/disaster-recovery.ts +500 -0
  14. package/src/backup/index.ts +34 -0
  15. package/src/backup/manager.test.ts +498 -0
  16. package/src/backup/manager.ts +432 -0
  17. package/src/cicd/circleci.ts +430 -0
  18. package/src/cicd/github-actions.ts +424 -0
  19. package/src/cicd/gitlab-ci.ts +255 -0
  20. package/src/cicd/index.ts +8 -0
  21. package/src/cli/history.ts +396 -0
  22. package/src/cli/index.ts +10 -0
  23. package/src/cli/progress.ts +458 -0
  24. package/src/cli/repl.ts +454 -0
  25. package/src/cli/suggestions.ts +327 -0
  26. package/src/cli/table.test.ts +319 -0
  27. package/src/cli/table.ts +332 -0
  28. package/src/cloudformation/builder.test.ts +327 -0
  29. package/src/cloudformation/builder.ts +378 -0
  30. package/src/cloudformation/builders/api-gateway.ts +449 -0
  31. package/src/cloudformation/builders/cache.ts +334 -0
  32. package/src/cloudformation/builders/cdn.ts +278 -0
  33. package/src/cloudformation/builders/compute.ts +485 -0
  34. package/src/cloudformation/builders/database.ts +392 -0
  35. package/src/cloudformation/builders/functions.ts +343 -0
  36. package/src/cloudformation/builders/messaging.ts +140 -0
  37. package/src/cloudformation/builders/monitoring.ts +300 -0
  38. package/src/cloudformation/builders/network.ts +264 -0
  39. package/src/cloudformation/builders/queue.ts +147 -0
  40. package/src/cloudformation/builders/security.ts +399 -0
  41. package/src/cloudformation/builders/storage.ts +285 -0
  42. package/src/cloudformation/index.ts +30 -0
  43. package/src/cloudformation/types.ts +173 -0
  44. package/src/compliance/aws-config.ts +543 -0
  45. package/src/compliance/cloudtrail.ts +376 -0
  46. package/src/compliance/compliance.test.ts +423 -0
  47. package/src/compliance/guardduty.ts +446 -0
  48. package/src/compliance/index.ts +66 -0
  49. package/src/compliance/security-hub.ts +456 -0
  50. package/src/containers/build-optimization.ts +416 -0
  51. package/src/containers/containers.test.ts +508 -0
  52. package/src/containers/image-scanning.ts +360 -0
  53. package/src/containers/index.ts +9 -0
  54. package/src/containers/registry.ts +293 -0
  55. package/src/containers/service-mesh.ts +520 -0
  56. package/src/database/database.test.ts +762 -0
  57. package/src/database/index.ts +9 -0
  58. package/src/database/migrations.ts +444 -0
  59. package/src/database/performance.ts +528 -0
  60. package/src/database/replicas.ts +534 -0
  61. package/src/database/users.ts +494 -0
  62. package/src/dependency-graph.ts +143 -0
  63. package/src/deployment/ab-testing.ts +582 -0
  64. package/src/deployment/blue-green.ts +452 -0
  65. package/src/deployment/canary.ts +500 -0
  66. package/src/deployment/deployment.test.ts +526 -0
  67. package/src/deployment/index.ts +61 -0
  68. package/src/deployment/progressive.ts +62 -0
  69. package/src/dns/dns.test.ts +641 -0
  70. package/src/dns/dnssec.ts +315 -0
  71. package/src/dns/index.ts +8 -0
  72. package/src/dns/resolver.ts +496 -0
  73. package/src/dns/routing.ts +593 -0
  74. package/src/email/advanced/analytics.ts +445 -0
  75. package/src/email/advanced/index.ts +11 -0
  76. package/src/email/advanced/rules.ts +465 -0
  77. package/src/email/advanced/scheduling.ts +352 -0
  78. package/src/email/advanced/search.ts +412 -0
  79. package/src/email/advanced/shared-mailboxes.ts +404 -0
  80. package/src/email/advanced/templates.ts +455 -0
  81. package/src/email/advanced/threading.ts +281 -0
  82. package/src/email/analytics.ts +467 -0
  83. package/src/email/bounce-handling.ts +425 -0
  84. package/src/email/email.test.ts +431 -0
  85. package/src/email/handlers/__tests__/inbound.test.ts +38 -0
  86. package/src/email/handlers/__tests__/outbound.test.ts +37 -0
  87. package/src/email/handlers/converter.ts +227 -0
  88. package/src/email/handlers/feedback.ts +228 -0
  89. package/src/email/handlers/inbound.ts +169 -0
  90. package/src/email/handlers/outbound.ts +178 -0
  91. package/src/email/index.ts +15 -0
  92. package/src/email/reputation.ts +303 -0
  93. package/src/email/templates.ts +352 -0
  94. package/src/errors/index.test.ts +434 -0
  95. package/src/errors/index.ts +416 -0
  96. package/src/health-checks/index.ts +40 -0
  97. package/src/index.ts +360 -0
  98. package/src/intrinsic-functions.ts +118 -0
  99. package/src/lambda/concurrency.ts +330 -0
  100. package/src/lambda/destinations.ts +345 -0
  101. package/src/lambda/dlq.ts +425 -0
  102. package/src/lambda/index.ts +11 -0
  103. package/src/lambda/lambda.test.ts +840 -0
  104. package/src/lambda/layers.ts +263 -0
  105. package/src/lambda/versions.ts +376 -0
  106. package/src/lambda/vpc.ts +399 -0
  107. package/src/local/config.ts +114 -0
  108. package/src/local/index.ts +6 -0
  109. package/src/local/mock-aws.ts +351 -0
  110. package/src/modules/ai.ts +340 -0
  111. package/src/modules/api.ts +478 -0
  112. package/src/modules/auth.ts +805 -0
  113. package/src/modules/cache.ts +417 -0
  114. package/src/modules/cdn.ts +1062 -0
  115. package/src/modules/communication.ts +1094 -0
  116. package/src/modules/compute.ts +3348 -0
  117. package/src/modules/database.ts +554 -0
  118. package/src/modules/deployment.ts +1079 -0
  119. package/src/modules/dns.ts +337 -0
  120. package/src/modules/email.ts +1538 -0
  121. package/src/modules/filesystem.ts +515 -0
  122. package/src/modules/index.ts +32 -0
  123. package/src/modules/messaging.ts +486 -0
  124. package/src/modules/monitoring.ts +2086 -0
  125. package/src/modules/network.ts +664 -0
  126. package/src/modules/parameter-store.ts +325 -0
  127. package/src/modules/permissions.ts +1081 -0
  128. package/src/modules/phone.ts +494 -0
  129. package/src/modules/queue.ts +1260 -0
  130. package/src/modules/redirects.ts +464 -0
  131. package/src/modules/registry.ts +699 -0
  132. package/src/modules/search.ts +401 -0
  133. package/src/modules/secrets.ts +416 -0
  134. package/src/modules/security.ts +731 -0
  135. package/src/modules/sms.ts +389 -0
  136. package/src/modules/storage.ts +1120 -0
  137. package/src/modules/workflow.ts +680 -0
  138. package/src/multi-account/config.ts +521 -0
  139. package/src/multi-account/index.ts +7 -0
  140. package/src/multi-account/manager.ts +427 -0
  141. package/src/multi-region/cross-region.ts +410 -0
  142. package/src/multi-region/index.ts +8 -0
  143. package/src/multi-region/manager.ts +483 -0
  144. package/src/multi-region/regions.ts +435 -0
  145. package/src/network-security/index.ts +48 -0
  146. package/src/observability/index.ts +9 -0
  147. package/src/observability/logs.ts +522 -0
  148. package/src/observability/metrics.ts +460 -0
  149. package/src/observability/observability.test.ts +782 -0
  150. package/src/observability/synthetics.ts +568 -0
  151. package/src/observability/xray.ts +358 -0
  152. package/src/phone/advanced/analytics.ts +349 -0
  153. package/src/phone/advanced/callbacks.ts +428 -0
  154. package/src/phone/advanced/index.ts +8 -0
  155. package/src/phone/advanced/ivr-builder.ts +504 -0
  156. package/src/phone/advanced/recording.ts +310 -0
  157. package/src/phone/handlers/__tests__/incoming-call.test.ts +40 -0
  158. package/src/phone/handlers/incoming-call.ts +117 -0
  159. package/src/phone/handlers/missed-call.ts +116 -0
  160. package/src/phone/handlers/voicemail.ts +179 -0
  161. package/src/phone/index.ts +9 -0
  162. package/src/presets/api-backend.ts +134 -0
  163. package/src/presets/data-pipeline.ts +204 -0
  164. package/src/presets/extend.test.ts +295 -0
  165. package/src/presets/extend.ts +297 -0
  166. package/src/presets/fullstack-app.ts +144 -0
  167. package/src/presets/index.ts +27 -0
  168. package/src/presets/jamstack.ts +135 -0
  169. package/src/presets/microservices.ts +167 -0
  170. package/src/presets/ml-api.ts +208 -0
  171. package/src/presets/nodejs-server.ts +104 -0
  172. package/src/presets/nodejs-serverless.ts +114 -0
  173. package/src/presets/realtime-app.ts +184 -0
  174. package/src/presets/static-site.ts +64 -0
  175. package/src/presets/traditional-web-app.ts +339 -0
  176. package/src/presets/wordpress.ts +138 -0
  177. package/src/preview/github.test.ts +249 -0
  178. package/src/preview/github.ts +297 -0
  179. package/src/preview/index.ts +37 -0
  180. package/src/preview/manager.test.ts +440 -0
  181. package/src/preview/manager.ts +326 -0
  182. package/src/preview/notifications.test.ts +582 -0
  183. package/src/preview/notifications.ts +341 -0
  184. package/src/queue/batch-processing.ts +402 -0
  185. package/src/queue/dlq-monitoring.ts +402 -0
  186. package/src/queue/fifo.ts +342 -0
  187. package/src/queue/index.ts +9 -0
  188. package/src/queue/management.ts +428 -0
  189. package/src/queue/queue.test.ts +429 -0
  190. package/src/resource-mgmt/index.ts +39 -0
  191. package/src/resource-naming.ts +62 -0
  192. package/src/s3/index.ts +523 -0
  193. package/src/schema/cloud-config.schema.json +554 -0
  194. package/src/schema/index.ts +68 -0
  195. package/src/security/certificate-manager.ts +492 -0
  196. package/src/security/index.ts +9 -0
  197. package/src/security/scanning.ts +545 -0
  198. package/src/security/secrets-manager.ts +476 -0
  199. package/src/security/secrets-rotation.ts +456 -0
  200. package/src/security/security.test.ts +738 -0
  201. package/src/sms/advanced/ab-testing.ts +389 -0
  202. package/src/sms/advanced/analytics.ts +336 -0
  203. package/src/sms/advanced/campaigns.ts +523 -0
  204. package/src/sms/advanced/chatbot.ts +224 -0
  205. package/src/sms/advanced/index.ts +10 -0
  206. package/src/sms/advanced/link-tracking.ts +248 -0
  207. package/src/sms/advanced/mms.ts +308 -0
  208. package/src/sms/handlers/__tests__/send.test.ts +40 -0
  209. package/src/sms/handlers/delivery-status.ts +133 -0
  210. package/src/sms/handlers/receive.ts +162 -0
  211. package/src/sms/handlers/send.ts +174 -0
  212. package/src/sms/index.ts +9 -0
  213. package/src/stack-diff.ts +389 -0
  214. package/src/static-site/index.ts +85 -0
  215. package/src/template-builder.ts +110 -0
  216. package/src/template-validator.ts +574 -0
  217. package/src/utils/cache.ts +291 -0
  218. package/src/utils/diff.ts +269 -0
  219. package/src/utils/hash.ts +227 -0
  220. package/src/utils/index.ts +8 -0
  221. package/src/utils/parallel.ts +294 -0
  222. package/src/validators/credentials.test.ts +274 -0
  223. package/src/validators/credentials.ts +233 -0
  224. package/src/validators/quotas.test.ts +434 -0
  225. package/src/validators/quotas.ts +217 -0
@@ -0,0 +1,726 @@
1
+ import { describe, expect, it, beforeEach } from 'bun:test'
2
+ import { DisasterRecoveryManager } from './disaster-recovery'
3
+
4
+ describe('DisasterRecoveryManager', () => {
5
+ let manager: DisasterRecoveryManager
6
+
7
+ beforeEach(() => {
8
+ manager = new DisasterRecoveryManager()
9
+ })
10
+
11
+ describe('DR Plan Creation', () => {
12
+ it('should create DR plan', () => {
13
+ const plan = manager.createDRPlan({
14
+ name: 'Test DR Plan',
15
+ primaryRegion: 'us-east-1',
16
+ secondaryRegion: 'us-west-2',
17
+ rto: 60,
18
+ rpo: 5,
19
+ resources: [
20
+ {
21
+ resourceId: 'db-1',
22
+ resourceType: 'rds',
23
+ primaryArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
24
+ secondaryArn: 'arn:aws:rds:us-west-2:123456789012:db:mydb-replica',
25
+ replicationEnabled: true,
26
+ },
27
+ ],
28
+ runbook: {
29
+ estimatedDuration: 60,
30
+ steps: [
31
+ {
32
+ order: 1,
33
+ name: 'Verify Failure',
34
+ description: 'Check if primary is down',
35
+ action: 'aws rds describe-db-instances',
36
+ automatable: true,
37
+ estimatedDuration: 2,
38
+ rollbackable: false,
39
+ },
40
+ ],
41
+ },
42
+ })
43
+
44
+ expect(plan.id).toMatch(/^dr-plan-\d+-\d+$/)
45
+ expect(plan.name).toBe('Test DR Plan')
46
+ expect(plan.primaryRegion).toBe('us-east-1')
47
+ expect(plan.secondaryRegion).toBe('us-west-2')
48
+ expect(plan.rto).toBe(60)
49
+ expect(plan.rpo).toBe(5)
50
+ expect(plan.resources).toHaveLength(1)
51
+ expect(plan.runbook.steps).toHaveLength(1)
52
+ })
53
+
54
+ it('should get DR plan by id', () => {
55
+ const plan = manager.createDRPlan({
56
+ name: 'Test DR Plan',
57
+ primaryRegion: 'us-east-1',
58
+ secondaryRegion: 'us-west-2',
59
+ rto: 60,
60
+ rpo: 5,
61
+ resources: [],
62
+ runbook: { estimatedDuration: 60, steps: [] },
63
+ })
64
+
65
+ const retrieved = manager.getDRPlan(plan.id)
66
+ expect(retrieved).toBeDefined()
67
+ expect(retrieved?.id).toBe(plan.id)
68
+ expect(retrieved?.name).toBe('Test DR Plan')
69
+ })
70
+
71
+ it('should return undefined for non-existent plan', () => {
72
+ const plan = manager.getDRPlan('non-existent')
73
+ expect(plan).toBeUndefined()
74
+ })
75
+
76
+ it('should list all DR plans', () => {
77
+ manager.createDRPlan({
78
+ name: 'Plan 1',
79
+ primaryRegion: 'us-east-1',
80
+ secondaryRegion: 'us-west-2',
81
+ rto: 60,
82
+ rpo: 5,
83
+ resources: [],
84
+ runbook: { estimatedDuration: 60, steps: [] },
85
+ })
86
+
87
+ manager.createDRPlan({
88
+ name: 'Plan 2',
89
+ primaryRegion: 'eu-west-1',
90
+ secondaryRegion: 'eu-central-1',
91
+ rto: 120,
92
+ rpo: 10,
93
+ resources: [],
94
+ runbook: { estimatedDuration: 120, steps: [] },
95
+ })
96
+
97
+ const plans = manager.listDRPlans()
98
+ expect(plans).toHaveLength(2)
99
+ expect(plans[0].name).toBe('Plan 1')
100
+ expect(plans[1].name).toBe('Plan 2')
101
+ })
102
+
103
+ it('should include test schedule', () => {
104
+ const plan = manager.createDRPlan({
105
+ name: 'Test DR Plan',
106
+ primaryRegion: 'us-east-1',
107
+ secondaryRegion: 'us-west-2',
108
+ rto: 60,
109
+ rpo: 5,
110
+ resources: [],
111
+ runbook: { estimatedDuration: 60, steps: [] },
112
+ testSchedule: '0 0 1 * *',
113
+ })
114
+
115
+ expect(plan.testSchedule).toBe('0 0 1 * *')
116
+ })
117
+ })
118
+
119
+ describe('RDS DR Plans', () => {
120
+ it('should create RDS DR plan with defaults', () => {
121
+ const plan = manager.createRDSDRPlan({
122
+ primaryDbArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
123
+ secondaryDbArn: 'arn:aws:rds:us-west-2:123456789012:db:mydb-replica',
124
+ primaryRegion: 'us-east-1',
125
+ secondaryRegion: 'us-west-2',
126
+ })
127
+
128
+ expect(plan.name).toBe('RDS Multi-Region DR')
129
+ expect(plan.primaryRegion).toBe('us-east-1')
130
+ expect(plan.secondaryRegion).toBe('us-west-2')
131
+ expect(plan.rto).toBe(60)
132
+ expect(plan.rpo).toBe(5)
133
+ expect(plan.resources).toHaveLength(1)
134
+ expect(plan.resources[0].resourceType).toBe('rds')
135
+ expect(plan.resources[0].replicationEnabled).toBe(true)
136
+ expect(plan.runbook.steps.length).toBeGreaterThan(0)
137
+ expect(plan.testSchedule).toBe('0 0 1 * *')
138
+ })
139
+
140
+ it('should create RDS DR plan with custom RTO/RPO', () => {
141
+ const plan = manager.createRDSDRPlan({
142
+ primaryDbArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
143
+ secondaryDbArn: 'arn:aws:rds:us-west-2:123456789012:db:mydb-replica',
144
+ primaryRegion: 'us-east-1',
145
+ secondaryRegion: 'us-west-2',
146
+ rto: 30,
147
+ rpo: 1,
148
+ })
149
+
150
+ expect(plan.rto).toBe(30)
151
+ expect(plan.rpo).toBe(1)
152
+ })
153
+
154
+ it('should generate comprehensive RDS runbook', () => {
155
+ const plan = manager.createRDSDRPlan({
156
+ primaryDbArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
157
+ secondaryDbArn: 'arn:aws:rds:us-west-2:123456789012:db:mydb-replica',
158
+ primaryRegion: 'us-east-1',
159
+ secondaryRegion: 'us-west-2',
160
+ })
161
+
162
+ expect(plan.runbook.estimatedDuration).toBe(60)
163
+ expect(plan.runbook.requiredApprovals).toContain('cto')
164
+ expect(plan.runbook.requiredApprovals).toContain('engineering-lead')
165
+ expect(plan.runbook.steps).toHaveLength(7)
166
+
167
+ // Verify step sequence
168
+ const stepNames = plan.runbook.steps.map(s => s.name)
169
+ expect(stepNames).toContain('Verify Primary Database Failure')
170
+ expect(stepNames).toContain('Check Replication Lag')
171
+ expect(stepNames).toContain('Promote Read Replica')
172
+ expect(stepNames).toContain('Update DNS Records')
173
+ expect(stepNames).toContain('Update Application Configuration')
174
+ expect(stepNames).toContain('Verify Application Connectivity')
175
+ expect(stepNames).toContain('Monitor for Issues')
176
+ })
177
+
178
+ it('should mark most RDS steps as automatable', () => {
179
+ const plan = manager.createRDSDRPlan({
180
+ primaryDbArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
181
+ secondaryDbArn: 'arn:aws:rds:us-west-2:123456789012:db:mydb-replica',
182
+ primaryRegion: 'us-east-1',
183
+ secondaryRegion: 'us-west-2',
184
+ })
185
+
186
+ const automatableSteps = plan.runbook.steps.filter(s => s.automatable)
187
+ expect(automatableSteps.length).toBeGreaterThan(4)
188
+ })
189
+ })
190
+
191
+ describe('DynamoDB DR Plans', () => {
192
+ it('should create DynamoDB DR plan with defaults', () => {
193
+ const plan = manager.createDynamoDBDRPlan({
194
+ tableArn: 'arn:aws:dynamodb:us-east-1:123456789012:table/my-table',
195
+ regions: ['us-east-1', 'us-west-2'],
196
+ })
197
+
198
+ expect(plan.name).toBe('DynamoDB Global Tables DR')
199
+ expect(plan.primaryRegion).toBe('us-east-1')
200
+ expect(plan.secondaryRegion).toBe('us-west-2')
201
+ expect(plan.rto).toBe(15)
202
+ expect(plan.rpo).toBe(1)
203
+ expect(plan.resources).toHaveLength(1)
204
+ expect(plan.resources[0].resourceType).toBe('dynamodb')
205
+ expect(plan.resources[0].replicationEnabled).toBe(true)
206
+ })
207
+
208
+ it('should create DynamoDB DR plan with custom RTO/RPO', () => {
209
+ const plan = manager.createDynamoDBDRPlan({
210
+ tableArn: 'arn:aws:dynamodb:us-east-1:123456789012:table/my-table',
211
+ regions: ['us-east-1', 'eu-west-1'],
212
+ rto: 10,
213
+ rpo: 0,
214
+ })
215
+
216
+ expect(plan.rto).toBe(10)
217
+ expect(plan.rpo).toBe(0)
218
+ })
219
+
220
+ it('should generate DynamoDB runbook', () => {
221
+ const plan = manager.createDynamoDBDRPlan({
222
+ tableArn: 'arn:aws:dynamodb:us-east-1:123456789012:table/my-table',
223
+ regions: ['us-east-1', 'us-west-2'],
224
+ })
225
+
226
+ expect(plan.runbook.estimatedDuration).toBe(15)
227
+ expect(plan.runbook.steps).toHaveLength(4)
228
+
229
+ const stepNames = plan.runbook.steps.map(s => s.name)
230
+ expect(stepNames).toContain('Verify Primary Region Failure')
231
+ expect(stepNames).toContain('Update Route53 Failover')
232
+ expect(stepNames).toContain('Update Application Endpoints')
233
+ expect(stepNames).toContain('Verify Data Consistency')
234
+ })
235
+
236
+ it('should mark all DynamoDB steps as automatable', () => {
237
+ const plan = manager.createDynamoDBDRPlan({
238
+ tableArn: 'arn:aws:dynamodb:us-east-1:123456789012:table/my-table',
239
+ regions: ['us-east-1', 'us-west-2'],
240
+ })
241
+
242
+ const allAutomatable = plan.runbook.steps.every(s => s.automatable)
243
+ expect(allAutomatable).toBe(true)
244
+ })
245
+ })
246
+
247
+ describe('Failover Execution', () => {
248
+ it('should execute failover in dry run mode', async () => {
249
+ const plan = manager.createRDSDRPlan({
250
+ primaryDbArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
251
+ secondaryDbArn: 'arn:aws:rds:us-west-2:123456789012:db:mydb-replica',
252
+ primaryRegion: 'us-east-1',
253
+ secondaryRegion: 'us-west-2',
254
+ })
255
+
256
+ const result = await manager.executeFailover(plan.id, true)
257
+
258
+ expect(result.success).toBe(true)
259
+ expect(result.completedSteps).toBe(7)
260
+ expect(result.duration).toBeGreaterThanOrEqual(0)
261
+ })
262
+
263
+ it('should execute failover in production mode', async () => {
264
+ const plan = manager.createRDSDRPlan({
265
+ primaryDbArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
266
+ secondaryDbArn: 'arn:aws:rds:us-west-2:123456789012:db:mydb-replica',
267
+ primaryRegion: 'us-east-1',
268
+ secondaryRegion: 'us-west-2',
269
+ })
270
+
271
+ const result = await manager.executeFailover(plan.id, false)
272
+
273
+ expect(result.success).toBe(true)
274
+ expect(result.completedSteps).toBe(7)
275
+ })
276
+
277
+ it('should throw error for non-existent plan', async () => {
278
+ await expect(manager.executeFailover('non-existent', true)).rejects.toThrow(
279
+ 'DR plan not found: non-existent',
280
+ )
281
+ })
282
+
283
+ it('should execute all steps in order', async () => {
284
+ const plan = manager.createDynamoDBDRPlan({
285
+ tableArn: 'arn:aws:dynamodb:us-east-1:123456789012:table/my-table',
286
+ regions: ['us-east-1', 'us-west-2'],
287
+ })
288
+
289
+ const result = await manager.executeFailover(plan.id, true)
290
+
291
+ expect(result.completedSteps).toBe(plan.runbook.steps.length)
292
+ })
293
+ })
294
+
295
+ describe('Failover Testing', () => {
296
+ it('should schedule failover test', () => {
297
+ const plan = manager.createRDSDRPlan({
298
+ primaryDbArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
299
+ secondaryDbArn: 'arn:aws:rds:us-west-2:123456789012:db:mydb-replica',
300
+ primaryRegion: 'us-east-1',
301
+ secondaryRegion: 'us-west-2',
302
+ })
303
+
304
+ const testDate = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 days from now
305
+ const test = manager.scheduleFailoverTest(plan.id, testDate)
306
+
307
+ expect(test.id).toMatch(/^failover-test-\d+-\d+$/)
308
+ expect(test.planId).toBe(plan.id)
309
+ expect(test.status).toBe('scheduled')
310
+ expect(test.startTime).toEqual(testDate)
311
+ })
312
+
313
+ it('should run failover test', async () => {
314
+ const plan = manager.createRDSDRPlan({
315
+ primaryDbArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
316
+ secondaryDbArn: 'arn:aws:rds:us-west-2:123456789012:db:mydb-replica',
317
+ primaryRegion: 'us-east-1',
318
+ secondaryRegion: 'us-west-2',
319
+ })
320
+
321
+ const test = await manager.runFailoverTest(plan.id)
322
+
323
+ expect(test.id).toMatch(/^failover-test-\d+-\d+$/)
324
+ expect(test.planId).toBe(plan.id)
325
+ expect(test.status).toBe('completed')
326
+ expect(test.startTime).toBeInstanceOf(Date)
327
+ expect(test.endTime).toBeInstanceOf(Date)
328
+ expect(test.results).toBeDefined()
329
+ expect(test.results).toHaveLength(plan.runbook.steps.length)
330
+ })
331
+
332
+ it('should throw error for non-existent plan in test', async () => {
333
+ await expect(manager.runFailoverTest('non-existent')).rejects.toThrow(
334
+ 'DR plan not found: non-existent',
335
+ )
336
+ })
337
+
338
+ it('should record test results for all steps', async () => {
339
+ const plan = manager.createDynamoDBDRPlan({
340
+ tableArn: 'arn:aws:dynamodb:us-east-1:123456789012:table/my-table',
341
+ regions: ['us-east-1', 'us-west-2'],
342
+ })
343
+
344
+ const test = await manager.runFailoverTest(plan.id)
345
+
346
+ expect(test.results).toHaveLength(4)
347
+ test.results!.forEach((result) => {
348
+ expect(result.status).toBe('success')
349
+ expect(result.duration).toBeGreaterThanOrEqual(0)
350
+ expect(result.message).toContain('Successfully validated')
351
+ })
352
+ })
353
+
354
+ it('should get failover test by id', async () => {
355
+ const plan = manager.createRDSDRPlan({
356
+ primaryDbArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
357
+ secondaryDbArn: 'arn:aws:rds:us-west-2:123456789012:db:mydb-replica',
358
+ primaryRegion: 'us-east-1',
359
+ secondaryRegion: 'us-west-2',
360
+ })
361
+
362
+ const test = await manager.runFailoverTest(plan.id)
363
+ const retrieved = manager.getFailoverTest(test.id)
364
+
365
+ expect(retrieved).toBeDefined()
366
+ expect(retrieved?.id).toBe(test.id)
367
+ expect(retrieved?.status).toBe('completed')
368
+ })
369
+
370
+ it('should list all failover tests', async () => {
371
+ const plan1 = manager.createRDSDRPlan({
372
+ primaryDbArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
373
+ secondaryDbArn: 'arn:aws:rds:us-west-2:123456789012:db:mydb-replica',
374
+ primaryRegion: 'us-east-1',
375
+ secondaryRegion: 'us-west-2',
376
+ })
377
+
378
+ const plan2 = manager.createDynamoDBDRPlan({
379
+ tableArn: 'arn:aws:dynamodb:us-east-1:123456789012:table/my-table',
380
+ regions: ['us-east-1', 'us-west-2'],
381
+ })
382
+
383
+ await manager.runFailoverTest(plan1.id)
384
+ await manager.runFailoverTest(plan2.id)
385
+
386
+ const tests = manager.listFailoverTests()
387
+ expect(tests.length).toBeGreaterThanOrEqual(2)
388
+ })
389
+ })
390
+
391
+ describe('DR Plan Validation', () => {
392
+ it('should validate valid DR plan', () => {
393
+ const plan = manager.createRDSDRPlan({
394
+ primaryDbArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
395
+ secondaryDbArn: 'arn:aws:rds:us-west-2:123456789012:db:mydb-replica',
396
+ primaryRegion: 'us-east-1',
397
+ secondaryRegion: 'us-west-2',
398
+ rto: 60,
399
+ rpo: 5,
400
+ })
401
+
402
+ const validation = manager.validateDRPlan(plan)
403
+
404
+ expect(validation.valid).toBe(true)
405
+ expect(validation.errors).toHaveLength(0)
406
+ })
407
+
408
+ it('should error when RTO is less than RPO', () => {
409
+ const plan = manager.createDRPlan({
410
+ name: 'Invalid Plan',
411
+ primaryRegion: 'us-east-1',
412
+ secondaryRegion: 'us-west-2',
413
+ rto: 5,
414
+ rpo: 10,
415
+ resources: [],
416
+ runbook: { estimatedDuration: 5, steps: [] },
417
+ })
418
+
419
+ const validation = manager.validateDRPlan(plan)
420
+
421
+ expect(validation.valid).toBe(false)
422
+ expect(validation.errors).toContain('RTO cannot be less than RPO')
423
+ })
424
+
425
+ it('should warn when RTO exceeds 4 hours', () => {
426
+ const plan = manager.createDRPlan({
427
+ name: 'Long RTO Plan',
428
+ primaryRegion: 'us-east-1',
429
+ secondaryRegion: 'us-west-2',
430
+ rto: 300,
431
+ rpo: 60,
432
+ resources: [
433
+ {
434
+ resourceId: 'db-1',
435
+ resourceType: 'rds',
436
+ primaryArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
437
+ replicationEnabled: true,
438
+ },
439
+ ],
440
+ runbook: { estimatedDuration: 60, steps: [
441
+ {
442
+ order: 1,
443
+ name: 'Test',
444
+ description: 'Test',
445
+ action: 'test',
446
+ automatable: true,
447
+ estimatedDuration: 5,
448
+ rollbackable: false,
449
+ },
450
+ ] },
451
+ })
452
+
453
+ const validation = manager.validateDRPlan(plan)
454
+
455
+ expect(validation.warnings).toContain('RTO exceeds 4 hours - consider improving recovery time')
456
+ })
457
+
458
+ it('should warn when RPO exceeds 1 hour', () => {
459
+ const plan = manager.createDRPlan({
460
+ name: 'Long RPO Plan',
461
+ primaryRegion: 'us-east-1',
462
+ secondaryRegion: 'us-west-2',
463
+ rto: 120,
464
+ rpo: 90,
465
+ resources: [
466
+ {
467
+ resourceId: 'db-1',
468
+ resourceType: 'rds',
469
+ primaryArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
470
+ replicationEnabled: true,
471
+ },
472
+ ],
473
+ runbook: { estimatedDuration: 60, steps: [
474
+ {
475
+ order: 1,
476
+ name: 'Test',
477
+ description: 'Test',
478
+ action: 'test',
479
+ automatable: true,
480
+ estimatedDuration: 5,
481
+ rollbackable: false,
482
+ },
483
+ ] },
484
+ })
485
+
486
+ const validation = manager.validateDRPlan(plan)
487
+
488
+ expect(validation.warnings).toContain('RPO exceeds 1 hour - consider more frequent backups')
489
+ })
490
+
491
+ it('should error when no resources defined', () => {
492
+ const plan = manager.createDRPlan({
493
+ name: 'No Resources Plan',
494
+ primaryRegion: 'us-east-1',
495
+ secondaryRegion: 'us-west-2',
496
+ rto: 60,
497
+ rpo: 5,
498
+ resources: [],
499
+ runbook: { estimatedDuration: 60, steps: [
500
+ {
501
+ order: 1,
502
+ name: 'Test',
503
+ description: 'Test',
504
+ action: 'test',
505
+ automatable: true,
506
+ estimatedDuration: 5,
507
+ rollbackable: false,
508
+ },
509
+ ] },
510
+ })
511
+
512
+ const validation = manager.validateDRPlan(plan)
513
+
514
+ expect(validation.valid).toBe(false)
515
+ expect(validation.errors).toContain('No resources defined in DR plan')
516
+ })
517
+
518
+ it('should warn when replication not enabled', () => {
519
+ const plan = manager.createDRPlan({
520
+ name: 'No Replication Plan',
521
+ primaryRegion: 'us-east-1',
522
+ secondaryRegion: 'us-west-2',
523
+ rto: 60,
524
+ rpo: 5,
525
+ resources: [
526
+ {
527
+ resourceId: 'db-1',
528
+ resourceType: 'rds',
529
+ primaryArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
530
+ replicationEnabled: false,
531
+ },
532
+ ],
533
+ runbook: { estimatedDuration: 60, steps: [
534
+ {
535
+ order: 1,
536
+ name: 'Test',
537
+ description: 'Test',
538
+ action: 'test',
539
+ automatable: true,
540
+ estimatedDuration: 5,
541
+ rollbackable: false,
542
+ },
543
+ ] },
544
+ })
545
+
546
+ const validation = manager.validateDRPlan(plan)
547
+
548
+ expect(validation.warnings).toContain('Resource db-1 does not have replication enabled')
549
+ })
550
+
551
+ it('should warn when no secondary ARN for non-DynamoDB resources', () => {
552
+ const plan = manager.createDRPlan({
553
+ name: 'No Secondary ARN Plan',
554
+ primaryRegion: 'us-east-1',
555
+ secondaryRegion: 'us-west-2',
556
+ rto: 60,
557
+ rpo: 5,
558
+ resources: [
559
+ {
560
+ resourceId: 'db-1',
561
+ resourceType: 'rds',
562
+ primaryArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
563
+ replicationEnabled: true,
564
+ },
565
+ ],
566
+ runbook: { estimatedDuration: 60, steps: [
567
+ {
568
+ order: 1,
569
+ name: 'Test',
570
+ description: 'Test',
571
+ action: 'test',
572
+ automatable: true,
573
+ estimatedDuration: 5,
574
+ rollbackable: false,
575
+ },
576
+ ] },
577
+ })
578
+
579
+ const validation = manager.validateDRPlan(plan)
580
+
581
+ expect(validation.warnings).toContain('Resource db-1 does not have secondary resource defined')
582
+ })
583
+
584
+ it('should error when no recovery steps defined', () => {
585
+ const plan = manager.createDRPlan({
586
+ name: 'No Steps Plan',
587
+ primaryRegion: 'us-east-1',
588
+ secondaryRegion: 'us-west-2',
589
+ rto: 60,
590
+ rpo: 5,
591
+ resources: [
592
+ {
593
+ resourceId: 'db-1',
594
+ resourceType: 'rds',
595
+ primaryArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
596
+ replicationEnabled: true,
597
+ },
598
+ ],
599
+ runbook: { estimatedDuration: 60, steps: [] },
600
+ })
601
+
602
+ const validation = manager.validateDRPlan(plan)
603
+
604
+ expect(validation.valid).toBe(false)
605
+ expect(validation.errors).toContain('No recovery steps defined in runbook')
606
+ })
607
+
608
+ it('should warn when estimated duration exceeds RTO', () => {
609
+ const plan = manager.createDRPlan({
610
+ name: 'Slow Recovery Plan',
611
+ primaryRegion: 'us-east-1',
612
+ secondaryRegion: 'us-west-2',
613
+ rto: 30,
614
+ rpo: 5,
615
+ resources: [
616
+ {
617
+ resourceId: 'db-1',
618
+ resourceType: 'rds',
619
+ primaryArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
620
+ replicationEnabled: true,
621
+ },
622
+ ],
623
+ runbook: {
624
+ estimatedDuration: 60,
625
+ steps: [
626
+ {
627
+ order: 1,
628
+ name: 'Step 1',
629
+ description: 'Test',
630
+ action: 'test',
631
+ automatable: true,
632
+ estimatedDuration: 20,
633
+ rollbackable: false,
634
+ },
635
+ {
636
+ order: 2,
637
+ name: 'Step 2',
638
+ description: 'Test',
639
+ action: 'test',
640
+ automatable: true,
641
+ estimatedDuration: 25,
642
+ rollbackable: false,
643
+ },
644
+ ],
645
+ },
646
+ })
647
+
648
+ const validation = manager.validateDRPlan(plan)
649
+
650
+ expect(validation.warnings).toContain('Estimated recovery duration (45m) exceeds RTO (30m)')
651
+ })
652
+
653
+ it('should warn when manual steps exist', () => {
654
+ const plan = manager.createDRPlan({
655
+ name: 'Manual Steps Plan',
656
+ primaryRegion: 'us-east-1',
657
+ secondaryRegion: 'us-west-2',
658
+ rto: 60,
659
+ rpo: 5,
660
+ resources: [
661
+ {
662
+ resourceId: 'db-1',
663
+ resourceType: 'rds',
664
+ primaryArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
665
+ replicationEnabled: true,
666
+ },
667
+ ],
668
+ runbook: {
669
+ estimatedDuration: 60,
670
+ steps: [
671
+ {
672
+ order: 1,
673
+ name: 'Automated Step',
674
+ description: 'Test',
675
+ action: 'test',
676
+ automatable: true,
677
+ estimatedDuration: 10,
678
+ rollbackable: false,
679
+ },
680
+ {
681
+ order: 2,
682
+ name: 'Manual Step 1',
683
+ description: 'Test',
684
+ action: 'test',
685
+ automatable: false,
686
+ estimatedDuration: 20,
687
+ rollbackable: false,
688
+ },
689
+ {
690
+ order: 3,
691
+ name: 'Manual Step 2',
692
+ description: 'Test',
693
+ action: 'test',
694
+ automatable: false,
695
+ estimatedDuration: 15,
696
+ rollbackable: false,
697
+ },
698
+ ],
699
+ },
700
+ })
701
+
702
+ const validation = manager.validateDRPlan(plan)
703
+
704
+ expect(validation.warnings).toContain('2 manual steps in runbook - consider automation')
705
+ })
706
+ })
707
+
708
+ describe('Clear Data', () => {
709
+ it('should clear all data', async () => {
710
+ const plan = manager.createRDSDRPlan({
711
+ primaryDbArn: 'arn:aws:rds:us-east-1:123456789012:db:mydb',
712
+ secondaryDbArn: 'arn:aws:rds:us-west-2:123456789012:db:mydb-replica',
713
+ primaryRegion: 'us-east-1',
714
+ secondaryRegion: 'us-west-2',
715
+ })
716
+
717
+ await manager.runFailoverTest(plan.id)
718
+
719
+ manager.clear()
720
+
721
+ expect(manager.getDRPlan(plan.id)).toBeUndefined()
722
+ expect(manager.listDRPlans()).toHaveLength(0)
723
+ expect(manager.listFailoverTests()).toHaveLength(0)
724
+ })
725
+ })
726
+ })