Skip to content
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
fbdd4d3
add inline completion project context call and refactor projectContex…
Will-ShaoHua Oct 17, 2024
b150cb8
projectContextController & abtest
Will-ShaoHua Oct 17, 2024
eaf4932
tst
Will-ShaoHua Oct 17, 2024
13f2f94
patch
Will-ShaoHua Oct 17, 2024
8577d78
add commented code
Will-ShaoHua Oct 17, 2024
43d698f
patch projectContextEditorListener not updating index
Will-ShaoHua Oct 17, 2024
0655f48
isBlank -> isNotBlank
Will-ShaoHua Oct 17, 2024
1ff4021
Merge remote-tracking branch 'upstream/main' into lsp-client
Will-ShaoHua Oct 21, 2024
a59204c
lint
Will-ShaoHua Oct 21, 2024
3840926
timeout LSP query inline for 50ms
Will-ShaoHua Oct 18, 2024
99c3437
dedupe repeated code
Will-ShaoHua Oct 21, 2024
fe86a2c
refactor ProjectContextProvider and add tests
Will-ShaoHua Oct 21, 2024
b642283
add more test
Will-ShaoHua Oct 21, 2024
7b5ea32
lint
Will-ShaoHua Oct 21, 2024
b684c01
Merge branch 'lsp-refactor' into lsp-client-backup
Will-ShaoHua Oct 21, 2024
1e2b21a
add test for queryInline
Will-ShaoHua Oct 21, 2024
3729d98
lint
Will-ShaoHua Oct 21, 2024
e45078e
Merge branch 'lsp-client' into lsp-client-backup
Will-ShaoHua Oct 21, 2024
47e3190
lint
Will-ShaoHua Oct 21, 2024
5f3ea1a
Merge remote-tracking branch 'upstream/main' into lsp-client
Will-ShaoHua Oct 21, 2024
1628d33
lint
Will-ShaoHua Oct 21, 2024
2249c8a
add test and fix broken test due to merge
Will-ShaoHua Oct 21, 2024
22acbf8
Merge remote-tracking branch 'upstream/main' into lsp-client
Will-ShaoHua Oct 21, 2024
cf4c29c
log
Will-ShaoHua Oct 21, 2024
149e6af
Merge remote-tracking branch 'upstream/main' into lsp-client
Will-ShaoHua Oct 21, 2024
9844c03
do index regardless isProjectContext is on/off
Will-ShaoHua Oct 21, 2024
5ab3d11
Merge remote-tracking branch 'upstream/main' into lsp-client
Will-ShaoHua Oct 22, 2024
71451b9
calc openTabsContext & projectContext concurrently and patch #4978
Will-ShaoHua Oct 22, 2024
d7d65d5
lint
Will-ShaoHua Oct 22, 2024
87f4f2c
Merge remote-tracking branch 'upstream/main' into lsp-client
Will-ShaoHua Oct 22, 2024
4663e48
test
Will-ShaoHua Oct 22, 2024
626079d
test
Will-ShaoHua Oct 22, 2024
64cd531
patch
Will-ShaoHua Oct 22, 2024
4b461c1
lint
Will-ShaoHua Oct 22, 2024
b1936f7
Merge remote-tracking branch 'upstream/main' into lsp-client
Will-ShaoHua Oct 23, 2024
f911b3d
move IndexUpdateMode to lspMessage
Will-ShaoHua Oct 23, 2024
91ae00e
move InlineBm25Chunk to LspMessage.kt
Will-ShaoHua Oct 23, 2024
9e79e54
don't try init encodeerserver in test env
Will-ShaoHua Oct 23, 2024
13e04d2
patch don't try init encodeerserver in test env
Will-ShaoHua Oct 23, 2024
4da830b
restructure concurrency and use suspend func
Will-ShaoHua Oct 23, 2024
ff27d01
Merge branch 'lsp-client-suspend-version' into lsp-client
Will-ShaoHua Oct 23, 2024
1966408
lsp version bump
Will-ShaoHua Oct 23, 2024
3c39f90
Merge remote-tracking branch 'upstream/main' into lsp-client
Will-ShaoHua Oct 23, 2024
7c7d58b
changelog
Will-ShaoHua Oct 23, 2024
9f097d7
Merge branch 'main' into lsp-client
rli Oct 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo
import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig
import com.github.tomakehurst.wiremock.http.Body
import com.github.tomakehurst.wiremock.junit.WireMockRule
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.replaceService
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.assertj.core.api.Assertions.assertThat
Expand All @@ -22,28 +25,37 @@ import org.junit.Rule
import org.junit.jupiter.api.assertThrows
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import software.aws.toolkits.jetbrains.services.amazonq.project.EncoderServer
import software.aws.toolkits.jetbrains.services.amazonq.project.IndexRequest
import software.aws.toolkits.jetbrains.services.amazonq.project.InlineBm25Chunk
import software.aws.toolkits.jetbrains.services.amazonq.project.LspMessage
import software.aws.toolkits.jetbrains.services.amazonq.project.ProjectContextProvider
import software.aws.toolkits.jetbrains.services.amazonq.project.QueryChatRequest
import software.aws.toolkits.jetbrains.services.amazonq.project.QueryInlineCompletionRequest
import software.aws.toolkits.jetbrains.services.amazonq.project.RelevantDocument
import software.aws.toolkits.jetbrains.services.amazonq.project.UpdateIndexRequest
import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
import software.aws.toolkits.jetbrains.utils.rules.CodeInsightTestFixtureRule
import software.aws.toolkits.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule
import java.net.ConnectException
import java.util.concurrent.TimeoutException
import kotlin.test.Test

