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

package software.aws.toolkits.jetbrains.core.credentials

import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.testFramework.ApplicationRule
import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.ExtensionTestUtil
import com.intellij.testFramework.runInEdtAndWait
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Rule
import org.junit.Test
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
import software.amazon.awssdk.auth.credentials.AwsCredentials
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
import software.aws.toolkits.core.credentials.CredentialIdentifier
import software.aws.toolkits.core.credentials.CredentialIdentifierBase
import software.aws.toolkits.core.credentials.CredentialProviderFactory
import software.aws.toolkits.core.credentials.CredentialProviderNotFoundException
import software.aws.toolkits.core.credentials.CredentialSourceId
import software.aws.toolkits.core.credentials.CredentialsChangeEvent
import software.aws.toolkits.core.credentials.CredentialsChangeListener
import software.aws.toolkits.core.region.AwsRegion
import software.aws.toolkits.core.region.anAwsRegion
import software.aws.toolkits.core.utils.test.aString
import software.aws.toolkits.jetbrains.core.region.MockRegionProviderRule
import software.aws.toolkits.jetbrains.core.region.getDefaultRegion
import software.aws.toolkits.jetbrains.utils.assertIsNonDispatchThread
import software.aws.toolkits.jetbrains.utils.computeOnEdt
import software.aws.toolkits.jetbrains.utils.isInstanceOf
import kotlin.test.assertNotNull

class CredentialManagerTest {
    @Rule
    @JvmField
    val disposableRule = DisposableRule()

    @Rule
    @JvmField
    val application = ApplicationRule()

    @Rule
    @JvmField
    val regionProvider = MockRegionProviderRule()

    @Test
    fun testCredentialsCanLoadFromExtensions() {
        val region = getDefaultRegion()

        addFactories(
            createTestCredentialFactory(
                "testFactory1",
                listOf("testFoo1", "testBar1")
            ),
            createTestCredentialFactory(
                "testFactory2",
                listOf("testFoo2", "testBar2")
            )
        )

        val credentialManager = DefaultCredentialManager()

        assertThat(credentialManager.getCredentialIdentifiers().map { it.id }).contains("testFoo1", "testFoo2", "testBar1", "testBar2")

        val credentialsIdentifier = credentialManager.getCredentialIdentifierById("testFoo2")
        assertNotNull(credentialsIdentifier)

        val credentialProvider = credentialManager.getAwsCredentialProvider(credentialsIdentifier, region)

        assertThat(credentialProvider.resolveCredentials()).isInstanceOf<AwsCredentials>()
    }

    @Test
    fun testCredentialsAreScopedToPartition() {
        val partition1 = anAwsRegion(partitionId = "part1")
        val partition1Region2 = anAwsRegion(partitionId = "part1")
        val partition2 = anAwsRegion(partitionId = "part2")

        addFactories(createTestCredentialFactory("testFactory1", listOf("testFoo1")))

        val credentialManager = DefaultCredentialManager()

        val credentialsIdentifier = credentialManager.getCredentialIdentifierById("testFoo1")
        assertNotNull(credentialsIdentifier)

        val partition1Credentials = credentialManager.getAwsCredentialProvider(credentialsIdentifier, partition1).resolveCredentials()
        val partition1Region2Credentials = credentialManager.getAwsCredentialProvider(credentialsIdentifier, partition1Region2).resolveCredentials()
        val partition2Credentials = credentialManager.getAwsCredentialProvider(credentialsIdentifier, partition2).resolveCredentials()

        assertThat(partition1Credentials).isEqualTo(partition1Region2Credentials).isNotEqualTo(partition2Credentials)
    }

    @Test
    fun testCredentialUpdatingDoesNotBreakExisting() {
        val region = getDefaultRegion()
        val originalCredentials = randomCredentialProvider()
        val credentialFactory = createTestCredentialFactory("testFactory1").apply {
            addCredentialProvider("testFoo1", originalCredentials)
        }

        addFactories(credentialFactory)

        val credentialManager = DefaultCredentialManager()

        val credentialsIdentifier = credentialManager.getCredentialIdentifierById("testFoo1")
        assertNotNull(credentialsIdentifier)

        val credentialProvider = credentialManager.getAwsCredentialProvider(credentialsIdentifier, region)

        assertThat(credentialProvider.resolveCredentials()).isEqualTo(originalCredentials.resolveCredentials())

        val updatedCredentials = randomCredentialProvider()
        credentialFactory.updateCredentials("testFoo1", region, updatedCredentials)

        // Existing references are good
        assertThat(credentialProvider.resolveCredentials()).isEqualTo(updatedCredentials.resolveCredentials())

        // New ones are good too
        assertThat(
            credentialManager.getAwsCredentialProvider(
                credentialsIdentifier,
                region
            ).resolveCredentials()
        ).isEqualTo(updatedCredentials.resolveCredentials())
    }

    @Test
    fun testRemovedCredentialsCeaseWorkingAfter() {
        val region = getDefaultRegion()
        val credentialFactory = createTestCredentialFactory("testFactory1", listOf("testFoo1"))

        addFactories(credentialFactory)

        val credentialManager = DefaultCredentialManager()

        val credentialsIdentifier = credentialManager.getCredentialIdentifierById("testFoo1")
        assertNotNull(credentialsIdentifier)

        val credentialProvider = credentialManager.getAwsCredentialProvider(credentialsIdentifier, region)
        assertThat(credentialProvider.resolveCredentials()).isInstanceOf<AwsCredentials>()

        credentialFactory.removeCredentials("testFoo1")

        // Existing references throw
        assertThatThrownBy { credentialProvider.resolveCredentials() }.isInstanceOf(CredentialProviderNotFoundException::class.java)

        // New ones fail too
        assertThatThrownBy {
            credentialManager.getAwsCredentialProvider(
                credentialsIdentifier,
                region
            ).resolveCredentials()
        }.isInstanceOf<CredentialProviderNotFoundException>()

        assertThat(credentialManager.getCredentialIdentifierById("testFoo1")).isNull()
    }

    @Test
    fun testUpdatedCredentialIdentifierIsApplied() {
        val region = getDefaultRegion()
        val credentialFactory = createTestCredentialFactory("testFactory1", listOf("testFoo1"))

        addFactories(credentialFactory)

        val credentialManager = DefaultCredentialManager()

        assertThat(credentialManager.getCredentialIdentifierById("testFoo1")?.defaultRegionId).isEqualTo(region.id)

        val newRegion = regionProvider.addRegion(AwsRegion("test", "test", "test"))
        credentialFactory.updateCredentials("testFoo1", newRegion)

        assertThat(credentialManager.getCredentialIdentifierById("testFoo1")?.defaultRegionId).isEqualTo(newRegion.id)
    }

    @Test
    fun resolvingCredentialsRunsInBackground() {
        val credentialFactory = createTestCredentialFactory("testFactory1").apply {
            addCredentialProvider("testFoo1") {
                assertIsNonDispatchThread()
                computeOnEdt {
                    ApplicationManager.getApplication().assertIsDispatchThread()

                    AwsBasicCredentials.create(aString(), aString())
                }
            }
        }

        addFactories(credentialFactory)

        val credentialManager = DefaultCredentialManager()
        val credentialsIdentifier = credentialManager.getCredentialIdentifierById("testFoo1")
        assertNotNull(credentialsIdentifier)
        val credentialProvider = credentialManager.getAwsCredentialProvider(credentialsIdentifier, getDefaultRegion())

        runInEdtAndWait {
            credentialProvider.resolveCredentials()
        }
    }

    @Test
    fun processCancellationBubblesOut() {
        val credentialFactory = createTestCredentialFactory("testFactory1").apply {
            addCredentialProvider("testFoo1") {
                throw ProcessCanceledException()
            }
        }

        addFactories(credentialFactory)

        val credentialManager = DefaultCredentialManager()
        val credentialsIdentifier = credentialManager.getCredentialIdentifierById("testFoo1")
        assertNotNull(credentialsIdentifier)
        val credentialProvider = credentialManager.getAwsCredentialProvider(credentialsIdentifier, getDefaultRegion())

        assertThatThrownBy {
            credentialProvider.resolveCredentials()
        }.isInstanceOf<ProcessCanceledException>()
    }

    private fun addFactories(vararg factories: CredentialProviderFactory) {
        ExtensionTestUtil.maskExtensions(DefaultCredentialManager.EP_NAME, factories.toList(), disposableRule.disposable)
    }

    private fun createTestCredentialFactory(id: String, initialProviderIds: List<String> = emptyList()) = TestCredentialProviderFactory(id).apply {
        initialProviderIds.forEach(this::addCredentialProvider)
    }

    private class TestCredentialProviderFactory(override val id: String) : CredentialProviderFactory {
        private val initialProviders = mutableMapOf<String, TestCredentialProviderIdentifier>()
        private val credentialsMapping = mutableMapOf<String, TestCredentialProviderIdentifier>()
        private lateinit var callback: CredentialsChangeListener

        override val credentialSourceId = CredentialSourceId.SharedCredentials

        override fun setUp(credentialLoadCallback: CredentialsChangeListener) {
            callback = credentialLoadCallback

            credentialsMapping.putAll(initialProviders)

            callback(
                CredentialsChangeEvent(
                    initialProviders.values.toList(),
                    emptyList(),
                    emptyList()
                )
            )

            initialProviders.clear()
        }

        fun addCredentialProvider(
            credentialId: String,
            awsCredentialsProvider: AwsCredentialsProvider? = null
        ) {
            val identifier = TestCredentialProviderIdentifier(credentialId, id, getDefaultRegion().id, awsCredentialsProvider)
            if (!::callback.isInitialized) {
                initialProviders[credentialId] = identifier
                return
            }

            credentialsMapping[credentialId] = identifier

            callback(
                CredentialsChangeEvent(
                    listOf(identifier),
                    emptyList(),
                    emptyList()
                )
            )
        }

        override fun createAwsCredentialProvider(providerId: CredentialIdentifier, region: AwsRegion): AwsCredentialsProvider =
            (providerId as TestCredentialProviderIdentifier).provider ?: StaticCredentialsProvider.create(AwsBasicCredentials.create(aString(), aString()))

        fun updateCredentials(providerId: String, region: AwsRegion, awsCredentialsProvider: AwsCredentialsProvider = randomCredentialProvider()) {
            val identifier = TestCredentialProviderIdentifier(providerId, id, region.id, awsCredentialsProvider)

            credentialsMapping[providerId] = identifier

            callback(
                CredentialsChangeEvent(
                    emptyList(),
                    listOf(identifier),
                    emptyList()
                )
            )
        }

