@@ -2,99 +2,174 @@ import { fromInstanceMetadata } from "./fromInstanceMetadata";
2
2
import { httpGet } from "./remoteProvider/httpGet" ;
3
3
import {
4
4
fromImdsCredentials ,
5
- ImdsCredentials
5
+ isImdsCredentials
6
6
} from "./remoteProvider/ImdsCredentials" ;
7
+ import { providerConfigFromInit } from "./remoteProvider/RemoteProviderInit" ;
8
+ import { retry } from "./remoteProvider/retry" ;
9
+ import { ProviderError } from "@aws-sdk/property-provider" ;
7
10
8
- const mockHttpGet = < any > httpGet ;
9
- jest . mock ( "./remoteProvider/httpGet" , ( ) => ( { httpGet : jest . fn ( ) } ) ) ;
10
-
11
- beforeEach ( ( ) => {
12
- mockHttpGet . mockReset ( ) ;
13
- } ) ;
11
+ jest . mock ( "./remoteProvider/httpGet" ) ;
12
+ jest . mock ( "./remoteProvider/ImdsCredentials" ) ;
13
+ jest . mock ( "./remoteProvider/retry" ) ;
14
+ jest . mock ( "./remoteProvider/RemoteProviderInit" ) ;
14
15
15
16
describe ( "fromInstanceMetadata" , ( ) => {
16
- const creds : ImdsCredentials = Object . freeze ( {
17
+ const mockTimeout = 1000 ;
18
+ const mockMaxRetries = 3 ;
19
+ const mockProfile = "foo" ;
20
+
21
+ const mockHttpGetOptions = {
22
+ host : "169.254.169.254" ,
23
+ path : "/latest/meta-data/iam/security-credentials/" ,
24
+ timeout : mockTimeout
25
+ } ;
26
+
27
+ const mockImdsCreds = Object . freeze ( {
17
28
AccessKeyId : "foo" ,
18
29
SecretAccessKey : "bar" ,
19
30
Token : "baz" ,
20
31
Expiration : new Date ( ) . toISOString ( )
21
32
} ) ;
22
33
23
- it ( "should resolve credentials by fetching them from the container metadata service" , async ( ) => {
24
- mockHttpGet . mockReturnValue ( Promise . resolve ( JSON . stringify ( creds ) ) ) ;
25
- expect ( await fromInstanceMetadata ( ) ( ) ) . toEqual ( fromImdsCredentials ( creds ) ) ;
34
+ const mockCreds = Object . freeze ( {
35
+ accessKeyId : mockImdsCreds . AccessKeyId ,
36
+ secretAccessKey : mockImdsCreds . SecretAccessKey ,
37
+ sessionToken : mockImdsCreds . Token ,
38
+ expiration : new Date ( mockImdsCreds . Expiration )
26
39
} ) ;
27
40
28
- it ( "should retry the fetching operation up to maxRetries times" , async ( ) => {
29
- const maxRetries = 5 ;
30
- mockHttpGet . mockReturnValueOnce ( Promise . resolve ( "foo" ) ) ;
31
- for ( let i = 0 ; i < maxRetries - 1 ; i ++ ) {
32
- mockHttpGet . mockReturnValueOnce ( Promise . reject ( "No!" ) ) ;
33
- }
34
- mockHttpGet . mockReturnValueOnce ( Promise . resolve ( JSON . stringify ( creds ) ) ) ;
35
-
36
- expect ( await fromInstanceMetadata ( { maxRetries } ) ( ) ) . toEqual (
37
- fromImdsCredentials ( creds )
38
- ) ;
39
- expect ( mockHttpGet . mock . calls . length ) . toEqual ( maxRetries + 1 ) ;
41
+ beforeEach ( ( ) => {
42
+ ( ( isImdsCredentials as unknown ) as jest . Mock ) . mockReturnValue ( true ) ;
43
+ ( providerConfigFromInit as jest . Mock ) . mockReturnValue ( {
44
+ timeout : mockTimeout ,
45
+ maxRetries : mockMaxRetries
46
+ } ) ;
40
47
} ) ;
41
48
42
- it ( "should retry responses that receive invalid response values" , async ( ) => {
43
- mockHttpGet . mockReturnValueOnce ( Promise . resolve ( "foo" ) ) ;
44
- for ( let key of Object . keys ( creds ) ) {
45
- const invalidCreds : any = { ...creds } ;
46
- delete invalidCreds [ key ] ;
47
- mockHttpGet . mockReturnValueOnce (
48
- Promise . resolve ( JSON . stringify ( invalidCreds ) )
49
- ) ;
50
- }
51
- mockHttpGet . mockReturnValueOnce ( Promise . resolve ( JSON . stringify ( creds ) ) ) ;
52
-
53
- await fromInstanceMetadata ( { maxRetries : 100 } ) ( ) ;
54
- expect ( mockHttpGet . mock . calls . length ) . toEqual (
55
- Object . keys ( creds ) . length + 2
56
- ) ;
49
+ afterEach ( ( ) => {
50
+ jest . resetAllMocks ( ) ;
57
51
} ) ;
58
52
59
- it ( "should pass relevant configuration to httpGet" , async ( ) => {
60
- const timeout = Math . ceil ( Math . random ( ) * 1000 ) ;
61
- const profile = "foo-profile" ;
62
- mockHttpGet . mockReturnValueOnce ( Promise . resolve ( profile ) ) ;
63
- mockHttpGet . mockReturnValue ( Promise . resolve ( JSON . stringify ( creds ) ) ) ;
64
- await fromInstanceMetadata ( { timeout } ) ( ) ;
65
- expect ( mockHttpGet . mock . calls . length ) . toEqual ( 2 ) ;
66
- expect ( mockHttpGet . mock . calls [ 0 ] [ 0 ] ) . toEqual ( {
67
- host : "169.254.169.254" ,
68
- path : "/latest/meta-data/iam/security-credentials/" ,
69
- timeout
70
- } ) ;
71
- expect ( mockHttpGet . mock . calls [ 1 ] [ 0 ] ) . toEqual ( {
72
- host : "169.254.169.254" ,
73
- path : `/latest/meta-data/iam/security-credentials/${ profile } ` ,
74
- timeout
53
+ it ( "gets profile name from IMDS, and passes profile name to fetch credentials" , async ( ) => {
54
+ ( httpGet as jest . Mock )
55
+ . mockResolvedValueOnce ( mockProfile )
56
+ . mockResolvedValueOnce ( JSON . stringify ( mockImdsCreds ) ) ;
57
+
58
+ ( retry as jest . Mock ) . mockImplementation ( ( fn : any ) => fn ( ) ) ;
59
+ ( fromImdsCredentials as jest . Mock ) . mockReturnValue ( mockCreds ) ;
60
+
61
+ await expect ( fromInstanceMetadata ( ) ( ) ) . resolves . toEqual ( mockCreds ) ;
62
+ expect ( httpGet ) . toHaveBeenCalledTimes ( 2 ) ;
63
+ expect ( httpGet ) . toHaveBeenNthCalledWith ( 1 , mockHttpGetOptions ) ;
64
+ expect ( httpGet ) . toHaveBeenNthCalledWith ( 2 , {
65
+ ...mockHttpGetOptions ,
66
+ path : `${ mockHttpGetOptions . path } ${ mockProfile } `
75
67
} ) ;
76
68
} ) ;
77
69
78
- it ( "should retry the profile name fetch as necessary" , async ( ) => {
79
- const defaultTimeout = 1000 ;
80
- const profile = "foo-profile" ;
81
- mockHttpGet . mockReturnValueOnce ( Promise . reject ( "Too busy" ) ) ;
82
- mockHttpGet . mockReturnValueOnce ( Promise . resolve ( profile ) ) ;
83
- mockHttpGet . mockReturnValueOnce ( Promise . resolve ( JSON . stringify ( creds ) ) ) ;
84
-
85
- await fromInstanceMetadata ( { maxRetries : 1 } ) ( ) ;
86
- expect ( mockHttpGet . mock . calls . length ) . toEqual ( 3 ) ;
87
- expect ( mockHttpGet . mock . calls [ 2 ] [ 0 ] ) . toEqual ( {
88
- host : "169.254.169.254" ,
89
- path : `/latest/meta-data/iam/security-credentials/${ profile } ` ,
90
- timeout : defaultTimeout
70
+ it ( "trims profile returned name from IMDS" , async ( ) => {
71
+ ( httpGet as jest . Mock )
72
+ . mockResolvedValueOnce ( " " + mockProfile + " " )
73
+ . mockResolvedValueOnce ( JSON . stringify ( mockImdsCreds ) ) ;
74
+
75
+ ( retry as jest . Mock ) . mockImplementation ( ( fn : any ) => fn ( ) ) ;
76
+ ( fromImdsCredentials as jest . Mock ) . mockReturnValue ( mockCreds ) ;
77
+
78
+ await expect ( fromInstanceMetadata ( ) ( ) ) . resolves . toEqual ( mockCreds ) ;
79
+ expect ( httpGet ) . toHaveBeenCalledTimes ( 2 ) ;
80
+ expect ( httpGet ) . toHaveBeenNthCalledWith ( 1 , mockHttpGetOptions ) ;
81
+ expect ( httpGet ) . toHaveBeenNthCalledWith ( 2 , {
82
+ ...mockHttpGetOptions ,
83
+ path : `${ mockHttpGetOptions . path } ${ mockProfile } `
91
84
} ) ;
92
- for ( let index of [ 0 , 1 ] ) {
93
- expect ( mockHttpGet . mock . calls [ index ] [ 0 ] ) . toEqual ( {
94
- host : "169.254.169.254" ,
95
- path : "/latest/meta-data/iam/security-credentials/" ,
96
- timeout : defaultTimeout
97
- } ) ;
98
- }
85
+ } ) ;
86
+
87
+ it ( "passes {} to providerConfigFromInit if init not defined" , async ( ) => {
88
+ ( retry as jest . Mock )
89
+ . mockResolvedValueOnce ( mockProfile )
90
+ . mockResolvedValueOnce ( mockCreds ) ;
91
+
92
+ await expect ( fromInstanceMetadata ( ) ( ) ) . resolves . toEqual ( mockCreds ) ;
93
+ expect ( providerConfigFromInit ) . toHaveBeenCalledTimes ( 1 ) ;
94
+ expect ( providerConfigFromInit ) . toHaveBeenCalledWith ( { } ) ;
95
+ } ) ;
96
+
97
+ it ( "passes init to providerConfigFromInit" , async ( ) => {
98
+ ( retry as jest . Mock )
99
+ . mockResolvedValueOnce ( mockProfile )
100
+ . mockResolvedValueOnce ( mockCreds ) ;
101
+
102
+ const init = { maxRetries : 5 , timeout : 1213 } ;
103
+ await expect ( fromInstanceMetadata ( init ) ( ) ) . resolves . toEqual ( mockCreds ) ;
104
+ expect ( providerConfigFromInit ) . toHaveBeenCalledTimes ( 1 ) ;
105
+ expect ( providerConfigFromInit ) . toHaveBeenCalledWith ( init ) ;
106
+ } ) ;
107
+
108
+ it ( "passes maxRetries returned from providerConfigFromInit to retry" , async ( ) => {
109
+ ( retry as jest . Mock )
110
+ . mockResolvedValueOnce ( mockProfile )
111
+ . mockResolvedValueOnce ( mockCreds ) ;
112
+
113
+ await expect ( fromInstanceMetadata ( ) ( ) ) . resolves . toEqual ( mockCreds ) ;
114
+ expect ( retry ) . toHaveBeenCalledTimes ( 2 ) ;
115
+ expect ( ( retry as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ) . toBe ( mockMaxRetries ) ;
116
+ expect ( ( retry as jest . Mock ) . mock . calls [ 1 ] [ 1 ] ) . toBe ( mockMaxRetries ) ;
117
+ } ) ;
118
+
119
+ it ( "throws ProviderError if credentials returned are incorrect" , async ( ) => {
120
+ ( httpGet as jest . Mock )
121
+ . mockResolvedValueOnce ( mockProfile )
122
+ . mockResolvedValueOnce ( JSON . stringify ( mockImdsCreds ) ) ;
123
+
124
+ ( retry as jest . Mock ) . mockImplementation ( ( fn : any ) => fn ( ) ) ;
125
+ ( ( isImdsCredentials as unknown ) as jest . Mock ) . mockReturnValueOnce ( false ) ;
126
+
127
+ await expect ( fromInstanceMetadata ( ) ( ) ) . rejects . toEqual (
128
+ new ProviderError (
129
+ "Invalid response received from instance metadata service."
130
+ )
131
+ ) ;
132
+ expect ( retry ) . toHaveBeenCalledTimes ( 2 ) ;
133
+ expect ( httpGet ) . toHaveBeenCalledTimes ( 2 ) ;
134
+ expect ( isImdsCredentials ) . toHaveBeenCalledTimes ( 1 ) ;
135
+ expect ( isImdsCredentials ) . toHaveBeenCalledWith ( mockImdsCreds ) ;
136
+ expect ( fromImdsCredentials ) . not . toHaveBeenCalled ( ) ;
137
+ } ) ;
138
+
139
+ it ( "throws Error if requestFromEc2Imds for profile fails" , async ( ) => {
140
+ const mockError = new Error ( "profile not found" ) ;
141
+ ( httpGet as jest . Mock ) . mockRejectedValueOnce ( mockError ) ;
142
+ ( retry as jest . Mock ) . mockImplementation ( ( fn : any ) => fn ( ) ) ;
143
+
144
+ await expect ( fromInstanceMetadata ( ) ( ) ) . rejects . toEqual ( mockError ) ;
145
+ expect ( retry ) . toHaveBeenCalledTimes ( 1 ) ;
146
+ expect ( httpGet ) . toHaveBeenCalledTimes ( 1 ) ;
147
+ } ) ;
148
+
149
+ it ( "throws Error if requestFromEc2Imds for credentials fails" , async ( ) => {
150
+ const mockError = new Error ( "creds not found" ) ;
151
+ ( httpGet as jest . Mock )
152
+ . mockResolvedValueOnce ( mockProfile )
153
+ . mockRejectedValueOnce ( mockError ) ;
154
+ ( retry as jest . Mock ) . mockImplementation ( ( fn : any ) => fn ( ) ) ;
155
+
156
+ await expect ( fromInstanceMetadata ( ) ( ) ) . rejects . toEqual ( mockError ) ;
157
+ expect ( retry ) . toHaveBeenCalledTimes ( 2 ) ;
158
+ expect ( httpGet ) . toHaveBeenCalledTimes ( 2 ) ;
159
+ expect ( fromImdsCredentials ) . not . toHaveBeenCalled ( ) ;
160
+ } ) ;
161
+
162
+ it ( "throws SyntaxError if requestFromEc2Imds returns unparseable creds" , async ( ) => {
163
+ ( httpGet as jest . Mock )
164
+ . mockResolvedValueOnce ( mockProfile )
165
+ . mockResolvedValueOnce ( "." ) ;
166
+ ( retry as jest . Mock ) . mockImplementation ( ( fn : any ) => fn ( ) ) ;
167
+
168
+ await expect ( fromInstanceMetadata ( ) ( ) ) . rejects . toEqual (
169
+ new SyntaxError ( "Unexpected token . in JSON at position 0" )
170
+ ) ;
171
+ expect ( retry ) . toHaveBeenCalledTimes ( 2 ) ;
172
+ expect ( httpGet ) . toHaveBeenCalledTimes ( 2 ) ;
173
+ expect ( fromImdsCredentials ) . not . toHaveBeenCalled ( ) ;
99
174
} ) ;
100
175
} ) ;
0 commit comments