class ProjectContextProviderTest {
@Rule
@JvmField
val projectRule: CodeInsightTestFixtureRule = JavaCodeInsightTestFixtureRule()

@Rule
@JvmField
val disposableRule: DisposableRule = DisposableRule()

@Rule
@JvmField
val wireMock: WireMockRule = createMockServer()
Expand All @@ -67,10 +79,10 @@ class ProjectContextProviderTest {
stubFor(any(urlPathEqualTo("/initialize")).willReturn(aResponse().withStatus(200).withResponseBody(Body("initialize response"))))

// build index
stubFor(any(urlPathEqualTo("/indexFiles")).willReturn(aResponse().withStatus(200).withResponseBody(Body("initialize response"))))
stubFor(any(urlPathEqualTo("/buildIndex")).willReturn(aResponse().withStatus(200).withResponseBody(Body("initialize response"))))

// update index
stubFor(any(urlPathEqualTo("/updateIndex")).willReturn(aResponse().withStatus(200).withResponseBody(Body("initialize response"))))
stubFor(any(urlPathEqualTo("/updateIndexV2")).willReturn(aResponse().withStatus(200).withResponseBody(Body("initialize response"))))

// query
stubFor(
Expand All @@ -80,6 +92,15 @@ class ProjectContextProviderTest {
.withResponseBody(Body(validQueryChatResponse))
)
)
stubFor(
any(urlPathEqualTo("/queryInlineProjectContext")).willReturn(
aResponse()
.withStatus(200)
.withResponseBody(
Body(validQueryInlineResponse)
)
)
)

stubFor(
any(urlPathEqualTo("/getUsage"))
Expand All @@ -92,32 +113,73 @@ class ProjectContextProviderTest {
}

@Test
fun `Lsp endpoint are correct`() {
fun `Lsp endpoint correctness`() {
assertThat(LspMessage.Initialize.endpoint).isEqualTo("initialize")
assertThat(LspMessage.Index.endpoint).isEqualTo("indexFiles")
assertThat(LspMessage.Index.endpoint).isEqualTo("buildIndex")
assertThat(LspMessage.UpdateIndex.endpoint).isEqualTo("updateIndexV2")
assertThat(LspMessage.QueryChat.endpoint).isEqualTo("query")
assertThat(LspMessage.QueryInlineCompletion.endpoint).isEqualTo("queryInlineProjectContext")
assertThat(LspMessage.GetUsageMetrics.endpoint).isEqualTo("getUsage")
}

@Test
fun `index should send files within the project to lsp`() {
fun `index should send files within the project to lsp - vector index enabled`() {
ApplicationManager.getApplication().replaceService(
CodeWhispererSettings::class.java,
mock { on { isProjectContextEnabled() } doReturn true },
disposableRule.disposable
)

projectRule.fixture.addFileToProject("Foo.java", "foo")
projectRule.fixture.addFileToProject("Bar.java", "bar")
projectRule.fixture.addFileToProject("Baz.java", "baz")

sut.index()

val request = IndexRequest(listOf("/src/Foo.java", "/src/Bar.java", "/src/Baz.java"), "/src", "all", "")
assertThat(request.filePaths).hasSize(3)
assertThat(request.filePaths).satisfies({
it.contains("/src/Foo.java") &&
it.contains("/src/Baz.java") &&
it.contains("/src/Bar.java")
})
assertThat(request.config).isEqualTo("all")

wireMock.verify(
1,
postRequestedFor(urlPathEqualTo("/buildIndex"))
.withHeader("Content-Type", equalTo("text/plain"))
// comment it out because order matters and will cause json string different
// .withRequestBody(equalTo(encryptedRequest))
)
}

@Test
fun `index should send files within the project to lsp - vector index disabled`() {
ApplicationManager.getApplication().replaceService(
CodeWhispererSettings::class.java,
mock { on { isProjectContextEnabled() } doReturn false },
disposableRule.disposable
)

projectRule.fixture.addFileToProject("Foo.java", "foo")
projectRule.fixture.addFileToProject("Bar.java", "bar")
projectRule.fixture.addFileToProject("Baz.java", "baz")

sut.index()

val request = IndexRequest(listOf("/src/Foo.java", "/src/Bar.java", "/src/Baz.java"), "/src", false)
val request = IndexRequest(listOf("/src/Foo.java", "/src/Bar.java", "/src/Baz.java"), "/src", "default", "")
assertThat(request.filePaths).hasSize(3)
assertThat(request.filePaths).satisfies({
it.contains("/src/Foo.java") &&
it.contains("/src/Baz.java") &&
it.contains("/src/Bar.java")
})
assertThat(request.config).isEqualTo("default")

wireMock.verify(
1,
postRequestedFor(urlPathEqualTo("/indexFiles"))
postRequestedFor(urlPathEqualTo("/buildIndex"))
.withHeader("Content-Type", equalTo("text/plain"))
// comment it out because order matters and will cause json string different
// .withRequestBody(equalTo(encryptedRequest))
Expand All @@ -126,17 +188,17 @@ class ProjectContextProviderTest {

@Test
fun `updateIndex should send correct encrypted request to lsp`() {
sut.updateIndex("foo.java")
val request = UpdateIndexRequest("foo.java")
sut.updateIndex(listOf("foo.java"), ProjectContextProvider.IndexUpdateMode.UPDATE)
val request = UpdateIndexRequest(listOf("foo.java"), ProjectContextProvider.IndexUpdateMode.UPDATE.value)
val requestJson = mapper.writeValueAsString(request)

assertThat(mapper.readTree(requestJson)).isEqualTo(mapper.readTree("""{ "filePath": "foo.java" }"""))
assertThat(mapper.readTree(requestJson)).isEqualTo(mapper.readTree("""{ "filePaths": ["foo.java"], "mode": "update" }"""))

val encryptedRequest = encoderServer.encrypt(requestJson)

wireMock.verify(
1,
postRequestedFor(urlPathEqualTo("/updateIndex"))
postRequestedFor(urlPathEqualTo("/updateIndexV2"))
.withHeader("Content-Type", equalTo("text/plain"))
.withRequestBody(equalTo(encryptedRequest))
)
Expand All @@ -161,6 +223,25 @@ class ProjectContextProviderTest {
)
}

@Test
fun `queryInline should send correct encrypted request to lsp`() {
sut.queryInline("foo", "Foo.java")

val request = QueryInlineCompletionRequest("foo", "Foo.java")
val requestJson = mapper.writeValueAsString(request)

assertThat(mapper.readTree(requestJson)).isEqualTo(mapper.readTree("""{ "query": "foo", "filePath": "Foo.java" }"""))

val encryptedRequest = encoderServer.encrypt(requestJson)

wireMock.verify(
1,
postRequestedFor(urlPathEqualTo("/queryInlineProjectContext"))
.withHeader("Content-Type", equalTo("text/plain"))
.withRequestBody(equalTo(encryptedRequest))
)
}

@Test
fun `query chat should return empty if result set non deserializable`() = runTest {
stubFor(
Expand Down Expand Up @@ -200,12 +281,78 @@ class ProjectContextProviderTest {
)
}

@Test
fun `query inline should throw if resultset not deserializable`() {
stubFor(
any(urlPathEqualTo("/queryInlineProjectContext")).willReturn(
aResponse().withStatus(200).withResponseBody(
Body(
"""
[
"foo", "bar"
]
""".trimIndent()
)
)
)
)

assertThrows<Exception> {
sut.queryInline("foo", "filepath")
}
}

@Test
fun `query inline should return deserialized bm25 chunks`() = runTest {
val r = sut.queryInline("foo", "filepath")
assertThat(r).hasSize(3)
assertThat(r[0]).isEqualTo(
InlineBm25Chunk(
"content1",
"file1",
0.1
)
)
assertThat(r[1]).isEqualTo(
InlineBm25Chunk(
"content2",
"file2",
0.2
)
)
assertThat(r[2]).isEqualTo(
InlineBm25Chunk(
"content3",
"file3",
0.3
)
)
}

@Test
fun `get usage should return memory, cpu usage`() = runTest {
val r = sut.getUsage()
assertThat(r).isEqualTo(ProjectContextProvider.Usage(123, 456))
}

@Test
fun `queryInline should throw if time elapsed is greater than 50ms`() {
stubFor(
any(urlPathEqualTo("/queryInlineProjectContext")).willReturn(
aResponse()
.withStatus(200)
.withResponseBody(
Body(validQueryInlineResponse)
)
.withFixedDelay(51) // 10 sec
)
)

assertThrows<TimeoutException> {
sut.queryInline("foo", "bar")
}
}

@Test
fun `test index payload is encrypted`() = runTest {
whenever(encoderServer.port).thenReturn(3000)
Expand All @@ -231,6 +378,27 @@ class ProjectContextProviderTest {
private fun createMockServer() = WireMockRule(wireMockConfig().dynamicPort())
}

// language=JSON
val validQueryInlineResponse = """
[
{
"content": "content1",
"filePath": "file1",
"score": 0.1
},
{
"content": "content2",
"filePath": "file2",
"score": 0.2
},
{
"content": "content3",
"filePath": "file3",
"score": 0.3
}
]
""".trimIndent()

// language=JSON
val validQueryChatResponse = """
[
Expand Down
Loading
Loading