Skip to content

Commit 6e9b34c

Browse files
Merge main into feature/build-execute
2 parents f883a31 + 48b30c0 commit 6e9b34c

File tree

5 files changed

+58
-92
lines changed

5 files changed

+58
-92
lines changed

plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml

+1-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@
3333
serviceImplementation="software.aws.toolkits.jetbrains.services.codewhisperer.customization.DefaultCodeWhispererModelConfigurator"/>
3434

3535
<projectService serviceInterface="software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor"
36-
serviceImplementation="software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptorImpl"
37-
testServiceImplementation="software.aws.toolkits.jetbrains.services.codewhisperer.credentials.MockCodeWhispererClientAdaptor"/>
36+
serviceImplementation="software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptorImpl"/>
3837
<projectService serviceInterface="software.aws.toolkits.jetbrains.services.codewhisperer.util.FileContextProvider"
3938
serviceImplementation="software.aws.toolkits.jetbrains.services.codewhisperer.util.DefaultCodeWhispererFileContextProvider"/>
4039
<projectService serviceImplementation="software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanManager"/>

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/credentials/CodeWhispererClientAdaptor.kt

+11-66
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44
package software.aws.toolkits.jetbrains.services.codewhisperer.credentials
55

6-
import com.intellij.openapi.Disposable
7-
import com.intellij.openapi.application.ApplicationManager
86
import com.intellij.openapi.components.service
97
import com.intellij.openapi.project.Project
108
import com.intellij.util.text.nullize
@@ -39,14 +37,9 @@ import software.amazon.awssdk.services.codewhispererruntime.model.TargetCode
3937
import software.amazon.awssdk.services.codewhispererruntime.model.UserIntent
4038
import software.aws.toolkits.core.utils.debug
4139
import software.aws.toolkits.core.utils.getLogger
42-
import software.aws.toolkits.core.utils.warn
43-
import software.aws.toolkits.jetbrains.core.AwsClientManager
4440
import software.aws.toolkits.jetbrains.core.awsClient
45-
import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection
46-
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection
4741
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
48-
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerListener
49-
import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection
42+
import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection
5043
import software.aws.toolkits.jetbrains.services.amazonq.codeWhispererUserContext
5144
import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization
5245
import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage
@@ -62,8 +55,11 @@ import java.time.Instant
6255
import java.util.concurrent.TimeUnit
6356

