// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.core.region

import com.intellij.openapi.application.ApplicationManager
import com.intellij.testFramework.ApplicationRule
import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.replaceService
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import software.aws.toolkits.core.rules.EnvironmentVariableHelper
import software.aws.toolkits.core.rules.SystemPropertyHelper
import software.aws.toolkits.core.utils.RemoteResource
import software.aws.toolkits.core.utils.RemoteResourceResolver
import software.aws.toolkits.core.utils.exists
import software.aws.toolkits.core.utils.writeText
import software.aws.toolkits.jetbrains.core.RemoteResourceResolverProvider
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionStage

class AwsRegionProviderTest {
    @Rule
    @JvmField
    val applicationRule = ApplicationRule()

    @Rule
    @JvmField
    val systemPropertyHelper = SystemPropertyHelper()

    @Rule
    @JvmField
    val environmentVariableHelper = EnvironmentVariableHelper()

    @Rule
    @JvmField
    val disposableRule = DisposableRule()

    @Before
    fun setUp() {
        // Isolate our tests
        System.getProperties().setProperty("aws.configFile", Files.createTempFile("dummy", null).toAbsolutePath().toString())
        System.getProperties().setProperty("aws.sharedCredentialsFile", Files.createTempFile("dummy", null).toAbsolutePath().toString())
        System.getProperties().remove("aws.region")
        environmentVariableHelper.remove("AWS_REGION")
    }

    @Test
    fun correctRegionDataIsFiltered() {
        createRegionDataProvider()

        val awsRegionProvider = AwsRegionProvider()
        assertThat(awsRegionProvider.regions("aws")).containsOnlyKeys("us-west-2001", "us-east-1")
    }

    @Test
    fun allRegionsWorks() {
        createRegionDataProvider()
        val awsRegionProvider = AwsRegionProvider()
        assertThat(awsRegionProvider.allRegions()).containsOnlyKeys("moon-east-2001", "us-west-2001", "us-east-1")
    }

    @Test
    fun allRegionsForServiceWorks() {
        createRegionDataProvider()
        val awsRegionProvider = AwsRegionProvider()
        assertThat(awsRegionProvider.allRegionsForService("single-region-service")).containsOnlyKeys("us-west-2001")
    }

    @Test
    fun allRegionsWorksFallsBackToBundledResource() {
        createRegionDataProvider("all-Regions-Works-Falls-Back-To-Bundled-Resource.json")
        val awsRegionProvider = AwsRegionProvider()
        assertThat(awsRegionProvider.partitions()).isNotEmpty
    }

    @Test
    fun noDefaultRegionFallsBackToUsEast1() {
        createRegionDataProvider("no-default-region-us-east-1-fallback-endpoints.json")
        val awsRegionProvider = AwsRegionProvider()
        assertThat(awsRegionProvider.defaultRegion().id).isEqualTo("us-east-1")
    }

    @Test
    fun noUsEast1FallbackToFirstRegionInMetadata() {
        createRegionDataProvider("no-default-region-no-us-east-1-endpoints.json")
        val awsRegionProvider = AwsRegionProvider()
        assertThat(awsRegionProvider.defaultRegion().id).isEqualTo("us-region-1")
    }

    @Test
    fun emptyRegionsCantHaveADefaultDueToError() {
        createRegionDataProvider("no-regions-endpoints.json")
        val awsRegionProvider = AwsRegionProvider()
        assertThatThrownBy { awsRegionProvider.defaultRegion() }.isInstanceOf(IllegalStateException::class.java)
    }

    @Test
    fun defaultProfileInCredentialsIsRespected() {
        val awsFolder = Files.createTempDirectory("aws")
        val configFile = awsFolder.resolve("config")
        System.getProperties().setProperty("aws.configFile", configFile.toAbsolutePath().toString())

        configFile.writeText(
            """
            [default]
            region = us-west-2001
            """.trimIndent()
        )

        createRegionDataProvider()

        val awsRegionProvider = AwsRegionProvider()
        assertThat(awsRegionProvider.defaultRegion().id).isEqualTo("us-west-2001")
    }

    @Test
    fun testGlobalServices() {
        createRegionDataProvider()

        val awsRegionProvider = AwsRegionProvider()
        val usEast1 = awsRegionProvider.regions("aws")["us-east-1"] ?: throw IllegalStateException("Bad test data")

        assertThat(awsRegionProvider.isServiceGlobal(usEast1, "dynamodb")).isFalse()
        assertThat(awsRegionProvider.isServiceGlobal(usEast1, "global-service")).isTrue()
        assertThat(awsRegionProvider.isServiceGlobal(usEast1, "non-existent-service")).isFalse()
        assertThat(awsRegionProvider.getGlobalRegionForService(usEast1, "global-service").id).isEqualTo("aws-global")
    }

    @Test
    fun defaultPartitionIsBasedOnDefaultRegion() {
        val awsFolder = Files.createTempDirectory("aws")
        val configFile = awsFolder.resolve("config")
        System.getProperties().setProperty("aws.configFile", configFile.toAbsolutePath().toString())

        configFile.writeText(
            """
            [default]
            region = moon-east-2001
            """.trimIndent()
        )

        createRegionDataProvider()

        val awsRegionProvider = AwsRegionProvider()

        assertThat(awsRegionProvider.defaultPartition()).satisfies {
            assertThat(it.id).isEqualTo("moon")
        }
    }