        fun removeCredentials(providerId: String) {
            callback(
                CredentialsChangeEvent(
                    emptyList(),
                    emptyList(),
                    listOf(credentialsMapping.remove(providerId)!!)
                )
            )
        }
    }

    private class TestCredentialProviderIdentifier(
        override val id: String,
        override val factoryId: String,
        override val defaultRegionId: String,
        val provider: AwsCredentialsProvider?
    ) : CredentialIdentifierBase(null) {
        override val displayName: String = "$factoryId:$id"
    }

    private companion object {
        private fun randomCredentialProvider() = StaticCredentialsProvider.create(AwsBasicCredentials.create(aString(), aString()))
    }
}


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

package software.aws.toolkits.jetbrains.core.credentials

import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonProcessingException
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.intellij.lang.annotations.Language
import org.junit.Test
import software.aws.toolkits.jetbrains.utils.isInstanceOf
import java.time.Instant

class CredentialProcessOutputParserTest {
    private val sut = DefaultCredentialProcessOutputParser

    @Test
    fun `can parse a basic input`() = runTest(
        """{"Version":1, "AccessKeyId":"foo", "SecretAccessKey":"bar"}""",
        CredentialProcessOutput("foo", "bar", null, null)
    )

    @Test
    fun `can parse a basic input with expiration`() = runTest(
        """{"Version":1, "AccessKeyId":"foo", "SecretAccessKey":"bar", "Expiration":"1970-01-01T00:00:00Z"}""",
        CredentialProcessOutput("foo", "bar", null, Instant.EPOCH)
    )

    @Test
    fun `can parse a basic input with session`() = runTest(
        """{"Version":1, "AccessKeyId":"foo", "SecretAccessKey":"bar", "SessionToken":"session"}""",
        CredentialProcessOutput("foo", "bar", "session", null)
    )

    @Test
    fun `non JSON throws`() {
        assertThatThrownBy { sut.parse("hello") }.isInstanceOf<JsonParseException>()
    }

    @Test
    fun `valid JSON missing required properties fails`() {
        assertThatThrownBy { sut.parse("""{"AccessKeyId": "foo"}""") }.hasMessageContaining("secretAccessKey")
    }

    @Test
    fun `exception does not contain raw JSON data`() {
        assertThatThrownBy { sut.parse("""{"hello": "world"}""") }.isInstanceOfSatisfying(JsonProcessingException::class.java) {
            assertThat(it.message).doesNotContain("hello")
        }
    }

    private fun runTest(@Language("JSON") input: String, expected: CredentialProcessOutput) {
        assertThat(sut.parse(input)).isEqualTo(expected)
    }
}


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

package software.aws.toolkits.jetbrains.core.credentials

import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.testFramework.EdtRule
import com.intellij.testFramework.RunsInEdt
import com.intellij.testFramework.runInEdtAndWait
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import software.aws.toolkits.core.rules.SystemPropertyHelper
import software.aws.toolkits.jetbrains.core.credentials.CredentialsFileHelpNotificationProvider.CredentialFileNotificationPanel
import software.aws.toolkits.jetbrains.core.getEditorNotifications
import software.aws.toolkits.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule

@RunsInEdt
class CredentialsFileHelpNotificationProviderTest {
    @Rule
    @JvmField
    val projectRule = HeavyJavaCodeInsightTestFixtureRule()

    @Rule
    @JvmField
    val systemPropertyHelper = SystemPropertyHelper()

    @Rule
    @JvmField
    val temporaryFolder = TemporaryFolder()

    @Rule
    @JvmField
    val edtRule = EdtRule()

    private lateinit var configFile: VirtualFile
    private lateinit var credentialsFile: VirtualFile

    @Before
    fun setUp() {
        runInEdtAndWait {
            configFile = projectRule.fixture.tempDirFixture.createFile("config", "[default]")
            credentialsFile = projectRule.fixture.tempDirFixture.createFile("credentials", "[default]")
        }

        System.getProperties().setProperty("aws.configFile", configFile.toNioPath().toString())
        System.getProperties().setProperty("aws.sharedCredentialsFile", credentialsFile.toNioPath().toString())
    }

    @Test
    fun `notification gets shown on config file`() {
        val editor = openEditor(credentialsFile)
        assertThat(getEditorNotifications(editor)).isNotNull
    }

    @Test
    fun `notification gets shown on credentials file`() {
        val editor = openEditor(credentialsFile)
        assertThat(getEditorNotifications(editor)).isNotNull
    }

    @Test
    fun `notification not shown on non credentials files`() {
        val editor = openEditor(projectRule.fixture.tempDirFixture.createFile("foo.txt"))
        assertThat(getEditorNotifications(editor)).isNull()
    }

    private fun openEditor(file: VirtualFile): FileEditor = FileEditorManagerEx.getInstanceEx(projectRule.project).openFile(file, true).single()

    private fun getEditorNotifications(editor: FileEditor): CredentialFileNotificationPanel? =
        getEditorNotifications(projectRule.project, editor, CredentialsFileHelpNotificationProvider::class.java, CredentialsFileHelpNotificationProvider.KEY)
            as CredentialFileNotificationPanel?
}


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

package software.aws.toolkits.jetbrains.core.credentials

import com.intellij.notification.Notification
import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.runInEdtAndWait
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import software.aws.toolkits.core.credentials.aCredentialsIdentifier
import software.aws.toolkits.core.region.anAwsRegion
import software.aws.toolkits.jetbrains.core.region.MockRegionProviderRule
import software.aws.toolkits.jetbrains.settings.AwsSettings
import software.aws.toolkits.jetbrains.settings.AwsSettingsRule
import software.aws.toolkits.jetbrains.settings.UseAwsCredentialRegion
import software.aws.toolkits.jetbrains.utils.rules.NotificationListenerRule
import software.aws.toolkits.resources.message

class CredentialsRegionHandlerTest {

    @Rule
    @JvmField
    val projectRule = ProjectRule()

    @Rule
    @JvmField
    val regionProviderRule = MockRegionProviderRule()

    @Rule
    @JvmField
    val disposableRule = DisposableRule()

    @Rule
    @JvmField
    val notificationListener = NotificationListenerRule(projectRule, disposableRule.disposable)

    private lateinit var sut: DefaultCredentialsRegionHandler

    @Rule
    @JvmField
    val settingsRule = AwsSettingsRule()

    @Before
    fun setup() {
        sut = DefaultCredentialsRegionHandler(projectRule.project)
        AwsSettings.getInstance().useDefaultCredentialRegion = UseAwsCredentialRegion.Always
    }

    @Test
    fun `Credential with no default region returns selected region`() {
        val identifier = aCredentialsIdentifier(defaultRegionId = null)
        val region = anAwsRegion()

        assertThat(sut.determineSelectedRegion(identifier, region)).isEqualTo(region)
    }

    @Test
    fun `When selected region is null always use credential region`() {
        val defaultRegion = regionProviderRule.createAwsRegion()
        val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id)

        assertThat(sut.determineSelectedRegion(identifier, selectedRegion = null)).isEqualTo(defaultRegion)
    }

    @Test
    fun `Always use credential region if its partition is different from the selected region`() {
        val defaultRegion = regionProviderRule.createAwsRegion()
        val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id)

        assertThat(sut.determineSelectedRegion(identifier, selectedRegion = regionProviderRule.createAwsRegion())).isEqualTo(defaultRegion)
    }

    @Test
    fun `Always use credential region if setting is set to Always`() {
        AwsSettings.getInstance().useDefaultCredentialRegion = UseAwsCredentialRegion.Always
        val defaultRegion = regionProviderRule.createAwsRegion()
        val selectedRegion = regionProviderRule.createAwsRegion(partitionId = defaultRegion.partitionId)
        val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id)

        assertThat(sut.determineSelectedRegion(identifier, selectedRegion = selectedRegion)).isEqualTo(defaultRegion)
    }

    @Test
    fun `Do not use credential region if setting is set to Never`() {
        AwsSettings.getInstance().useDefaultCredentialRegion = UseAwsCredentialRegion.Never
        val defaultRegion = regionProviderRule.createAwsRegion()
        val selectedRegion = regionProviderRule.createAwsRegion(partitionId = defaultRegion.partitionId)
        val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id)

        assertThat(sut.determineSelectedRegion(identifier, selectedRegion = selectedRegion)).isEqualTo(selectedRegion)
    }

    @Test
    fun `Do not use credential region if setting is set to Never, even if the partition is different`() {
        settingsRule.settings.useDefaultCredentialRegion = UseAwsCredentialRegion.Never
        val defaultRegion = regionProviderRule.createAwsRegion()
        val selectedRegion = regionProviderRule.createAwsRegion()
        val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id)

        assertThat(sut.determineSelectedRegion(identifier, selectedRegion = selectedRegion)).isEqualTo(selectedRegion)
    }

    @Test
    fun `Do not use credential region if setting is set to Never, even if the region is null`() {
        settingsRule.settings.useDefaultCredentialRegion = UseAwsCredentialRegion.Never
        val defaultRegion = regionProviderRule.createAwsRegion()
        val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id)

        assertThat(sut.determineSelectedRegion(identifier, selectedRegion = null)).isNull()
    }

    @Test
    fun `Prompt appears when setting is set to prompt, selected region remains active`() {
        settingsRule.settings.useDefaultCredentialRegion = UseAwsCredentialRegion.Prompt

        val defaultRegion = regionProviderRule.createAwsRegion()
        val selectedRegion = regionProviderRule.createAwsRegion(partitionId = defaultRegion.partitionId)
        val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id)

        val newSelected = sut.determineSelectedRegion(identifier, selectedRegion = selectedRegion)

        assertThat(newSelected).isEqualTo(selectedRegion)
        val notification = getOnlyNotification()
        assertThat(notification.actions).hasSize(3)
    }

    @Test
    fun `Prompt only appears when region is different than default`() {
        settingsRule.settings.useDefaultCredentialRegion = UseAwsCredentialRegion.Prompt

        val defaultRegion = regionProviderRule.createAwsRegion()
        val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id)

        val newSelected = sut.determineSelectedRegion(identifier, selectedRegion = defaultRegion)

        assertThat(newSelected).isEqualTo(defaultRegion)
        assertThat(notificationListener.notifications.filter { it.title == message("aws.notification.title") }).isEmpty()
    }

    @Test
    fun `Selecting Never at the prompt sets setting to Never`() {
        settingsRule.settings.useDefaultCredentialRegion = UseAwsCredentialRegion.Prompt

        val defaultRegion = regionProviderRule.createAwsRegion()
        val selectedRegion = regionProviderRule.createAwsRegion(partitionId = defaultRegion.partitionId)
        val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id)

        sut.determineSelectedRegion(identifier, selectedRegion = selectedRegion)

        val notification = getOnlyNotification()

        runInEdtAndWait {
            Notification.fire(notification, notification.actions.first { it.templateText == "Never" }, null)
        }

        assertThat(AwsSettings.getInstance().useDefaultCredentialRegion).isEqualTo(UseAwsCredentialRegion.Never)
    }

    @Test
    fun `Selecting Always at the prompt sets setting to Always`() {
        settingsRule.settings.useDefaultCredentialRegion = UseAwsCredentialRegion.Prompt

        val defaultRegion = regionProviderRule.createAwsRegion()
        val selectedRegion = regionProviderRule.createAwsRegion(partitionId = defaultRegion.partitionId)
        val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id)

        sut.determineSelectedRegion(identifier, selectedRegion = selectedRegion)

        val notification = getOnlyNotification()

        runInEdtAndWait {
            Notification.fire(notification, notification.actions.first { it.templateText == "Always" }, null)
        }

        assertThat(AwsSettings.getInstance().useDefaultCredentialRegion).isEqualTo(UseAwsCredentialRegion.Always)
    }

    private fun getOnlyNotification(): Notification {
        val credentialNotifications = notificationListener.notifications.filter { it.title == message("aws.notification.title") }
        assertThat(credentialNotifications).hasSize(1)

        return credentialNotifications.first()
    }
}


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

