mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 22:51:17 +07:00
* platform's HttpRequest-based downloader for library roots * test that it can be used for all repository libraries in monorepo * unify getting authentication data from all providers (now can be used with multiple resolvers) Resolver is turned off by default and can be enabled by `jar.http.downloader.enabled` registry key GitOrigin-RevId: 31227e90763b77de6602354fcef9170ac6e42277
246 lines
9.0 KiB
Kotlin
246 lines
9.0 KiB
Kotlin
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
|
package com.intellij.jarRepository
|
|
|
|
import com.intellij.jarRepository.JarHttpDownloader.RemoteRepository
|
|
import com.intellij.jarRepository.JarHttpDownloader.downloadArtifact
|
|
import com.intellij.jarRepository.JarHttpDownloaderTestUtil.createContext
|
|
import com.intellij.jarRepository.JarHttpDownloaderTestUtil.url
|
|
import com.intellij.jarRepository.JarRepositoryAuthenticationDataProvider.AuthenticationData
|
|
import com.intellij.testFramework.rules.TempDirectoryExtension
|
|
import com.intellij.util.io.DigestUtil
|
|
import com.intellij.util.io.HttpRequests
|
|
import io.ktor.http.HttpStatusCode
|
|
import io.ktor.server.engine.ApplicationEngine
|
|
import org.jetbrains.idea.maven.aether.RetryProvider
|
|
import org.junit.jupiter.api.AfterEach
|
|
import org.junit.jupiter.api.BeforeEach
|
|
import org.junit.jupiter.api.Test
|
|
import org.junit.jupiter.api.extension.RegisterExtension
|
|
import java.io.IOException
|
|
import java.net.InetAddress
|
|
import java.net.ServerSocket
|
|
import java.nio.file.Path
|
|
import kotlin.io.path.readText
|
|
import kotlin.test.assertEquals
|
|
import kotlin.test.assertFailsWith
|
|
import kotlin.test.assertTrue
|
|
|
|
class JarHttpDownloaderArtifactTest {
|
|
@JvmField
|
|
@RegisterExtension
|
|
internal val serverExtension = JarHttpDownloaderTestUtil.TestHttpServerExtension()
|
|
private val server: ApplicationEngine get() = serverExtension.server
|
|
|
|
@JvmField
|
|
@RegisterExtension
|
|
internal val tempDirectory = TempDirectoryExtension()
|
|
|
|
private val localRepository by lazy {
|
|
tempDirectory.newDirectoryPath("local")
|
|
}
|
|
|
|
private val remoteRepositories by lazy {
|
|
listOf(
|
|
RemoteRepository(server.url + "/a", null),
|
|
RemoteRepository(server.url + "/b", AuthenticationData("u", "pass")),
|
|
RemoteRepository(server.url + "/c", null),
|
|
)
|
|
}
|
|
|
|
@BeforeEach
|
|
fun setUp() {
|
|
JarHttpDownloader.forceHttps = false
|
|
|
|
}
|
|
|
|
@AfterEach
|
|
fun tearDown() {
|
|
JarHttpDownloader.forceHttps = true
|
|
}
|
|
|
|
@Test
|
|
fun downloadArtifact_second_succeed() {
|
|
server.createContext("/b/c/file.data", HttpStatusCode.OK, response = "Hello, world!")
|
|
|
|
val localFile = downloadArtifact(
|
|
artifactPath = JarHttpDownloader.RelativePathToDownload(Path.of("c/file.data"), null),
|
|
localRepository = localRepository,
|
|
remoteRepositories = remoteRepositories,
|
|
retry = RetryProvider.disabled(),
|
|
)
|
|
|
|
assertEquals(localRepository.resolve("c/file.data"), localFile)
|
|
assertEquals("Hello, world!", localFile.readText())
|
|
|
|
assertEquals("""
|
|
/a/c/file.data: 404
|
|
/b/c/file.data: 200
|
|
""".trimIndent(), serverExtension.log.trim())
|
|
}
|
|
|
|
@Test
|
|
fun downloadArtifact_authenticate() {
|
|
server.createContext("/b/c/file.data", HttpStatusCode.OK, response = "Hello, world!", auth = AuthenticationData("u", "pass"))
|
|
|
|
val localFile = downloadArtifact(
|
|
artifactPath = JarHttpDownloader.RelativePathToDownload(Path.of("c/file.data"), null),
|
|
localRepository = localRepository,
|
|
remoteRepositories = remoteRepositories,
|
|
retry = RetryProvider.disabled(),
|
|
)
|
|
|
|
assertEquals(localRepository.resolve("c/file.data"), localFile)
|
|
assertEquals("Hello, world!", localFile.readText())
|
|
}
|
|
|
|
@Test
|
|
fun downloadArtifact_authenticate_wrong_password() {
|
|
server.createContext("/b/c/file.data", HttpStatusCode.OK, response = "Hello, world!", auth = AuthenticationData("u", "another password"))
|
|
|
|
val e = assertFailsWith<IllegalStateException> {
|
|
downloadArtifact(
|
|
artifactPath = JarHttpDownloader.RelativePathToDownload(Path.of("c/file.data"), null),
|
|
localRepository = localRepository,
|
|
remoteRepositories = remoteRepositories,
|
|
retry = RetryProvider.disabled(),
|
|
)
|
|
}
|
|
|
|
assertEquals("Artifact 'c/file.data' was not found in remote repositories, some of them returned 401 Unauthorized: [${server.url}/b/c/file.data]", e.message)
|
|
val suppressed = e.suppressedExceptions.single() as HttpRequests.HttpStatusException
|
|
assertEquals("Request failed with status code 401", suppressed.message)
|
|
assertEquals("${server.url}/b/c/file.data", suppressed.url)
|
|
}
|
|
|
|
@Test
|
|
fun downloadArtifact_skip_wrong_authentication() {
|
|
// repository 'a': artifact missing
|
|
// repository 'b': wrong authentication
|
|
// repository 'c': OK
|
|
server.createContext("/b/c/file.data", HttpStatusCode.OK, response = "secret data", auth = AuthenticationData("u", "another password"))
|
|
server.createContext("/c/c/file.data", HttpStatusCode.OK, response = "Hello, world!")
|
|
|
|
downloadArtifact(
|
|
artifactPath = JarHttpDownloader.RelativePathToDownload(Path.of("c/file.data"), null),
|
|
localRepository = localRepository,
|
|
remoteRepositories = remoteRepositories,
|
|
retry = RetryProvider.disabled(),
|
|
)
|
|
|
|
assertEquals("Hello, world!", localRepository.resolve("c/file.data").readText())
|
|
|
|
assertEquals("""
|
|
/a/c/file.data: 404
|
|
/b/c/file.data: 401
|
|
/c/c/file.data: 200
|
|
""".trimIndent(), serverExtension.log.trim())
|
|
}
|
|
|
|
@Test
|
|
fun downloadArtifact_do_not_skip_internal_server_error() {
|
|
// repository 'a': artifact missing
|
|
// repository 'b': 500
|
|
// ... we should not query 'c' for security reasons (when checksum missing, we can't guarantee what we'll find in 'c')
|
|
//
|
|
// case when 'expectedSha256!=null' was not implemented (we may query everything then), but could be
|
|
|
|
server.createContext("/b/c/file.data", HttpStatusCode.InternalServerError)
|
|
|
|
val e = assertFailsWith<HttpRequests.HttpStatusException> {
|
|
downloadArtifact(
|
|
artifactPath = JarHttpDownloader.RelativePathToDownload(Path.of("c/file.data"), null),
|
|
localRepository = localRepository,
|
|
remoteRepositories = remoteRepositories,
|
|
retry = RetryProvider.disabled(),
|
|
)
|
|
}
|
|
|
|
assertEquals("Request failed with status code 500", e.message)
|
|
assertEquals("${server.url}/b/c/file.data", e.url)
|
|
|
|
assertEquals("""
|
|
/a/c/file.data: 404
|
|
/b/c/file.data: 500
|
|
""".trimIndent(), serverExtension.log.trim())
|
|
}
|
|
|
|
@Test
|
|
fun downloadArtifact_do_not_skip_connection_refused() {
|
|
// repository 'a': artifact missing
|
|
// repository 'b': connection refused
|
|
// ... we should not query 'c' for security reasons (when checksum missing, we can't guarantee what we'll find in 'c')
|
|
//
|
|
// case when 'expectedSha256!=null' was not implemented (we may query everything then), but could be
|
|
|
|
val bindAddr = InetAddress.getByName("127.0.0.1")
|
|
val unusedLocalPort = ServerSocket(0, 10, bindAddr).use { it.localPort }
|
|
|
|
val e = assertFailsWith<IOException> {
|
|
downloadArtifact(
|
|
artifactPath = JarHttpDownloader.RelativePathToDownload(Path.of("c/file.data"), null),
|
|
localRepository = localRepository,
|
|
remoteRepositories = listOf(
|
|
RemoteRepository(server.url + "/a", null),
|
|
RemoteRepository("http://127.0.0.1:${unusedLocalPort}/root", null),
|
|
RemoteRepository(server.url + "/c", null),
|
|
),
|
|
retry = RetryProvider.disabled(),
|
|
)
|
|
}
|
|
|
|
assertTrue(e.message!!.contains("http://127.0.0.1:${unusedLocalPort}/root/c/file.data"), e.message)
|
|
assertTrue(e.message!!.contains("Connection refused"), e.message)
|
|
|
|
assertEquals("""
|
|
/a/c/file.data: 404
|
|
""".trimIndent(), serverExtension.log.trim())
|
|
}
|
|
|
|
@Test
|
|
fun downloadArtifact_wrong_checksum() {
|
|
val response = "Hello, world!"
|
|
server.createContext("/b/c/file.data", HttpStatusCode.OK, response = response)
|
|
|
|
val actualSha256 = DigestUtil.sha256Hex(response.toByteArray())
|
|
val expectedSha256 = DigestUtil.sha256Hex("wrong".toByteArray())
|
|
|
|
val e = assertFailsWith<JarHttpDownloader.BadChecksumException> {
|
|
downloadArtifact(
|
|
artifactPath = JarHttpDownloader.RelativePathToDownload(Path.of("c/file.data"), expectedSha256),
|
|
localRepository = localRepository,
|
|
remoteRepositories = remoteRepositories,
|
|
retry = RetryProvider.disabled(),
|
|
)
|
|
}
|
|
|
|
assertTrue(e.message!!.startsWith("Wrong file checksum after downloading '${server.url}/b/c/file.data'"), e.message)
|
|
assertTrue(e.message!!.contains(expectedSha256), e.message)
|
|
assertTrue(e.message!!.contains(actualSha256), e.message)
|
|
assertTrue(e.message!!.contains("(fileSize: 13)"), e.message)
|
|
|
|
assertEquals("""
|
|
/a/c/file.data: 404
|
|
/b/c/file.data: 200
|
|
""".trimIndent(), serverExtension.log.trim())
|
|
}
|
|
|
|
@Test
|
|
fun downloadArtifact_missing_artifact() {
|
|
val e = assertFailsWith<IllegalStateException> {
|
|
downloadArtifact(
|
|
artifactPath = JarHttpDownloader.RelativePathToDownload(Path.of("c/file.data"), null),
|
|
localRepository = localRepository,
|
|
remoteRepositories = remoteRepositories,
|
|
retry = RetryProvider.disabled(),
|
|
)
|
|
}
|
|
|
|
assertEquals("Artifact 'c/file.data' was not found in remote repositories: [${server.url}/a/c/file.data, ${server.url}/b/c/file.data, ${server.url}/c/c/file.data]", e.message)
|
|
|
|
assertEquals("""
|
|
/a/c/file.data: 404
|
|
/b/c/file.data: 404
|
|
/c/c/file.data: 404
|
|
""".trimIndent(), serverExtension.log.trim())
|
|
}
|
|
} |