11package io.github.amichne.kast.intellij
22
3+ import io.github.amichne.kast.api.HealthResponse
4+ import io.github.amichne.kast.api.ServerInstanceDescriptor
35import io.github.amichne.kast.testing.AnalysisBackendContractAssertions
46import io.github.amichne.kast.testing.AnalysisBackendContractFixture
57import kotlinx.coroutines.runBlocking
8+ import kotlinx.serialization.json.Json
9+ import org.junit.jupiter.api.Assertions.assertEquals
610import org.junit.jupiter.api.Test
11+ import org.junit.jupiter.api.io.TempDir
12+ import java.net.URI
13+ import java.net.http.HttpClient
14+ import java.net.http.HttpRequest
15+ import java.net.http.HttpResponse
716import java.nio.file.Path
17+ import kotlin.io.path.listDirectoryEntries
18+ import kotlin.io.path.notExists
19+ import kotlin.io.path.readText
820
921class IntelliJAnalysisBackendContractTest : IntelliJFixtureTestCase () {
22+ @TempDir
23+ lateinit var tempHome: Path
24+
1025 @Test
1126 fun `intellij backend satisfies the shared contract fixture` () = runBlocking {
1227 val fixtureProject = createContractFixture()
@@ -29,11 +44,99 @@ class IntelliJAnalysisBackendContractTest : IntelliJFixtureTestCase() {
2944 )
3045 }
3146
47+ @Test
48+ fun `project activity starts a project scoped server and registers a descriptor` () = runBlocking {
49+ val startupProperty = " kast.enable.startup.activity.tests"
50+ val originalUserHome = System .getProperty(" user.home" )
51+ val originalStartupFlag = System .getProperty(startupProperty)
52+ val descriptorDirectory = tempHome.resolve(" .kast/instances" )
53+ val projectBasePath = checkNotNull(fixture.project.basePath)
54+ val service = fixture.project.getService(KastProjectService ::class .java)
55+ var descriptorPath: Path ? = null
56+
57+ System .setProperty(" user.home" , tempHome.toString())
58+ System .setProperty(startupProperty, " true" )
59+ try {
60+ KastProjectActivity ().execute(fixture.project)
61+
62+ descriptorPath = waitForCondition(" server descriptor" ) {
63+ if (descriptorDirectory.toFile().isDirectory) {
64+ descriptorDirectory.listDirectoryEntries(" *.json" ).singleOrNull()
65+ } else {
66+ null
67+ }
68+ }
69+ val descriptor = Json .decodeFromString<ServerInstanceDescriptor >(descriptorPath.readText())
70+
71+ assertEquals(" intellij" , descriptor.backendName)
72+ assertEquals(" 0.1.0" , descriptor.backendVersion)
73+ assertEquals(projectBasePath, descriptor.workspaceRoot)
74+
75+ val healthResponse = waitForCondition(" health response" ) {
76+ fetchHealth(descriptor)
77+ }
78+ assertEquals(" ok" , healthResponse.status)
79+ assertEquals(descriptor.workspaceRoot, healthResponse.workspaceRoot)
80+ } finally {
81+ service.dispose()
82+ descriptorPath?.let { path ->
83+ waitForCondition(" descriptor cleanup" ) {
84+ if (path.notExists()) true else null
85+ }
86+ }
87+ if (originalStartupFlag == null ) {
88+ System .clearProperty(startupProperty)
89+ } else {
90+ System .setProperty(startupProperty, originalStartupFlag)
91+ }
92+ System .setProperty(" user.home" , originalUserHome)
93+ }
94+ }
95+
3296 private fun createContractFixture (): AnalysisBackendContractFixture {
3397 return AnalysisBackendContractFixture .create(
3498 workspaceRoot = workspaceRoot(),
3599 ) { relativePath, content ->
36100 writeWorkspaceFile(relativePath, content)
37101 }
38102 }
103+
104+ private fun fetchHealth (descriptor : ServerInstanceDescriptor ): HealthResponse ? {
105+ val response = runCatching {
106+ HttpClient .newHttpClient().send(
107+ HttpRequest .newBuilder(
108+ URI .create(" http://${descriptor.host} :${descriptor.port} /api/v1/health" ),
109+ ).GET ().build(),
110+ HttpResponse .BodyHandlers .ofString(),
111+ )
112+ }.getOrNull() ? : return null
113+
114+ if (response.statusCode() != 200 ) {
115+ return null
116+ }
117+
118+ return Json .decodeFromString<HealthResponse >(response.body())
119+ }
120+
121+ private fun <T : Any > waitForCondition (
122+ label : String ,
123+ timeoutMillis : Long = 15_000,
124+ pollIntervalMillis : Long = 100,
125+ probe : () -> T ? ,
126+ ): T {
127+ val deadline = System .nanoTime() + timeoutMillis * 1_000_000
128+ while (System .nanoTime() < deadline) {
129+ probe()?.let { return it }
130+ Thread .sleep(pollIntervalMillis)
131+ }
132+
133+ val directoryContents = if (tempHome.toFile().exists()) {
134+ tempHome.toFile().walkTopDown().joinToString(separator = " \n " ) { it.relativeTo(tempHome.toFile()).path }
135+ } else {
136+ " <temp home missing>"
137+ }
138+ throw AssertionError (
139+ " Timed out waiting for $label under $tempHome . Current contents:\n $directoryContents " ,
140+ )
141+ }
39142}
0 commit comments