package software.aws.toolkits.jetbrains.core.credentials

import com.intellij.openapi.application.ApplicationManager
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import software.aws.toolkits.core.credentials.CredentialIdentifier
import software.aws.toolkits.core.credentials.aCredentialsIdentifier
import software.aws.toolkits.core.region.AwsRegion
import software.aws.toolkits.core.rules.EnvironmentVariableHelper
import software.aws.toolkits.core.utils.test.notNull
import software.aws.toolkits.jetbrains.core.MockResourceCacheRule
import software.aws.toolkits.jetbrains.core.credentials.AwsConnectionManager.Companion.selectedPartition
import software.aws.toolkits.jetbrains.core.credentials.profiles.DEFAULT_PROFILE_ID
import software.aws.toolkits.jetbrains.core.region.AwsRegionProvider
import software.aws.toolkits.jetbrains.core.region.MockRegionProviderRule
import software.aws.toolkits.jetbrains.core.region.getDefaultRegion
import software.aws.toolkits.jetbrains.services.sts.StsResources
import software.aws.toolkits.jetbrains.utils.deserializeState
import software.aws.toolkits.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule
import software.aws.toolkits.jetbrains.utils.serializeState
import java.nio.file.Files
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean

class DefaultAwsConnectionManagerTest {
    @Rule
    @JvmField
    val projectRule = HeavyJavaCodeInsightTestFixtureRule()

    @Rule
    @JvmField
    val environmentVariableHelper = EnvironmentVariableHelper()

    @Rule
    @JvmField
    val mockCredentialManager = MockCredentialManagerRule()

    @Rule
    @JvmField
    val regionProviderRule = MockRegionProviderRule()

    @JvmField
    @Rule
    val resourceCache = MockResourceCacheRule()

    private lateinit var manager: DefaultAwsConnectionManager

