@@ -5,8 +5,6 @@ import { Sha256 } from "@aws-crypto/sha256-js";
5
5
import {
6
6
CreateEndpointCommand ,
7
7
CreateEventBusCommand ,
8
- DeleteEndpointCommand ,
9
- DeleteEventBusCommand ,
10
8
DescribeEndpointCommand ,
11
9
EndpointState ,
12
10
EventBridgeClient ,
@@ -15,27 +13,24 @@ import {
15
13
PutEventsCommandOutput ,
16
14
ReplicationState ,
17
15
} from "@aws-sdk/client-eventbridge" ;
18
- import { CreateHealthCheckCommand , DeleteHealthCheckCommand , Route53Client } from "@aws-sdk/client-route-53" ;
19
- import { GetCallerIdentityCommand , STSClient } from "@aws-sdk/client-sts" ;
16
+ import { CreateHealthCheckCommand , Route53Client } from "@aws-sdk/client-route-53" ;
20
17
import { defaultProvider } from "@aws-sdk/credential-provider-node" ;
21
18
import { SignatureV4MultiRegion } from "@aws-sdk/signature-v4-multi-region" ;
22
19
import { HttpRequest } from "@smithy/protocol-http" ;
23
20
import { afterAll , beforeAll , describe , expect , it , vi } from "vitest" ;
24
21
25
22
const LONG_TIMEOUT = 5 * 60 * 1000 ;
26
- const POLLING_DELAY = 5000 ;
27
- const MAX_POLLING_ATTEMPTS = 40 ;
23
+
24
+ // Long-lived resources
25
+ const RESOURCE_PREFIX = "jsv3-e2e-global" ;
28
26
29
27
const wait = ( ms : number ) => new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
30
28
31
29
const getSubdomainFromUrl = ( url : string | undefined ) : string | undefined => {
32
30
if ( ! url ) return undefined ;
33
31
try {
34
32
const parsedUrl = new URL ( url ) ;
35
- // Hostname will be like abcde.xyz.endpoint.events.amazonaws.com
36
33
const hostnameParts = parsedUrl . hostname . split ( "." ) ;
37
- // We can expect at least 5 parts: subdomain[0].subdomain[1].endpoint.events.amazonaws.com
38
- // Check if the 3rd part from the end is 'events'
39
34
if ( hostnameParts . length >= 5 && hostnameParts [ hostnameParts . length - 3 ] === "events" ) {
40
35
return `${ hostnameParts [ 0 ] } .${ hostnameParts [ 1 ] } ` ;
41
36
}
@@ -46,71 +41,26 @@ const getSubdomainFromUrl = (url: string | undefined): string | undefined => {
46
41
} ;
47
42
48
43
describe ( "EventBridge Client with SignatureV4a" , ( ) => {
49
- let stsClient : STSClient ;
50
44
let primaryEbClient : EventBridgeClient ;
51
45
let secondaryEbClient : EventBridgeClient ;
52
46
let route53Client : Route53Client ;
53
47
let signer : SignatureV4MultiRegion ;
54
48
let globalEbClient : EventBridgeClient ;
55
-
56
- let accountId : string ;
57
- let timestamp : number ;
58
49
let primaryRegion : string ;
59
50
let secondaryRegion : string ;
60
51
let eventBusName : string ;
52
+ let endpointName : string ;
61
53
let primaryEventBusArn : string | undefined ;
62
54
let secondaryEventBusArn : string | undefined ;
63
55
let healthCheckId : string | undefined ;
64
- let endpointName : string ;
65
56
let endpointArn : string | undefined ;
66
57
67
- const cleanupResources = async ( ) => {
68
- try {
69
- if ( endpointArn ) {
70
- const managementClient = new EventBridgeClient ( { region : primaryRegion } ) ;
71
- await managementClient . send ( new DeleteEndpointCommand ( { Name : endpointName } ) ) ;
72
- managementClient . destroy ( ) ;
73
- endpointArn = undefined ; // Mark as deleted
74
- }
75
- } catch ( error ) {
76
- console . error ( `Error deleting endpoint ${ endpointName } :` , error ) ;
77
- }
78
-
79
- try {
80
- if ( healthCheckId ) {
81
- await route53Client . send ( new DeleteHealthCheckCommand ( { HealthCheckId : healthCheckId } ) ) ;
82
- healthCheckId = undefined ;
83
- }
84
- } catch ( error ) {
85
- console . error ( `Error deleting health check ${ healthCheckId } :` , error ) ;
86
- }
87
-
88
- try {
89
- if ( primaryEventBusArn ) {
90
- await primaryEbClient . send ( new DeleteEventBusCommand ( { Name : eventBusName } ) ) ;
91
- primaryEventBusArn = undefined ;
92
- }
93
- } catch ( error ) {
94
- console . error ( `Error deleting event bus ${ eventBusName } from ${ primaryRegion } :` , error ) ;
95
- }
96
-
97
- try {
98
- if ( secondaryEventBusArn ) {
99
- await secondaryEbClient . send ( new DeleteEventBusCommand ( { Name : eventBusName } ) ) ;
100
- secondaryEventBusArn = undefined ;
101
- }
102
- } catch ( error ) {
103
- console . error ( `Error deleting event bus ${ eventBusName } from ${ secondaryRegion } :` , error ) ;
104
- }
105
- } ;
106
-
107
58
beforeAll ( async ( ) => {
108
59
vi . setConfig ( { hookTimeout : LONG_TIMEOUT } ) ;
109
60
110
61
primaryRegion = "us-west-2" ;
111
62
secondaryRegion = "us-east-1" ;
112
63
113
- stsClient = new STSClient ( { region : primaryRegion } ) ;
114
64
primaryEbClient = new EventBridgeClient ( { region : primaryRegion } ) ;
115
65
secondaryEbClient = new EventBridgeClient ( { region : secondaryRegion } ) ;
116
66
route53Client = new Route53Client ( { region : "us-west-2" } ) ;
@@ -127,82 +77,60 @@ describe("EventBridge Client with SignatureV4a", () => {
127
77
signer,
128
78
} ) ;
129
79
130
- // Account ID & Timestamp for Unique Names
80
+ eventBusName = `${ RESOURCE_PREFIX } -bus` ;
81
+ endpointName = `${ RESOURCE_PREFIX } -endpoint` ;
82
+
83
+ const managementClient = new EventBridgeClient ( { region : primaryRegion } ) ;
131
84
try {
132
- const identity = await stsClient . send ( new GetCallerIdentityCommand ( { } ) ) ;
133
- accountId = identity . Account ! ;
134
- timestamp = Date . now ( ) ;
135
-
136
- eventBusName = `test-global-bus-${ accountId } -${ timestamp } ` ;
137
- endpointName = `test-global-endpoint-${ accountId } -${ timestamp } ` ;
138
-
139
- const primaryBus = await primaryEbClient . send ( new CreateEventBusCommand ( { Name : eventBusName } ) ) ;
140
- primaryEventBusArn = primaryBus . EventBusArn ;
141
- expect ( primaryEventBusArn ) . toBeDefined ( ) ;
142
-
143
- const secondaryBus = await secondaryEbClient . send ( new CreateEventBusCommand ( { Name : eventBusName } ) ) ;
144
- secondaryEventBusArn = secondaryBus . EventBusArn ;
145
- expect ( secondaryEventBusArn ) . toBeDefined ( ) ;
146
-
147
- // Create Route 53 Health Check: a basic one against a dummy target.
148
- const healthCheckCallerReference = `eb-putevents-test-${ timestamp } ` ;
149
- const healthCheck = await route53Client . send (
150
- new CreateHealthCheckCommand ( {
151
- CallerReference : healthCheckCallerReference ,
152
- HealthCheckConfig : {
153
- Type : "HTTP" ,
154
- FullyQualifiedDomainName : "example.com" ,
155
- Port : 80 ,
156
- ResourcePath : "/" ,
157
- RequestInterval : 30 ,
158
- FailureThreshold : 3 ,
159
- } ,
160
- } )
161
- ) ;
162
- healthCheckId = healthCheck . HealthCheck ?. Id ;
163
- expect ( healthCheckId ) . toBeDefined ( ) ;
164
- await wait ( 10000 ) ;
165
-
166
- let managementClient : EventBridgeClient | undefined ;
167
- try {
168
- managementClient = new EventBridgeClient ( { region : primaryRegion } ) ;
169
- const createInput = {
170
- Name : endpointName ,
171
- RoutingConfig : {
172
- FailoverConfig : {
173
- Primary : { HealthCheck : `arn:aws:route53:::healthcheck/${ healthCheckId } ` } ,
174
- Secondary : { Route : secondaryRegion } ,
85
+ const existingEndpoint = await managementClient . send ( new DescribeEndpointCommand ( { Name : endpointName } ) ) ;
86
+ endpointArn = existingEndpoint . Arn ;
87
+ } catch ( error : any ) {
88
+ if ( error . name === "ResourceNotFoundException" ) {
89
+ const primaryBus = await primaryEbClient . send ( new CreateEventBusCommand ( { Name : eventBusName } ) ) ;
90
+ primaryEventBusArn = primaryBus . EventBusArn ;
91
+
92
+ const secondaryBus = await secondaryEbClient . send ( new CreateEventBusCommand ( { Name : eventBusName } ) ) ;
93
+ secondaryEventBusArn = secondaryBus . EventBusArn ;
94
+
95
+ const healthCheck = await route53Client . send (
96
+ new CreateHealthCheckCommand ( {
97
+ CallerReference : `${ RESOURCE_PREFIX } -${ Date . now ( ) } ` ,
98
+ HealthCheckConfig : {
99
+ Type : "HTTP" ,
100
+ FullyQualifiedDomainName : "example.com" ,
101
+ Port : 80 ,
102
+ ResourcePath : "/" ,
103
+ RequestInterval : 30 ,
104
+ FailureThreshold : 3 ,
175
105
} ,
176
- } ,
177
- ReplicationConfig : { State : ReplicationState . DISABLED } ,
178
- EventBuses : [ { EventBusArn : primaryEventBusArn } , { EventBusArn : secondaryEventBusArn } ] ,
179
- } ;
180
- const createResponse = await managementClient . send ( new CreateEndpointCommand ( createInput ) ) ;
181
-
182
- expect ( createResponse . $metadata . httpStatusCode ) . toBe ( 200 ) ;
183
- expect ( createResponse . Name ) . toBe ( endpointName ) ;
184
- expect ( createResponse . Arn ) . toBeDefined ( ) ;
185
-
186
- endpointArn = createResponse . Arn ! ;
187
- } catch ( error ) {
188
- console . error ( "Error during prerequisite resource creation:" , error ) ;
189
- await cleanupResources ( ) ;
106
+ } )
107
+ ) ;
108
+ healthCheckId = healthCheck . HealthCheck ?. Id ;
109
+ await wait ( 10000 ) ;
110
+
111
+ const createEndpointResponse = await managementClient . send (
112
+ new CreateEndpointCommand ( {
113
+ Name : endpointName ,
114
+ RoutingConfig : {
115
+ FailoverConfig : {
116
+ Primary : { HealthCheck : `arn:aws:route53:::healthcheck/${ healthCheckId } ` } ,
117
+ Secondary : { Route : secondaryRegion } ,
118
+ } ,
119
+ } ,
120
+ ReplicationConfig : { State : ReplicationState . DISABLED } ,
121
+ EventBuses : [ { EventBusArn : primaryEventBusArn } , { EventBusArn : secondaryEventBusArn } ] ,
122
+ } )
123
+ ) ;
124
+ endpointArn = createEndpointResponse . Arn ;
125
+ } else {
190
126
throw error ;
191
- } finally {
192
- managementClient ?. destroy ( ) ;
193
127
}
194
- } catch ( error ) {
195
- console . error ( "Error during prerequisite resource creation:" , error ) ;
196
- await cleanupResources ( ) ;
197
- throw error ;
128
+ } finally {
129
+ managementClient . destroy ( ) ;
198
130
}
199
131
} , LONG_TIMEOUT ) ;
200
132
201
133
afterAll ( async ( ) => {
202
- vi . setConfig ( { hookTimeout : LONG_TIMEOUT } ) ;
203
- await cleanupResources ( ) ;
204
- // Clean up SDK clients
205
- stsClient ?. destroy ( ) ;
206
134
primaryEbClient ?. destroy ( ) ;
207
135
secondaryEbClient ?. destroy ( ) ;
208
136
route53Client ?. destroy ( ) ;
@@ -251,42 +179,15 @@ describe("EventBridge Client with SignatureV4a", () => {
251
179
it (
252
180
"should send an event to an EventBridge Global Endpoint using SignatureV4a" ,
253
181
async ( ) => {
254
- expect ( endpointArn ) . toBeDefined ( ) ;
255
-
256
- let attempts = 0 ;
257
- let currentState : EndpointState | string | undefined ;
258
- let endpointUrl : string | undefined ;
259
182
const managementClient = new EventBridgeClient ( { region : primaryRegion } ) ;
260
-
261
- while ( attempts < MAX_POLLING_ATTEMPTS ) {
262
- attempts ++ ;
263
- try {
264
- const describeResponse = await managementClient . send ( new DescribeEndpointCommand ( { Name : endpointName } ) ) ;
265
- currentState = describeResponse . State ;
266
- if ( currentState === EndpointState . ACTIVE ) {
267
- endpointUrl = describeResponse . EndpointUrl ;
268
- break ;
269
- }
270
- if ( currentState === EndpointState . CREATE_FAILED || currentState === EndpointState . UPDATE_FAILED ) {
271
- throw new Error ( `Endpoint entered failed state: ${ currentState } ` ) ;
272
- }
273
- } catch ( error ) {
274
- console . warn ( `DescribeEndpoint failed (attempt ${ attempts } ):` , error ) ;
275
- // Continue polling unless it's a definitive failure state
276
- }
277
- await wait ( POLLING_DELAY ) ;
278
- }
183
+ const describeResponse = await managementClient . send ( new DescribeEndpointCommand ( { Name : endpointName } ) ) ;
279
184
managementClient . destroy ( ) ;
280
185
281
- if ( currentState !== EndpointState . ACTIVE ) {
282
- throw new Error ( `Endpoint ${ endpointName } did not become ACTIVE after ${ attempts } attempts.` ) ;
283
- }
284
- expect ( endpointUrl ) . toBeDefined ( ) ;
186
+ expect ( describeResponse . State ) . toBe ( EndpointState . ACTIVE ) ;
187
+ expect ( describeResponse . EndpointUrl ) . toBeDefined ( ) ;
285
188
286
- const endpointSubdomain = getSubdomainFromUrl ( endpointUrl ) ;
287
- if ( ! endpointSubdomain ) {
288
- throw new Error ( `Failed to extract subdomain from Endpoint URL: ${ endpointUrl } ` ) ;
289
- }
189
+ const endpointSubdomain = getSubdomainFromUrl ( describeResponse . EndpointUrl ) ;
190
+ expect ( endpointSubdomain ) . toBeDefined ( ) ;
290
191
291
192
const putEventsInput : PutEventsCommandInput = {
292
193
Entries : [
@@ -306,7 +207,6 @@ describe("EventBridge Client with SignatureV4a", () => {
306
207
expect ( putEventsResponse . FailedEntryCount ) . toBe ( 0 ) ;
307
208
expect ( putEventsResponse . Entries ) . toHaveLength ( 1 ) ;
308
209
expect ( putEventsResponse . Entries ?. [ 0 ] ?. EventId ) . toBeDefined ( ) ;
309
-
310
210
// Note: Verifying the event *arrived* in the target bus (primary or secondary)
311
211
// would require additional setup and is omitted
312
212
// here to focus on the PutEvents call itself succeeding with SigV4a.
0 commit comments