6457
// As the connection is project-level, we need to make this project-level too
65-
@Deprecated("Methods can throw a NullPointerException if callee does not check if connection is valid")
66-
interface CodeWhispererClientAdaptor : Disposable {
58+
@Deprecated(
59+
"It was needed as we were supporting two service models (sigv4 & bearer), " +
60+
"it's no longer the case as we remove sigv4 support, should use AwsClientManager.getClient() directly"
61+
)
62+
interface CodeWhispererClientAdaptor {
6763
val project: Project
6864

6965
fun generateCompletionsPaginator(
@@ -261,32 +257,11 @@ interface CodeWhispererClientAdaptor : Disposable {
261257
}
262258
}
263259

264-
open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeWhispererClientAdaptor {
265-
@Volatile
266-
private var myBearerClient: CodeWhispererRuntimeClient? = null
267-
268-
init {
269-
initClientUpdateListener()
270-
}
271-
272-
private fun initClientUpdateListener() {
273-
ApplicationManager.getApplication().messageBus.connect(this).subscribe(
274-
ToolkitConnectionManagerListener.TOPIC,
275-
object : ToolkitConnectionManagerListener {
276-
override fun activeConnectionChanged(newConnection: ToolkitConnection?) {
277-
if (newConnection is AwsBearerTokenConnection) {
278-
myBearerClient = getBearerClient(newConnection.getConnectionSettings().providerId)
279-
}
280-
}
281-
}
282-
)
283-
}
284-
285-
private fun bearerClient(): CodeWhispererRuntimeClient {
286-
if (myBearerClient != null) return myBearerClient as CodeWhispererRuntimeClient
287-
myBearerClient = getBearerClient()
288-
return myBearerClient as CodeWhispererRuntimeClient
289-
}
260+
class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeWhispererClientAdaptor {
261+
fun bearerClient(): CodeWhispererRuntimeClient =
262+
ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance())?.getConnectionSettings()
263+
?.awsClient<CodeWhispererRuntimeClient>()
264+
?: throw Exception("attempt to get bearer client while there is no valid credential")
290265

291266
override fun generateCompletionsPaginator(firstRequest: GenerateCompletionsRequest) = sequence<GenerateCompletionsResponse> {
292267
var nextToken: String? = firstRequest.nextToken()
@@ -809,41 +784,11 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW
809784
requestBuilder.userContext(codeWhispererUserContext())
810785
}
811786

812-
override fun dispose() {
813-
myBearerClient?.close()
814-
}
815-
816-
/**
817-
* Every different SSO/AWS Builder ID connection requires a new client which has its corresponding bearer token provider,
818-
* thus we have to create them dynamically.
819-
* Invalidate and recycle the old client first, and create a new client with the new connection.
820-
* This makes sure when we invoke CW, we always use the up-to-date connection.
821-
* In case this fails to close the client, myBearerClient is already set to null thus next time when we invoke CW,
822-
* it will go through this again which should get the current up-to-date connection. This stale client would be
823-
* unused and stay in memory for a while until eventually closed by ToolkitClientManager.
824-
*/
825-
open fun getBearerClient(oldProviderIdToRemove: String = ""): CodeWhispererRuntimeClient? {
826-
myBearerClient = null
827-
828-
val connection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(CodeWhispererConnection.getInstance())
829-
connection as? AwsBearerTokenConnection ?: run {
830-
LOG.warn { "$connection is not a bearer token connection" }
831-
return null
832-
}
833-
834-
return AwsClientManager.getInstance().getClient<CodeWhispererRuntimeClient>(connection.getConnectionSettings())
835-
}
836-
837787
companion object {
838788
private val LOG = getLogger<CodeWhispererClientAdaptorImpl>()
839789
}
840790
}
841791

842-
class MockCodeWhispererClientAdaptor(override val project: Project) : CodeWhispererClientAdaptorImpl(project) {
843-
override fun getBearerClient(oldProviderIdToRemove: String): CodeWhispererRuntimeClient = project.awsClient()
844-
override fun dispose() {}
845-
}
846-
847792
private fun CodewhispererSuggestionState.toCodeWhispererSdkType() = when {
848793
this == CodewhispererSuggestionState.Accept -> SuggestionState.ACCEPT
849794
this == CodewhispererSuggestionState.Reject -> SuggestionState.REJECT

plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt

+34-21
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
package software.aws.toolkits.jetbrains.services.codewhisperer
55

66
import com.intellij.openapi.application.ApplicationManager
7-
import com.intellij.openapi.util.Disposer
87
import com.intellij.openapi.util.SystemInfo
98
import com.intellij.testFramework.DisposableRule
109
import com.intellij.testFramework.RuleChain
@@ -15,6 +14,7 @@ import org.junit.After
1514
import org.junit.Before
1615
import org.junit.Rule
1716
import org.junit.Test
17+
import org.junit.jupiter.api.assertThrows
1818
import org.mockito.kotlin.any
1919
import org.mockito.kotlin.argThat
2020
import org.mockito.kotlin.argumentCaptor
@@ -24,7 +24,6 @@ import org.mockito.kotlin.mock
2424
import org.mockito.kotlin.stub
2525
import org.mockito.kotlin.times
2626
import org.mockito.kotlin.verify
27-
import org.mockito.kotlin.whenever
2827
import software.amazon.awssdk.services.codewhispererruntime.CodeWhispererRuntimeClient
2928
import software.amazon.awssdk.services.codewhispererruntime.model.ArtifactType
3029
import software.amazon.awssdk.services.codewhispererruntime.model.CodeAnalysisFindingsSchema
@@ -54,21 +53,22 @@ import software.amazon.awssdk.services.codewhispererruntime.model.SuggestionStat
5453
import software.amazon.awssdk.services.codewhispererruntime.paginators.GenerateCompletionsIterable
5554
import software.amazon.awssdk.services.codewhispererruntime.paginators.ListAvailableCustomizationsIterable
5655
import software.amazon.awssdk.services.ssooidc.SsoOidcClient
57-
import software.aws.toolkits.core.TokenConnectionSettings
5856
import software.aws.toolkits.core.utils.test.aString
5957
import software.aws.toolkits.jetbrains.core.MockClientManagerRule
6058
import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection
6159
import software.aws.toolkits.jetbrains.core.credentials.ManagedSsoProfile
6260
import software.aws.toolkits.jetbrains.core.credentials.MockCredentialManagerRule
6361
import software.aws.toolkits.jetbrains.core.credentials.MockToolkitAuthManagerRule
6462
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
63+
import software.aws.toolkits.jetbrains.core.credentials.logoutFromSsoConnection
64+
import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection
65+
import software.aws.toolkits.jetbrains.core.credentials.sono.Q_SCOPES
6566
import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_REGION
6667
import software.aws.toolkits.jetbrains.services.amazonq.FEATURE_EVALUATION_PRODUCT_NAME
6768
import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.metadata
6869
import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonRequest
6970
import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonResponseWithToken
7071
import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.sdkHttpResponse
71-
import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor
7272
import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptorImpl
7373
import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization
7474
import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator
@@ -93,13 +93,12 @@ class CodeWhispererClientAdaptorTest {
9393

9494
@Rule
9595
@JvmField
96-
val ruleChain = RuleChain(projectRule, mockCredentialRule, mockClientManagerRule, disposableRule)
96+
val ruleChain = RuleChain(projectRule, mockCredentialRule, mockClientManagerRule, authManagerRule, disposableRule)
9797

9898
private lateinit var bearerClient: CodeWhispererRuntimeClient
9999
private lateinit var ssoClient: SsoOidcClient
100100

101-
private lateinit var sut: CodeWhispererClientAdaptor
102-
private lateinit var connectionManager: ToolkitConnectionManager
101+
private lateinit var sut: CodeWhispererClientAdaptorImpl
103102
private var isTelemetryEnabledDefault: Boolean = false
104103

105104
@Before
@@ -117,15 +116,8 @@ class CodeWhispererClientAdaptorTest {
117116
on { listFeatureEvaluations(any<ListFeatureEvaluationsRequest>()) } doReturn listFeatureEvaluationsResponse
118117
}
119118

120-
val mockConnection = mock<AwsBearerTokenConnection>()
121-
whenever(mockConnection.getConnectionSettings()) doReturn mock<TokenConnectionSettings>()
122-
123-
connectionManager = mock {
124-
on {
125-
activeConnectionForFeature(any())
126-
} doReturn authManagerRule.createConnection(ManagedSsoProfile("us-east-1", aString(), listOf("scopes"))) as AwsBearerTokenConnection
127-
}
128-
projectRule.project.replaceService(ToolkitConnectionManager::class.java, connectionManager, disposableRule.disposable)
119+
val conn = authManagerRule.createConnection(ManagedSsoProfile("us-east-1", "url", Q_SCOPES))
120+
ToolkitConnectionManager.getInstance(projectRule.project).switchConnection(conn)
129121

130122
isTelemetryEnabledDefault = AwsSettings.getInstance().isTelemetryEnabled
131123
}
@@ -135,16 +127,37 @@ class CodeWhispererClientAdaptorTest {
135127
AwsSettings.getInstance().isTelemetryEnabled = isTelemetryEnabledDefault
136128
}
137129

138-
@After
139-
fun cleanup() {
140-
Disposer.dispose(sut)
141-
}
142-
143130
@Test
144131
fun `Sono region is us-east-1`() {
145132
assertThat("us-east-1").isEqualTo(SONO_REGION)
146133
}
147134

135+
@Test
136+
fun `should throw if there is no valid credential, otherwise return codewhispererRuntimeClient`() {
137+
val connectionManager = ToolkitConnectionManager.getInstance(projectRule.project)
138+
139+
assertThat(connectionManager.activeConnectionForFeature(QConnection.getInstance()))
140+
.isNotNull
141+
assertThat(sut.bearerClient())
142+
.isNotNull
143+
.isInstanceOf(CodeWhispererRuntimeClient::class.java)
144+
145+
logoutFromSsoConnection(projectRule.project, connectionManager.activeConnectionForFeature(QConnection.getInstance()) as AwsBearerTokenConnection)
146+
assertThat(connectionManager.activeConnectionForFeature(QConnection.getInstance())).isNull()
147+
assertThrows<Exception>("attempt to get bearer client while there is no valid credential") {
148+
sut.bearerClient()
149+
}
150+
151+
val anotherQConnection = authManagerRule.createConnection(ManagedSsoProfile("us-east-1", aString(), Q_SCOPES))
152+
connectionManager.switchConnection(anotherQConnection)
153+
assertThat(connectionManager.activeConnectionForFeature(QConnection.getInstance()))
154+
.isNotNull
155+
.isEqualTo(anotherQConnection)
156+
assertThat(sut.bearerClient())
157+
.isNotNull
158+
.isInstanceOf(CodeWhispererRuntimeClient::class.java)
159+
}
160+
148161
@Test
149162
fun `listCustomizations`() {
150163
val sdkIterable = ListAvailableCustomizationsIterable(bearerClient, ListAvailableCustomizationsRequest.builder().build())

plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class CodeWhispererSettingsTest : CodeWhispererTestBase() {
105105
stateManager.loadState(CodeWhispererExploreActionState())
106106
CodeWhispererSettings.getInstance().loadState(CodeWhispererConfiguration())
107107

108-
val problemsWindow = ProblemsView.getToolWindow(projectRule.project) ?: fail("Problems window not found")
108+
ProblemsView.getToolWindow(projectRule.project) ?: fail("Problems window not found")
109109
val codeReferenceWindow = ToolWindowManager.getInstance(projectRule.project).getToolWindow(
110110
CodeWhispererCodeReferenceToolWindowFactory.id
111111
) ?: fail("Code Reference Log window not found")
@@ -114,7 +114,6 @@ class CodeWhispererSettingsTest : CodeWhispererTestBase() {
114114
} ?: fail("CodeWhisperer status bar widget not found")
115115

116116
runInEdtAndWait {
117-
assertThat(problemsWindow.contentManager.contentCount).isEqualTo(0)
118117
assertThat(codeReferenceWindow.isAvailable).isFalse
119118
assertThat(statusBarWidgetFactory.isAvailable(projectRule.project)).isTrue
120119
assertThat(settingsManager.isIncludeCodeWithReference()).isFalse

plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt

+11-1
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,13 @@ import org.mockito.kotlin.verify
3030
import software.amazon.awssdk.services.codewhispererruntime.CodeWhispererRuntimeClient
3131
import software.amazon.awssdk.services.codewhispererruntime.model.GenerateCompletionsRequest
3232
import software.amazon.awssdk.services.codewhispererruntime.paginators.GenerateCompletionsIterable
33+
import software.amazon.awssdk.services.ssooidc.SsoOidcClient
3334
import software.aws.toolkits.jetbrains.core.MockClientManagerRule
35+
import software.aws.toolkits.jetbrains.core.credentials.ManagedSsoProfile
3436
import software.aws.toolkits.jetbrains.core.credentials.MockCredentialManagerRule
37+
import software.aws.toolkits.jetbrains.core.credentials.MockToolkitAuthManagerRule
38+
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
39+
import software.aws.toolkits.jetbrains.core.credentials.sono.Q_SCOPES
3540
import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.codeWhispererRecommendationActionId
3641
import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonFileName
3742
import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonResponse
@@ -65,10 +70,11 @@ open class CodeWhispererTestBase {
6570
val mockClientManagerRule = MockClientManagerRule()
6671
val mockCredentialRule = MockCredentialManagerRule()
6772
val disposableRule = DisposableRule()
73+
val authManagerRule = MockToolkitAuthManagerRule()
6874

6975
@Rule
7076
@JvmField
71-
val ruleChain = RuleChain(projectRule, mockCredentialRule, mockClientManagerRule, disposableRule)
77+
val ruleChain = RuleChain(projectRule, mockCredentialRule, mockClientManagerRule, authManagerRule, disposableRule)
7278

7379
protected lateinit var mockClient: CodeWhispererRuntimeClient
7480

@@ -86,6 +92,7 @@ open class CodeWhispererTestBase {
8692
@Before
8793
open fun setUp() {
8894
mockClient = mockClientManagerRule.create()
95+
mockClientManagerRule.create<SsoOidcClient>()
8996
val requestCaptor = argumentCaptor<GenerateCompletionsRequest>()
9097
mockClient.stub {
9198
on {
@@ -159,6 +166,9 @@ open class CodeWhispererTestBase {
159166
projectRule.project.replaceService(CodeWhispererClientAdaptor::class.java, clientAdaptorSpy, disposableRule.disposable)
160167
ApplicationManager.getApplication().replaceService(CodeWhispererExplorerActionManager::class.java, stateManager, disposableRule.disposable)
161168
stateManager.setAutoEnabled(false)
169+
170+
val conn = authManagerRule.createConnection(ManagedSsoProfile("us-east-1", "url", Q_SCOPES))
171+
ToolkitConnectionManager.getInstance(projectRule.project).switchConnection(conn)
162172
}
163173

164174
@After

0 commit comments

Comments
 (0)