    @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")
        manager = DefaultAwsConnectionManager(projectRule.project)
    }

    @After
    fun tearDown() {
        mockCredentialManager.reset()
    }

    @Test
    fun `Starts with no active credentials`() {
        assertThat(manager.isValidConnectionSettings()).isFalse
        assertThat(manager.recentlyUsedCredentials()).isEmpty()
    }

    @Test
    fun `On load, automatically selects default profile if present and no other active credentials`() {
        val credentials = mockCredentialManager.addCredentials(DEFAULT_PROFILE_ID)
        markConnectionSettingsAsValid(credentials, AwsRegionProvider.getInstance().defaultRegion())

        manager.noStateLoaded()
        manager.waitUntilConnectionStateIsStable()

        assertThat(manager.selectedCredentialIdentifier).notNull.satisfies {
            assertThat(it.id).isEqualTo(credentials.id)
        }
    }

    @Test
    fun `On load, default region of credential is used if there is no other active region`() {
        val region = AwsRegion("us-west-2", "Oregon", "AWS")
        val credentials = mockCredentialManager.addCredentials(id = "Mock", region = region)
        markConnectionSettingsAsValid(credentials, regionProviderRule.defaultRegion())
        regionProviderRule.addRegion(region)

        deserializeState(
            """
             <AccountState>
                 <option name="activeProfile" value="Mock" />
             </AccountState>
             """,
            manager,
        )

        manager.waitUntilConnectionStateIsStable()

        assertThat(manager.selectedRegion).notNull.satisfies {
            assertThat(it.id).isEqualTo("us-west-2")
        }
    }

    @Test
    fun `Activated credential are validated and added to the recently used list`() {
        changeRegion(AwsRegionProvider.getInstance().defaultRegion())

        assertThat(manager.recentlyUsedCredentials()).isEmpty()

        val credentials = mockCredentialManager.addCredentials("Mock1")
        val credentials2 = mockCredentialManager.addCredentials("Mock2")

        markConnectionSettingsAsValid(credentials, AwsRegionProvider.getInstance().defaultRegion())
        markConnectionSettingsAsValid(credentials2, AwsRegionProvider.getInstance().defaultRegion())

        changeCredentialProvider(credentials)

        assertThat(manager.isValidConnectionSettings()).isTrue
        assertThat(manager.connectionSettings()?.credentials?.id).isEqualTo(credentials.id)

        assertThat(manager.recentlyUsedCredentials()).element(0).isEqualTo(credentials)

        changeCredentialProvider(credentials2)

        assertThat(manager.isValidConnectionSettings()).isTrue
        assertThat(manager.connectionSettings()?.credentials?.id).isEqualTo(credentials2.id)

        assertThat(manager.recentlyUsedCredentials()).element(0).isEqualTo(credentials2)
        assertThat(manager.recentlyUsedCredentials()).element(1).isEqualTo(credentials)
    }

    @Test
    fun `Activated regions are validated and added to the recently used list`() {
        val mockRegion1 = regionProviderRule.addRegion(AwsRegion("MockRegion-1", "MockRegion-1", "aws"))
        val mockRegion2 = regionProviderRule.addRegion(AwsRegion("MockRegion-2", "MockRegion-2", "aws"))

        assertThat(manager.recentlyUsedRegions()).isEmpty()

        changeRegion(mockRegion1)

        assertThat(manager.selectedRegion).isEqualTo(mockRegion1)
        assertThat(manager.recentlyUsedRegions()).element(0).isEqualTo(mockRegion1)

        changeRegion(mockRegion2)

        assertThat(manager.selectedRegion).isEqualTo(mockRegion2)
        assertThat(manager.recentlyUsedRegions()).element(0).isEqualTo(mockRegion2)
        assertThat(manager.recentlyUsedRegions()).element(1).isEqualTo(mockRegion1)
    }

    @Test
    fun `Activating a region fires a state change notification`() {
        val project = projectRule.project

        val gotNotification = AtomicBoolean(false)
        val latch = CountDownLatch(1)

        val busConnection = project.messageBus.connect()
        busConnection.subscribe(
            AwsConnectionManager.CONNECTION_SETTINGS_STATE_CHANGED,
            object : ConnectionSettingsStateChangeNotifier {
                override fun settingsStateChanged(newState: ConnectionState) {
                    gotNotification.set(true)
                    latch.countDown()
                }
            }
        )

        changeRegion(AwsRegionProvider.getInstance().defaultRegion())

        assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue
        assertThat(gotNotification).isTrue
    }

    @Test
    fun `Activating a credential fires a state change notification`() {
        val project = projectRule.project

        val gotNotification = AtomicBoolean(false)
        val latch = CountDownLatch(1)

        val busConnection = project.messageBus.connect()
        busConnection.subscribe(
            AwsConnectionManager.CONNECTION_SETTINGS_STATE_CHANGED,
            object : ConnectionSettingsStateChangeNotifier {
                override fun settingsStateChanged(newState: ConnectionState) {
                    gotNotification.set(true)
                    latch.countDown()
                }
            }
        )

        changeCredentialProvider(mockCredentialManager.addCredentials("Mock"))

        assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue
        assertThat(gotNotification).isTrue
    }

    @Test
    fun `Active region is persisted`() {
        manager.changeRegion(AwsRegion.GLOBAL)
        assertThat(serializeState("AccountState", manager)).isEqualToIgnoringWhitespace(
            """
            <AccountState>
                <option name="activeRegion" value="aws-global" />
                <option name="recentlyUsedRegions">
                    <list>
                        <option value="aws-global" />
                    </list>
                </option>
            </AccountState>
            """
        )
    }

    @Test
    fun `Active credential is persisted`() {
        val credentials = mockCredentialManager.addCredentials("Mock")
        markConnectionSettingsAsValid(credentials, manager.activeRegion)
        changeCredentialProvider(credentials)

        assertThat(serializeState("AccountState", manager)).isEqualToIgnoringWhitespace(
            """
            <AccountState>
                <option name="activeProfile" value="Mock" />
                <option name="recentlyUsedProfiles">
                    <list>
                        <option value="Mock" />
                    </list>
                </option>
            </AccountState>
            """
        )
    }

    @Test
    fun `Active credential can be restored from persistence`() {
        val credentials = mockCredentialManager.addCredentials("Mock")
        markConnectionSettingsAsValid(credentials, regionProviderRule.defaultRegion())

        deserializeState(
            """
            <AccountState>
                <option name="activeProfile" value="Mock" />
                <option name="recentlyUsedProfiles">
                    <list>
                        <option value="Mock" />
                    </list>
                </option>
            </AccountState>
             """,
            manager,
        )

        manager.waitUntilConnectionStateIsStable()

        assertThat(manager.selectedCredentialIdentifier).isEqualTo(credentials)
        assertThat(manager.recentlyUsedCredentials()).element(0).isEqualTo(credentials)
    }

    @Test
    fun `Active region can be restored from persistence`() {
        deserializeState(
            """
            <AccountState>
                <option name="activeRegion" value="${getDefaultRegion().id}" />
                <option name="recentlyUsedRegions">
                    <list>
                        <option value="${getDefaultRegion().id}" />
                    </list>
                </option>
            </AccountState>
             """,
            manager,
        )

        manager.waitUntilConnectionStateIsStable()

        val region = regionProviderRule.defaultRegion()
        assertThat(manager.selectedRegion).isEqualTo(region)
        assertThat(manager.selectedPartition?.id).isEqualTo(region.partitionId)
        assertThat(manager.recentlyUsedRegions()).element(0).isEqualTo(region)
    }

    @Test
    fun `Attempting to restore a region that no longer exists is handled gracefully`() {
        deserializeState(
            """
            <AccountState>
                <option name="activeRegion" value="DoesNotExist" />
                <option name="recentlyUsedRegions">
                    <list>
                        <option value="DoesNotExist" />
                    </list>
                </option>
            </AccountState>
             """,
            manager,
        )

        manager.waitUntilConnectionStateIsStable()

        assertThat(manager.connectionSettings()?.region).isNull()
        assertThat(manager.recentlyUsedRegions()).isEmpty()
    }

    @Test
    fun `Attempting to restore a credential that no longer exists is handled gracefully`() {
        deserializeState(
            """
            <AccountState>
                <option name="activeProfile" value="DoesNotExist" />
                <option name="recentlyUsedProfiles">
                    <list>
                        <option value="DoesNotExist" />
                    </list>
                </option>
            </AccountState>
             """,
            manager,
        )

        manager.waitUntilConnectionStateIsStable()

        assertThat(manager.isValidConnectionSettings()).isFalse
        assertThat(manager.recentlyUsedCredentials()).isEmpty()
        assertThat(manager.connectionSettings()).isNull()
    }

    @Test
    fun `Credentials are validated when restored from persistence`() {
        val mockCredentials = mockCredentialManager.addCredentials("Mock")

        markConnectionSettingsAsInvalid(mockCredentials, regionProviderRule.defaultRegion())

        deserializeState(
            """
            <AccountState>
                <option name="activeProfile" value="${mockCredentials.id}" />
                <option name="recentlyUsedProfiles">
                    <list>
                        <option value="${mockCredentials.id}" />
                    </list>
                </option>
            </AccountState>
             """,
            manager,
        )

        manager.waitUntilConnectionStateIsStable()

        assertThat(manager.isValidConnectionSettings()).isFalse
    }

    @Test
    fun `On load, default credential is selected if no other credential is active`() {
        val credentials = mockCredentialManager.addCredentials(DEFAULT_PROFILE_ID)
        markConnectionSettingsAsValid(credentials, regionProviderRule.defaultRegion())

        deserializeState(
            """
            <AccountState/>
             """,
            manager,
        )

        manager.waitUntilConnectionStateIsStable()

        assertThat(manager.isValidConnectionSettings()).isTrue
        assertThat(manager.connectionSettings()?.credentials?.id).isEqualTo(DEFAULT_PROFILE_ID)

        assertThat(manager.recentlyUsedCredentials()).hasSize(1)
        assertThat(manager.recentlyUsedCredentials().first().id).isEqualTo(DEFAULT_PROFILE_ID)
    }

    @Test
    fun `Removal of the active credential falls back to 'no credential selected' state`() {
        val someOtherCredential = aCredentialsIdentifier().also { mockCredentialManager.addCredentials(it.id) }
        val adminCredentials = aCredentialsIdentifier().also { mockCredentialManager.addCredentials(it.id) }

        markConnectionSettingsAsValid(someOtherCredential, AwsRegionProvider.getInstance().defaultRegion())
        markConnectionSettingsAsValid(adminCredentials, AwsRegionProvider.getInstance().defaultRegion())

        changeRegion(AwsRegionProvider.getInstance().defaultRegion())
        changeCredentialProvider(adminCredentials)

        assertThat(manager.isValidConnectionSettings()).isTrue

        assertThat(manager.selectedCredentialIdentifier?.id).isEqualTo(adminCredentials.id)

        ApplicationManager.getApplication().messageBus.syncPublisher(CredentialManager.CREDENTIALS_CHANGED).providerRemoved(adminCredentials)

        assertThat(manager.isValidConnectionSettings()).isFalse
        assertThat(manager.selectedCredentialIdentifier).isNull()
        assertThat(manager.connectionSettings()).isNull()
    }

    @Test
    fun `Refreshing state triggers connection to be re-validated`() {
        val defaultCredentials = aCredentialsIdentifier().also { mockCredentialManager.addCredentials(it.id) }

        markConnectionSettingsAsInvalid(defaultCredentials, AwsRegionProvider.getInstance().defaultRegion())

        changeRegion(AwsRegionProvider.getInstance().defaultRegion())
        changeCredentialProvider(defaultCredentials)

        assertThat(manager.isValidConnectionSettings()).isFalse

        markConnectionSettingsAsValid(defaultCredentials, AwsRegionProvider.getInstance().defaultRegion())
        manager.refreshConnectionState()
        manager.waitUntilConnectionStateIsStable()

        assertThat(manager.isValidConnectionSettings()).isTrue
    }

    @Test
    fun `A change to the selected credential triggers a refresh if the current state is invalid`() {
        val defaultCredentials = aCredentialsIdentifier().also { mockCredentialManager.addCredentials(it.id) }

        markConnectionSettingsAsInvalid(defaultCredentials, AwsRegionProvider.getInstance().defaultRegion())

        changeRegion(AwsRegionProvider.getInstance().defaultRegion())
        changeCredentialProvider(defaultCredentials)

        assertThat(manager.isValidConnectionSettings()).isFalse

        markConnectionSettingsAsValid(defaultCredentials, AwsRegionProvider.getInstance().defaultRegion())
        ApplicationManager.getApplication().messageBus.syncPublisher(CredentialManager.CREDENTIALS_CHANGED).providerModified(defaultCredentials)
        manager.waitUntilConnectionStateIsStable()

        assertThat(manager.isValidConnectionSettings()).isTrue
    }

    @Test
    fun `A change to the selected credential triggers a refresh if the current state is valid`() {
        val credentials = aCredentialsIdentifier().also { mockCredentialManager.addCredentials(it.id) }

        markConnectionSettingsAsValid(credentials, AwsRegionProvider.getInstance().defaultRegion())

        changeRegion(AwsRegionProvider.getInstance().defaultRegion())
        changeCredentialProvider(credentials)

        assertThat(manager.isValidConnectionSettings()).isTrue

        markConnectionSettingsAsInvalid(credentials, AwsRegionProvider.getInstance().defaultRegion())
        ApplicationManager.getApplication().messageBus.syncPublisher(CredentialManager.CREDENTIALS_CHANGED).providerModified(credentials)
        manager.waitUntilConnectionStateIsStable()

        assertThat(manager.isValidConnectionSettings()).isFalse
    }

    private fun markConnectionSettingsAsValid(credentialsIdentifier: CredentialIdentifier, region: AwsRegion) {
        resourceCache.addEntry(StsResources.ACCOUNT, region.id, credentialsIdentifier.id, "1111222233333")
    }

    private fun markConnectionSettingsAsInvalid(credentialsIdentifier: CredentialIdentifier, region: AwsRegion) {
        resourceCache.addEntry(
            StsResources.ACCOUNT,
            region.id,
            credentialsIdentifier.id,
            CompletableFuture.failedFuture(IllegalStateException("Invalid AWS credentials $credentialsIdentifier"))
        )
    }

    private fun changeCredentialProvider(credentialsProvider: CredentialIdentifier) {
        manager.changeCredentialProvider(credentialsProvider)

        manager.waitUntilConnectionStateIsStable()
    }

    private fun changeRegion(region: AwsRegion) {
        manager.changeRegion(region)

        manager.waitUntilConnectionStateIsStable()
    }
}


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

package software.aws.toolkits.jetbrains.core.credentials

import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.IterableAssert
import org.junit.Assume
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.attribute.PosixFilePermission
import java.nio.file.attribute.PosixFilePermissions

class DefaultConfigFileWriterTest {

    @Rule
    @JvmField
    val folderRule = TemporaryFolder()

    @Test
    fun canCreateCredentialsFileTemplateWithAppropriatePermissions() {
        val baseFolder = folderRule.newFolder()
        val file = Paths.get(baseFolder.absolutePath, ".aws", "credentials").toFile()
        val sut = DefaultConfigFileWriter
        sut.createFile(file)

        assertThat(file).exists().hasContent(DefaultConfigFileWriter.TEMPLATE)

        assumeNoException<UnsupportedOperationException> {
            assertThat(Files.getPosixFilePermissions(file.toPath())).matches("rw-------")
            assertThat(Files.getPosixFilePermissions(file.parentFile.toPath())).matches("rwxr-xr-x")
        }
    }

    @Test
    fun existingFolderPermissionsAreNotModified() {
        val baseFolder = folderRule.newFolder()
        baseFolder.setExecutable(true, false)
        baseFolder.setWritable(true, false)
        baseFolder.setReadable(true, false)
        val file = Paths.get(baseFolder.absolutePath, "credentials").toFile()

        val sut = DefaultConfigFileWriter
        sut.createFile(file)

        assumeNoException<UnsupportedOperationException> {
            assertThat(Files.getPosixFilePermissions(file.toPath())).matches("rw-------")
            assertThat(Files.getPosixFilePermissions(file.parentFile.toPath())).matches("rwxrwxrwx")
        }
    }

    private fun IterableAssert<PosixFilePermission>.matches(permissionString: String) {
        containsOnly(*PosixFilePermissions.fromString(permissionString).toTypedArray())
    }

    private inline fun <reified T> assumeNoException(block: () -> Unit) {
        try {
            block()
        } catch (e: Exception) {
            if (e is T) {
                Assume.assumeNoException(e)
            } else {
                throw e
            }
        }
    }
}


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

package software.aws.toolkits.jetbrains.core.credentials