    private fun createRegionDataProvider(endpointsFile: String = "simplified-multi-partition-endpoint.json") {
        val file = javaClass.getResource(endpointsFile)?.let { Paths.get(it.toURI()).takeIf { f -> f.exists() } }
            ?: throw RuntimeException("Test file $endpointsFile not found")

        val mockRemoteResource = object : RemoteResourceResolverProvider {
            override fun get() = object : RemoteResourceResolver {
                override fun resolve(resource: RemoteResource): CompletionStage<Path> = CompletableFuture<Path>().apply {
                    complete(file)
                }
            }
        }
        ApplicationManager.getApplication().replaceService(RemoteResourceResolverProvider::class.java, mockRemoteResource, disposableRule.disposable)
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.core.region

import com.intellij.openapi.components.service
import com.intellij.testFramework.ApplicationRule
import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.ExtensionContext
import software.amazon.awssdk.regions.Region
import software.aws.toolkits.core.region.AwsPartition
import software.aws.toolkits.core.region.AwsRegion
import software.aws.toolkits.core.region.Service
import software.aws.toolkits.core.region.ToolkitRegionProvider
import software.aws.toolkits.core.region.aRegionId
import software.aws.toolkits.core.region.anAwsRegion
import software.aws.toolkits.core.utils.test.aString
import software.aws.toolkits.jetbrains.utils.rules.ClearableLazy

private class MockRegionProvider : ToolkitRegionProvider() {
    private val overrideRegions: MutableMap<String, AwsRegion> = mutableMapOf()
    private val services: MutableMap<String, Service> = mutableMapOf()

    fun addRegion(region: AwsRegion): AwsRegion {
        overrideRegions[region.id] = region
        return region
    }

    fun addService(serviceName: String, service: Service) {
        services[serviceName] = service
    }

    fun reset() {
        overrideRegions.clear()
        services.clear()
    }

    override fun partitionData(): Map<String, PartitionData> {
        val combinedRegions = regions + overrideRegions
        return combinedRegions
            .asSequence()
            .associate {
                it.value.partitionId to PartitionData(
                    it.value.partitionId,
                    services,
                    combinedRegions.filterValues { regions -> regions.partitionId == it.value.partitionId }
                )
            }
    }

    override fun isServiceGlobal(region: AwsRegion, serviceId: String) =
        if (serviceId in services.keys) super.isServiceGlobal(region, serviceId) else false

    override fun isServiceSupported(region: AwsRegion, serviceName: String): Boolean = true

    override fun defaultPartition(): AwsPartition = AWS_CLASSIC

    override fun defaultRegion(): AwsRegion = US_EAST_1

    companion object {
        private val AWS_CLASSIC = AwsPartition("aws", "AWS Classic", listOf(US_EAST_1))
        private val regions = mapOf(US_EAST_1.id to US_EAST_1)
        fun getInstance(): MockRegionProvider = service<ToolkitRegionProvider>() as MockRegionProvider
    }
}

sealed class MockRegionProviderBase : ApplicationRule() {
    private val lazyRegionProvider = ClearableLazy {
        MockRegionProvider.getInstance()
    }

    private val regionManager: MockRegionProvider
        get() = lazyRegionProvider.value

    fun addRegion(region: AwsRegion): AwsRegion = regionManager.addRegion(region)
    fun addRegion(sdkRegion: Region): AwsRegion = regionManager.addRegion(
        AwsRegion(
            id = sdkRegion.id(),
            name = sdkRegion.toString(),
            partitionId = sdkRegion.metadata().partition().id()
        )
    )

    fun createAwsRegion(id: String = uniqueRegionId(), partitionId: String = aString()): AwsRegion =
        anAwsRegion(id = id, partitionId = partitionId).also { regionManager.addRegion(it) }

    private fun uniqueRegionId(): String {
        repeat(10) {
            val generatedId = aRegionId()
            if (regionManager[generatedId] == null) {
                return generatedId
            }
        }
        error("Failed to generate a unique region ID")
    }

    fun defaultPartition(): AwsPartition = regionManager.defaultPartition()

    fun defaultRegion(): AwsRegion = regionManager.defaultRegion()

    fun addService(serviceName: String, service: Service) = regionManager.addService(serviceName, service)

    override fun after() {
        lazyRegionProvider.ifSet {
            reset()
            lazyRegionProvider.clear()
        }
    }

    fun reset() {
        regionManager.reset()
    }
}

class MockRegionProviderRule : MockRegionProviderBase()

class MockRegionProviderExtension : MockRegionProviderBase(), AfterEachCallback {
    override fun afterEach(context: ExtensionContext?) {
        after()
    }
}

// dynamically get the default region from whatever is currently registered
fun getDefaultRegion() = service<ToolkitRegionProvider>().defaultRegion()

val US_EAST_1 = AwsRegion("us-east-1", "US East (N. Virginia)", "aws")


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.core.tools

import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import software.aws.toolkits.jetbrains.utils.isInstanceOf

class FourPartVersionTest {
    @Test
    fun `versions can be compared`() {
        val base = FourPartVersion(1, 2, 3, 4)

        assertThat(base).isLessThan(FourPartVersion(1, 2, 3, 5))
        assertThat(base).isLessThan(FourPartVersion(1, 2, 4, 4))
        assertThat(base).isLessThan(FourPartVersion(1, 3, 3, 4))
        assertThat(base).isLessThan(FourPartVersion(2, 2, 3, 4))

        assertThat(base).isEqualTo(FourPartVersion(1, 2, 3, 4))

        assertThat(base).isGreaterThan(FourPartVersion(1, 2, 3, 3))
        assertThat(base).isGreaterThan(FourPartVersion(1, 2, 2, 4))
        assertThat(base).isGreaterThan(FourPartVersion(1, 1, 3, 4))
        assertThat(base).isGreaterThan(FourPartVersion(0, 2, 3, 4))
    }

    @Test
    fun `display name is human readable`() {
        assertThat(FourPartVersion(1, 2, 3, 4).displayValue()).isEqualTo("1.2.3.4")
    }

    @Test
    fun `parts are correct`() {
        assertThat(FourPartVersion(1, 2, 3, 4)).satisfies {
            assertThat(it.major).isEqualTo(1)
            assertThat(it.minor).isEqualTo(2)
            assertThat(it.patch).isEqualTo(3)
            assertThat(it.build).isEqualTo(4)
        }
    }

    @Test
    fun `versions are parsed correctly`() {
        assertThatThrownBy { FourPartVersion.parse("") }.isInstanceOf<IllegalArgumentException>()
        assertThatThrownBy { FourPartVersion.parse("1") }.isInstanceOf<IllegalArgumentException>()
        assertThatThrownBy { FourPartVersion.parse("1.") }.isInstanceOf<IllegalArgumentException>()
        assertThatThrownBy { FourPartVersion.parse("1.2") }.isInstanceOf<IllegalArgumentException>()
        assertThatThrownBy { FourPartVersion.parse("1.2.") }.isInstanceOf<IllegalArgumentException>()
        assertThatThrownBy { FourPartVersion.parse("1.2.") }.isInstanceOf<IllegalArgumentException>()
        assertThatThrownBy { FourPartVersion.parse("1.2.3") }.isInstanceOf<IllegalArgumentException>()
        assertThatThrownBy { FourPartVersion.parse("1.2.3.") }.isInstanceOf<IllegalArgumentException>()
        assertThat(FourPartVersion.parse("1.2.3.4")).isEqualTo(FourPartVersion(1, 2, 3, 4))
        assertThat(FourPartVersion.parse("10.20.30.40")).isEqualTo(FourPartVersion(10, 20, 30, 40))
        assertThatThrownBy { FourPartVersion.parse("1.2.3.4.") }.isInstanceOf<IllegalArgumentException>()
        assertThatThrownBy { FourPartVersion.parse("1.2.3.4.5") }.isInstanceOf<IllegalArgumentException>()

        assertThatThrownBy { FourPartVersion.parse("...") }.isInstanceOf<IllegalArgumentException>()
    }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.core.tools

import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.testFramework.ApplicationRule
import com.intellij.testFramework.replaceService
import java.nio.file.Path

@Suppress("UNCHECKED_CAST")
internal class MockToolManager : ToolManager {

    internal val tools = mutableMapOf<ToolType<*>, Tool<ToolType<*>>>()

    override fun <V : Version> getTool(type: ToolType<V>): Tool<ToolType<V>>? = tools[type]?.let { it as Tool<ToolType<V>> }

    override fun <V : Version> getOrInstallTool(type: ManagedToolType<V>, project: Project?): Tool<ToolType<V>> =
        getTool(type) ?: error("Must register ManagedToolType via a MockToolManagerRule before using it in a test")

    override fun <V : Version> getToolForPath(type: ToolType<V>, toolExecutablePath: Path): Tool<ToolType<V>> {
        TODO("Not yet implemented")
    }

    override fun <V : Version> detectTool(type: ToolType<V>): Path? {
        TODO("Not yet implemented")
    }

    override fun <T : Version> validateCompatability(tool: Tool<ToolType<T>>?, stricterMinVersion: T?, project: Project?): Validity {
        TODO("Not yet implemented")
    }
}

// Scoped to this file only, users should be using MockClientManagerRule to enforce cleanup correctly
private fun getMockInstance(): MockToolManager = service<ToolManager>() as MockToolManager

class MockToolManagerRule : ApplicationRule() {
    fun <V : Version> registerTool(type: ToolType<V>, tool: Tool<ToolType<V>>) {
        getMockInstance().tools[type] = tool
    }

    override fun after() {
        getMockInstance().tools.clear()
    }

    companion object {
        fun useRealTools(disposable: Disposable) {
            val toolManager = DefaultToolManager()
            ApplicationManager.getApplication().replaceService(ToolManager::class.java, toolManager, disposable)
        }
    }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.core.tools

import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import software.aws.toolkits.jetbrains.utils.isInstanceOf

class SemanticVersionTest {
    @Test
    fun `versions can be compared`() {
        val base = SemanticVersion(1, 2, 3)

        assertThat(base).isLessThan(SemanticVersion(1, 2, 4))
        assertThat(base).isLessThan(SemanticVersion(1, 3, 3))
        assertThat(base).isLessThan(SemanticVersion(2, 2, 3))

        assertThat(base).isEqualTo(SemanticVersion(1, 2, 3))

        assertThat(base).isGreaterThan(SemanticVersion(1, 2, 2))
        assertThat(base).isGreaterThan(SemanticVersion(1, 1, 3))
        assertThat(base).isGreaterThan(SemanticVersion(0, 2, 3))
    }

    @Test
    fun `display name is human readable`() {
        assertThat(SemanticVersion(1, 2, 3).displayValue()).isEqualTo("1.2.3")
    }

    @Test
    fun `parts are correct`() {
        assertThat(SemanticVersion(1, 2, 3)).satisfies {
            assertThat(it.major).isEqualTo(1)
            assertThat(it.minor).isEqualTo(2)
            assertThat(it.patch).isEqualTo(3)
        }
    }

    @Test
    fun `versions are parsed correctly`() {
        assertThatThrownBy { SemanticVersion.parse("") }.isInstanceOf<IllegalArgumentException>()
        assertThatThrownBy { SemanticVersion.parse("1") }.isInstanceOf<IllegalArgumentException>()
        assertThatThrownBy { SemanticVersion.parse("1.") }.isInstanceOf<IllegalArgumentException>()
        assertThatThrownBy { SemanticVersion.parse("1.2") }.isInstanceOf<IllegalArgumentException>()
        assertThatThrownBy { SemanticVersion.parse("1.2.") }.isInstanceOf<IllegalArgumentException>()
        assertThatThrownBy { SemanticVersion.parse("1.2.") }.isInstanceOf<IllegalArgumentException>()
        assertThat(SemanticVersion.parse("1.2.3")).isEqualTo(SemanticVersion(1, 2, 3))
        assertThat(SemanticVersion.parse("10.20.30")).isEqualTo(SemanticVersion(10, 20, 30))
        assertThatThrownBy { SemanticVersion.parse("1.2.3.") }.isInstanceOf<IllegalArgumentException>()
        assertThatThrownBy { SemanticVersion.parse("1.2.3.4") }.isInstanceOf<IllegalArgumentException>()

        assertThatThrownBy { SemanticVersion.parse("..") }.isInstanceOf<IllegalArgumentException>()

        assertThat(SemanticVersion.parse("1.2.3-nightly")).isEqualTo(SemanticVersion(1, 2, 3))
        assertThat(SemanticVersion.parse("1.2.3+nightly")).isEqualTo(SemanticVersion(1, 2, 3))
        assertThat(SemanticVersion.parse("1.2.3-nightly-build")).isEqualTo(SemanticVersion(1, 2, 3))
        assertThat(SemanticVersion.parse("1.2.3-nightly+build")).isEqualTo(SemanticVersion(1, 2, 3))
        assertThat(SemanticVersion.parse("1.2.3+nightly-build")).isEqualTo(SemanticVersion(1, 2, 3))
        assertThat(SemanticVersion.parse("1.2.3+nightly+build")).isEqualTo(SemanticVersion(1, 2, 3))
    }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.core.tools

import com.intellij.openapi.application.ApplicationManager
import com.intellij.testFramework.ApplicationRule
import com.intellij.testFramework.runInEdtAndWait
import com.intellij.util.io.write
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.mockito.kotlin.KStubbing
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.doReturnConsecutively
import org.mockito.kotlin.doThrow
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.reset
import org.mockito.kotlin.stub
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.stubbing.Answer
import software.aws.toolkits.core.utils.test.aString
import software.aws.toolkits.jetbrains.core.tools.DefaultToolManager.Companion.managedToolInstallDir
import software.aws.toolkits.jetbrains.core.tools.DefaultToolManager.Companion.managedToolMarkerFile
import software.aws.toolkits.jetbrains.utils.assertIsNonDispatchThread
import software.aws.toolkits.jetbrains.utils.isInstanceOf
import software.aws.toolkits.jetbrains.utils.isInstanceOfSatisfying
import software.aws.toolkits.resources.message
import software.aws.toolkits.telemetry.ToolId
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.attribute.FileTime
import java.time.Clock
import java.time.Duration
import java.time.Instant
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

class ToolManagerTest {
    @Rule
    @JvmField
    val application = ApplicationRule()

    @Rule
    @JvmField
    val tempFolder = TemporaryFolder()

    private lateinit var sut: DefaultToolManager
    private lateinit var clock: Clock

    @Before
    fun setUp() {
        clock = mock {
            on { instant() } doReturn Instant.MIN
        }
        sut = DefaultToolManager(clock)
    }

    @Test
    fun `a configured tool overrides a detected tool`() {
        val type = createDetectableMock {
            on { resolve() } doReturn Path.of(aString())
        }
        val savedPath = aString()
        ToolSettings.getInstance().setExecutablePath(type, savedPath)

        assertThat(sut.getTool(type = type)?.path?.toString()).isEqualTo(savedPath)
    }

    @Test
    fun `a detectable tool can be detected if not explicitly configured`() {
        val path = Path.of(aString())
        val type = createDetectableMock {
            on { resolve() } doReturn path
        }

        assertThat(sut.getTool(type = type)?.path).isEqualTo(path)
    }

    @Test
    fun `a detectable tool not found returns null`() {
        val type = createDetectableMock {
            on { resolve() } doReturn null
        }

        assertThat(sut.getTool(type = type)?.path).isNull()
    }

    @Test
    fun `an undetectable tool not configured returns null`() {
        val type = createUndetectableMock()

        assertThat(sut.getTool(type = type)?.path).isNull()
    }

    @Test
    fun `validating from the UI thread does not run on the UI thread`() {
        runInEdtAndWait {
            val type = createUndetectableMock {
                on { determineVersion(any()) } doAnswer {
                    assertIsNonDispatchThread()
                    SemanticVersion(1, 2, 3)
                }
            }

            val tool = sut.getToolForPath(type, tempFolder.newFile().toPath())
            sut.validateCompatability(tool = tool)
        }
    }

    @Test
    fun `validating can be performed from a non-UI thread`() {
        ApplicationManager.getApplication().executeOnPooledThread {
            val type = createUndetectableMock {
                on { determineVersion(any()) } doAnswer {
                    assertIsNonDispatchThread()
                    SemanticVersion(1, 2, 3)
                }
            }

            val tool = sut.getToolForPath(type, tempFolder.newFile().toPath())
            sut.validateCompatability(tool = tool)
        }.get()
    }

    @Test
    fun `validation can be performed on a path`() {
        val type = createUndetectableMock {
            on { determineVersion(any()) } doReturn SemanticVersion(1, 2, 3)
        }

        assertThat(sut.validateCompatability(path = tempFolder.newFile().toPath(), type = type)).isInstanceOf<Validity.Valid>()
    }

    @Test
    fun `validation can be performed on a tool instance`() {
        val type = createUndetectableMock {
            on { determineVersion(any()) } doReturn SemanticVersion(1, 2, 3)
        }

        val tool = sut.getToolForPath(type, tempFolder.newFile().toPath())
        assertThat(sut.validateCompatability(tool = tool)).isInstanceOf<Validity.Valid>()
    }

    @Test
    fun `validation on a path that does not exist is NotInstalled`() {
        val type = createUndetectableMock {
            on { determineVersion(any()) } doReturn SemanticVersion(1, 2, 3)
        }

        val tool = sut.getToolForPath(type, Path.of(aString()))
        assertThat(sut.validateCompatability(tool = tool)).isInstanceOf<Validity.ValidationFailed>()
    }

    @Test
    fun `validation on a null tool is NotInstalled`() {
        assertThat(sut.validateCompatability<SemanticVersion>(tool = null)).isInstanceOf<Validity.NotInstalled>()
    }

    @Test
    fun `calling validate multiple times with no change hits cache`() {
        val type = createUndetectableMock {
            on { determineVersion(any()) } doReturn SemanticVersion(1, 2, 3)
        }

        val tool = sut.getToolForPath(type, tempFolder.newFile().toPath())

        sut.validateCompatability(tool = tool)
        sut.validateCompatability(tool = tool)
        sut.validateCompatability(tool = tool)

        verify(type, times(1)).determineVersion(any())
    }

    @Test
    fun `an error while validating is considered not installed`() {
        val error = IllegalStateException("fake error occurred")
        val type = createUndetectableMock {
            on { determineVersion(any()) } doThrow error
        }

        val tool = sut.getToolForPath(type, tempFolder.newFile().toPath())
        val validity = sut.validateCompatability(tool = tool)
        assertThat(validity).isInstanceOfSatisfying<Validity.ValidationFailed> {
            assertThat(it.detailedMessage).isEqualTo(error.message)
        }
    }

    @Test
    fun `an error while validating is idempotent`() {
        val type = createUndetectableMock {
            on { determineVersion(any()) } doThrow IllegalStateException("fake error occurred") doReturn SemanticVersion(1, 2, 3)
        }

        val tool = sut.getToolForPath(type, tempFolder.newFile().toPath())

        assertThat(sut.validateCompatability(tool = tool)).isInstanceOf<Validity.ValidationFailed>()
        assertThat(sut.validateCompatability(tool = tool)).isInstanceOf<Validity.ValidationFailed>()

        verify(type, times(1)).determineVersion(any())
    }

    @Test
    fun `calling validate revalidates if file is modified`() {
        runInEdtAndWait {
            val type = createUndetectableMock {
                on { determineVersion(any()) } doReturn SemanticVersion(1, 2, 3)
            }

            val tool = sut.getToolForPath(type, tempFolder.newFile().toPath())
            Files.setLastModifiedTime(tool.path, FileTime.from(Instant.now().minusSeconds(10)))
            sut.validateCompatability(tool = tool)

            Files.setLastModifiedTime(tool.path, FileTime.from(Instant.now()))
            sut.validateCompatability(tool = tool)

            verify(type, times(2)).determineVersion(any())
        }
    }

    @Test
    fun `a version new low returns VersionTooNew`() {
        val type = createUndetectableMock {
            on { determineVersion(any()) } doReturn SemanticVersion(2, 3, 4)
            on { supportedVersions() } doReturn (SemanticVersion(1, 0, 0) until SemanticVersion(2, 0, 0))
        }

        val tool = sut.getToolForPath(type, tempFolder.newFile().toPath())

        assertThat(sut.validateCompatability(tool = tool, stricterMinVersion = SemanticVersion(2, 0, 0))).isInstanceOf<Validity.VersionTooNew>()
    }

    @Test
    fun `a version too low returns VersionTooOld`() {
        val type = createUndetectableMock {
            on { determineVersion(any()) } doReturn SemanticVersion(1, 2, 3)
            on { supportedVersions() } doReturn (SemanticVersion(2, 0, 0) until SemanticVersion(3, 0, 0))
        }

        val tool = sut.getToolForPath(type, tempFolder.newFile().toPath())

        assertThat(sut.validateCompatability(tool = tool, stricterMinVersion = SemanticVersion(2, 0, 0))).isInstanceOf<Validity.VersionTooOld>()
    }

    @Test
    fun `a stricter min version can be applied`() {
        val type = createUndetectableMock {
            on { determineVersion(any()) } doReturn SemanticVersion(1, 2, 3)
            on { supportedVersions() } doReturn (SemanticVersion(1, 0, 0) until SemanticVersion(3, 0, 0))
        }

        val tool = sut.getToolForPath(type, tempFolder.newFile().toPath())

        assertThat(sut.validateCompatability(tool = tool, stricterMinVersion = SemanticVersion(2, 0, 0))).isInstanceOf<Validity.VersionTooOld>()
    }

    @Test
    fun `a managed tool will be installed if not installed`() {
        val downloadFile = tempFolder.newFile().toPath()
        val toolId = aString()
        val version = SemanticVersion(1, 2, 3)
        val markerFile = managedToolMarkerFile(toolId)
        val installPath = managedToolInstallDir(toolId, version.displayValue())
        val toolBinary = installPath.resolve("myExe")
        val type = createManagedToolMock(toolId) {
            on { determineLatestVersion() } doReturn version
            on { downloadVersion(eq(version), any(), anyOrNull()) } doReturn downloadFile
            on { toTool(eq(installPath)) } doReturn Tool(this.mock, toolBinary)
        }

        assertThat(markerFile).doesNotExist()
        assertThat(installPath).doesNotExist()
        assertThat(sut.getOrInstallTool(type).path).isEqualTo(toolBinary)
        assertThat(markerFile).hasContent(version.displayValue())

        verify(type).determineLatestVersion()
        verify(type).downloadVersion(any(), any(), anyOrNull())
        verify(type).installVersion(eq(downloadFile), eq(installPath), anyOrNull())
        verify(type).toTool(eq(installPath))
    }

    @Test
    fun `a managed tool will be returned if installed`() {
        val toolId = aString()
        val version = SemanticVersion(1, 2, 3)
        val markerFile = managedToolMarkerFile(toolId)
        val installPath = managedToolInstallDir(toolId, version.displayValue())
        val toolBinary = installPath.resolve("myExe")
        val type = createManagedToolMock(toolId) {
            on { toTool(eq(installPath)) } doReturn Tool(this.mock, toolBinary)
        }

        markerFile.write(version.displayValue())
        toolBinary.write("aFile")
        assertThat(sut.getOrInstallTool(type).path).isEqualTo(toolBinary)

        verify(type, never()).determineLatestVersion()
        verify(type, never()).downloadVersion(any(), any(), anyOrNull())
    }

    @Test
    fun `a managed tool with corrupt marker will be treated as not installed`() {
        listOf("deadPointer", "../1.2.3", "..\\1.2.3").forEach {
            val downloadFile = tempFolder.newFile().toPath()
            val toolId = aString()
            val version = SemanticVersion(1, 2, 3)
            val markerFile = managedToolMarkerFile(toolId)
            val installPath = managedToolInstallDir(toolId, version.displayValue())
            val toolBinary = installPath.resolve("myExe")
            val type = createManagedToolMock(toolId) {
                on { determineLatestVersion() } doReturn version
                on { downloadVersion(eq(version), any(), anyOrNull()) } doReturn downloadFile
                on { toTool(eq(installPath)) } doReturn Tool(this.mock, toolBinary)
            }

            markerFile.write(it)
            assertThat(sut.getOrInstallTool(type).path).isEqualTo(toolBinary)
            assertThat(markerFile).hasContent(version.displayValue())

            verify(type).determineLatestVersion()
            verify(type).downloadVersion(any(), any(), anyOrNull())
            verify(type).installVersion(eq(downloadFile), eq(installPath), anyOrNull())
            verify(type).toTool(eq(installPath))
        }
    }

    @Test
    fun `a managed tool install clears out its install location first`() {
        val downloadFile = tempFolder.newFile().toPath()
        val toolId = aString()
        val version = SemanticVersion(1, 2, 3)
        val markerFile = managedToolMarkerFile(toolId)
        val installPath = managedToolInstallDir(toolId, version.displayValue())
        val toolBinary = installPath.resolve("myExe")
        val type = createManagedToolMock(toolId) {
            on { determineLatestVersion() } doReturn version
            on { downloadVersion(eq(version), any(), anyOrNull()) } doReturn downloadFile
            on { toTool(eq(installPath)) } doReturn Tool(this.mock, toolBinary)
        }

        assertThat(markerFile).doesNotExist()
        val oldFile = installPath.resolve("someFile").write("hello")
        assertThat(sut.getOrInstallTool(type).path).isEqualTo(toolBinary)
        assertThat(oldFile).doesNotExist()
    }

    @Test
    fun `a managed tool can be updated`() {
    }

    @Test
    fun `only one managed tool can be installed at a time`() {
    }

    @Test
    fun `old managed tool versions are cleaned up`() {
    }

    @Test
    fun `a managed tool that fails to install bubbles up`() {
        val downloadFile = tempFolder.newFile().toPath()
        val toolId = aString()
        val version = SemanticVersion(1, 2, 3)
        val markerFile = managedToolMarkerFile(toolId)
        val installPath = managedToolInstallDir(toolId, version.displayValue())
        val error = IllegalStateException("Boom!")
        val type = createManagedToolMock(toolId) {
            on { determineLatestVersion() } doReturn version
            on { downloadVersion(eq(version), any(), anyOrNull()) } doReturn downloadFile
            on { installVersion(any(), any(), anyOrNull()) } doThrow error
        }

        assertThat(markerFile).doesNotExist()
        assertThat(installPath).doesNotExist()
        assertThatThrownBy { sut.getOrInstallTool(type) }
            .hasMessage(message("executableCommon.failed_install", type.displayName))
            .hasRootCause(error)
        assertThat(markerFile).doesNotExist()

        verify(type).determineLatestVersion()
        verify(type).downloadVersion(any(), any(), anyOrNull())
        verify(type).installVersion(any(), any(), anyOrNull())
    }

    @Test
    fun `managed tool update checks respect TTL`() {
        val toolId = aString()
        val version = SemanticVersion(1, 2, 3)
        val markerFile = managedToolMarkerFile(toolId)
        val installPath = managedToolInstallDir(toolId, version.displayValue())
        val toolBinary = installPath.resolve("myExe")
        val now = Instant.now()
        // Clear out default mock
        reset(clock)
        clock.stub {
            on { instant() } doReturnConsecutively listOf(
                now.plus(Duration.ofDays(7)),
                now.plus(Duration.ofDays(7).plusHours(1)),
                now.plus(Duration.ofDays(9))
            )
        }

        val type = createManagedToolMock(toolId) {
            on { determineLatestVersion() } doReturn version
            on { toTool(eq(installPath)) } doReturn Tool(this.mock, toolBinary)
            on { determineVersion(eq(toolBinary)) } doReturn version
        }

        markerFile.write(version.displayValue())
        toolBinary.write("someExe")
        sut.checkForUpdates(type)
        sut.checkForUpdates(type)
        sut.checkForUpdates(type)

        verify(type, times(2)).determineLatestVersion()
    }

    @Test
    fun `managed tool checks for updates when retrieved non-blocking`() {
        val toolId = aString()
        val version = SemanticVersion(1, 2, 3)
        val markerFile = managedToolMarkerFile(toolId)
        val installPath = managedToolInstallDir(toolId, version.displayValue())
        val toolBinary = installPath.resolve("myExe")
        val latch = CountDownLatch(1)
        val postCheckLatch = CountDownLatch(1)
        // Clear out default mock
        reset(clock)
        clock.stub {
            on { instant() } doReturn Instant.MAX
        }

        val type = createManagedToolMock(toolId) {
            on { determineLatestVersion() } doAnswer Answer<Version> { latch.countDown(); postCheckLatch.await(); version }
            on { toTool(eq(installPath)) } doReturn Tool(this.mock, toolBinary)
        }

        markerFile.write(version.displayValue())
        toolBinary.write("aFile")

        sut.getTool(type)

        postCheckLatch.countDown()

        assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue
    }

    @Test
    fun `a managed tool with no supporting version can't be installed`() {
        val toolId = aString()
        val markerFile = managedToolMarkerFile(toolId)
        val supportedRange = SemanticVersion(1, 0, 0) until SemanticVersion(2, 0, 0)
        val type = createManagedToolMock(toolId) {
            on { determineLatestVersion() } doReturn SemanticVersion(3, 0, 0)
            on { supportedVersions() } doReturn supportedRange
        }

        assertThat(markerFile).doesNotExist()
        assertThatThrownBy { sut.getOrInstallTool(type) }
            .hasMessage(message("executableCommon.failed_install", type.displayName))
            .hasRootCauseMessage(message("executableCommon.latest_not_compatible", type.displayName, supportedRange.displayValue()))
    }

    @Test
    fun `a managed tool with unsupported newer version won't update to it`() {
        val toolId = aString()
        val version = SemanticVersion(1, 2, 3)
        val markerFile = managedToolMarkerFile(toolId)
        val supportedRange = SemanticVersion(1, 0, 0) until SemanticVersion(2, 0, 0)

        // Clear out default mock
        reset(clock)
        clock.stub {
            on { instant() } doReturn Instant.MAX
        }

        val type = createManagedToolMock(toolId) {
            on { determineLatestVersion() } doReturn SemanticVersion(3, 0, 0)
            on { supportedVersions() } doReturn supportedRange
        }

        markerFile.write(version.displayValue())
        sut.checkForUpdates(type)

        verify(type).determineLatestVersion()
        verify(type, never()).downloadVersion(any(), any(), anyOrNull())
        verify(type, never()).installVersion(any(), any(), anyOrNull())
    }

    @Test
    fun `a managed tool with same latest newer version won't update to it`() {
        val toolId = aString()
        val version = SemanticVersion(1, 2, 3)
        val markerFile = managedToolMarkerFile(toolId)
        val supportedRange = SemanticVersion(1, 0, 0) until SemanticVersion(2, 0, 0)
        val installPath = managedToolInstallDir(toolId, version.displayValue())
        val toolBinary = installPath.resolve("myExe")

        // Clear out default mock
        reset(clock)
        clock.stub {
            on { instant() } doReturn Instant.MAX
        }

        val type = createManagedToolMock(toolId) {
            on { determineLatestVersion() } doReturn version
            on { supportedVersions() } doReturn supportedRange
            on { toTool(eq(installPath)) } doReturn Tool(this.mock, toolBinary)
            on { determineVersion(eq(toolBinary)) } doReturn version
        }

        markerFile.write(version.displayValue())
        toolBinary.write("someExe")
        sut.checkForUpdates(type)

        verify(type).determineLatestVersion()
        verify(type, never()).downloadVersion(any(), any(), anyOrNull())
        verify(type, never()).installVersion(any(), any(), anyOrNull())
    }

    private fun createUndetectableMock(toolId: String = aString(), stubBuilder: (KStubbing<ToolType<SemanticVersion>>).() -> Unit = {}) =
        mock<ToolType<SemanticVersion>>()
            .stub {
                on { id } doReturn toolId
                on { displayName } doReturn toolId
                stubBuilder(this)
            }

    private fun createDetectableMock(toolId: String = aString(), stubBuilder: (KStubbing<AutoDetectableToolType<SemanticVersion>>).() -> Unit = {}) =
        mock<AutoDetectableToolType<SemanticVersion>>()
            .stub {
                on { id } doReturn toolId
                on { displayName } doReturn toolId
                stubBuilder(this)
            }

    private fun createManagedToolMock(toolId: String = aString(), stubBuilder: (KStubbing<ManagedToolType<SemanticVersion>>).() -> Unit = {}) =
        mock<ManagedToolType<SemanticVersion>>(verboseLogging = true)
            .stub {
                on { id } doReturn toolId
                on { telemetryId } doReturn ToolId.Unknown
                on { displayName } doReturn toolId
                stubBuilder(this)
            }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.core.tools

import com.intellij.testFramework.ApplicationRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import software.aws.toolkits.core.utils.test.aString
import software.aws.toolkits.jetbrains.utils.deserializeState
import software.aws.toolkits.jetbrains.utils.serializeState
import java.nio.file.Path

class ToolSettingsTest {
    @Rule
    @JvmField
    val application = ApplicationRule()

    @Test
    fun `null path removes its state`() {
        val settings = ToolSettings.getInstance()
        val path = aString()
        settings.setExecutablePath(TestExecutable, path)
        assertThat(settings.getExecutablePath(TestExecutable)).isEqualTo(path)

        settings.setExecutablePath(TestExecutable, null)
        assertThat(settings.getExecutablePath(TestExecutable)).isNull()
    }

    @Test
    fun `state can be loaded`() {
        val settings = ToolSettings.getInstance()

        deserializeState(
            """
                <executables>
                    <option name="value">
                        <map>
                            <entry key="testExecutable">
                                <value>
                                    <ExecutableState path="/some/path" />
                                </value>
                            </entry>
                        </map>
                    </option>
                </executables>
            """,
            settings
        )

        assertThat(settings.getExecutablePath(TestExecutable)).isEqualTo("/some/path")
    }

    @Test
    fun `state can be saved`() {
        val settings = ToolSettings.getInstance()
        settings.setExecutablePath(TestExecutable, "/some/path")

        assertThat(serializeState("executables", settings))
            .isEqualToIgnoringWhitespace(
                """
                    <executables>
                        <option name="value">
                            <map>
                                <entry key="testExecutable">
                                    <value>
                                        <ExecutableState path="/some/path" />
                                    </value>
                                </entry>
                            </map>
                        </option>
                    </executables>
                """.trimIndent()
            )
    }

    object TestExecutable : ToolType<SemanticVersion> {
        override val displayName: String = "Test Tool"
        override val id: String = "testExecutable"

        override fun determineVersion(path: Path) = SemanticVersion(1, 2, 3)
        override fun supportedVersions(): VersionRange<SemanticVersion>? = null
    }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.core.tools

import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import software.aws.toolkits.core.utils.test.aString
import java.nio.file.Path

class ToolTest {
    @Test
    fun `tool instances are equal if type ID is equal`() {
        val path = Path.of(aString())

        val type1 = TestToolType("myId")
        val type2 = TestToolType("myId")
        val type3 = TestToolType("myDifferentId")

        assertThat(type1).isNotEqualTo(type2)

        val tool1 = Tool(type1, path)
        val tool2 = Tool(type2, path)
        val tool3 = Tool(type3, path)

        assertThat(tool1).isEqualTo(tool2)
        assertThat(tool1).isNotEqualTo(tool3)
    }

    @Test
    fun `tool instances are equal if paths are equal`() {
        val type = TestToolType("myId")

        val path1 = Path.of(aString())
        val path2 = Path.of(aString())

        val tool1 = Tool(type, path1)
        val tool2 = Tool(type, path1)
        val tool3 = Tool(type, path2)

        assertThat(tool1).isEqualTo(tool2)
        assertThat(tool1).isNotEqualTo(tool3)
    }

    class TestToolType(override val id: String) : ToolType<SemanticVersion> {
        override val displayName: String = id
        override fun determineVersion(path: Path): SemanticVersion = SemanticVersion(1, 2, 3)
        override fun supportedVersions(): VersionRange<SemanticVersion>? = null
    }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.core.tools

import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import software.aws.toolkits.jetbrains.utils.isInstanceOf
import software.aws.toolkits.jetbrains.utils.isInstanceOfSatisfying

class VersionsTest {
    private val testRange = VersionRange(IntegerVersion(10), IntegerVersion(12))

    @Test
    fun `no version range means any version is compatible`() {
        assertThat(IntegerVersion(4).isValid(null)).isInstanceOf<Validity.Valid>()
    }

    @Test
    fun `version below min version returns VersionTooOld`() {
        assertThat(
            IntegerVersion(4).isValid(testRange)
        ).isInstanceOfSatisfying<Validity.VersionTooOld> {
            assertThat(it.minVersion).isEqualTo(testRange.minVersion)
        }
    }

    @Test
    fun `version above max version returns VersionTooNew`() {
        assertThat(
            IntegerVersion(40).isValid(testRange)
        ).isInstanceOfSatisfying<Validity.VersionTooNew> {
            assertThat(it.maxVersion).isEqualTo(testRange.maxVersion)
        }
    }

    @Test
    fun `version in range is valid`() {
        assertThat(IntegerVersion(11).isValid(testRange)).isInstanceOf<Validity.Valid>()
    }

    @Test
    fun `minVersion is inclusive`() {
        assertThat(IntegerVersion(10).isValid(testRange)).isInstanceOf<Validity.Valid>()
    }

    @Test
    fun `maxVersion is exclusive`() {
        assertThat(IntegerVersion(12).isValid(testRange)).isInstanceOf(Validity.VersionTooNew::class.java)
    }

    data class IntegerVersion(val version: Int) : Version {
        override fun displayValue(): String = version.toString()
        override fun compareTo(other: Version): Int = version.compareTo((other as IntegerVersion).version)
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.core.toolwindow

import com.intellij.openapi.wm.RegisterToolWindowTask
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.testFramework.ProjectRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.swing.JLabel

class ToolkitToolWindowTest {

    @Rule
    @JvmField
    val projectRule = ProjectRule()

    private lateinit var jbToolWindowManager: ToolWindowManager
    private lateinit var sut: ToolkitToolWindow

    @Before
    fun setUp() {
        jbToolWindowManager = ToolWindowManager.getInstance(projectRule.project)

        val testWindowId = "testWindow"
        ToolWindowManager.getInstance(projectRule.project).registerToolWindow(
            RegisterToolWindowTask(
                id = testWindowId
            )
        )
        sut = object : ToolkitToolWindow {
            override val project = projectRule.project
            override val toolWindowId = testWindowId
        }
    }

    @Test
    fun `can add a tab`() {
        sut.addTab("World", JLabel().also { it.text = "Hello" })

        val label = (jbToolWindowManager.getToolWindow(sut.toolWindowId)?.contentManager?.getContent(0)?.component as? JLabel)

        assertThat(label?.text).isEqualTo("Hello")
    }

    @Test
    fun `can remove a tab`() {
        val tab = sut.addTab("World", JLabel().also { it.text = "Hello" })
        val tab2 = sut.addTab("Tab2", JLabel().also { it.text = "Hello" })
        assertThat(jbToolWindowManager.getToolWindow(sut.toolWindowId)?.contentManager?.contentCount).isEqualTo(2)

        sut.removeContent(tab)

        assertThat(jbToolWindowManager.getToolWindow(sut.toolWindowId)?.contentManager).satisfies {
            it!!
            assertThat(it.contentCount).isEqualTo(1)
            assertThat(it.getContent(0)).isEqualTo(tab2)
        }
    }

    @Test
    fun `can find added tab`() {
        val tab = sut.addTab("World", JLabel().also { it.text = "Hello" })
        val tab2 = sut.addTab("Tab2", JLabel().also { it.text = "Hello" })

        assertThat(sut.find("World")).isEqualTo(tab)
        assertThat(sut.find("Tab2")).isEqualTo(tab2)
    }

    @Test
    fun `can show previously added tab`() {
        sut.addTab("World", JLabel().also { it.text = "Hello" }, id = "myId")

        assertThat(sut.showExistingContent("myId")).isTrue
    }

    @Test
    fun `can activate requested tab`() {
        val tab = sut.addTab("World", JLabel().also { it.text = "Hello" })
        val tab2 = sut.addTab("Tab2", JLabel().also { it.text = "Hello" })
        val jbManager = jbToolWindowManager.getToolWindow(sut.toolWindowId)?.contentManager!!

        sut.show(tab2)
        assertThat(jbManager.selectedContent).isEqualTo(tab2)

        sut.show(tab)
        assertThat(jbManager.selectedContent).isEqualTo(tab)
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services

import com.intellij.openapi.util.SystemInfo
import org.assertj.core.api.Assertions.assertThat
import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.nio.file.Files

class PathMapperTest {
    @Rule
    @JvmField
    val tempFolder = TemporaryFolder()

    private lateinit var mapper: PathMapper

    @Test
    fun firstMatchWinsL() {
        initMapper {
            addMapping("local", "remote")
            addMapping("local2", "remote2")
            addMapping("local2", "remote2/sub/")
        }

        createLocalFile("local2/foo")

        assertBidirectionalMapping("local2/foo", "remote2/foo")
    }

    @Test
    fun onlyPrefixIsReplaced() {
        initMapper {
            addMapping("local/sub", "remote/sub/sub")
        }

        assertBidirectionalMapping("local/sub/foo", "remote/sub/sub/foo")
    }

    @Test
    fun matchesAtFolderBoundary() {
        initMapper {
            addMapping("local", "remote")
            addMapping("local-folder", "remote2")
        }

        assertBidirectionalMapping("local-folder/foo", "remote2/foo")
    }

    @Test
    fun fileMustExistLocally() {
        initMapper {
            addMapping("local1", "remote")
            addMapping("local2", "remote")
        }

        createLocalFile("local2/foo")

        assertThat(convertToLocal("remote/foo")).isEqualTo("local2/foo")
    }

    @Test
    fun trailingSlashIsIgnored() {
        initMapper {
            addMapping("local/", "remote/")
            addMapping("local2", "remote2")
        }

        assertBidirectionalMapping("local/foo", "remote/foo")
        assertBidirectionalMapping("local2/foo", "remote2/foo")
    }

    @Test
    fun directoriesAreCaseInsensitiveOnWindows() {
        assumeTrue(SystemInfo.isWindows)

        initMapper {
            addMapping("LOCAL/", "remote/")
            addMapping("LOCAL2/Folder/", "remote2/Folder/")
        }

        assertBidirectionalMapping("local/Foo", "remote/Foo")
        assertThat(mapper.convertToRemote(createLocalFile("LOCAL/Foo"))).isEqualTo("remote/Foo")
        assertBidirectionalMapping("local2/folder/Bar", "remote2/Folder/Bar")
        assertThat(mapper.convertToRemote(createLocalFile("LOCAL2/Folder/Bar"))).isEqualTo("remote2/Folder/Bar")
    }

    @Test
    fun unmappedDirectoriesAreCaseSensitiveOnWindows() {
        assumeTrue(SystemInfo.isWindows)

        initMapper {
            addMapping("LOCAL/", "remote/")
        }

        assertBidirectionalMapping("local/tHiSiSaLeGiTiMaTePaThNaMe/FiLe.ExT", "remote/tHiSiSaLeGiTiMaTePaThNaMe/FiLe.ExT")
        assertThat(mapper.convertToRemote(createLocalFile("LOCAL/tHiSiSaLeGiTiMaTePaThNaMe/FiLe.ExT"))).isEqualTo("remote/tHiSiSaLeGiTiMaTePaThNaMe/FiLe.ExT")
    }

    @Test
    fun pathsAreCaseSensitiveWhenNotOnWindows() {
        assumeFalse(SystemInfo.isWindows)

        initMapper {
            addMapping("LOCAL/", "remote/")
        }

        assertBidirectionalMapping("LOCAL/foo", "remote/foo")
        assertThat(mapper.convertToRemote(createLocalFile("local/foo"))).isNull()
    }

    @Test
    fun unknownPathsReturnNull() {
        initMapper {
        }

        assertThat(mapper.convertToRemote(createLocalFile("foo"))).isNull()
        assertThat(convertToLocal("foo")).isNull()
    }

    @Test
    fun canonicalizeInput() {
        initMapper {
            addMapping("local/./", "remote/")
            addMapping("local2/../local3/", "remote2")
        }

        assertBidirectionalMapping("local/foo", "remote/foo")
        assertBidirectionalMapping("local3/foo", "remote2/foo")
    }

    @Test
    fun convertsLocalFilePathToLocalDirectory() {
        createLocalFile("local/foo")

        initMapper {
            addMapping("local/foo", "remote/")
        }

        assertBidirectionalMapping("local/foo", "remote/foo")
    }

    private fun assertBidirectionalMapping(local: String, remote: String) {
        assertThat(mapper.convertToRemote(createLocalFile(local))).isEqualTo(remote)
        assertThat(convertToLocal(remote)).isEqualTo(local)
    }

    private fun convertToLocal(remote: String) = mapper.convertToLocal(remote)
        ?.removePrefix(PathMapper.normalizeLocal(tempFolder.root.absolutePath))
        ?.removePrefix("/")

    private fun createLocalFile(path: String): String {
        val file = tempFolder.root.toPath().resolve(path)
        Files.createDirectories(file.parent)
        if (!Files.exists(file)) {
            Files.createFile(file)
        }

        return file.toString()
    }

    private fun initMapper(init: MutableList<PathMapping>.() -> Unit) {
        val mappings = mutableListOf<PathMapping>()
        mappings.init()
        mapper = PathMapper(mappings)
    }

    private fun MutableList<PathMapping>.addMapping(local: String, remote: String) {
        val file = tempFolder.root.toPath().resolve(local)
        Files.createDirectories(file.parent)
        this.add(PathMapping(file.toString(), remote))
    }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.apprunner

import com.intellij.testFramework.ProjectRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import software.amazon.awssdk.services.apprunner.model.ServiceStatus
import software.amazon.awssdk.services.apprunner.model.ServiceSummary
import software.aws.toolkits.jetbrains.core.MockResourceCacheRule
import software.aws.toolkits.jetbrains.core.explorer.nodes.AppRunnerExplorerRootNode
import software.aws.toolkits.jetbrains.core.explorer.nodes.AwsExplorerEmptyNode
import software.aws.toolkits.jetbrains.core.explorer.nodes.AwsExplorerErrorNode
import software.aws.toolkits.jetbrains.services.apprunner.resources.AppRunnerResources
import java.util.concurrent.CompletableFuture

class AppRunnerServiceNodeTest {
    @JvmField
    @Rule
    val projectRule = ProjectRule()

    @JvmField
    @Rule
    val resourceCache = MockResourceCacheRule()

    @Test
    fun `AppRunner services are listed`() {
        resourceCache.addEntry(
            projectRule.project,
            AppRunnerResources.LIST_SERVICES,
            listOf(
                ServiceSummary.builder().serviceArn("arn1").serviceName("service1").status(ServiceStatus.RUNNING).build(),
                ServiceSummary.builder().serviceArn("arn3").serviceName("service2").status(ServiceStatus.OPERATION_IN_PROGRESS).build()
            )
        )

        val children = AppRunnerNode(projectRule.project, APPRUNNER_EXPLORER_NODE).children

        assertThat(children).allMatch { it is AppRunnerServiceNode }
        assertThat(children.map { it.displayName() }).containsExactlyInAnyOrder("service1", "service2")
        assertThat(children.filterIsInstance<AppRunnerServiceNode>().map { it.resourceArn() }).containsExactlyInAnyOrder("arn1", "arn3")
    }

    @Test
    fun `No services listed`() {
        resourceCache.addEntry(projectRule.project, AppRunnerResources.LIST_SERVICES, listOf())
        val children = AppRunnerNode(projectRule.project, APPRUNNER_EXPLORER_NODE).children
        assertThat(children).singleElement().isInstanceOf(AwsExplorerEmptyNode::class.java)
    }

    @Test
    fun `Error loading repositories`() {
        resourceCache.addEntry(projectRule.project, AppRunnerResources.LIST_SERVICES, CompletableFuture.failedFuture(RuntimeException("network broke")))
        val children = AppRunnerNode(projectRule.project, APPRUNNER_EXPLORER_NODE).children
        assertThat(children).singleElement().isInstanceOf(AwsExplorerErrorNode::class.java)
    }

    private companion object {
        val APPRUNNER_EXPLORER_NODE = AppRunnerExplorerRootNode()
    }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.apprunner.actions

import com.intellij.openapi.ide.CopyPasteManager
import com.intellij.testFramework.ProjectRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.mock
import software.amazon.awssdk.services.apprunner.model.ServiceSummary
import software.aws.toolkits.core.utils.test.aString
import software.aws.toolkits.jetbrains.services.apprunner.AppRunnerServiceNode
import java.awt.datatransfer.DataFlavor

class CopyServiceUrlActionTest {
    @JvmField
    @Rule
    val projectRule = ProjectRule()

    private val url = aString()

    @Test
    fun `Copy Service Url copies correct field`() {
        val action = CopyServiceUrlAction()
        action.actionPerformed(AppRunnerServiceNode(projectRule.project, ServiceSummary.builder().serviceName(aString()).serviceUrl(url).build()), mock())
        val data = CopyPasteManager.getInstance().getContents<String>(DataFlavor.stringFlavor)
        assertThat(data).isEqualTo("https://$url")
    }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.apprunner.actions

import com.intellij.testFramework.ProjectRule
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.stub
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import software.amazon.awssdk.services.apprunner.AppRunnerClient
import software.amazon.awssdk.services.apprunner.model.DeleteServiceRequest
import software.amazon.awssdk.services.apprunner.model.DeleteServiceResponse
import software.amazon.awssdk.services.apprunner.model.ServiceSummary
import software.aws.toolkits.core.utils.test.aString
import software.aws.toolkits.jetbrains.core.MockClientManagerRule
import software.aws.toolkits.jetbrains.services.apprunner.AppRunnerServiceNode

class DeleteServiceActionTest {
    @JvmField
    @Rule
    val projectRule = ProjectRule()

    @JvmField
    @Rule
    val mockClientManagerRule = MockClientManagerRule()

    private val arn = aString()

    @Test
    fun `Delete service action calls delete`() {
        val action = DeleteServiceAction()
        val client: AppRunnerClient = mockClientManagerRule.create()
        client.stub {
            on { deleteService(any<DeleteServiceRequest>()) } doAnswer { DeleteServiceResponse.builder().build() }
        }
        val node = AppRunnerServiceNode(projectRule.project, ServiceSummary.builder().serviceName(aString()).serviceArn(arn).build())
        action.performDelete(node)
        verify(client, times(1)).deleteService(DeleteServiceRequest.builder().serviceArn(arn).build())
    }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.apprunner.actions

import com.intellij.notification.Notification
import com.intellij.notification.Notifications
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.runInEdtAndWait
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runBlockingTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
import org.mockito.kotlin.verify
import software.amazon.awssdk.services.apprunner.AppRunnerClient
import software.amazon.awssdk.services.apprunner.model.ServiceSummary
import software.amazon.awssdk.services.apprunner.model.StartDeploymentRequest
import software.amazon.awssdk.services.apprunner.model.StartDeploymentResponse
import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsRequest
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsResponse
import software.amazon.awssdk.services.cloudwatchlogs.model.LogStream
import software.aws.toolkits.core.utils.test.aString
import software.aws.toolkits.jetbrains.core.ToolWindowHeadlessManagerImpl
import software.aws.toolkits.jetbrains.core.toolwindow.ToolkitToolWindow
import software.aws.toolkits.jetbrains.services.apprunner.AppRunnerServiceNode
import software.aws.toolkits.jetbrains.services.cloudwatch.logs.CloudWatchLogWindow
import software.aws.toolkits.jetbrains.services.cloudwatch.logs.toolwindow.CloudWatchLogsToolWindowFactory
import software.aws.toolkits.jetbrains.utils.BaseCoroutineTest
import software.aws.toolkits.resources.message

@ExperimentalCoroutinesApi
class DeployActionTest : BaseCoroutineTest(30) {
    private val action = DeployAction()
    private lateinit var toolWindow: ToolkitToolWindow

    private lateinit var appRunnerClient: AppRunnerClient
    private lateinit var cloudwatchClient: CloudWatchLogsClient

    private val operationId = aString()

    @Rule
    @JvmField
    val disposableRule = DisposableRule()

    @Before
    fun setup() {
        (ToolWindowManager.getInstance(projectRule.project) as ToolWindowHeadlessManagerImpl)
            .doRegisterToolWindow(CloudWatchLogsToolWindowFactory.TOOLWINDOW_ID)

        toolWindow = CloudWatchLogWindow.getInstance(projectRule.project)
        appRunnerClient = mockClientManagerRule.create()
        cloudwatchClient = mockClientManagerRule.create()
    }

    @After
    fun cleanup() {
        // close tabs we created
        toolWindow.findPrefix("").forEach {
            runInEdtAndWait {
                it.dispose()
            }
        }
    }

    @Test
    fun `deploy fails`() {
        val notificationMock = mock<Notifications>()

        projectRule.project.messageBus.connect(disposableRule.disposable).subscribe(Notifications.TOPIC, notificationMock)

        appRunnerClient.stub {
            on { startDeployment(any<StartDeploymentRequest>()) } doAnswer { throw RuntimeException("Failed to start deployment") }
        }

        runBlockingTest {
            action.deploy(
                AppRunnerServiceNode(projectRule.project, ServiceSummary.builder().serviceName(aString()).serviceArn(aString()).build()),
                appRunnerClient,
                cloudwatchClient
            )
        }

        argumentCaptor<Notification>().apply {
            verify(notificationMock).notify(capture())

            assertThat(firstValue.content).contains(message("apprunner.action.deploy.failed"))
        }
    }

    @Test
    fun `deploy fails to open logs`() {
        val notificationMock = mock<Notifications>()

        projectRule.project.messageBus.connect(disposableRule.disposable).subscribe(Notifications.TOPIC, notificationMock)

        appRunnerClient.stub {
            on { startDeployment(any<StartDeploymentRequest>()) } doAnswer { StartDeploymentResponse.builder().operationId(operationId).build() }
        }

        cloudwatchClient.stub {
            on { describeLogStreams(any<DescribeLogStreamsRequest>()) } doAnswer { throw RuntimeException("broke") }
        }

        runBlockingTest {
            action.deploy(
                AppRunnerServiceNode(projectRule.project, ServiceSummary.builder().serviceName(aString()).serviceArn(aString()).build()),
                appRunnerClient,
                cloudwatchClient
            )
        }

        argumentCaptor<Notification>().apply {
            verify(notificationMock).notify(capture())

            assertThat(firstValue.content).contains(message("apprunner.action.deploy.unableToFindLogStream", "deployment/$operationId"))
        }
    }

    @Test
    fun `deploy succeeds and opens logs`() {
        appRunnerClient.stub {
            on { startDeployment(any<StartDeploymentRequest>()) } doAnswer { StartDeploymentResponse.builder().operationId(operationId).build() }
        }

        cloudwatchClient.stub {
            on { describeLogStreams(any<DescribeLogStreamsRequest>()) } doAnswer {
                DescribeLogStreamsResponse.builder().logStreams(LogStream.builder().logStreamName("deployment/$operationId").build()).build()
            }
        }

        runBlocking {
            action.deploy(
                AppRunnerServiceNode(projectRule.project, ServiceSummary.builder().serviceName(aString()).serviceArn(aString()).build()),
                appRunnerClient,
                cloudwatchClient
            )
        }

        runInEdtAndWait {
            val windows = toolWindow.findPrefix("")
            assertThat(windows.size).isEqualTo(1)
            assertThat(windows.first().displayName).isEqualTo(message("cloudwatch.logs.log_stream_title", "deployment/$operationId"))
        }
    }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.apprunner.actions

import com.intellij.ide.browsers.BrowserLauncher
import com.intellij.openapi.application.ApplicationManager
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.replaceService
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import software.amazon.awssdk.services.apprunner.model.ServiceSummary
import software.aws.toolkits.core.utils.test.aString
import software.aws.toolkits.jetbrains.services.apprunner.AppRunnerServiceNode

class OpenServiceUrlActionTest {
    @JvmField
    @Rule
    val projectRule = ProjectRule()

    private val url = aString()

    @Test
    fun `Open Service Url passes the correct URL to the browser launcher`() {
        val launcher = mock<BrowserLauncher>()
        val action = OpenServiceUrlAction()

        ApplicationManager.getApplication().replaceService(BrowserLauncher::class.java, launcher, projectRule.project)
        action.actionPerformed(AppRunnerServiceNode(projectRule.project, ServiceSummary.builder().serviceName(aString()).serviceUrl(url).build()), mock())

        verify(launcher).browse("https://$url", project = projectRule.project)
    }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.apprunner.actions

import com.intellij.notification.Notification
import com.intellij.notification.Notifications
import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.TestActionEvent
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
import org.mockito.kotlin.verify
import software.amazon.awssdk.awscore.exception.AwsErrorDetails
import software.amazon.awssdk.services.apprunner.AppRunnerClient
import software.amazon.awssdk.services.apprunner.model.AppRunnerException
import software.amazon.awssdk.services.apprunner.model.PauseServiceRequest
import software.amazon.awssdk.services.apprunner.model.PauseServiceResponse
import software.amazon.awssdk.services.apprunner.model.ServiceStatus
import software.amazon.awssdk.services.apprunner.model.ServiceSummary
import software.aws.toolkits.core.utils.RuleUtils
import software.aws.toolkits.jetbrains.core.MockClientManagerRule
import software.aws.toolkits.jetbrains.services.apprunner.AppRunnerServiceNode
import software.aws.toolkits.resources.message

class PauseServiceActionTest {
    @JvmField
    @Rule
    val projectRule = ProjectRule()

    @Rule
    @JvmField
    val disposableRule = DisposableRule()

    @JvmField
    @Rule
    val mockClientManagerRule = MockClientManagerRule()

    private val action = PauseServiceAction()
    private lateinit var appRunnerClient: AppRunnerClient

    private val serviceName = RuleUtils.randomName()
    private val serviceArn = "arn::$serviceName"
    private val serviceSummary = ServiceSummary.builder().serviceName(serviceName).serviceArn(serviceArn).build()

    @Before
    fun setup() {
        appRunnerClient = mockClientManagerRule.create()
    }

    @Test
    fun `Pause fails`() {
        val notificationMock = mock<Notifications>()

        projectRule.project.messageBus.connect(disposableRule.disposable).subscribe(Notifications.TOPIC, notificationMock)

        appRunnerClient.stub {
            on { pauseService(any<PauseServiceRequest>()) } doAnswer { throw RuntimeException("Failed to pause!") }
        }

        action.performPause(projectRule.project, appRunnerClient, serviceSummary)

        argumentCaptor<Notification>().apply {
            verify(notificationMock).notify(capture())

            assertThat(firstValue.content).contains(message("apprunner.pause.failed", serviceName, ""))
        }
    }

    @Test
    fun `Pause fails already paused`() {
        val message = RuleUtils.randomName()
        val notificationMock = mock<Notifications>()

        projectRule.project.messageBus.connect(disposableRule.disposable).subscribe(Notifications.TOPIC, notificationMock)

        appRunnerClient.stub {
            on { pauseService(any<PauseServiceRequest>()) } doAnswer {
                throw AppRunnerException.builder().awsErrorDetails(AwsErrorDetails.builder().errorMessage(message).build()).build()
            }
        }

        action.performPause(projectRule.project, appRunnerClient, serviceSummary)

        argumentCaptor<Notification>().apply {
            verify(notificationMock).notify(capture())

            assertThat(firstValue.content).contains(message("apprunner.pause.failed", serviceName, message))
        }
    }

    @Test
    fun `Pause succeeds`() {
        val notificationMock = mock<Notifications>()

        projectRule.project.messageBus.connect(disposableRule.disposable).subscribe(Notifications.TOPIC, notificationMock)

        appRunnerClient.stub {
            on { pauseService(any<PauseServiceRequest>()) } doAnswer { PauseServiceResponse.builder().build() }
        }

        action.performPause(projectRule.project, appRunnerClient, serviceSummary)

        argumentCaptor<Notification>().apply {
            verify(notificationMock).notify(capture())

            assertThat(firstValue.content).contains(message("apprunner.pause.succeeded", serviceName))
        }
    }

    @Test
    fun `Hides node if service is not running`() {
        ServiceStatus.knownValues().filter { it != ServiceStatus.RUNNING }.forEach {
            val actionEvent = TestActionEvent()
            action.update(AppRunnerServiceNode(projectRule.project, ServiceSummary.builder().serviceName(serviceName).status(it).build()), actionEvent)
            assertThat(actionEvent.presentation.isVisible).isFalse
        }
    }

    @Test
    fun `Shows node if service is running`() {
        val actionEvent = TestActionEvent()
        action.update(
            AppRunnerServiceNode(projectRule.project, ServiceSummary.builder().serviceName(serviceName).status(ServiceStatus.RUNNING).build()),
            actionEvent
        )
        assertThat(actionEvent.presentation.isVisible).isTrue
    }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.apprunner.actions

import com.intellij.notification.Notification
import com.intellij.notification.Notifications
import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.TestActionEvent
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
import org.mockito.kotlin.verify
import software.amazon.awssdk.awscore.exception.AwsErrorDetails
import software.amazon.awssdk.services.apprunner.AppRunnerClient
import software.amazon.awssdk.services.apprunner.model.AppRunnerException
import software.amazon.awssdk.services.apprunner.model.ResumeServiceRequest
import software.amazon.awssdk.services.apprunner.model.ResumeServiceResponse
import software.amazon.awssdk.services.apprunner.model.ServiceStatus
import software.amazon.awssdk.services.apprunner.model.ServiceSummary
import software.aws.toolkits.core.utils.RuleUtils
import software.aws.toolkits.jetbrains.core.MockClientManagerRule
import software.aws.toolkits.jetbrains.services.apprunner.AppRunnerServiceNode
import software.aws.toolkits.resources.message

class ResumeServiceActionTest {
    @JvmField
    @Rule
    val projectRule = ProjectRule()

    @Rule
    @JvmField
    val disposableRule = DisposableRule()

    @JvmField
    @Rule
    val mockClientManagerRule = MockClientManagerRule()

    private val action = ResumeServiceAction()
    private lateinit var appRunnerClient: AppRunnerClient

    private val serviceName = RuleUtils.randomName()
    private val serviceArn = "arn::$serviceName"
    private val serviceSummary = ServiceSummary.builder().serviceName(serviceName).serviceArn(serviceArn).build()

    @Before
    fun setup() {
        appRunnerClient = mockClientManagerRule.create()
    }

    @Test
    fun `Resume fails`() {
        val notificationMock = mock<Notifications>()

        projectRule.project.messageBus.connect(disposableRule.disposable).subscribe(Notifications.TOPIC, notificationMock)

        appRunnerClient.stub {
            on { resumeService(any<ResumeServiceRequest>()) } doAnswer { throw RuntimeException("Failed to resume!") }
        }

        action.performResume(projectRule.project, appRunnerClient, serviceSummary)

        argumentCaptor<Notification>().apply {
            verify(notificationMock).notify(capture())

            assertThat(firstValue.content).contains(message("apprunner.resume.failed", serviceName, ""))
        }
    }

    @Test
    fun `Resume fails not paused`() {
        val message = RuleUtils.randomName()
        val notificationMock = mock<Notifications>()

        projectRule.project.messageBus.connect(disposableRule.disposable).subscribe(Notifications.TOPIC, notificationMock)

        appRunnerClient.stub {
            on { resumeService(any<ResumeServiceRequest>()) } doAnswer {
                throw AppRunnerException.builder().awsErrorDetails(AwsErrorDetails.builder().errorMessage(message).build()).build()
            }
        }

        action.performResume(projectRule.project, appRunnerClient, serviceSummary)

        argumentCaptor<Notification>().apply {
            verify(notificationMock).notify(capture())

            assertThat(firstValue.content).contains(message("apprunner.resume.failed", serviceName, message))
        }
    }

    @Test
    fun `Resume succeeds`() {
        val notificationMock = mock<Notifications>()

        projectRule.project.messageBus.connect(disposableRule.disposable).subscribe(Notifications.TOPIC, notificationMock)

        appRunnerClient.stub {
            on { resumeService(any<ResumeServiceRequest>()) } doAnswer { ResumeServiceResponse.builder().build() }
        }

        action.performResume(projectRule.project, appRunnerClient, serviceSummary)

        argumentCaptor<Notification>().apply {
            verify(notificationMock).notify(capture())

            assertThat(firstValue.content).contains(message("apprunner.resume.succeeded", serviceName))
        }
    }

    @Test
    fun `Hides node if service is not paused`() {
        ServiceStatus.knownValues().filter { it != ServiceStatus.PAUSED }.forEach {
            val actionEvent = TestActionEvent()
            action.update(AppRunnerServiceNode(projectRule.project, ServiceSummary.builder().serviceName(serviceName).status(it).build()), actionEvent)
            assertThat(actionEvent.presentation.isVisible).isFalse
        }
    }

    @Test
    fun `Shows node if service is paused`() {
        val actionEvent = TestActionEvent()
        action.update(
            AppRunnerServiceNode(projectRule.project, ServiceSummary.builder().serviceName(serviceName).status(ServiceStatus.PAUSED).build()),
            actionEvent
        )
        assertThat(actionEvent.presentation.isVisible).isTrue
    }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.apprunner.actions

import com.intellij.notification.Notification
import com.intellij.notification.Notifications
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.runInEdtAndGet
import com.intellij.testFramework.runInEdtAndWait
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
import org.mockito.kotlin.verify
import software.amazon.awssdk.services.apprunner.model.ServiceSummary
import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogGroupsRequest
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogGroupsResponse
import software.amazon.awssdk.services.cloudwatchlogs.model.LogGroup
import software.aws.toolkits.core.utils.test.aString
import software.aws.toolkits.jetbrains.core.MockClientManagerRule
import software.aws.toolkits.jetbrains.core.ToolWindowHeadlessManagerImpl
import software.aws.toolkits.jetbrains.core.toolwindow.ToolkitToolWindow
import software.aws.toolkits.jetbrains.services.apprunner.AppRunnerServiceNode
import software.aws.toolkits.jetbrains.services.cloudwatch.logs.CloudWatchLogWindow
import software.aws.toolkits.jetbrains.services.cloudwatch.logs.toolwindow.CloudWatchLogsToolWindowFactory
import software.aws.toolkits.resources.message

class ViewLogsActionTest {
    @Rule
    @JvmField
    val projectRule = ProjectRule()

    @Rule
    @JvmField
    val disposableRule = DisposableRule()

    @JvmField
    @Rule
    val mockClientManagerRule = MockClientManagerRule()

    private lateinit var node: AppRunnerServiceNode
    private lateinit var toolWindow: ToolkitToolWindow

    @Before
    fun setup() {
        (ToolWindowManager.getInstance(projectRule.project) as ToolWindowHeadlessManagerImpl)
            .doRegisterToolWindow(CloudWatchLogsToolWindowFactory.TOOLWINDOW_ID)
        toolWindow = CloudWatchLogWindow.getInstance(projectRule.project)
        node = AppRunnerServiceNode(projectRule.project, ServiceSummary.builder().serviceName(aString()).serviceId(aString()).build())
    }

    @After
    fun cleanup() {
        // close tabs we created
        runInEdtAndWait {
            toolWindow.findPrefix("").forEach {
                toolWindow.removeContent(it)
            }
        }
    }

    @Test
    fun `Shows log group`() {
        val client: CloudWatchLogsClient = mockClientManagerRule.create()
        val logGroupName = "/aws/apprunner/${node.service.serviceName()}/${node.service.serviceId()}/service"
        client.stub {
            on { describeLogGroups(any<DescribeLogGroupsRequest>()) } doAnswer {
                DescribeLogGroupsResponse.builder().logGroups(LogGroup.builder().logGroupName(logGroupName).build()).build()
            }
        }

        val windows = runInEdtAndGet {
            viewLogGroup(node, "service")
            toolWindow.findPrefix("")
        }

        assertThat(windows.size).isEqualTo(1)
        assertThat(windows.first().displayName).isEqualTo(message("cloudwatch.logs.log_group_title", "service"))
    }

    @Test
    fun `Shows error when exception is thrown`() {
        val notificationMock = mock<Notifications>()
        mockClientManagerRule.create<CloudWatchLogsClient>()

        projectRule.project.messageBus.connect(disposableRule.disposable).subscribe(Notifications.TOPIC, notificationMock)

        viewLogGroup(node, "service")

        argumentCaptor<Notification>().apply {
            verify(notificationMock).notify(capture())

            assertThat(firstValue.content).contains(message("apprunner.view_service_log_streams.error", ""))
        }
    }

    @Test
    fun `Shows error when log group doesn't exist yet`() {
        val notificationMock = mock<Notifications>()
        mockClientManagerRule.create<CloudWatchLogsClient>()

        projectRule.project.messageBus.connect(disposableRule.disposable).subscribe(Notifications.TOPIC, notificationMock)

        val client: CloudWatchLogsClient = mockClientManagerRule.create()
        client.stub {
            on { describeLogGroups(any<DescribeLogGroupsRequest>()) } doAnswer {
                DescribeLogGroupsResponse.builder().logGroups(listOf()).build()
            }
        }

        viewLogGroup(node, "service")

        argumentCaptor<Notification>().apply {
            verify(notificationMock).notify(capture())

            assertThat(firstValue.content).contains(message("apprunner.view_service_log_streams.error_not_created"))
        }
    }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.apprunner.ui

import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.runInEdtAndGet
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import software.amazon.awssdk.services.apprunner.model.ConfigurationSource
import software.amazon.awssdk.services.apprunner.model.ConnectionSummary
import software.amazon.awssdk.services.apprunner.model.ImageRepositoryType
import software.amazon.awssdk.services.apprunner.model.ProviderType
import software.amazon.awssdk.services.apprunner.model.Runtime
import software.amazon.awssdk.services.apprunner.model.SourceCodeVersionType
import software.amazon.awssdk.services.iam.model.Role
import software.aws.toolkits.core.utils.RuleUtils
import software.aws.toolkits.core.utils.test.aString
import software.aws.toolkits.jetbrains.core.MockResourceCacheRule
import software.aws.toolkits.jetbrains.services.apprunner.resources.AppRunnerResources
import software.aws.toolkits.jetbrains.services.iam.IamResources
import java.util.concurrent.CompletableFuture

class AppRunnerCreateServiceDialogTest {
    private val connectionName = RuleUtils.randomName()
    private val connectionArn = "arn::$connectionName"
    private val roleName = RuleUtils.randomName()
    private val roleArn = "arn::$roleName"

    @JvmField
    @Rule
    val projectRule = ProjectRule()

    @JvmField
    @Rule
    val resourceCache = MockResourceCacheRule()

    @Before
    fun fillResourceCache() {
        resourceCache.addEntry(
            projectRule.project,
            AppRunnerResources.LIST_CONNECTIONS,
            CompletableFuture.completedFuture(
                listOf(
                    ConnectionSummary
                        .builder()
                        .connectionName(connectionName)
                        .connectionArn(connectionArn)
                        .providerType(ProviderType.GITHUB).build()
                )
            )
        )
        resourceCache.addEntry(
            projectRule.project,
            IamResources.LIST_RAW_ROLES,
            CompletableFuture.completedFuture(
                listOf(
                    Role.builder().roleName(roleName).arn(roleArn).build()
                )
            )
        )
    }

    @Test
    fun `ECR Deployment builds request properly`() {
        val dialog = runInEdtAndGet { CreationDialog(projectRule.project) }
        val panel = CreationPanel(projectRule.project).apply { ecr.isSelected = true }
        // apply to set iam role arn
        panel.component.apply()
        val request = dialog.buildRequest(panel)
        assertThat(request.instanceConfiguration().cpu()).isEqualTo(CreationPanel.cpuValues.first())
        assertThat(request.instanceConfiguration().memory()).isEqualTo(CreationPanel.memoryValues.first())
        assertThat(request.sourceConfiguration().autoDeploymentsEnabled()).isTrue
        assertThat(request.sourceConfiguration().imageRepository().imageRepositoryType()).isEqualTo(ImageRepositoryType.ECR)
        assertThat(request.sourceConfiguration().authenticationConfiguration().accessRoleArn()).isEqualTo(roleArn)
    }

    @Test
    fun `ECR Public Deployment builds request properly`() {
        val dialog = runInEdtAndGet { CreationDialog(projectRule.project) }
        val panel = CreationPanel(projectRule.project).apply {
            ecrPublic.isSelected = true
        }
        val request = dialog.buildRequest(panel)
        // this is actually supposed to be null, but AssertJ throws that it can't be null so we have to do this funky cast
        assertThat(request.sourceConfiguration().autoDeploymentsEnabled() as Any?).isNull()
        assertThat(request.sourceConfiguration().imageRepository().imageRepositoryType()).isEqualTo(ImageRepositoryType.ECR_PUBLIC)
    }

    @Test
    fun `Repository Deployment builds request properly`() {
        val repoUrl = RuleUtils.randomName()

        val dialog = runInEdtAndGet { CreationDialog(projectRule.project) }
        val panel = CreationPanel(projectRule.project).apply {
            repo.isSelected = true
            repoConfigFromSettings.isSelected = true
            repository = repoUrl
            runtime = Runtime.NODEJS_12
        }
        panel.component.apply()
        val request = dialog.buildRequest(panel)
        assertThat(request.sourceConfiguration().autoDeploymentsEnabled()).isTrue
        assertThat(request.sourceConfiguration().codeRepository().codeConfiguration().configurationSource()).isEqualTo(ConfigurationSource.API)
        assertThat(request.sourceConfiguration().codeRepository().repositoryUrl()).isEqualTo(repoUrl)
        assertThat(request.sourceConfiguration().codeRepository().codeConfiguration().codeConfigurationValues().port()).isEqualTo("80")
        assertThat(request.sourceConfiguration().codeRepository().codeConfiguration().codeConfigurationValues().runtime()).isEqualTo(Runtime.NODEJS_12)
        assertThat(request.sourceConfiguration().codeRepository().sourceCodeVersion().type()).isEqualTo(SourceCodeVersionType.BRANCH)
        assertThat(request.sourceConfiguration().authenticationConfiguration().connectionArn()).isEqualTo(connectionArn)
    }

    @Test
    fun `Repository Deployment with apprunner yaml builds request properly`() {
        val repoUrl = RuleUtils.randomName()
        val dialog = runInEdtAndGet { CreationDialog(projectRule.project).apply { panel.repo.isSelected = true } }
        val panel = CreationPanel(projectRule.project).apply {
            repo.isSelected = true
            repoConfigFromFile.isSelected = true
            manualDeployment.isSelected = true
            repository = repoUrl
        }
        val request = dialog.buildRequest(panel)
        assertThat(request.sourceConfiguration().autoDeploymentsEnabled()).isFalse
        assertThat(request.sourceConfiguration().codeRepository().repositoryUrl()).isEqualTo(repoUrl)
        assertThat(request.sourceConfiguration().codeRepository().codeConfiguration().configurationSource()).isEqualTo(ConfigurationSource.REPOSITORY)
        assertThat(request.sourceConfiguration().codeRepository().codeConfiguration().codeConfigurationValues()).isNull()
        assertThat(request.sourceConfiguration().codeRepository().sourceCodeVersion().type()).isEqualTo(SourceCodeVersionType.BRANCH)
    }

    @Test
    fun `Panel's special setters work`() {
        val panel = CreationPanel(projectRule.project)
        panel.startCommand = "    "
        assertThat(panel.startCommand).isNull()
        panel.repository = " https://abc/ "
        assertThat(panel.repository).isEqualTo("https://abc")
    }

    @Test
    fun `EcrTag URI can be passed through`() {
        val uri = aString()
        val dialog = runInEdtAndGet { CreationDialog(projectRule.project, uri) }
        assertThat(dialog.panel.containerUri).isEqualTo(uri)
    }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.caws

import com.intellij.openapi.application.PathManager
import com.intellij.openapi.vfs.impl.http.HttpVirtualFile
import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess
import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.runInEdtAndGet
import com.intellij.testFramework.runInEdtAndWait
import com.jetbrains.jsonSchema.ide.JsonSchemaService
import org.jetbrains.yaml.schema.YamlJsonSchemaHighlightingInspection
import org.junit.Rule
import org.junit.Test
import software.aws.toolkits.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule
import software.aws.toolkits.jetbrains.utils.rules.openFile
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

class DevFileSchemaProviderFactoryTest {
    @Rule
    @JvmField
    val projectRule = JavaCodeInsightTestFixtureRule()

    @Rule
    @JvmField
    val disposableRule = DisposableRule()

    @Test
    fun testSchemaIsApplied() {
        VfsRootAccess.allowRootAccess(disposableRule.disposable, PathManager.getSystemPath())
        val yamlJsonSchemaHighlightingInspection = YamlJsonSchemaHighlightingInspection()
        val fixture = projectRule.fixture

        try {
            fixture.enableInspections(yamlJsonSchemaHighlightingInspection)

            fixture.openFile(
                "devfile.yaml",
                """
                <warning descr="Schema validation: Missing required property 'schemaVersion'"> </warning>
                """.trimIndent()
            )

            val schemaService = JsonSchemaService.Impl.get(projectRule.project)

            // Get the schema and force the download since the framework disables HTTP based schemas
            val schemas = runInEdtAndGet {
                schemaService.getSchemaFilesForFile(fixture.file.virtualFile)
            }

            val wait = CountDownLatch(schemas.size)
            schemas.filterIsInstance<HttpVirtualFile>().forEach {
                it.refresh(false, false) {
                    wait.countDown()
                }
            }

            assert(wait.await(5, TimeUnit.SECONDS)).equals(true)
            runInEdtAndWait {
                fixture.checkHighlighting()
            }
        } finally {
            fixture.disableInspections(yamlJsonSchemaHighlightingInspection)
        }
    }
}


// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.caws.envclient

import com.github.tomakehurst.wiremock.client.WireMock
import com.github.tomakehurst.wiremock.core.WireMockConfiguration
import com.github.tomakehurst.wiremock.junit.WireMockRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import software.aws.toolkits.jetbrains.services.caws.envclient.models.GetStatusResponse

class CawsEnvironmentClientTest {
    @Rule
    @JvmField
    val wireMockRule = WireMockRule(
        WireMockConfiguration.wireMockConfig()
            .dynamicPort()
    )

    private lateinit var sut: CawsEnvironmentClient

    @Before
    fun setUp() {
        sut = CawsEnvironmentClient(wireMockRule.baseUrl())
    }

    @Test
    fun `getStatus() can safely handles unknown values`() {
        wireMockRule.stubFor(
            WireMock.any(WireMock.urlPathEqualTo("/status"))
                .willReturn(
                    WireMock.aResponse().withBody(
                        // language=JSON
                        """
                        {
                                "anUnmodeledField": "value",
                                "status": "some-unmodeled-status",
                                "location": "devfile.yaml"
                        }
                        """.trimIndent()
                    )
                )
        )

        val status = sut.getStatus()
        assertThat(status.status).isEqualTo(GetStatusResponse.Status.UNKNOWN)
        assertThat(status.location).isEqualTo("devfile.yaml")
    }

    @Test
    fun `getStatus() can deserialize IMAGES-UPDATE-AVAILABLE`() {
        wireMockRule.stubFor(
            WireMock.any(WireMock.urlPathEqualTo("/status"))
                .willReturn(
                    WireMock.aResponse().withBody(
                        // language=JSON
                        """
                        {
                                "status": "IMAGES-UPDATE-AVAILABLE",
                                "location": "devfile.yaml"
                        }
                        """.trimIndent()
                    )
                )
        )

        assertThat(sut.getStatus().status).isEqualTo(GetStatusResponse.Status.IMAGES_UPDATE_AVAILABLE)
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.cloudformation

import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

class CloudFormationParametersTest {

    val fooParameter = mockTemplateParameter("Foo", "foo")
    val bazParameter = mockTemplateParameter("Baz", "baz")

    val remoteFooParameter = mockRemoteParameter("Foo", "fooValue")
    val remoteBarParameter = mockRemoteParameter("Bar", "barValue")

    @Test
    fun mergeParameters_emptyRemote() {
        val mergedParameters = listOf(fooParameter, bazParameter)
            .mergeRemoteParameters(listOf())
        assertThat(mergedParameters).hasSize(2)
        assertThat(mergedParameters).anySatisfy {
            it.logicalName == "Foo" && it.defaultValue() == "foo"
        }
        assertThat(mergedParameters).anySatisfy {
            it.logicalName == "Baz" && it.defaultValue() == "baz"
        }
    }

    @Test
    fun mergeParameters_emptyTemplate() {
        val mergedParameters = listOf<Parameter>()
            .mergeRemoteParameters(listOf(remoteFooParameter, remoteBarParameter))
        assertThat(mergedParameters).isEmpty()
    }

    @Test
    fun mergeParameters_withOverlap() {
        val mergedParameters = listOf(fooParameter, bazParameter)
            .mergeRemoteParameters(listOf(remoteFooParameter, remoteBarParameter))
        assertThat(mergedParameters).hasSize(2)
        assertThat(mergedParameters).anySatisfy {
            it.logicalName == "Foo" && it.defaultValue() == "fooValue"
        }
        assertThat(mergedParameters).anySatisfy {
            it.logicalName == "Baz" && it.defaultValue() == "baz"
        }
    }

    private fun mockTemplateParameter(logicalName: String, defaultValue: String): Parameter {
        val mockParameter = mock<Parameter>()
        whenever(mockParameter.logicalName).thenReturn(logicalName)
        whenever(mockParameter.defaultValue()).thenReturn(defaultValue)
        return mockParameter
    }

    private fun mockRemoteParameter(key: String, value: String): software.amazon.awssdk.services.cloudformation.model.Parameter =
        software.amazon.awssdk.services.cloudformation.model.Parameter.builder()
            .parameterKey(key)
            .parameterValue(value)
            .build()
}