Skip to content

Commit e35e956

Browse files
committed
Tests
1 parent 7e74c9e commit e35e956

File tree

2 files changed

+234
-3
lines changed

2 files changed

+234
-3
lines changed

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,12 @@ import com.intellij.openapi.util.Key
2121
import com.intellij.util.io.await
2222
import kotlinx.coroutines.CoroutineScope
2323
import kotlinx.coroutines.Deferred
24-
import kotlinx.coroutines.Dispatchers
2524
import kotlinx.coroutines.TimeoutCancellationException
2625
import kotlinx.coroutines.async
2726
import kotlinx.coroutines.launch
2827
import kotlinx.coroutines.runBlocking
2928
import kotlinx.coroutines.sync.Mutex
3029
import kotlinx.coroutines.sync.withLock
31-
import kotlinx.coroutines.withContext
3230
import kotlinx.coroutines.withTimeout
3331
import org.eclipse.lsp4j.ClientCapabilities
3432
import org.eclipse.lsp4j.ClientInfo
@@ -62,7 +60,6 @@ import java.io.PrintWriter
6260
import java.io.StringWriter
6361
import java.nio.charset.StandardCharsets
6462
import java.util.concurrent.Future
65-
import kotlin.coroutines.cancellation.CancellationException
6663
import kotlin.time.Duration.Companion.seconds
6764