import com.intellij.openapi.application.ApplicationManager
import com.intellij.testFramework.ApplicationRule
import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.replaceService
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.mockConstruction
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doNothing
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.ssooidc.SsoOidcClient
import software.aws.toolkits.core.utils.test.aString
import software.aws.toolkits.jetbrains.core.MockClientManagerRule
import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState
import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener
import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider
import software.aws.toolkits.jetbrains.core.region.MockRegionProviderRule
import software.aws.toolkits.jetbrains.utils.isInstanceOf
import software.aws.toolkits.jetbrains.utils.isInstanceOfSatisfying

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

    @JvmField
    @Rule
    val projectRule = ProjectRule()

    @JvmField
    @Rule
    val regionProvider = MockRegionProviderRule()

    @JvmField
    @Rule
    val disposableRule = DisposableRule()

    @JvmField
    @Rule
    val mockClientManager = MockClientManagerRule()

    private lateinit var sut: DefaultToolkitAuthManager

    @Before
    fun setUp() {
        mockClientManager.create<SsoOidcClient>()
        sut = DefaultToolkitAuthManager()
    }

    @Test
    fun `creates ManagedBearerSsoConnection from ManagedSsoProfile`() {
        val profile = ManagedSsoProfile(
            "us-east-1",
            aString(),
            listOf(aString())
        )
        val connection = sut.createConnection(profile)

        assertThat(connection).isInstanceOf<ManagedBearerSsoConnection>()
        connection as ManagedBearerSsoConnection
        assertThat(connection.region).isEqualTo(profile.ssoRegion)
        assertThat(connection.startUrl).isEqualTo(profile.startUrl)
        assertThat(connection.scopes).isEqualTo(profile.scopes)
    }

    @Test
    fun `creates ManagedBearerSsoConnection from serialized ManagedSsoProfile`() {
        val profile = ManagedSsoProfile(
            "us-east-1",
            aString(),
            listOf(aString())
        )
        sut.createConnection(profile)

        assertThat(sut.state?.ssoProfiles).satisfies { profiles ->
            assertThat(profiles).isNotNull()
            assertThat(profiles).singleElement().isEqualTo(profile)
        }
    }

    @Test
    fun `serializes ManagedSsoProfile from ManagedBearerSsoConnection`() {
        val profile = ManagedSsoProfile(
            "us-east-1",
            aString(),
            listOf(aString())
        )

        sut.loadState(
            ToolkitAuthManagerState(
                ssoProfiles = listOf(profile)
            )
        )

        assertThat(sut.listConnections()).singleElement().satisfies {
            assertThat(it).isInstanceOfSatisfying<ManagedBearerSsoConnection> { connection ->
                assertThat(connection.region).isEqualTo(profile.ssoRegion)
                assertThat(connection.startUrl).isEqualTo(profile.startUrl)
                assertThat(connection.scopes).isEqualTo(profile.scopes)
            }
        }
    }

    @Test
    fun `loadState dedupes profiles`() {
        val profile = ManagedSsoProfile(
            "us-east-1",
            aString(),
            listOf(aString())
        )

        sut.loadState(
            ToolkitAuthManagerState(
                ssoProfiles = listOf(
                    profile,
                    profile,
                    profile
                )
            )
        )

        assertThat(sut.listConnections()).singleElement().satisfies {
            assertThat(it).isInstanceOfSatisfying<ManagedBearerSsoConnection> { connection ->
                assertThat(connection.region).isEqualTo(profile.ssoRegion)
                assertThat(connection.startUrl).isEqualTo(profile.startUrl)
                assertThat(connection.scopes).isEqualTo(profile.scopes)
            }
        }
    }

    @Test
    fun `loginSso with an working existing connection`() {
        val connectionManager: ToolkitConnectionManager = mock()
        regionProvider.addRegion(Region.US_EAST_1)
        projectRule.project.replaceService(ToolkitConnectionManager::class.java, connectionManager, disposableRule.disposable)
        ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, sut, disposableRule.disposable)

        mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ ->
            whenever(context.state()).thenReturn(BearerTokenAuthState.AUTHORIZED)
        }.use {
            val existingConnection = sut.createConnection(
                ManagedSsoProfile(
                    "us-east-1",
                    "foo",
                    emptyList()
                )
            )

            loginSso(projectRule.project, "foo", "us-east-1", emptyList())

            val tokenProvider = it.constructed()[0]
            verify(tokenProvider).state()
            verifyNoMoreInteractions(tokenProvider)
            verify(connectionManager).switchConnection(eq(existingConnection))
        }
    }

    @Test
    fun `loginSso with an existing connection but expired and refresh token is valid, should refreshToken`() {
        val connectionManager: ToolkitConnectionManager = mock()
        regionProvider.addRegion(Region.US_EAST_1)
        projectRule.project.replaceService(ToolkitConnectionManager::class.java, connectionManager, disposableRule.disposable)
        ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, sut, disposableRule.disposable)

        mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ ->
            whenever(context.id).thenReturn("id")
            whenever(context.state()).thenReturn(BearerTokenAuthState.NEEDS_REFRESH)
        }.use {
            val existingConnection = sut.createConnection(
                ManagedSsoProfile(
                    "us-east-1",
                    "foo",
                    emptyList()
                )
            )

            loginSso(projectRule.project, "foo", "us-east-1", emptyList())

            val tokenProvider = it.constructed()[0]
            verify(tokenProvider).resolveToken()
            verify(connectionManager).switchConnection(eq(existingConnection))
        }
    }

    @Test
    fun `loginSso with an existing connection that token is invalid and there's no refresh token, should re-authenticate`() {
        val connectionManager: ToolkitConnectionManager = mock()
        regionProvider.addRegion(Region.US_EAST_1)
        projectRule.project.replaceService(ToolkitConnectionManager::class.java, connectionManager, disposableRule.disposable)
        ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, sut, disposableRule.disposable)

        mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ ->
            whenever(context.state()).thenReturn(BearerTokenAuthState.NOT_AUTHENTICATED)
        }.use {
            val existingConnection = sut.createConnection(
                ManagedSsoProfile(
                    "us-east-1",
                    "foo",
                    emptyList()
                )
            )

            loginSso(projectRule.project, "foo", "us-east-1", emptyList())

            val tokenProvider = it.constructed()[0]
            verify(tokenProvider).reauthenticate()
            verify(connectionManager).switchConnection(eq(existingConnection))
        }
    }

    @Test
    fun `loginSso reuses connection if requested scopes are subset of existing`() {
        val connectionManager: ToolkitConnectionManager = mock()
        regionProvider.addRegion(Region.US_EAST_1)
        projectRule.project.replaceService(ToolkitConnectionManager::class.java, connectionManager, disposableRule.disposable)
        ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, sut, disposableRule.disposable)

        mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ ->
            whenever(context.state()).thenReturn(BearerTokenAuthState.AUTHORIZED)
        }.use {
            val existingConnection = sut.createConnection(
                ManagedSsoProfile(
                    "us-east-1",
                    "foo",
                    listOf("existing1", "existing2", "existing3")
                )
            )

            loginSso(projectRule.project, "foo", "us-east-1", listOf("existing1"))

            val tokenProvider = it.constructed()[0]
            verify(tokenProvider).state()
            verifyNoMoreInteractions(tokenProvider)
            verify(connectionManager).switchConnection(eq(existingConnection))
        }
    }

    @Test
    fun `loginSso forces reauth if requested scopes are not complete subset`() {
        val connectionManager: ToolkitConnectionManager = mock()
        regionProvider.addRegion(Region.US_EAST_1)
        projectRule.project.replaceService(ToolkitConnectionManager::class.java, connectionManager, disposableRule.disposable)
        ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, sut, disposableRule.disposable)

        mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ ->
            whenever(context.state()).thenReturn(BearerTokenAuthState.AUTHORIZED)
        }.use {
            val existingConnection = sut.createConnection(
                ManagedSsoProfile(
                    "us-east-1",
                    "foo",
                    listOf("existing1", "existing2", "existing3")
                )
            )

            val newScopes = listOf("existing1", "new1")
            loginSso(projectRule.project, "foo", "us-east-1", newScopes)

            val captor = argumentCaptor<ManagedBearerSsoConnection>()

            verify(connectionManager, times(2)).switchConnection(captor.capture())

            assertThat(captor.secondValue).satisfies { connection ->
                assertThat(connection.scopes.toSet()).isEqualTo(setOf("existing1", "existing2", "existing3", "new1"))
            }
            assertThat(sut.listConnections()).singleElement().isInstanceOfSatisfying<BearerSsoConnection> { connection ->
                assertThat(connection).usingRecursiveComparison().isNotEqualTo(existingConnection)
                assertThat(connection.scopes.toSet()).isEqualTo(setOf("existing1", "existing2", "existing3", "new1"))
            }
        }
    }

    @Test
    fun `loginSso with a new connection`() {
        val connectionManager: ToolkitConnectionManager = mock()
        ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, sut, disposableRule.disposable)
        projectRule.project.replaceService(ToolkitConnectionManager::class.java, connectionManager, disposableRule.disposable)

        mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ ->
            doNothing().whenever(context).reauthenticate()
            whenever(context.state()).thenReturn(BearerTokenAuthState.NOT_AUTHENTICATED)
        }.use {
            // before
            assertThat(sut.listConnections()).hasSize(0)

            loginSso(projectRule.project, "foo", "us-east-1", listOf("scope1", "scope2"))

            // after
            assertThat(sut.listConnections()).hasSize(1)
            verify(connectionManager).switchConnection(any())

            val expectedConnection = ManagedBearerSsoConnection(
                "foo",
                "us-east-1",
                listOf("scope1", "scope2")
            )

            sut.listConnections()[0].let { conn ->
                assertThat(conn.getConnectionSettings())
                    .usingRecursiveComparison()
                    .isEqualTo(expectedConnection.getConnectionSettings())
                assertThat(conn.id).isEqualTo(expectedConnection.id)
                assertThat(conn.label).isEqualTo(expectedConnection.label)
            }
        }
    }

    @Test
    fun `logoutFromConnection should invalidate the token provider and the connection and invoke callback`() {
        regionProvider.addRegion(Region.US_EAST_1)
        val connectionManager: ToolkitConnectionManager = mock()
        projectRule.project.replaceService(ToolkitConnectionManager::class.java, connectionManager, disposableRule.disposable)
        val authManager: ToolkitAuthManager = mock()
        ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, authManager, disposableRule.disposable)

        val connection = ManagedBearerSsoConnection("startUrl000", "us-east-1", listOf())

        var messageReceived = 0
        var callbackInvoked = 0
        ApplicationManager.getApplication().messageBus.connect().subscribe(
            BearerTokenProviderListener.TOPIC,
            object : BearerTokenProviderListener {
                override fun invalidate(providerId: String) {
                    if (providerId == "sso;us-east-1;startUrl000") {
                        messageReceived += 1
                    }
                }
            }
        )

        logoutFromSsoConnection(projectRule.project, connection) { callbackInvoked += 1 }
        assertThat(messageReceived).isEqualTo(1)
        assertThat(callbackInvoked).isEqualTo(1)
        verify(authManager).deleteConnection(eq("sso;us-east-1;startUrl000"))
        verify(connectionManager).switchConnection(eq(null))
    }
}


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

package software.aws.toolkits.jetbrains.core.credentials

