3
3
4
4
package software.aws.toolkits.jetbrains.services.codewhisperer
5
5
6
- import com.intellij.openapi.application.ApplicationManager
7
6
import com.intellij.openapi.project.Project
8
- import com.intellij.testFramework.ApplicationRule
9
7
import com.intellij.testFramework.DisposableRule
10
8
import com.intellij.testFramework.ProjectRule
11
9
import com.intellij.testFramework.replaceService
12
10
import org.assertj.core.api.Assertions.assertThat
13
11
import org.junit.Before
14
12
import org.junit.Rule
15
13
import org.junit.Test
14
+ import org.junit.rules.TemporaryFolder
16
15
import org.mockito.kotlin.any
17
16
import org.mockito.kotlin.mock
18
17
import org.mockito.kotlin.spy
@@ -23,16 +22,31 @@ import software.aws.toolkits.jetbrains.core.MockClientManagerRule
23
22
import software.aws.toolkits.jetbrains.core.credentials.ManagedBearerSsoConnection
24
23
import software.aws.toolkits.jetbrains.core.credentials.MockToolkitAuthManagerRule
25
24
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
25
+ import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection
26
+ import software.aws.toolkits.jetbrains.core.credentials.pinning.ConnectionPinningManager
27
+ import software.aws.toolkits.jetbrains.core.credentials.sono.CODEWHISPERER_SCOPES
26
28
import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_URL
29
+ import software.aws.toolkits.jetbrains.core.credentials.sso.AccessToken
30
+ import software.aws.toolkits.jetbrains.core.credentials.sso.AccessTokenCacheKey
31
+ import software.aws.toolkits.jetbrains.core.credentials.sso.DiskCache
32
+ import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState
33
+ import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider
27
34
import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererLoginType
28
- import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExploreActionState
29
35
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager
30
36
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled
37
+ import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererExpired
38
+ import java.nio.file.Files
39
+ import java.nio.file.Path
40
+ import java.nio.file.Paths
41
+ import java.time.Clock
42
+ import java.time.Instant
43
+ import java.time.ZoneOffset
44
+ import java.time.temporal.ChronoUnit
31
45
32
46
class CodeWhispererExplorerActionManagerTest {
33
47
@JvmField
34
48
@Rule
35
- val applicationRule = ApplicationRule ()
49
+ val tempFolder = TemporaryFolder ()
36
50
37
51
@JvmField
38
52
@Rule
@@ -50,17 +64,24 @@ class CodeWhispererExplorerActionManagerTest {
50
64
@Rule
51
65
val mockClientManager = MockClientManagerRule ()
52
66
67
+ private val now = Instant .now()
68
+ private val clock = Clock .fixed(now, ZoneOffset .UTC )
69
+
53
70
private lateinit var mockManager: CodeWhispererExplorerActionManager
54
71
private lateinit var project: Project
55
- private lateinit var connectionManager: ToolkitConnectionManager
72
+ private lateinit var cacheRoot: Path
73
+ private lateinit var cacheLocation: Path
74
+ private lateinit var testDiskCache: DiskCache
56
75
57
76
@Before
58
77
fun setup () {
78
+ cacheRoot = tempFolder.root.toPath().toAbsolutePath()
79
+ cacheLocation = Paths .get(cacheRoot.toString(), " fakehome" , " .aws" , " sso" , " cache" )
80
+ Files .createDirectories(cacheLocation)
81
+ testDiskCache = DiskCache (cacheLocation, clock)
82
+
59
83
mockClientManager.create<SsoOidcClient >()
60
84
project = projectRule.project
61
- connectionManager = mock()
62
-
63
- project.replaceService(ToolkitConnectionManager ::class .java, connectionManager, disposableRule.disposable)
64
85
}
65
86
66
87
/* *
@@ -69,49 +90,14 @@ class CodeWhispererExplorerActionManagerTest {
69
90
@Test
70
91
fun `when there is no connection, should return logout` () {
71
92
mockManager = spy()
72
- whenever(connectionManager.activeConnectionForFeature(any())).thenReturn(null )
93
+ val mockConnectionManager = mock<ToolkitConnectionManager >()
94
+ whenever(mockConnectionManager.activeConnectionForFeature(any())).thenReturn(null )
95
+ project.replaceService(ToolkitConnectionManager ::class .java, mockConnectionManager, disposableRule.disposable)
73
96
74
97
val actual = mockManager.checkActiveCodeWhispererConnectionType(project)
75
98
assertThat(actual).isEqualTo(CodeWhispererLoginType .Logout )
76
- }
77
-
78
- @Test
79
- fun `when ToS accepted and there is an accountless token, should return accountless` () {
80
- mockManager = spy()
81
- mockManager.loadState(
82
- // set up accountless token
83
- CodeWhispererExploreActionState ().apply {
84
- this .token = " foo"
85
- }
86
- )
87
-
88
- val actual = mockManager.checkActiveCodeWhispererConnectionType(project)
89
- assertThat(actual).isEqualTo(CodeWhispererLoginType .Accountless )
90
- }
91
-
92
- @Test
93
- fun `when ToS accepted, no accountless token and there is an AWS Builder ID connection, should return Sono` () {
94
- assertLoginType(SONO_URL , CodeWhispererLoginType .Sono )
95
- }
96
-
97
- @Test
98
- fun `when ToS accepted, no accountless token and there is an SSO connection, should return SSO` () {
99
- assertLoginType(aString(), CodeWhispererLoginType .SSO )
100
- }
101
-
102
- @Test
103
- fun `test nullifyAccountlessCredentialIfNeeded` () {
104
- mockManager = CodeWhispererExplorerActionManager ()
105
- mockManager.loadState(CodeWhispererExploreActionState ().apply { this .token = " foo" })
106
-
107
- assertThat(mockManager.state.token)
108
- .isNotNull
109
- .isEqualTo(" foo" )
110
-
111
- mockManager.nullifyAccountlessCredentialIfNeeded()
112
-
113
- assertThat(mockManager.state.token)
114
- .isNull()
99
+ assertThat(isCodeWhispererEnabled(project)).isFalse
100
+ assertThat(isCodeWhispererExpired(project)).isFalse
115
101
}
116
102
117
103
/* *
@@ -120,31 +106,113 @@ class CodeWhispererExplorerActionManagerTest {
120
106
* - should return true if loginType == Accountless || Sono || SSO
121
107
*/
122
108
@Test
123
- fun `test isCodeWhispererEnabled` () {
124
- mockManager = mock()
125
- ApplicationManager .getApplication().replaceService(CodeWhispererExplorerActionManager ::class .java, mockManager, disposableRule.disposable)
126
-
127
- whenever(mockManager.checkActiveCodeWhispererConnectionType(project)).thenReturn(CodeWhispererLoginType .Logout )
128
- assertThat(isCodeWhispererEnabled(project)).isFalse
129
-
130
- whenever(mockManager.checkActiveCodeWhispererConnectionType(project)).thenReturn(CodeWhispererLoginType .Accountless )
131
- assertThat(isCodeWhispererEnabled(project)).isTrue
109
+ fun `test connection state` () {
110
+ assertConnectionState(
111
+ startUrl = SONO_URL ,
112
+ refreshToken = aString(),
113
+ expirationTime = now.plus(1 , ChronoUnit .DAYS ),
114
+ expectedState = BearerTokenAuthState .AUTHORIZED ,
115
+ expectedLoginType = CodeWhispererLoginType .Sono ,
116
+ expectedIsCwEnabled = true ,
117
+ expectedIsCwExpired = false
118
+ )
119
+ assertThat(ConnectionPinningManager .getInstance().isFeaturePinned(CodeWhispererConnection .getInstance())).isFalse
120
+
121
+ assertConnectionState(
122
+ startUrl = SONO_URL ,
123
+ refreshToken = aString(),
124
+ expirationTime = now.minus(1 , ChronoUnit .DAYS ),
125
+ expectedState = BearerTokenAuthState .NEEDS_REFRESH ,
126
+ expectedLoginType = CodeWhispererLoginType .Expired ,
127
+ expectedIsCwEnabled = true ,
128
+ expectedIsCwExpired = true
129
+ )
130
+ assertThat(ConnectionPinningManager .getInstance().isFeaturePinned(CodeWhispererConnection .getInstance())).isFalse
131
+
132
+ assertConnectionState(
133
+ startUrl = SONO_URL ,
134
+ refreshToken = null ,
135
+ expirationTime = now.minus(1 , ChronoUnit .DAYS ),
136
+ expectedState = BearerTokenAuthState .NOT_AUTHENTICATED ,
137
+ expectedLoginType = CodeWhispererLoginType .Logout ,
138
+ expectedIsCwEnabled = false ,
139
+ expectedIsCwExpired = false
140
+ )
141
+ assertThat(ConnectionPinningManager .getInstance().isFeaturePinned(CodeWhispererConnection .getInstance())).isFalse
142
+
143
+ assertConnectionState(
144
+ startUrl = aString(),
145
+ refreshToken = aString(),
146
+ expirationTime = now.plus(1 , ChronoUnit .DAYS ),
147
+ expectedState = BearerTokenAuthState .AUTHORIZED ,
148
+ expectedLoginType = CodeWhispererLoginType .SSO ,
149
+ expectedIsCwEnabled = true ,
150
+ expectedIsCwExpired = false
151
+ )
152
+ assertThat(ConnectionPinningManager .getInstance().isFeaturePinned(CodeWhispererConnection .getInstance())).isFalse
153
+
154
+ assertConnectionState(
155
+ startUrl = aString(),
156
+ refreshToken = aString(),
157
+ expirationTime = now.minus(1 , ChronoUnit .DAYS ),
158
+ expectedState = BearerTokenAuthState .NEEDS_REFRESH ,
159
+ expectedLoginType = CodeWhispererLoginType .Expired ,
160
+ expectedIsCwEnabled = true ,
161
+ expectedIsCwExpired = true
162
+ )
163
+ assertThat(ConnectionPinningManager .getInstance().isFeaturePinned(CodeWhispererConnection .getInstance())).isFalse
164
+
165
+ assertConnectionState(
166
+ startUrl = aString(),
167
+ refreshToken = null ,
168
+ expirationTime = now.minus(1 , ChronoUnit .DAYS ),
169
+ expectedState = BearerTokenAuthState .NOT_AUTHENTICATED ,
170
+ expectedLoginType = CodeWhispererLoginType .Logout ,
171
+ expectedIsCwEnabled = false ,
172
+ expectedIsCwExpired = false
173
+ )
174
+ assertThat(ConnectionPinningManager .getInstance().isFeaturePinned(CodeWhispererConnection .getInstance())).isFalse
175
+ }
132
176
133
- whenever(mockManager.checkActiveCodeWhispererConnectionType(project)).thenReturn(CodeWhispererLoginType .Sono )
134
- assertThat(isCodeWhispererEnabled(project)).isTrue
177
+ private fun assertConnectionState (
178
+ startUrl : String ,
179
+ refreshToken : String? ,
180
+ expirationTime : Instant ,
181
+ expectedState : BearerTokenAuthState ,
182
+ expectedLoginType : CodeWhispererLoginType ,
183
+ expectedIsCwEnabled : Boolean ,
184
+ expectedIsCwExpired : Boolean
185
+ ) {
186
+ testDiskCache.saveAccessToken(
187
+ AccessTokenCacheKey (
188
+ connectionId = " us-east-1" ,
189
+ startUrl = startUrl,
190
+ scopes = CODEWHISPERER_SCOPES
191
+ ),
192
+ AccessToken (
193
+ startUrl = startUrl,
194
+ region = " us-east-1" ,
195
+ accessToken = aString(),
196
+ refreshToken = refreshToken,
197
+ expiresAt = expirationTime
198
+ )
199
+ )
135
200
136
- whenever(mockManager.checkActiveCodeWhispererConnectionType(project)).thenReturn(CodeWhispererLoginType .SSO )
137
- assertThat(isCodeWhispererEnabled(project)).isTrue
138
- }
201
+ val myConnection = ManagedBearerSsoConnection (
202
+ startUrl,
203
+ " us-east-1" ,
204
+ CODEWHISPERER_SCOPES ,
205
+ testDiskCache
206
+ )
139
207
140
- private fun assertLoginType (startUrl : String , expectedType : CodeWhispererLoginType ) {
141
- mockManager = spy()
142
- val conn: ManagedBearerSsoConnection = mock()
143
- whenever(connectionManager.activeConnectionForFeature(any())).thenReturn(conn)
144
- whenever(conn.startUrl).thenReturn(startUrl)
145
- whenever(conn.getConnectionSettings()).thenReturn(null )
208
+ ToolkitConnectionManager .getInstance(project).switchConnection(myConnection)
209
+ val activeCwConn = ToolkitConnectionManager .getInstance(project).activeConnectionForFeature(CodeWhispererConnection .getInstance())
210
+ val myTokenProvider = myConnection.getConnectionSettings().tokenProvider.delegate as InteractiveBearerTokenProvider
146
211
147
- val actual = mockManager.checkActiveCodeWhispererConnectionType(project)
148
- assertThat(actual).isEqualTo(expectedType)
212
+ assertThat(activeCwConn).isEqualTo(myConnection)
213
+ assertThat(myTokenProvider.state()).isEqualTo(expectedState)
214
+ assertThat(CodeWhispererExplorerActionManager .getInstance().checkActiveCodeWhispererConnectionType(project)).isEqualTo(expectedLoginType)
215
+ assertThat(isCodeWhispererEnabled(project)).isEqualTo(expectedIsCwEnabled)
216
+ assertThat(isCodeWhispererExpired(project)).isEqualTo(expectedIsCwExpired)
149
217
}
150
218
}
0 commit comments