6865
// https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/server/LSPProcessListener.java
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies
4+
5+
import com.intellij.openapi.Disposable
6+
import com.intellij.openapi.application.Application
7+
import com.intellij.openapi.application.ApplicationManager
8+
import com.intellij.openapi.components.serviceIfCreated
9+
import com.intellij.openapi.extensions.ExtensionPointName
10+
import com.intellij.openapi.module.Module
11+
import com.intellij.openapi.module.ModuleManager
12+
import com.intellij.openapi.project.Project
13+
import com.intellij.openapi.roots.ModuleRootEvent
14+
import com.intellij.util.messages.MessageBus
15+
import com.intellij.util.messages.MessageBusConnection
16+
import io.mockk.every
17+
import io.mockk.just
18+
import io.mockk.mockk
19+
import io.mockk.mockkObject
20+
import io.mockk.mockkStatic
21+
import io.mockk.runs
22+
import io.mockk.verify
23+
import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage
24+
import org.junit.jupiter.api.BeforeEach
25+
import org.junit.jupiter.api.Test
26+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer
27+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
28+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.ModuleDependencyProvider.Companion.EP_NAME
29+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.dependencies.SyncModuleDependenciesParams
30+
import java.util.concurrent.CompletableFuture
31+
import java.util.function.Consumer
32+
33+
class DefaultModuleDependenciesServiceTest {
34+
private lateinit var project: Project
35+
private lateinit var mockLanguageServer: AmazonQLanguageServer
36+
private lateinit var mockModuleManager: ModuleManager
37+
private lateinit var sut: DefaultModuleDependenciesService
38+
private lateinit var mockApplication: Application
39+
private lateinit var mockDependencyProvider: ModuleDependencyProvider
40+
41+
@BeforeEach
42+
fun setUp() {
43+
project = mockk()
44+
mockModuleManager = mockk()
45+
mockDependencyProvider = mockk<ModuleDependencyProvider>()
46+
mockLanguageServer = mockk()
47+
48+
every { mockLanguageServer.syncModuleDependencies(any()) } returns CompletableFuture<Unit>()
49+
50+
// Mock Application
51+
mockApplication = mockk()
52+
mockkStatic(ApplicationManager::class)
53+
every { ApplicationManager.getApplication() } returns mockApplication
54+
55+
// Mock message bus
56+
val messageBus = mockk<MessageBus>()
57+
every { project.messageBus } returns messageBus
58+
val mockConnection = mockk<MessageBusConnection>()
59+
every { messageBus.connect(any<Disposable>()) } returns mockConnection
60+
every { mockConnection.subscribe(any(), any()) } just runs
61+
62+
// Mock ModuleManager
63+
mockkStatic(ModuleManager::class)
64+
every { ModuleManager.getInstance(project) } returns mockModuleManager
65+
every { mockModuleManager.modules } returns Array(0) { mockk() }
66+
67+
// Mock LSP service
68+
val mockLspService = mockk<AmazonQLspService>()
69+
every { project.getService(AmazonQLspService::class.java) } returns mockLspService
70+
every { project.serviceIfCreated<AmazonQLspService>() } returns mockLspService
71+
every {
72+
mockLspService.executeSync<CompletableFuture<ResponseMessage>>(any())
73+
} coAnswers {
74+
val func = firstArg<suspend AmazonQLspService.(AmazonQLanguageServer) -> CompletableFuture<ResponseMessage>>()
75+
func.invoke(mockLspService, mockLanguageServer)
76+
}
77+
78+
// Mock extension point
79+
mockkObject(ModuleDependencyProvider.Companion)
80+
val epName = mockk<ExtensionPointName<ModuleDependencyProvider>>()
81+
every { EP_NAME } returns epName
82+
every { epName.forEachExtensionSafe(any()) } answers {
83+
val callback = firstArg<(ModuleDependencyProvider) -> Unit>()
84+
callback(mockDependencyProvider)
85+
}
86+
}
87+
88+
@Test
89+
fun `test initial sync on construction`() {
90+
// Arrange
91+
val module = mockk<Module>()
92+
val params = SyncModuleDependenciesParams(
93+
moduleName = "testModule",
94+
programmingLanguage = "Java",
95+
files = listOf("src/main"),
96+
dirs = listOf("lib"),
97+
includePatterns = emptyList(),
98+
excludePatterns = emptyList()
99+
)
100+
101+
every { mockModuleManager.modules } returns arrayOf(module)
102+
103+
prepDependencyProvider(listOf(Pair(module, params)))
104+
105+
sut = DefaultModuleDependenciesService(project, mockk())
106+
107+
verify { mockLanguageServer.syncModuleDependencies(params) }
108+
}
109+
110+
@Test
111+
fun `test rootsChanged with multiple modules`() {
112+
// Arrange
113+
val module1 = mockk<Module>()
114+
val module2 = mockk<Module>()
115+
val params1 = SyncModuleDependenciesParams(
116+
moduleName = "module1",
117+
programmingLanguage = "Java",
118+
files = listOf("src/main1"),
119+
dirs = listOf("lib1"),
120+
includePatterns = emptyList(),
121+
excludePatterns = emptyList()
122+
)
123+
val params2 = SyncModuleDependenciesParams(
124+
moduleName = "module2",
125+
programmingLanguage = "Java",
126+
files = listOf("src/main2"),
127+
dirs = listOf("lib2"),
128+
includePatterns = emptyList(),
129+
excludePatterns = emptyList()
130+
)
131+
132+
prepDependencyProvider(
133+
listOf(
134+
Pair(module1, params1),
135+
Pair(module2, params2)
136+
)
137+
)
138+
139+
sut = DefaultModuleDependenciesService(project, mockk())
140+
141+
// Verify both modules were synced
142+
verify { mockLanguageServer.syncModuleDependencies(params1) }
143+
verify { mockLanguageServer.syncModuleDependencies(params2) }
144+
}
145+
146+
@Test
147+
fun `test rootsChanged with non-applicable module`() {
148+
// Arrange
149+
val module = mockk<Module>()
150+
151+
every { mockModuleManager.modules } returns arrayOf(module)
152+
153+
every {
154+
EP_NAME.forEachExtensionSafe(any<Consumer<ModuleDependencyProvider>>())
155+
} answers {
156+
val consumer = firstArg<Consumer<ModuleDependencyProvider>>()
157+
every { mockDependencyProvider.isApplicable(any()) } returns false
158+
consumer.accept(mockDependencyProvider)
159+
}
160+
161+
sut = DefaultModuleDependenciesService(project, mockk())
162+
163+
// Verify no sync occurred
164+
verify(exactly = 0) { mockLanguageServer.syncModuleDependencies(any()) }
165+
}
166+
167+
@Test
168+
fun `test rootsChanged withFileTypesChange`() {
169+
// Arrange
170+
val module = mockk<Module>()
171+
val params = SyncModuleDependenciesParams(
172+
moduleName = "testModule",
173+
programmingLanguage = "Java",
174+
files = listOf("src/main"),
175+
dirs = listOf("lib"),
176+
includePatterns = emptyList(),
177+
excludePatterns = emptyList()
178+
)
179+
prepDependencyProvider(listOf(Pair(module, params)))
180+
val event = mockk<ModuleRootEvent>()
181+
every { event.isCausedByFileTypesChange } returns true
182+
183+
sut = DefaultModuleDependenciesService(project, mockk())
184+
185+
// Act
186+
sut.rootsChanged(event)
187+
188+
// Verify sync occurred once - once on init and rootsChange ignores
189+
verify(exactly = 1) { mockLanguageServer.syncModuleDependencies(params) }
190+
}
191+
192+
@Test
193+
fun `test rootsChanged after module changes`() {
194+
// Arrange
195+
val module = mockk<Module>()
196+
val params = SyncModuleDependenciesParams(
197+
moduleName = "testModule",
198+
programmingLanguage = "Java",
199+
files = listOf("src/main"),
200+
dirs = listOf("lib"),
201+
includePatterns = emptyList(),
202+
excludePatterns = emptyList()
203+
)
204+
val event = mockk<ModuleRootEvent>()
205+
206+
every { mockModuleManager.modules } returns arrayOf(module)
207+
every { event.isCausedByFileTypesChange } returns false
208+
209+
prepDependencyProvider(listOf(Pair(module, params)))
210+
211+
sut = DefaultModuleDependenciesService(project, mockk())
212+
213+
// Act
214+
sut.rootsChanged(event)
215+
216+
// Verify sync occurred twice - once on init and once after rootsChanged
217+
verify(exactly = 2) { mockLanguageServer.syncModuleDependencies(params) }
218+
}
219+
220+
private fun prepDependencyProvider(moduleParamPairs: List<Pair<Module, SyncModuleDependenciesParams>>) {
221+
every { mockModuleManager.modules } returns moduleParamPairs.map { it.first }.toTypedArray()
222+
223+
every {
224+
EP_NAME.forEachExtensionSafe(any<Consumer<ModuleDependencyProvider>>())
225+
} answers {
226+
val consumer = firstArg<Consumer<ModuleDependencyProvider>>()
227+
moduleParamPairs.forEach { (module, params) ->
228+
every { mockDependencyProvider.isApplicable(module) } returns true
229+
every { mockDependencyProvider.createParams(module) } returns params
230+
}
231+
consumer.accept(mockDependencyProvider)
232+
}
233+
}
234+
}

0 commit comments

Comments
 (0)