import com.intellij.openapi.application.ApplicationManager
import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.replaceService
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.doReturn
import org.mockito.kotlin.mock
import software.amazon.awssdk.services.ssooidc.SsoOidcClient
import software.aws.toolkits.core.utils.test.aString
import software.aws.toolkits.jetbrains.core.MockClientManagerRule
import software.aws.toolkits.jetbrains.core.credentials.pinning.ConnectionPinningManager
import software.aws.toolkits.jetbrains.core.credentials.pinning.FeatureWithPinnedConnection
import software.aws.toolkits.jetbrains.utils.isInstanceOf

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

    @JvmField
    @Rule
    val credManager = MockCredentialManagerRule()

    @JvmField
    @Rule
    val authManager = MockToolkitAuthManagerRule()

    @JvmField
    @Rule
    val disposableRule = DisposableRule()

    @JvmField
    @Rule
    val mockClientManager = MockClientManagerRule()

    private lateinit var sut: DefaultToolkitConnectionManager

    @Before
    fun setUp() {
        mockClientManager.create<SsoOidcClient>()
        sut = DefaultToolkitConnectionManager(projectRule.project)
    }

    @Test
    fun `active connection is null if no connection or credentials`() {
        credManager.clear()
        assertThat(sut.activeConnection()).isNull()
    }

    @Test
    fun `active connection defaults to credentials`() {
        assertThat(sut.activeConnection()).isInstanceOf<AwsConnectionManagerConnection>()
    }

    @Test
    fun `loads connection from state`() {
        credManager.clear()
        assertThat(sut.activeConnection()).isEqualTo(null)

        val connection = authManager.createConnection(ManagedSsoProfile("us-east-1", aString(), emptyList()))
        sut.loadState(ToolkitConnectionManagerState(connection.id))

        assertThat(sut.activeConnection()).isEqualTo(connection)
    }

    @Test
    fun `loads a us-east-1 connection from state that does not contain the region string`() {
        credManager.clear()
        assertThat(sut.activeConnection()).isEqualTo(null)

        val connection = authManager.createConnection(ManagedSsoProfile("us-east-1", aString(), emptyList()))
        sut.loadState(ToolkitConnectionManagerState("sso;https://view.awsapps.com/start"))

        assertThat(sut.activeConnection()).isEqualTo(connection)
    }

    @Test
    fun `loads null connection from state which has an invalid format`() {
        credManager.clear()
        assertThat(sut.activeConnection()).isEqualTo(null)

        sut.loadState(ToolkitConnectionManagerState("An invalid active connection id"))

        assertThat(sut.activeConnection()).isEqualTo(null)
    }

    @Test
    fun `switch connection to null will fall back to IAM credential if applicable`() {
        val bearerConnection = ManagedBearerSsoConnection(aString(), "us-east-1", emptyList())
        configureSut(sut, bearerConnection)

        sut.switchConnection(null)

        assertThat(sut.activeConnection()).isInstanceOf<AwsConnectionManagerConnection>()
    }

    @Test
    fun `switch connection to null will fall back to the first SSO connection in the list if IAM credential is not available`() {
        credManager.clear()
        val conneciton1 = authManager.createConnection(ManagedSsoProfile("us-east-1", aString(), emptyList()))
        authManager.createConnection(ManagedSsoProfile("us-east-1", aString(), emptyList()))
        authManager.createConnection(ManagedSsoProfile("us-east-1", aString(), emptyList()))
        configureSut(sut, conneciton1)

        sut.switchConnection(null)

        assertThat(sut.activeConnection())
            .isEqualTo(ToolkitAuthManager.getInstance().listConnections()[0])
            .isInstanceOf<AwsBearerTokenConnection>()
    }

    @Test
    fun `activeConnectionForFeature falls back to default if not pinned`() {
        credManager.clear()
        configureSut(sut, null)
        val pinningMock = mock<ConnectionPinningManager>()
        val feature = mock<FeatureWithPinnedConnection> {
            on { it.supportsConnectionType(any()) } doReturn true
            on { it.featureId } doReturn "test"
        }

        ApplicationManager.getApplication().replaceService(ConnectionPinningManager::class.java, pinningMock, disposableRule.disposable)
        assertThat(sut.activeConnectionForFeature(feature)).isNull()

        val connection = authManager.createConnection(ManagedSsoProfile("us-east-1", aString(), emptyList()))
        assertThat(sut.activeConnectionForFeature(feature)).isEqualTo(connection)
    }

    private fun configureSut(sut: ToolkitConnectionManager, conneciton1: ToolkitConnection?) {
        val clazz = sut::class.java
        clazz.getDeclaredField("connection").apply {
            this.trySetAccessible()
            this.set(sut, conneciton1)
        }
    }
}


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

package software.aws.toolkits.jetbrains.core.credentials

import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.testFramework.ProjectRule
import org.junit.rules.ExternalResource
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider
import software.aws.toolkits.core.credentials.CredentialIdentifier
import software.aws.toolkits.core.credentials.ToolkitCredentialsProvider
import software.aws.toolkits.core.region.AwsRegion
import software.aws.toolkits.jetbrains.core.region.AwsRegionProvider
import software.aws.toolkits.jetbrains.utils.rules.CodeInsightTestFixtureRule
import software.aws.toolkits.jetbrains.utils.spinUntil
import java.time.Duration

class MockAwsConnectionManager(project: Project) : AwsConnectionManager(project) {
    init {
        reset()
    }

    fun reset() {
        recentlyUsedRegions.clear()
        recentlyUsedProfiles.clear()
        val regionProvider = AwsRegionProvider.getInstance()
        @Suppress("DEPRECATION")
        changeConnectionSettings(DUMMY_PROVIDER_IDENTIFIER, regionProvider.defaultRegion())

        waitUntilConnectionStateIsStable()
    }

    fun changeRegionAndWait(region: AwsRegion) {
        changeRegion(region)
        waitUntilConnectionStateIsStable()
    }

    fun changeCredentialProviderAndWait(identifier: CredentialIdentifier) {
        changeCredentialProvider(identifier)
        waitUntilConnectionStateIsStable()
    }

    fun nullifyCredentialProviderAndWait() {
        changeConnectionSettings(null, selectedRegion)
        waitUntilConnectionStateIsStable()
    }

    fun setConnectionState(state: ConnectionState) {
        connectionState = state
    }

    fun addRecentRegion(region: AwsRegion) {
        recentlyUsedRegions.add(region.id)
    }

    fun clearRecentRegions() {
        recentlyUsedRegions.clear()
    }

    fun addRecentCredentials(identifier: CredentialIdentifier) {
        recentlyUsedProfiles.add(identifier.id)
    }

    fun clearRecentCredentials() {
        recentlyUsedProfiles.clear()
    }

    override fun validate(credentialsProvider: ToolkitCredentialsProvider, region: AwsRegion) {}

    companion object {
        fun getInstance(project: Project): MockAwsConnectionManager = project.service<AwsConnectionManager>() as MockAwsConnectionManager
    }

    class ProjectAccountSettingsManagerRule private constructor(projectSupplier: () -> Project) : ExternalResource() {
        constructor(projectRule: ProjectRule) : this({ projectRule.project })
        constructor(projectRule: CodeInsightTestFixtureRule) : this({ projectRule.project })

        private val settingsManagerDelegate = lazy {
            getInstance(projectSupplier())
        }

        val settingsManager: MockAwsConnectionManager
            get() = settingsManagerDelegate.value

        override fun before() {
            if (settingsManagerDelegate.isInitialized()) {
                settingsManager.reset()
            }
        }

        override fun after() {
            if (settingsManagerDelegate.isInitialized()) {
                settingsManager.reset()
            }
        }
    }
}

fun <T> runUnderRealCredentials(project: Project, block: () -> T): T {
    val credentials = DefaultCredentialsProvider.create().resolveCredentials()

    val manager = MockAwsConnectionManager.getInstance(project)
    val credentialsManager = MockCredentialsManager.getInstance()
    val oldActive = manager.connectionSettings()?.credentials
    val realCredentialsProvider = credentialsManager.addCredentials("RealCredentials", credentials)
    try {
        println("Running using real credentials")

        manager.changeCredentialProviderAndWait(realCredentialsProvider)

        return block.invoke()
    } finally {
        oldActive?.let {
            credentialsManager.getCredentialIdentifierById(oldActive.id)?.let {
                manager.changeCredentialProviderAndWait(it)
            }
        }
        credentialsManager.removeCredentials(realCredentialsProvider)
    }
}

fun AwsConnectionManager.waitUntilConnectionStateIsStable() = spinUntil(Duration.ofSeconds(10)) {
    connectionState.isTerminal
}


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

package software.aws.toolkits.jetbrains.core.credentials

import com.intellij.openapi.components.service
import com.intellij.testFramework.ApplicationRule
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
import software.amazon.awssdk.auth.credentials.AwsCredentials
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
import software.aws.toolkits.core.credentials.CredentialIdentifier
import software.aws.toolkits.core.credentials.CredentialIdentifierBase
import software.aws.toolkits.core.credentials.CredentialProviderFactory
import software.aws.toolkits.core.credentials.CredentialSourceId
import software.aws.toolkits.core.credentials.CredentialsChangeListener
import software.aws.toolkits.core.credentials.ToolkitCredentialsProvider
import software.aws.toolkits.core.region.AwsRegion
import software.aws.toolkits.core.utils.test.aString
import software.aws.toolkits.jetbrains.core.region.getDefaultRegion
import software.aws.toolkits.jetbrains.utils.rules.ClearableLazy

@Deprecated("Use MockCredentialManagerRule")
class MockCredentialsManager : CredentialManager() {
    init {
        reset()
    }

    @Suppress("DEPRECATION")
    fun reset() {
        getCredentialIdentifiers().filterNot { it.id == DUMMY_PROVIDER_IDENTIFIER.id }.forEach { removeProvider(it) }

        addProvider(DUMMY_PROVIDER_IDENTIFIER)
    }

    fun addCredentials(
        id: String,
        credentials: AwsCredentials = AwsBasicCredentials.create("Access", "Secret"),
        regionId: String? = null
    ): CredentialIdentifier = addCredentials(id, StaticCredentialsProvider.create(credentials), regionId)

    fun addCredentials(
        id: String,
        credentials: AwsCredentialsProvider,
        regionId: String? = null
    ): MockCredentialIdentifier = MockCredentialIdentifier(id, credentials, regionId).also {
        addProvider(it)
    }

    fun createCredentialProvider(
        id: String = aString(),
        credentials: AwsCredentials,
        region: AwsRegion
    ): ToolkitCredentialsProvider {
        val credentialIdentifier = MockCredentialIdentifier(id, StaticCredentialsProvider.create(credentials), null)

        addProvider(credentialIdentifier)

        return getAwsCredentialProvider(credentialIdentifier, region)
    }

    fun removeCredentials(credentialIdentifier: CredentialIdentifier) {
        removeProvider(credentialIdentifier)
    }

    override fun factoryMapping(): Map<String, CredentialProviderFactory> =
        mapOf<String, CredentialProviderFactory>(MockCredentialProviderFactory.id to MockCredentialProviderFactory)

    companion object {
        @Suppress("DEPRECATION")
        fun getInstance(): MockCredentialsManager = service<CredentialManager>() as MockCredentialsManager
    }

    class MockCredentialIdentifier(override val displayName: String, val credentials: AwsCredentialsProvider, override val defaultRegionId: String?) :
        CredentialIdentifierBase(null) {
        override val id: String = displayName
        override val factoryId: String = "mockCredentialProviderFactory"
    }

    private object MockCredentialProviderFactory : CredentialProviderFactory {
        override val id: String = "mockCredentialProviderFactory"
        override val credentialSourceId: CredentialSourceId = CredentialSourceId.SharedCredentials

        override fun setUp(credentialLoadCallback: CredentialsChangeListener) {}

        override fun createAwsCredentialProvider(
            providerId: CredentialIdentifier,
            region: AwsRegion
        ): ToolkitCredentialsProvider = ToolkitCredentialsProvider(providerId, (providerId as MockCredentialIdentifier).credentials)
    }
}

@Suppress("DEPRECATION")
class MockCredentialManagerRule : ApplicationRule() {
    private val lazyCredentialManager = ClearableLazy {
        MockCredentialsManager.getInstance()
    }

    private val credentialManager: MockCredentialsManager
        get() = lazyCredentialManager.value

    fun addCredentials(
        id: String = aString(),
        credentials: AwsCredentials = AwsBasicCredentials.create("Access", "Secret"),
        region: AwsRegion? = null
    ): CredentialIdentifier = credentialManager.addCredentials(id, credentials, region?.id)

    fun addCredentials(
        id: String,
        credentials: AwsCredentialsProvider,
        regionId: String? = null
    ): MockCredentialsManager.MockCredentialIdentifier = credentialManager.addCredentials(id, credentials, regionId)

    fun createCredentialProvider(
        id: String = aString(),
        credentials: AwsCredentials = AwsBasicCredentials.create("Access", "Secret"),
        // Do not store this value as we should be able to dynamically change it
        region: AwsRegion = getDefaultRegion()
    ): ToolkitCredentialsProvider = credentialManager.createCredentialProvider(id, credentials, region)

    fun getAwsCredentialProvider(providerId: CredentialIdentifier, region: AwsRegion): ToolkitCredentialsProvider =
        credentialManager.getAwsCredentialProvider(providerId, region)

    fun removeCredentials(credentialIdentifier: CredentialIdentifier) = credentialManager.removeCredentials(credentialIdentifier)

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

    fun clear() {
        reset()
        credentialManager.removeCredentials(DUMMY_PROVIDER_IDENTIFIER)
    }

    fun reset() {
        credentialManager.reset()
    }
}

@Deprecated(
    "DUMMY_PROVIDER_IDENTIFIER should not be used outside of the MockCredentialsManager, if you " +
        "need mock credentials set them up in the test instead of relying on the global one"
)
@Suppress("DEPRECATION")
val DUMMY_PROVIDER_IDENTIFIER: CredentialIdentifier = MockCredentialsManager.MockCredentialIdentifier(
    "DUMMY_CREDENTIALS",
    StaticCredentialsProvider.create(AwsBasicCredentials.create("DummyAccess", "DummySecret")),
    null
)


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

package software.aws.toolkits.jetbrains.core.credentials

import software.aws.toolkits.core.credentials.CredentialIdentifier
import software.aws.toolkits.core.region.AwsRegion

class MockCredentialsRegionHandler : CredentialsRegionHandler {
    override fun determineSelectedRegion(identifier: CredentialIdentifier, selectedRegion: AwsRegion?): AwsRegion? = selectedRegion
}


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

package software.aws.toolkits.jetbrains.core.credentials

import com.intellij.testFramework.ApplicationRule
import software.aws.toolkits.jetbrains.utils.rules.ClearableLazy

class MockToolkitAuthManagerRule : ApplicationRule() {
    private val lazyAuthManager = ClearableLazy {
        ToolkitAuthManager.getInstance()
    }

    private val authManager
        get() = lazyAuthManager.value

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

    fun reset() {
        authManager.listConnections().forEach {
            authManager.deleteConnection(it)
        }
    }

    fun createConnection(profile: AuthProfile) = authManager.createConnection(profile)
}


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

package software.aws.toolkits.jetbrains.core.credentials

import com.intellij.openapi.actionSystem.DataContext
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.TestActionEvent
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import software.aws.toolkits.core.credentials.aToolkitCredentialsProvider
import software.aws.toolkits.core.region.anAwsRegion
import software.aws.toolkits.core.utils.test.aString
import software.aws.toolkits.jetbrains.core.AwsResourceCacheTest.Companion.dummyResource
import software.aws.toolkits.jetbrains.core.MockResourceCacheRule
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

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

    @JvmField
    @Rule
    val resourceCache = MockResourceCacheRule()

    private val sut = RefreshConnectionAction()

    @Test
    fun refreshActionClearsCacheAndUpdatesConnectionState() {
        resourceCache.addEntry(projectRule.project, dummyResource(), aString())

        val latch = CountDownLatch(1)
        val states = ConcurrentHashMap.newKeySet<ConnectionState>()
        projectRule.project.messageBus.connect()
            .subscribe(
                AwsConnectionManager.CONNECTION_SETTINGS_STATE_CHANGED,
                object : ConnectionSettingsStateChangeNotifier {
                    override fun settingsStateChanged(newState: ConnectionState) {
                        states.add(newState)
                        latch.countDown()
                    }
                }
            )

        sut.actionPerformed(testAction())

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

        assertThat(resourceCache.size()).isZero()
        assertThat(states).hasAtLeastOneElementOfType(ConnectionState.ValidatingConnection::class.java)
    }

    @Test
    fun actionIsEnabledIfConnectionStateIsInvalid() {
        checkExpectedActionState(ConnectionState.InvalidConnection(RuntimeException("Boom")), shouldBeEnabled = true)
    }

    @Test
    fun actionIsEnabledIfConnectionStateIsValid() {
        checkExpectedActionState(ConnectionState.ValidConnection(aToolkitCredentialsProvider(), anAwsRegion()), shouldBeEnabled = true)
    }

    @Test
    fun actionIsDisabledIfConnectionStateIsIncomplete() {
        checkExpectedActionState(ConnectionState.IncompleteConfiguration(null, null), shouldBeEnabled = false)
    }

    @Test
    fun actionIsDisabledIfConnectionStateIsNotTerminal() {
        checkExpectedActionState(ConnectionState.InitializingToolkit, shouldBeEnabled = false)
    }

    private fun checkExpectedActionState(connectionState: ConnectionState, shouldBeEnabled: Boolean) {
        val mockProjectAccountSettingsManager = MockAwsConnectionManager.getInstance(projectRule.project)

        mockProjectAccountSettingsManager.setConnectionState(connectionState)

        val actionEvent = testAction()
        sut.update(actionEvent)

        assertThat(actionEvent.presentation.isEnabled).isEqualTo(shouldBeEnabled)
    }

    private fun testAction() = TestActionEvent(DataContext { projectRule.project })
}


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

package software.aws.toolkits.jetbrains.core.credentials

import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.Presentation
import com.intellij.testFramework.ApplicationRule
import com.intellij.testFramework.TestActionEvent
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import software.aws.toolkits.core.credentials.CredentialIdentifier
import software.aws.toolkits.core.credentials.aCredentialsIdentifier
import software.aws.toolkits.core.region.AwsRegion
import software.aws.toolkits.core.region.anAwsRegion

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

    private val dummyRegion = anAwsRegion()
    private val dummyCredential = aCredentialsIdentifier()

    @Test
    fun `respects updates to regions`() {
        val testSelector = TestSelector(ChangeSettingsMode.REGIONS)
        val comboBox = SettingsSelectorComboBoxAction(testSelector)

        var presentation = comboBox.updatePresentation()
        assertThat(presentation.text).isEqualTo(testSelector.displayValue())
        assertThat(presentation.description).isEqualTo(testSelector.tooltip())

        testSelector.currentRegion = dummyRegion

        presentation = comboBox.updatePresentation()
        assertThat(presentation.text).isEqualTo(testSelector.displayValue())
        assertThat(presentation.description).isEqualTo(testSelector.tooltip())
    }

    @Test
    fun `respects updates to credentials`() {
        val testSelector = TestSelector(ChangeSettingsMode.CREDENTIALS)
        val comboBox = SettingsSelectorComboBoxAction(testSelector)

        var presentation = comboBox.updatePresentation()
        assertThat(presentation.text).isEqualTo(testSelector.displayValue())
        assertThat(presentation.description).isEqualTo(testSelector.tooltip())

        testSelector.currentCredentials = dummyCredential

        presentation = comboBox.updatePresentation()
        assertThat(presentation.text).isEqualTo(testSelector.displayValue())
        assertThat(presentation.description).isEqualTo(testSelector.tooltip())
    }

    private fun AnAction.updatePresentation(): Presentation = TestActionEvent().also { this.update(it) }.presentation

    class TestSelector(menuMode: ChangeSettingsMode) : SettingsSelectorLogicBase(menuMode) {
        var currentRegion: AwsRegion? = null
        var currentCredentials: CredentialIdentifier? = null

        override fun currentRegion(): AwsRegion? = currentRegion

        override fun onRegionChange(region: AwsRegion) {}

        override fun currentCredentials(): CredentialIdentifier? = currentCredentials

        override fun onCredentialChange(identifier: CredentialIdentifier) {}
    }
}


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

package software.aws.toolkits.jetbrains.core.credentials

import com.intellij.testFramework.ApplicationRule
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 software.aws.toolkits.core.credentials.CredentialIdentifier
import software.aws.toolkits.core.region.AwsRegion
import software.aws.toolkits.jetbrains.core.credentials.ConnectionSettingsMenuBuilder.SwitchCredentialsAction
import software.aws.toolkits.jetbrains.core.credentials.ConnectionSettingsMenuBuilder.SwitchRegionAction
import software.aws.toolkits.jetbrains.core.region.MockRegionProviderRule
import software.aws.toolkits.resources.message

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

    @Rule
    @JvmField
    val regionProviderRule = MockRegionProviderRule()

    @Rule
    @JvmField
    val credentialManagerRule = MockCredentialManagerRule()

    private lateinit var testRegion: AwsRegion
    private lateinit var testCredentials: CredentialIdentifier

    @Before
    fun setUp() {
        testRegion = regionProviderRule.createAwsRegion()
        testCredentials = credentialManagerRule.addCredentials()
    }

    @Test
    fun `text is correct in region only mode`() {
        val selector = TestSelector(ChangeSettingsMode.REGIONS)

        assertThat(selector.displayValue()).isEqualTo(message("settings.regions.none_selected"))
        assertThat(selector.tooltip()).isNull()

        selector.changeRegion(testRegion)

        assertThat(selector.displayValue()).isEqualTo(testRegion.id)
        assertThat(selector.tooltip()).isEqualTo(testRegion.displayName)
    }

    @Test
    fun `text is correct in credential only mode`() {
        val selector = TestSelector(ChangeSettingsMode.CREDENTIALS)

        assertThat(selector.displayValue()).isEqualTo(message("settings.credentials.none_selected"))
        assertThat(selector.tooltip()).isNull()

        selector.changeCredentials(testCredentials)

        assertThat(selector.displayValue()).isEqualTo(testCredentials.shortName)
        assertThat(selector.tooltip()).isEqualTo(testCredentials.displayName)
    }

    @Test
    fun `text is correct in both mode with none set`() {
        val selector = TestSelector(ChangeSettingsMode.BOTH)
        assertThat(selector.displayValue()).isEqualTo(message("settings.credentials.none_selected") + "@" + message("settings.regions.none_selected"))
        assertThat(selector.tooltip()).isNull()
    }

    @Test
    fun `text is correct in both mode with only credentials set`() {
        val selector = TestSelector(ChangeSettingsMode.BOTH)
        selector.changeCredentials(testCredentials)

        assertThat(selector.displayValue()).isEqualTo(testCredentials.shortName + "@" + message("settings.regions.none_selected"))
        assertThat(selector.tooltip()).isNull()
    }

    @Test
    fun `text is correct in both mode with only region set`() {
        val selector = TestSelector(ChangeSettingsMode.BOTH)
        selector.changeRegion(testRegion)

        assertThat(selector.displayValue()).isEqualTo(message("settings.credentials.none_selected") + "@" + testRegion.id)
        assertThat(selector.tooltip()).isNull()
    }

    @Test
    fun `text is correct in both mode with both set`() {
        val selector = TestSelector(ChangeSettingsMode.BOTH)

        selector.changeCredentials(testCredentials)
        selector.changeRegion(testRegion)

        assertThat(selector.displayValue()).isEqualTo(testCredentials.shortName + "@" + testRegion.id)
        assertThat(selector.tooltip()).isNull()
    }

    @Test
    fun `listeners get invoked on region change`() {
        var hit = false
        val selector = TestSelector(ChangeSettingsMode.BOTH)
        selector.addChangeListener { hit = true }

        selector.changeRegion(testRegion)

        assertThat(hit).isTrue
    }

    @Test
    fun `listeners get invoked on credential change`() {
        var hit = false
        val selector = TestSelector(ChangeSettingsMode.BOTH)
        selector.addChangeListener { hit = true }

        selector.changeCredentials(testCredentials)

        assertThat(hit).isTrue
    }

    class TestSelector(menuMode: ChangeSettingsMode) : SettingsSelectorLogicBase(menuMode) {
        private var currentRegion: AwsRegion? = null
        private var currentCredentials: CredentialIdentifier? = null

        override fun currentRegion(): AwsRegion? = currentRegion
        override fun currentCredentials(): CredentialIdentifier? = currentCredentials

        override fun onRegionChange(region: AwsRegion) {
            currentRegion = region
        }

        override fun onCredentialChange(identifier: CredentialIdentifier) {
            currentCredentials = identifier
        }

        fun changeRegion(region: AwsRegion) = selectionMenuActions()
            .getChildren(TestActionEvent())
            .filterIsInstance<SwitchRegionAction>()
            .filter { it.value == region }
            .forEach { it.actionPerformed(TestActionEvent()) }

        fun changeCredentials(identifier: CredentialIdentifier) = selectionMenuActions()
            .getChildren(TestActionEvent())
            .filterIsInstance<SwitchCredentialsAction>()
            .filter { it.value == identifier }
            .forEach { it.actionPerformed(TestActionEvent()) }
    }
}


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

package software.aws.toolkits.jetbrains.core.credentials

import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.registry.Registry
import com.intellij.testFramework.ProjectRule
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.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
import org.mockito.kotlin.reset
import org.mockito.kotlin.stub
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials
import java.io.File
import java.time.Duration
import java.time.Instant
import kotlin.time.ExperimentalTime
import kotlin.time.measureTime
import kotlin.time.toJavaDuration

class ToolkitCredentialProcessProviderTest {
    @Rule
    @JvmField
    val project = ProjectRule()

    @Rule
    @JvmField
    val folder = TemporaryFolder()

    private val parser = mock<CredentialProcessOutputParser>()

    @Before
    fun setup() {
        reset(parser)
        Registry.get("aws.credentialProcess.timeout").resetToDefault()
    }

    @Test
    fun `basic credential fetch`() {
        val blah = "echo"
        val sut = createSut(blah)

        stubParser(CredentialProcessOutput("foo", "bar", null, null))

        val credentials = sut.resolveCredentials()

        assertThat(credentials).isInstanceOfSatisfying(AwsBasicCredentials::class.java) {
            assertThat(it.accessKeyId()).isEqualTo("foo")
            assertThat(it.secretAccessKey()).isEqualTo("bar")
        }
    }

    @Test
    fun `session credentials fetch`() {
        val sut = createSut("echo")

        stubParser(CredentialProcessOutput("foo", "bar", "token", null))

        val credentials = sut.resolveCredentials()

        assertThat(credentials).isInstanceOfSatisfying(AwsSessionCredentials::class.java) {
            assertThat(it.accessKeyId()).isEqualTo("foo")
            assertThat(it.secretAccessKey()).isEqualTo("bar")
            assertThat(it.sessionToken()).isEqualTo("token")
        }
    }

    @Test
    fun `actually executes the command`() {
        val sut = createSut("echo hello")

        stubParser(CredentialProcessOutput("foo", "bar", null, null))

        sut.resolveCredentials()

        val captor = argumentCaptor<String>().apply {
            verify(parser).parse(capture())
        }

        assertThat(captor.firstValue).contains("hello")
    }

    @Test
    fun `expiry in the future means command is not re-run`() {
        val sut = createSut("echo")
        stubParser(CredentialProcessOutput("foo", "bar", null, Instant.now().plus(Duration.ofHours(1))))

        sut.resolveCredentials()
        sut.resolveCredentials()

        verify(parser).parse(any())
        verifyNoMoreInteractions(parser)
    }

    @Test
    fun `no expiry means command is not re-run`() {
        val sut = createSut("echo")
        stubParser(CredentialProcessOutput("foo", "bar", null, null))

        sut.resolveCredentials()
        sut.resolveCredentials()

        verify(parser).parse(any())
        verifyNoMoreInteractions(parser)
    }

    @Test
    fun `expiry in the past means command is not re-run`() {
        // Java SDK prefers threads to block when this happens https://github.com/aws/aws-sdk-java-v2/commit/5151e4049382bdb5ea6b487e6f150314b579181d
        val sut = createSut("echo")
        stubParser(CredentialProcessOutput("foo", "bar", null, Instant.now().minus(Duration.ofHours(1))))

        sut.resolveCredentials()
        sut.resolveCredentials()

        verify(parser).parse(any())
    }

    @Test
    fun `spaces in commands are handled`() {
        val cmd = if (SystemInfo.isWindows) {
            "dir"
        } else {
            "ls"
        }
        val folderWithSpaceInItsName = folder.newFolder("hello world")
        val file = File(folderWithSpaceInItsName, "foo")
        file.writeText("bar")

        val sut = createSut("""$cmd ${folder.root.absolutePath}${File.separator}"hello world"""")
        stubParser()

        sut.resolveCredentials()

        val captor = argumentCaptor<String>().apply {
            verify(parser).parse(capture())
        }

        assertThat(captor.firstValue).contains("foo")
    }

    @Test
    fun `handles non-zero exit codes appropriately`() {
        val cmd = if (SystemInfo.isWindows) {
            "dir"
        } else {
            "ls"
        }

        val sut = createSut("$cmd non-existing-folder")

        assertThatThrownBy { sut.resolveCredentials() }.hasMessageContaining("Failed to execute credential_process ($cmd)")
        verifyNoMoreInteractions(parser)
    }

    @Test
    fun `handles quoted commands`() {
        val cmd = if (SystemInfo.isWindows) {
            """
                "dir"
            """.trimIndent()
        } else {
            """
                ls
            """.trimIndent()
        }

        val folderWithSpaceInItsName = folder.newFolder("hello world")
        val file = File(folderWithSpaceInItsName, "foo")
        file.writeText("bar")

        val sut = createSut("""$cmd ${folder.root.absolutePath}${File.separator}"hello world"""")
        stubParser()

        sut.resolveCredentials()

        val captor = argumentCaptor<String>().apply {
            verify(parser).parse(capture())
        }

        assertThat(captor.firstValue).contains("foo")
    }

    @Test
    fun `has path`() {
        val cmd = if (SystemInfo.isWindows) {
            "SET"
        } else {
            "env"
        }
        val sut = createSut(cmd)
        stubParser()
        sut.resolveCredentials()

        val captor = argumentCaptor<String>().apply {
            verify(parser).parse(capture())
        }

        assertThat(captor.firstValue).contains("PATH")
    }

    @Test
    fun `can handle parse exception`() {
        assertThatThrownBy {
            ToolkitCredentialProcessProvider("echo hello").resolveCredentials()
        }.hasMessage("Failed to parse credential_process response")
    }

    @ExperimentalTime
    @Test
    fun `command times out after specified period`() {
        val cmd = if (SystemInfo.isWindows) {
            "ping -n 10 127.0.0.1"
        } else {
            "sleep 5"
        }
        val timeoutSetting = Registry.get("aws.credentialProcess.timeout")
        val timeout = timeoutSetting.asInteger().toLong()
        timeoutSetting.setValue(200)
        val time = measureTime {
            assertThatThrownBy { createSut(cmd).resolveCredentials() }.hasMessageContaining("timed out")
        }

        assertThat(time.toJavaDuration()).isLessThan(Duration.ofMillis(timeout))
    }

    private fun stubParser(output: CredentialProcessOutput = CredentialProcessOutput("foo", "bar", null, null)) {
        stub {
            on { parser.parse(any()) }.thenReturn(output)
        }
    }

    private fun createSut(cmd: String) = ToolkitCredentialProcessProvider(cmd, parser)
}