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

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

import com.intellij.testFramework.ProjectRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import software.amazon.awssdk.services.cloudformation.model.StackStatus
import software.amazon.awssdk.services.cloudformation.model.StackSummary
import software.aws.toolkits.jetbrains.core.MockResourceCacheRule
import software.aws.toolkits.jetbrains.core.explorer.nodes.AwsExplorerEmptyNode
import software.aws.toolkits.jetbrains.core.explorer.nodes.CloudFormationExplorerRootNode
import software.aws.toolkits.jetbrains.services.cloudformation.resources.CloudFormationResources
import java.util.concurrent.CompletableFuture

class CloudFormationServiceNodeTest {

    @JvmField
    @Rule
    val projectRule = ProjectRule()

    @JvmField
    @Rule
    val resourceCache = MockResourceCacheRule()

    @Test
    fun completedStacksAreShown() {
        stacksWithNames(listOf("Stack" to StackStatus.CREATE_COMPLETE))

        val node = CloudFormationServiceNode(projectRule.project, CF_EXPLORER_NODE)

        assertThat(node.children).singleElement().matches { it.displayName() == "Stack" }
    }

    @Test
    fun noStacksShowsEmptyNode() {
        stacksWithNames(emptyList())

        val node = CloudFormationServiceNode(projectRule.project, CF_EXPLORER_NODE)

        assertThat(node.children).singleElement().isInstanceOf(AwsExplorerEmptyNode::class.java)
    }

    private fun stacksWithNames(names: List<Pair<String, StackStatus>>) {
        resourceCache.addEntry(
            projectRule.project,
            CloudFormationResources.ACTIVE_STACKS,
            CompletableFuture.completedFuture(
                names.map {
                    StackSummary.builder()
                        .stackName(it.first)
                        .stackId(it.first)
                        .stackStatus(it.second)
                        .build()
                }
            )
        )
    }

    private companion object {
        val CF_EXPLORER_NODE = CloudFormationExplorerRootNode()
    }
}


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

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

import com.intellij.testFramework.runInEdtAndWait
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import software.aws.toolkits.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule
import software.aws.toolkits.jetbrains.utils.rules.openFile
import software.aws.toolkits.resources.message

class CloudFormationTemplateCanDeployTest {

    @Rule
    @JvmField
    val projectRule = JavaCodeInsightTestFixtureRule()

    @Test
    fun nonDeployable_emptyFile() {
        val virtualFile = projectRule.fixture.openFile("template.yaml", "")

        runInEdtAndWait {
            assertThat(projectRule.project.validateSamTemplateHasResources(virtualFile)).isEqualTo(
                message(
                    "serverless.application.deploy.error.no_resources",
                    virtualFile.path
                )
            )
        }
    }

    @Test
    fun nonDeployable_incompleteResources() {
        val virtualFile = projectRule.fixture.openFile(
            "template.yaml",
            """
Resources:
  ServerlessFunction:
"""
        )

        runInEdtAndWait {
            assertThat(projectRule.project.validateSamTemplateHasResources(virtualFile)).isEqualTo(
                message(
                    "serverless.application.deploy.error.no_resources",
                    virtualFile.path
                )
            )
        }
    }

    @Test
    fun deployable_validatableEnough() {
        val virtualFile = projectRule.fixture.openFile(
            "template.yaml",
            """
Resources:
  ServerlessFunction:
    Type: AWS::Serverless::Function
"""
        )

        runInEdtAndWait {
            assertThat(projectRule.project.validateSamTemplateHasResources(virtualFile)).isNull()
        }
    }
}


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

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

import com.intellij.testFramework.runInEdtAndWait
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import software.aws.toolkits.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule
import software.aws.toolkits.jetbrains.utils.rules.openFile

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

    @Test
    fun listFunctions_serverlessAndLambdaFunctions() {
        val fixture = projectRule.fixture

        fixture.openFile(
            "template.yaml",
            """
Resources:
  ServerlessFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: foo
      Handler: foo::foo
      Runtime: foo
      Timeout: 800

  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code: bar
      Handler: bar::bar
      Runtime: bar
"""
        )

        runInEdtAndWait {
            val functions = CloudFormationTemplateIndex.listFunctions(projectRule.project)
            assertThat(functions).hasSize(2)
            assertThat(functions.find { it.handler() == "foo::foo" }).isNotNull
            assertThat(functions.find { it.handler() == "bar::bar" }).isNotNull
        }
    }

    @Test
    fun listFunctions_serverlessFunction() {
        val fixture = projectRule.fixture

        fixture.openFile(
            "template.yaml",
            """
Resources:
  ServerlessFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: serverless
      Handler: serverless::foo
      Runtime: java8
"""
        )

        runInEdtAndWait {
            val functions = CloudFormationTemplateIndex.listFunctions(projectRule.project)
            assertThat(functions).hasSize(1)
            val indexedFunction = functions.toList()[0]
            assertThat(indexedFunction.handler()).isEqualTo("serverless::foo")
            assertThat(indexedFunction.runtime()).isEqualTo("java8")
        }
    }

    @Test
    fun listFunctions_lambdaFunction() {
        val fixture = projectRule.fixture

        fixture.openFile(
            "template.yaml",
            """
Resources:
  ServerlessFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code: lambda
      Handler: lambda::bar
      Runtime: java8
"""
        )

        runInEdtAndWait {
            val functions = CloudFormationTemplateIndex.listFunctions(projectRule.project)
            assertThat(functions).hasSize(1)
            val indexedFunction = functions.toList()[0]
            assertThat(indexedFunction.handler()).isEqualTo("lambda::bar")
            assertThat(indexedFunction.runtime()).isEqualTo("java8")
        }
    }

    @Test
    fun listFunctions_missingType() {
        val fixture = projectRule.fixture

        fixture.openFile(
            "template.yaml",
            """
Resources:
  ServerlessFunction:
    Type:
    Properties:
      CodeUri: target/HelloWorld-1.0.jar
      Handler: bar
      Runtime: java8
"""
        )

        runInEdtAndWait {
            val functions = CloudFormationTemplateIndex.listFunctions(projectRule.project)
            assertThat(functions).isEmpty()
        }
    }

    @Test
    fun listResources_nullType() {
        val fixture = projectRule.fixture

        fixture.openFile(
            "template.yaml",
            """
Resources:
  ServerlessFunction:
    Properties:
      CodeUri: target/HelloWorld-1.0.jar
      Handler: bar
      Runtime: java8
"""
        )

        runInEdtAndWait {
            val resources = CloudFormationTemplateIndex.listResources(projectRule.project)
            assertThat(resources).isEmpty()
        }
    }

    @Test
    fun nullHandlerAndRuntime() {
        val fixture = projectRule.fixture

        fixture.openFile(
            "template.yaml",
            """
Resources:
  ServerlessFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: target/HelloWorld-1.0.jar
"""
        )

        runInEdtAndWait {
            val functions = CloudFormationTemplateIndex.listFunctions(projectRule.project)
            assertThat(functions).hasSize(1)
            val indexedFunction = functions.toList()[0]
            assertThat(indexedFunction.handler()).isNull()
            assertThat(indexedFunction.runtime()).isNull()
        }
    }

    @Test
    fun handlerAndRuntimeInGlobals() {
        val fixture = projectRule.fixture

        fixture.openFile(
            "template.yaml",
            """
Globals:
    Function:
        Runtime: java8
        Handler: bar
Resources:
  ServerlessFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: target/HelloWorld-1.0.jar
"""
        )

        runInEdtAndWait {
            val functions = CloudFormationTemplateIndex.listFunctions(projectRule.project)
            assertThat(functions).hasSize(1)
            val indexedFunction = functions.toList()[0]
            assertThat(indexedFunction.handler()).isEqualTo("bar")
            assertThat(indexedFunction.runtime()).isEqualTo("java8")
        }
    }

    @Test
    fun onlyHandlerInGlobals() {
        val fixture = projectRule.fixture

        fixture.openFile(
            "template.yaml",
            """
Globals:
    Function:
        Handler: bar
Resources:
  ServerlessFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: target/HelloWorld-1.0.jar
"""
        )

        runInEdtAndWait {
            val functions = CloudFormationTemplateIndex.listFunctions(projectRule.project)
            assertThat(functions).hasSize(1)
            val indexedFunction = functions.toList()[0]
            assertThat(indexedFunction.handler()).isEqualTo("bar")
            assertThat(indexedFunction.runtime()).isNull()
        }
    }

    @Test
    fun localRuntimeOverridesGlobals() {
        val fixture = projectRule.fixture

        fixture.openFile(
            "template.yaml",
            """
Globals:
    Function:
        Runtime: python3.9
        Handler: bar
Resources:
  ServerlessFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: target/HelloWorld-1.0.jar
      Runtime: python3.9
"""
        )

        runInEdtAndWait {
            val functions = CloudFormationTemplateIndex.listFunctions(projectRule.project)
            assertThat(functions).hasSize(1)
            val indexedFunction = functions.toList()[0]
            assertThat(indexedFunction.handler()).isEqualTo("bar")
            assertThat(indexedFunction.runtime()).isEqualTo("python3.9")
        }
    }

    @Test
    fun emptyHandlerAndRuntime() {
        val fixture = projectRule.fixture

        fixture.openFile(
            "template.yaml",
            """
Resources:
  ServerlessFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: target/HelloWorld-1.0.jar
      Handler:
      Runtime:
"""
        )

        runInEdtAndWait {
            val functions = CloudFormationTemplateIndex.listFunctions(projectRule.project)
            assertThat(functions).hasSize(1)
            val indexedFunction = functions.toList()[0]
            assertThat(indexedFunction.handler()).isEmpty()
            assertThat(indexedFunction.runtime()).isEmpty()
        }
    }

    @Test
    fun listResourcesByType_simpleTable() {
        val fixture = projectRule.fixture

        fixture.openFile(
            "template.yaml",
            """
Resources:
  ServerlessFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: baz
      Handler: foo
      Runtime: bar
      Environment:
        Variables:
          TABLE_NAME: my-table

  DynamodbTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      TableName: my-table
      PrimaryKey:
        Name: id
        Type: String
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5
      Tags:
        Department: Engineering
        AppType: Serverless
      SSESpecification:
        SSEEnabled: true
"""
        )

        runInEdtAndWait {
            val resources = CloudFormationTemplateIndex.listResourcesByType(projectRule.project, "AWS::Serverless::SimpleTable")
            assertThat(resources).hasSize(1)
            val resource = resources.toList()[0]
            assertThat(resource.indexedProperties).isEmpty()
        }
    }

    @Test
    fun listResources_fromFile() {
        val fixture = projectRule.fixture

        val file1 = fixture.openFile(
            "template1.yaml",
            """
Resources:
  ServerlessFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: baz
      Handler: foo
      Runtime: bar
      Environment:
        Variables:
          TABLE_NAME: my-table
"""
        )
        val file2 = fixture.openFile(
            "template2.yaml",
            """
Resources:
  DynamodbTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      TableName: my-table
      PrimaryKey:
        Name: id
        Type: String
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5
      Tags:
        Department: Engineering
        AppType: Serverless
      SSESpecification:
        SSEEnabled: true
"""
        )

        runInEdtAndWait {
            val resources = CloudFormationTemplateIndex.listResources(projectRule.project).toList()
            val resources1 = CloudFormationTemplateIndex.listResources(projectRule.project, virtualFile = file1).toList()
            val resources2 = CloudFormationTemplateIndex.listResources(projectRule.project, virtualFile = file2).toList()

            assertThat(resources).hasSize(2)
            assertThat(resources1).hasSize(1)
            assertThat(resources2).hasSize(1)

            assertThat(resources1[0].type).isEqualTo("AWS::Serverless::Function")
            assertThat(resources2[0].type).isEqualTo("AWS::Serverless::SimpleTable")
        }
    }

    @Test
    fun invalidTemplateDoesntIndex() {
        val fixture = projectRule.fixture

        fixture.openFile(
            "template.yaml",
            """
            foo:
              bar
            ---
            hello:
              world:
            """.trimIndent()
        )

        runInEdtAndWait {
            val functions = CloudFormationTemplateIndex.listFunctions(projectRule.project)
            assertThat(functions).hasSize(0)
        }
    }

    @Test
    fun `supports BuildMethod in Metadata block`() {
        val fixture = projectRule.fixture

        fixture.openFile(
            "template.yaml",
            """
Resources:
  ServerlessFunction:
    Type: AWS::Lambda::Function
    Metadata:
      BuildMethod: makefile
    Properties:
      Code: lambda
      Handler: lambda::bar
      Runtime: java8
"""
        )

        runInEdtAndWait {
            val functions = CloudFormationTemplateIndex.listFunctions(projectRule.project)
            assertThat(functions).hasSize(1)
            val indexedFunction = functions.toList()[0]
            assertThat(indexedFunction.buildMethod()).isEqualTo("makefile")
        }
    }

    @Test
    fun `supports no Metadata block`() {
        val fixture = projectRule.fixture

        fixture.openFile(
            "template.yaml",
            """
Resources:
  ServerlessFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code: lambda
      Handler: lambda::bar
      Runtime: java8
"""
        )

        runInEdtAndWait {
            val functions = CloudFormationTemplateIndex.listFunctions(projectRule.project)
            assertThat(functions).hasSize(1)
            val indexedFunction = functions.toList()[0]
            assertThat(indexedFunction.buildMethod()).isNull()
        }
    }
}


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

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

import com.intellij.testFramework.ProjectRule
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import software.amazon.awssdk.awscore.exception.AwsErrorDetails
import software.amazon.awssdk.services.cloudformation.CloudFormationClient
import software.amazon.awssdk.services.cloudformation.model.CloudFormationException
import software.amazon.awssdk.services.cloudformation.model.DescribeStacksRequest
import software.amazon.awssdk.services.cloudformation.model.DescribeStacksResponse
import software.amazon.awssdk.services.cloudformation.model.Stack
import software.amazon.awssdk.services.cloudformation.model.StackStatus
import software.aws.toolkits.core.utils.WaiterTimeoutException
import software.aws.toolkits.core.utils.WaiterUnrecoverableException
import software.aws.toolkits.jetbrains.core.MockClientManagerRule
import java.time.Duration

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

    @JvmField
    @Rule
    val mockClientManagerRule = MockClientManagerRule()

    private val mockClient by lazy { mockClientManagerRule.create<CloudFormationClient>() }

    @Test
    fun createSuccessful() {
        val describeStacksRequest = argumentCaptor<DescribeStacksRequest>()

        mockClient.stackReturn("foo", 1, StackStatus.CREATE_COMPLETE)
        mockClient.waitForStackCreateComplete("foo", delay = DEFAULT_DELAY)

        verify(mockClient, times(2)).describeStacks(describeStacksRequest.capture())
        assertThat(describeStacksRequest.allValues).allSatisfy { assertThat(it.stackName()).isEqualTo("foo") }
    }

    @Test
    fun createFailedValidationError() {
        val describeStacksRequest = argumentCaptor<DescribeStacksRequest>()

        mockClient.stackThrowValidationError("foo", 1)
        assertThatThrownBy { mockClient.waitForStackCreateComplete("foo", delay = DEFAULT_DELAY) }
            .isInstanceOf(WaiterUnrecoverableException::class.java)
            .hasMessageContaining("validation error")

        verify(mockClient, times(2)).describeStacks(describeStacksRequest.capture())
        assertThat(describeStacksRequest.allValues).allSatisfy { assertThat(it.stackName()).isEqualTo("foo") }
    }

    @Test
    fun createFailedStatus() {
        assertFailureStatus("baz", 2, StackStatus.CREATE_FAILED)
    }

    @Test
    fun deleteCompleteStatus() {
        assertFailureStatus("foo", 1, StackStatus.DELETE_COMPLETE)
    }

    @Test
    fun deleteFailedStatus() {
        assertFailureStatus("foo", 1, StackStatus.DELETE_FAILED)
    }

    @Test
    fun updateRollbackCompleteStatus() {
        assertFailureStatus("foo", 1, StackStatus.ROLLBACK_COMPLETE)
    }

    @Test
    fun rollbackFailedStatus() {
        assertFailureStatus("bar", 0, StackStatus.ROLLBACK_FAILED)
    }

    @Test
    fun timeoutError() {
        mockClient.stackReturn("foo", 100000, StackStatus.CREATE_IN_PROGRESS)
        assertThatThrownBy {
            mockClient.waitForStackCreateComplete("foo", 1, delay = DEFAULT_DELAY)
        }.isInstanceOf(WaiterTimeoutException::class.java)
    }

    private fun assertFailureStatus(stackName: String, times: Int, failureStatus: StackStatus) {
        mockClient.stackReturn(stackName, times, failureStatus)
        val describeStacksRequest = argumentCaptor<DescribeStacksRequest>()
        assertThatThrownBy { mockClient.waitForStackCreateComplete(stackName, delay = DEFAULT_DELAY) }
            .isInstanceOf(WaiterUnrecoverableException::class.java)
            .hasMessageContaining(failureStatus.toString())
        verify(mockClient, times(times + 1)).describeStacks(describeStacksRequest.capture())
        assertThat(describeStacksRequest.allValues).allSatisfy { assertThat(it.stackName()).isEqualTo(stackName) }
    }

    private fun CloudFormationClient.stackReturn(stackName: String, times: Int, status: StackStatus) {
        val responses = mutableListOf<DescribeStacksResponse>()
        val inProgressResponseBuilder = DescribeStacksResponse.builder()
            .stacks(
                Stack.builder()
                    .stackName(stackName)
                    .stackStatus(StackStatus.CREATE_IN_PROGRESS)
                    .build()
            )

        val finalResponse = DescribeStacksResponse.builder()
            .stacks(
                Stack.builder()
                    .stackName(stackName)
                    .stackStatus(status)
                    .build()
            )
            .build()

        val firstResponse = if (times <= 0) finalResponse else inProgressResponseBuilder.build()

        if (times > 0) {
            repeat(times - 1) {
                responses.add(inProgressResponseBuilder.build())
            }
            responses.add(finalResponse)
        }

        whenever(
            describeStacks(
                DescribeStacksRequest.builder()
                    .stackName(stackName)
                    .build()
            )
        ).thenReturn(firstResponse, *responses.toTypedArray())
    }

    private fun CloudFormationClient.stackThrowValidationError(stackName: String, times: Int) {
        val responses = mutableListOf<DescribeStacksResponse>()
        val inProgressResponseBuilder = DescribeStacksResponse.builder()
            .stacks(
                Stack.builder()
                    .stackName(stackName)
                    .stackStatus(StackStatus.CREATE_IN_PROGRESS)
                    .build()
            )
        val finalResponse = CloudFormationException.builder()
            .awsErrorDetails(
                AwsErrorDetails.builder()
                    .errorCode("ValidationError")
                    .build()
            )
            .build()

        if (times <= 0) {
            whenever(
                describeStacks(
                    DescribeStacksRequest.builder()
                        .stackName(stackName)
                        .build()
                )
            ).thenThrow(finalResponse)
        } else {
            repeat(times - 1) {
                responses.add(inProgressResponseBuilder.build())
            }
            whenever(
                describeStacks(
                    DescribeStacksRequest.builder()
                        .stackName(stackName)
                        .build()
                )
            ).thenReturn(inProgressResponseBuilder.build(), *responses.toTypedArray())
                .thenThrow(finalResponse)
        }
    }

    companion object {
        private val DEFAULT_DELAY = Duration.ofMillis(5)
    }
}


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

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

import com.intellij.testFramework.ProjectRule
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import software.amazon.awssdk.awscore.exception.AwsErrorDetails
import software.amazon.awssdk.services.cloudformation.CloudFormationClient
import software.amazon.awssdk.services.cloudformation.model.CloudFormationException
import software.amazon.awssdk.services.cloudformation.model.DescribeStacksRequest
import software.amazon.awssdk.services.cloudformation.model.DescribeStacksResponse
import software.amazon.awssdk.services.cloudformation.model.Stack
import software.amazon.awssdk.services.cloudformation.model.StackStatus
import software.aws.toolkits.core.utils.WaiterTimeoutException
import software.aws.toolkits.core.utils.WaiterUnrecoverableException
import software.aws.toolkits.jetbrains.core.MockClientManagerRule
import java.time.Duration

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

    @JvmField
    @Rule
    val mockClientManagerRule = MockClientManagerRule()

    private val mockClient by lazy { mockClientManagerRule.create<CloudFormationClient>() }

    @Test
    fun deleteSuccessful() {
        val describeStacksRequest = argumentCaptor<DescribeStacksRequest>()

        mockClient.stackReturn("foo", 1, StackStatus.DELETE_COMPLETE)
        mockClient.waitForStackDeletionComplete("foo", delay = DEFAULT_DELAY)

        verify(mockClient, times(2)).describeStacks(describeStacksRequest.capture())
        assertThat(describeStacksRequest.allValues).allSatisfy { assertThat(it.stackName()).isEqualTo("foo") }
    }

    @Test
    fun deleteSuccessful_stackNotExist() {
        val describeStacksRequest = argumentCaptor<DescribeStacksRequest>()

        mockClient.stackThrowValidationError("foo", 1)
        mockClient.waitForStackDeletionComplete("foo", delay = DEFAULT_DELAY)

        verify(mockClient, times(2)).describeStacks(describeStacksRequest.capture())
        assertThat(describeStacksRequest.allValues).allSatisfy { assertThat(it.stackName()).isEqualTo("foo") }
    }

    @Test
    fun deleteFailedStatus() {
        assertFailureStatus("foo", 1, StackStatus.DELETE_FAILED)
    }

    @Test
    fun rollbackFailedStatus() {
        assertFailureStatus("bar", 0, StackStatus.ROLLBACK_FAILED)
    }

    @Test
    fun updateRollbackInProgressStatus() {
        assertFailureStatus("foo", 1, StackStatus.UPDATE_ROLLBACK_IN_PROGRESS)
    }

    @Test
    fun createFailedStatus() {
        assertFailureStatus("baz", 2, StackStatus.CREATE_FAILED)
    }

    @Test
    fun updateRollbackFailedStatus() {
        assertFailureStatus("baa", 3, StackStatus.UPDATE_ROLLBACK_FAILED)
    }

    @Test
    fun timeoutError() {
        mockClient.stackReturn("foo", 100000, StackStatus.DELETE_IN_PROGRESS)
        assertThatThrownBy {
            mockClient.waitForStackDeletionComplete("foo", 1, delay = DEFAULT_DELAY)
        }.isInstanceOf(WaiterTimeoutException::class.java)
    }

    private fun assertFailureStatus(stackName: String, times: Int, failureStatus: StackStatus) {
        mockClient.stackReturn(stackName, times, failureStatus)
        val describeStacksRequest = argumentCaptor<DescribeStacksRequest>()
        assertThatThrownBy { mockClient.waitForStackDeletionComplete(stackName, delay = DEFAULT_DELAY) }
            .isInstanceOf(WaiterUnrecoverableException::class.java)
            .hasMessageContaining(failureStatus.toString())
        verify(mockClient, times(times + 1)).describeStacks(describeStacksRequest.capture())
        assertThat(describeStacksRequest.allValues).allSatisfy { assertThat(it.stackName()).isEqualTo(stackName) }
    }

    private fun CloudFormationClient.stackReturn(stackName: String, times: Int, status: StackStatus) {
        val responses = mutableListOf<DescribeStacksResponse>()
        val inProgressResponseBuilder = DescribeStacksResponse.builder()
            .stacks(
                Stack.builder()
                    .stackName(stackName)
                    .stackStatus(StackStatus.DELETE_IN_PROGRESS)
                    .build()
            )

        val finalResponse = DescribeStacksResponse.builder()
            .stacks(
                Stack.builder()
                    .stackName(stackName)
                    .stackStatus(status)
                    .build()
            )
            .build()

        val firstResponse = if (times <= 0) finalResponse else inProgressResponseBuilder.build()

        if (times > 0) {
            repeat(times - 1) {
                responses.add(inProgressResponseBuilder.build())
            }
            responses.add(finalResponse)
        }

        whenever(
            describeStacks(
                DescribeStacksRequest.builder()
                    .stackName(stackName)
                    .build()
            )
        )
            .thenReturn(firstResponse, *responses.toTypedArray())
    }

    private fun CloudFormationClient.stackThrowValidationError(stackName: String, times: Int) {
        val responses = mutableListOf<DescribeStacksResponse>()
        val inProgressResponseBuilder = DescribeStacksResponse.builder()
            .stacks(
                Stack.builder()
                    .stackName(stackName)
                    .stackStatus(StackStatus.DELETE_IN_PROGRESS)
                    .build()
            )
        val finalResponse = CloudFormationException.builder()
            .awsErrorDetails(
                AwsErrorDetails.builder()
                    .errorCode("ValidationError")
                    .build()
            )
            .build()

        if (times <= 0) {
            whenever(
                describeStacks(
                    DescribeStacksRequest.builder()
                        .stackName(stackName)
                        .build()
                )
            )
                .thenThrow(finalResponse)
        } else {
            repeat(times - 1) {
                responses.add(inProgressResponseBuilder.build())
            }
            whenever(
                describeStacks(
                    DescribeStacksRequest.builder()
                        .stackName(stackName)
                        .build()
                )
            )
                .thenReturn(inProgressResponseBuilder.build(), *responses.toTypedArray())
                .thenThrow(finalResponse)
        }
    }

    companion object {
        private val DEFAULT_DELAY = Duration.ofMillis(5)
    }
}


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

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

import com.intellij.testFramework.ProjectRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.stub
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import software.amazon.awssdk.awscore.exception.AwsErrorDetails
import software.amazon.awssdk.services.cloudformation.CloudFormationClient
import software.amazon.awssdk.services.cloudformation.model.CloudFormationException
import software.amazon.awssdk.services.cloudformation.model.DescribeStacksRequest
import software.amazon.awssdk.services.cloudformation.model.DescribeStacksResponse
import software.amazon.awssdk.services.cloudformation.model.ExecuteChangeSetRequest
import software.amazon.awssdk.services.cloudformation.model.ExecuteChangeSetResponse
import software.amazon.awssdk.services.cloudformation.model.Stack
import software.amazon.awssdk.services.cloudformation.model.StackStatus
import software.aws.toolkits.jetbrains.core.MockClientManagerRule

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

    @JvmField
    @Rule
    val mockClientManagerRule = MockClientManagerRule()

    private val mockClient by lazy { mockClientManagerRule.create<CloudFormationClient>() }

    @Test
    fun stackDoesNotExist() {
        val describeCaptor = argumentCaptor<DescribeStacksRequest>()
        val executeCaptor = argumentCaptor<ExecuteChangeSetRequest>()

        mockClient.stub {
            on { describeStacks(describeCaptor.capture()) }
                .thenThrow(
                    CloudFormationException.builder()
                        .awsErrorDetails(
                            AwsErrorDetails.builder()
                                .errorMessage("Stack with id foo does not exist")
                                .build()
                        ).build()
                )
                .thenReturn(describeResponse(StackStatus.CREATE_COMPLETE))

            on { executeChangeSet(executeCaptor.capture()) }.thenReturn(ExecuteChangeSetResponse.builder().build())
        }

        mockClient.executeChangeSetAndWait("foo", "changeBar")

        verify(mockClient, times(2)).describeStacks(any<DescribeStacksRequest>())
        verify(mockClient).executeChangeSet(any<ExecuteChangeSetRequest>())

        assertThat(describeCaptor.allValues).allSatisfy { assertThat(it.stackName()).isEqualTo("foo") }
        assertThat(executeCaptor.firstValue.stackName()).isEqualTo("foo")
        assertThat(executeCaptor.firstValue.changeSetName()).isEqualTo("changeBar")
    }

    @Test
    fun executeCreate() {
        val describeCaptor = argumentCaptor<DescribeStacksRequest>()
        val executeCaptor = argumentCaptor<ExecuteChangeSetRequest>()

        mockClient.stub {
            on { describeStacks(describeCaptor.capture()) }.thenReturn(
                describeResponse(StackStatus.REVIEW_IN_PROGRESS),
                describeResponse(StackStatus.CREATE_COMPLETE)
            )

            on { executeChangeSet(executeCaptor.capture()) }.thenReturn(ExecuteChangeSetResponse.builder().build())
        }

        mockClient.executeChangeSetAndWait("foo", "changeBar")

        verify(mockClient, times(2)).describeStacks(any<DescribeStacksRequest>())
        verify(mockClient).executeChangeSet(any<ExecuteChangeSetRequest>())

        assertThat(describeCaptor.allValues).allSatisfy { assertThat(it.stackName()).isEqualTo("foo") }
        assertThat(executeCaptor.firstValue.stackName()).isEqualTo("foo")
        assertThat(executeCaptor.firstValue.changeSetName()).isEqualTo("changeBar")
    }

    @Test
    fun executeUpdate() {
        val describeCaptor = argumentCaptor<DescribeStacksRequest>()
        val executeCaptor = argumentCaptor<ExecuteChangeSetRequest>()

        mockClient.stub {
            on { describeStacks(describeCaptor.capture()) }.thenReturn(
                describeResponse(StackStatus.CREATE_COMPLETE),
                describeResponse(StackStatus.UPDATE_COMPLETE)
            )

            on { executeChangeSet(executeCaptor.capture()) }.thenReturn(ExecuteChangeSetResponse.builder().build())
        }

        mockClient.executeChangeSetAndWait("foo", "changeBar")

        verify(mockClient, times(2)).describeStacks(any<DescribeStacksRequest>())
        verify(mockClient).executeChangeSet(any<ExecuteChangeSetRequest>())

        assertThat(describeCaptor.allValues).allSatisfy { assertThat(it.stackName()).isEqualTo("foo") }
        assertThat(executeCaptor.firstValue.stackName()).isEqualTo("foo")
        assertThat(executeCaptor.firstValue.changeSetName()).isEqualTo("changeBar")
    }

    private fun describeResponse(status: StackStatus) =
        DescribeStacksResponse.builder()
            .stacks(
                Stack.builder().stackName("foo")
                    .stackStatus(status)
                    .build()
            ).build()
}


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

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

import com.intellij.testFramework.ProjectRule
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import software.amazon.awssdk.awscore.exception.AwsErrorDetails
import software.amazon.awssdk.services.cloudformation.CloudFormationClient
import software.amazon.awssdk.services.cloudformation.model.CloudFormationException
import software.amazon.awssdk.services.cloudformation.model.DescribeStacksRequest
import software.amazon.awssdk.services.cloudformation.model.DescribeStacksResponse
import software.amazon.awssdk.services.cloudformation.model.Stack
import software.amazon.awssdk.services.cloudformation.model.StackStatus
import software.aws.toolkits.core.utils.WaiterTimeoutException
import software.aws.toolkits.core.utils.WaiterUnrecoverableException
import software.aws.toolkits.jetbrains.core.MockClientManagerRule
import java.time.Duration

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

    @JvmField
    @Rule
    val mockClientManagerRule = MockClientManagerRule()

    private val mockClient by lazy { mockClientManagerRule.create<CloudFormationClient>() }

    @Test
    fun updateSuccessful() {
        val describeStacksRequest = argumentCaptor<DescribeStacksRequest>()

        mockClient.stackReturn("foo", 1, StackStatus.UPDATE_COMPLETE)
        mockClient.waitForStackUpdateComplete("foo", delay = DEFAULT_DELAY)

        verify(mockClient, times(2)).describeStacks(describeStacksRequest.capture())
        assertThat(describeStacksRequest.allValues).allSatisfy { assertThat(it.stackName()).isEqualTo("foo") }
    }

    @Test
    fun updateFailedValidationError() {
        val describeStacksRequest = argumentCaptor<DescribeStacksRequest>()

        mockClient.stackThrowValidationError("foo", 1)
        assertThatThrownBy { mockClient.waitForStackUpdateComplete("foo", delay = DEFAULT_DELAY) }
            .isInstanceOf(WaiterUnrecoverableException::class.java)
            .hasMessageContaining("validation error")

        verify(mockClient, times(2)).describeStacks(describeStacksRequest.capture())
        assertThat(describeStacksRequest.allValues).allSatisfy { assertThat(it.stackName()).isEqualTo("foo") }
    }

    @Test
    fun updateRollbackFailedStatus() {
        assertFailureStatus("baz", 2, StackStatus.UPDATE_ROLLBACK_FAILED)
    }

    @Test
    fun updateRollbackCompleteStatus() {
        assertFailureStatus("foo", 1, StackStatus.UPDATE_ROLLBACK_COMPLETE)
    }

    @Test
    fun timeoutError() {
        mockClient.stackReturn("foo", 100000, StackStatus.CREATE_IN_PROGRESS)
        assertThatThrownBy {
            mockClient.waitForStackUpdateComplete("foo", 1, delay = DEFAULT_DELAY)
        }.isInstanceOf(WaiterTimeoutException::class.java)
    }

    private fun assertFailureStatus(stackName: String, times: Int, failureStatus: StackStatus) {
        mockClient.stackReturn(stackName, times, failureStatus)
        val describeStacksRequest = argumentCaptor<DescribeStacksRequest>()
        assertThatThrownBy { mockClient.waitForStackUpdateComplete(stackName, delay = DEFAULT_DELAY) }
            .isInstanceOf(WaiterUnrecoverableException::class.java)
            .hasMessageContaining(failureStatus.toString())
        verify(mockClient, times(times + 1)).describeStacks(describeStacksRequest.capture())
        assertThat(describeStacksRequest.allValues).allSatisfy { assertThat(it.stackName()).isEqualTo(stackName) }
    }

    private fun CloudFormationClient.stackReturn(stackName: String, times: Int, status: StackStatus) {
        val responses = mutableListOf<DescribeStacksResponse>()
        val inProgressResponseBuilder = DescribeStacksResponse.builder()
            .stacks(
                Stack.builder()
                    .stackName(stackName)
                    .stackStatus(StackStatus.UPDATE_IN_PROGRESS)
                    .build()
            )

        val finalResponse = DescribeStacksResponse.builder()
            .stacks(
                Stack.builder()
                    .stackName(stackName)
                    .stackStatus(status)
                    .build()
            )
            .build()

        val firstResponse = if (times <= 0) finalResponse else inProgressResponseBuilder.build()

        if (times > 0) {
            repeat(times - 1) {
                responses.add(inProgressResponseBuilder.build())
            }
            responses.add(finalResponse)
        }

        whenever(
            describeStacks(
                DescribeStacksRequest.builder()
                    .stackName(stackName)
                    .build()
            )
        ).thenReturn(firstResponse, *responses.toTypedArray())
    }

    private fun CloudFormationClient.stackThrowValidationError(stackName: String, times: Int) {
        val responses = mutableListOf<DescribeStacksResponse>()
        val inProgressResponseBuilder = DescribeStacksResponse.builder()
            .stacks(
                Stack.builder()
                    .stackName(stackName)
                    .stackStatus(StackStatus.CREATE_IN_PROGRESS)
                    .build()
            )
        val finalResponse = CloudFormationException.builder()
            .awsErrorDetails(
                AwsErrorDetails.builder()
                    .errorCode("ValidationError")
                    .build()
            )
            .build()

        if (times <= 0) {
            whenever(
                describeStacks(
                    DescribeStacksRequest.builder()
                        .stackName(stackName)
                        .build()
                )
            ).thenThrow(finalResponse)
        } else {
            repeat(times - 1) {
                responses.add(inProgressResponseBuilder.build())
            }
            whenever(
                describeStacks(
                    DescribeStacksRequest.builder()
                        .stackName(stackName)
                        .build()
                )
            ).thenReturn(inProgressResponseBuilder.build(), *responses.toTypedArray())
                .thenThrow(finalResponse)
        }
    }

    companion object {
        private val DEFAULT_DELAY = Duration.ofMillis(5)
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.aws.toolkits.jetbrains.services.cloudformation.stack

import com.intellij.testFramework.ProjectRule
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
import software.amazon.awssdk.services.cloudformation.model.StackEvent
import software.aws.toolkits.jetbrains.core.MockClientManagerRule

private fun expectRange(from: String, to: String, events: List<StackEvent>, expectedSize: Int = 1024) {
    Assert.assertEquals("Wrong number of items", expectedSize, events.size)
    Assert.assertEquals("Wrong page start", from, events.first().eventId())
    Assert.assertEquals("Wrong page end", to, events.last().eventId())
}

private const val nonEmptyMessage = "Second call on the same page must not return anything"
private const val wrongPageMessage = "Wrong list of available pages"

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

    @JvmField
    @Rule
    val mockClientManagerRule = MockClientManagerRule()

    @Test
    fun onlyNewEvents() {
        val generator = MockEventsGenerator()
        val client = mockClientManagerRule.createMock(generator)

        val fetcher = EventsFetcher("myStack")
        fetcher.fetchEvents(client, null).apply {
            expectRange("4096", "3073", first)
        }
        fetcher.fetchEvents(client, null).apply {
            Assert.assertTrue(nonEmptyMessage, first.isEmpty())
        }
        generator.addEvent() // New event arrived
        fetcher.fetchEvents(client, null).apply {
            expectRange("4097", "4097", first, expectedSize = 1)
        }
        fetcher.fetchEvents(client, null).apply {
            Assert.assertTrue(nonEmptyMessage, first.isEmpty())
        }
    }

    @Test
    fun paging() {
        val client = mockClientManagerRule.createMock(MockEventsGenerator())
        val fetcher = EventsFetcher("myStack")
        fetcher.fetchEvents(client, null).apply {
            expectRange("4096", "3073", first)
            Assert.assertEquals(wrongPageMessage, setOf(Page.NEXT), second)
        }
        fetcher.fetchEvents(client, null).apply {
            Assert.assertTrue(nonEmptyMessage, first.isEmpty())
            Assert.assertEquals(wrongPageMessage, setOf(Page.NEXT), second)
        }
        fetcher.fetchEvents(client, Page.NEXT).apply {
            expectRange("3072", "2049", first)
            Assert.assertEquals(wrongPageMessage, setOf(Page.PREVIOUS, Page.NEXT), second)
        }
        fetcher.fetchEvents(client, null).apply {
            Assert.assertTrue(nonEmptyMessage, first.isEmpty())
            Assert.assertEquals(wrongPageMessage, setOf(Page.PREVIOUS, Page.NEXT), second)
        }
        fetcher.fetchEvents(client, Page.NEXT) // Scroll to the end
        fetcher.fetchEvents(client, Page.NEXT).apply {
            expectRange("1024", "1", first)
            Assert.assertEquals(wrongPageMessage, setOf(Page.PREVIOUS), second)
        }
        fetcher.fetchEvents(client, Page.PREVIOUS).apply {
            expectRange("2048", "1025", first)
            Assert.assertEquals(wrongPageMessage, setOf(Page.PREVIOUS, Page.NEXT), second)
        }
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.aws.toolkits.jetbrains.services.cloudformation.stack

import com.intellij.testFramework.runInEdtAndWait
import com.intellij.ui.components.JBScrollPane
import com.intellij.ui.table.JBTable
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import software.amazon.awssdk.services.cloudformation.model.ResourceStatus
import software.amazon.awssdk.services.cloudformation.model.StackEvent
import java.time.Instant

class EventsTableTest {

    @Test
    fun `StackEvents are mapped to the table columns as expected`() {
        val now = Instant.now()
        val event = StackEvent.builder()
            .timestamp(now)
            .resourceStatus(ResourceStatus.CREATE_COMPLETE)
            .logicalResourceId("logical")
            .physicalResourceId("physical")
            .resourceStatusReason("reasons")
            .build()

        val sut = EventsTableImpl()

        runInEdtAndWait {
            sut.insertEvents(listOf(event), pageChanged = false)
        }

        with((sut.component as JBScrollPane).viewport.view as JBTable) {
            assertThat(model.rowCount).isEqualTo(1)
            assertThat(model.getValueAt(0, 0)).isEqualTo(now)
            assertThat(model.getValueAt(0, 1)).isEqualTo("CREATE_COMPLETE")
            assertThat(model.getValueAt(0, 2)).isEqualTo("logical")
            assertThat(model.getValueAt(0, 3)).isEqualTo("physical")
            assertThat(model.getValueAt(0, 4)).isEqualTo("reasons")
        }
    }

    @Test
    fun `StackEvents are added to the table in reverse order`() {
        val fooEvent = StackEvent.builder().logicalResourceId("foo").resourceStatus(ResourceStatus.CREATE_COMPLETE).build()
        val barEvent = StackEvent.builder().logicalResourceId("bar").resourceStatus(ResourceStatus.CREATE_COMPLETE).build()

        val sut = EventsTableImpl()

        runInEdtAndWait {
            sut.insertEvents(listOf(fooEvent), pageChanged = false)
            sut.insertEvents(listOf(barEvent), pageChanged = false)
        }

        with((sut.component as JBScrollPane).viewport.view as JBTable) {
            assertThat(model.rowCount).isEqualTo(2)
            assertThat(model.getValueAt(0, 2)).isEqualTo("bar")
            assertThat(model.getValueAt(1, 2)).isEqualTo("foo")
        }
    }

    @Test
    fun `StackEvents added as part of a page-change replace existing events`() {
        val fooEvent = StackEvent.builder().logicalResourceId("foo").resourceStatus(ResourceStatus.CREATE_COMPLETE).build()
        val barEvent = StackEvent.builder().logicalResourceId("bar").resourceStatus(ResourceStatus.CREATE_COMPLETE).build()

        val sut = EventsTableImpl()

        runInEdtAndWait {
            sut.insertEvents(listOf(fooEvent), pageChanged = false)
            sut.insertEvents(listOf(barEvent), pageChanged = true)
        }

        with((sut.component as JBScrollPane).viewport.view as JBTable) {
            assertThat(model.rowCount).isEqualTo(1)
            assertThat(model.getValueAt(0, 2)).isEqualTo("bar")
        }
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.aws.toolkits.jetbrains.services.cloudformation.stack

import org.junit.Assert
import org.mockito.Mockito
import org.mockito.kotlin.whenever
import software.amazon.awssdk.services.cloudformation.CloudFormationClient
import software.amazon.awssdk.services.cloudformation.model.DescribeStackEventsRequest
import software.amazon.awssdk.services.cloudformation.model.DescribeStackEventsResponse
import software.amazon.awssdk.services.cloudformation.model.ResourceStatus
import software.amazon.awssdk.services.cloudformation.model.StackEvent
import software.aws.toolkits.jetbrains.core.MockClientManagerRule
import java.time.Instant
import javax.swing.SwingUtilities

internal class MockEventsGenerator {
    private val events = mutableListOf<StackEvent>()
    private var lastEventId = 1

    init {
        repeat(4096) { addEvent() }
    }

    fun addEvent() {
        events.add(
            0,
            StackEvent.builder()
                .physicalResourceId("P$lastEventId")
                .logicalResourceId("L$lastEventId")
                .resourceType("Type")
                .resourceStatus(ResourceStatus.CREATE_IN_PROGRESS)
                .timestamp(Instant.now())
                .eventId(lastEventId.toString())
                .build()
        )
        lastEventId++
    }

    val currentPage: List<StackEvent> get() = getEvents(DescribeStackEventsRequest.builder().stackName("foo").build()).stackEvents()

    fun getEvents(request: DescribeStackEventsRequest): DescribeStackEventsResponse {
        Assert.assertNotNull("No stack name provided", request.stackName())
        val page = request.nextToken()
        assert(!SwingUtilities.isEventDispatchThread())
        Thread.sleep(600)
        val responseBuilder = DescribeStackEventsResponse.builder()
        val pageSize = 1024
        val maxPage = (events.size / pageSize) - 1
        val pageN = page?.let { page.toInt() } ?: 0
        assert(pageN in 0..maxPage)
        responseBuilder.stackEvents(events.subList(pageN * pageSize, ((pageN + 1) * pageSize)))
        if (pageN < maxPage) {
            responseBuilder.nextToken((pageN + 1).toString())
        }
        return responseBuilder.build()
    }
}

internal fun MockClientManagerRule.createMock(
    events: MockEventsGenerator,
    postprocess: (CloudFormationClient) -> Unit = { }
) = create<CloudFormationClient>().apply {
    whenever(describeStackEvents(Mockito.any<DescribeStackEventsRequest>())).then { invocation ->
        events.getEvents((invocation.arguments[0] as DescribeStackEventsRequest))
    }
    postprocess(this)
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.aws.toolkits.jetbrains.services.cloudformation.stack

import com.intellij.testFramework.runInEdtAndWait
import com.intellij.ui.components.JBScrollPane
import com.intellij.ui.table.JBTable
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import software.amazon.awssdk.services.cloudformation.model.Output

class OutputsTableViewTest {

    @Test
    fun `output response is mapped to table correctly`() {
        val output = Output.builder().outputKey("key").outputValue("value").description("description").exportName("export").build()

        val sut = OutputsTableView()

        runInEdtAndWait {
            sut.updatedOutputs(listOf(output))
        }

        with((sut.component as JBScrollPane).viewport.view as JBTable) {
            assertThat(model.rowCount).isEqualTo(1)
            assertThat(model.getValueAt(0, 0)).isEqualTo("key")
            assertThat(model.getValueAt(0, 1)).isEqualTo("value")
            assertThat(model.getValueAt(0, 2)).isEqualTo("description")
            assertThat(model.getValueAt(0, 3)).isEqualTo("export")
        }
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.aws.toolkits.jetbrains.services.cloudformation.stack

import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.awt.Container
import java.awt.event.ActionEvent
import javax.swing.AbstractButton
import javax.swing.SwingUtilities

class PageButtonsTest {

    private fun PageButtons.getButton(page: Page): AbstractButton {
        val buttons = (component as Container).components.filterIsInstance<AbstractButton>()
        assertThat(buttons).hasSize(2)
        return if (page == Page.PREVIOUS) buttons[0] else buttons[1]
    }

    private fun PageButtons.click(page: Page) {
        getButton(page).actionListeners.forEach {
            it.actionPerformed(ActionEvent("Click", 1, "command"))
        }
    }

    private val PageButtons.enabledButtons get() = Page.values().filter { getButton(it).isEnabled }.toSet()

    private fun ensureClick(pageToClick: Page) {
        var clickedButton: Page? = null
        val buttons = PageButtons { clicked -> clickedButton = clicked }
        buttons.click(pageToClick)
        assertThat(clickedButton).isEqualTo(pageToClick)
    }

    @Test
    fun click() {
        Page.values().forEach { ensureClick(it) }
    }

    @Test
    fun enabled() {
        SwingUtilities.invokeAndWait {
            val buttons = PageButtons {}.apply { setPagesAvailable(emptySet()) }
            val pages = Page.values()
            assertThat(buttons.enabledButtons).isEmpty()
            buttons.setPagesAvailable(pages.toSet())
            assertThat(buttons.enabledButtons).hasSameElementsAs(pages.toSet())
            pages.forEach { page ->
                buttons.setPagesAvailable(setOf(page))
                assertThat(buttons.enabledButtons).containsOnlyOnce(page)
            }
        }
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.aws.toolkits.jetbrains.services.cloudformation.stack

import com.intellij.testFramework.ProjectRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.atLeast
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import software.amazon.awssdk.services.cloudformation.CloudFormationClient
import software.amazon.awssdk.services.cloudformation.model.DescribeStackEventsRequest
import software.amazon.awssdk.services.cloudformation.model.DescribeStackEventsResponse
import software.amazon.awssdk.services.cloudformation.model.DescribeStackResourcesRequest
import software.amazon.awssdk.services.cloudformation.model.DescribeStackResourcesResponse
import software.amazon.awssdk.services.cloudformation.model.DescribeStacksRequest
import software.amazon.awssdk.services.cloudformation.model.DescribeStacksResponse
import software.amazon.awssdk.services.cloudformation.model.Output
import software.amazon.awssdk.services.cloudformation.model.ResourceStatus
import software.amazon.awssdk.services.cloudformation.model.Stack
import software.amazon.awssdk.services.cloudformation.model.StackResource
import software.amazon.awssdk.services.cloudformation.model.StackStatus
import software.aws.toolkits.core.utils.delegateMock
import software.aws.toolkits.jetbrains.core.MockClientManagerRule
import java.time.Duration
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import javax.swing.JLabel
import javax.swing.SwingUtilities

private fun createSemaphore() = Semaphore(1).apply { acquire() }
private fun Semaphore.waitFor() = assert(tryAcquire(10, TimeUnit.SECONDS)) { "operation never completed" }

private fun StackStatus.asResponse(outputs: List<Output> = emptyList()) = DescribeStacksResponse.builder().stacks(
    Stack.builder().stackStatus(this).outputs(outputs).build()
).build()

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

    @JvmField
    @Rule
    val mockClientManagerRule = MockClientManagerRule()

    private val treeView = mock<TreeView>()
    private val eventsTable = mock<EventsTable>()
    private val outputTable = mock<OutputsListener>()
    private val updateListener = mock<UpdateListener>()
    private val resourceListener = mock<ResourceListener>()

    @Before
    fun setUp() {
        arrayOf(treeView, eventsTable).forEach { whenever(it.component).thenReturn(JLabel()) }
    }

    @Test
    fun `view filled with data`() {
        val setStackStatus = createSemaphore()
        val fillResources = createSemaphore()
        val insertEvents = createSemaphore()
        whenever(treeView.fillResources(any())).then { fillResources.release() }
        whenever(treeView.setStackStatus(StackStatus.CREATE_COMPLETE)).then { setStackStatus.release() }
        whenever(eventsTable.insertEvents(any(), any())).then { insertEvents.release() }

        val mockEventsGenerator = MockEventsGenerator()

        val resources = listOf(
            StackResource.builder()
                .physicalResourceId("P1")
                .logicalResourceId("L1")
                .resourceStatus(ResourceStatus.CREATE_IN_PROGRESS)
                .build(),
            StackResource.builder()
                .physicalResourceId("P2")
                .logicalResourceId("L2")
                .resourceStatus(ResourceStatus.CREATE_COMPLETE)
                .build()
        )

        val outputs = listOf(
            Output.builder().outputKey("hello").outputValue("world").build()
        )

        var availablePages = emptySet<Page>()
        SwingUtilities.invokeLater {
            val client = mockClientManagerRule.createMock(mockEventsGenerator) { mock ->
                whenever(mock.describeStacks(any<DescribeStacksRequest>()))
                    .thenReturn(StackStatus.CREATE_IN_PROGRESS.asResponse())
                    .thenReturn(StackStatus.CREATE_COMPLETE.asResponse(outputs))
                whenever(mock.describeStackResources(any<DescribeStackResourcesRequest>()))
                    .thenReturn(DescribeStackResourcesResponse.builder().stackResources(resources).build())
            }
            Updater(
                treeView = treeView,
                eventsTable = eventsTable,
                outputsTable = outputTable,
                resourceListener = resourceListener,
                updateInterval = Duration.ofMillis(1),
                updateIntervalOnFinalState = Duration.ofMillis(10),
                listener = updateListener,
                client = client,
                setPagesAvailable = { p -> availablePages = p },
                stackId = "1234"
            ).start()
        }

        arrayOf(setStackStatus, fillResources, insertEvents).forEach { it.waitFor() }

        verify(treeView, atLeast(1)).setStackStatus(StackStatus.CREATE_IN_PROGRESS)
        verify(treeView, atLeast(1)).setStackStatus(StackStatus.CREATE_COMPLETE)
        verify(treeView, atLeast(1)).fillResources(resources)
        verify(outputTable, atLeast(1)).updatedOutputs(outputs)
        verify(eventsTable, atLeast(1)).insertEvents(mockEventsGenerator.currentPage, false)
        Assert.assertEquals("Wrong button for first page", availablePages, setOf(Page.NEXT))
    }

    @Test
    fun `can apply a filter to the resource tree`() {
        val fillResources = createSemaphore()
        whenever(treeView.fillResources(any())).then { fillResources.release() }

        val resources = listOf(
            StackResource.builder()
                .physicalResourceId("P1")
                .logicalResourceId("L1")
                .resourceStatus(ResourceStatus.CREATE_IN_PROGRESS)
                .build(),
            StackResource.builder()
                .physicalResourceId("P2")
                .logicalResourceId("L2")
                .resourceStatus(ResourceStatus.CREATE_COMPLETE)
                .build()
        )

        val client = delegateMock<CloudFormationClient> {
            on { describeStacks(any<DescribeStacksRequest>()) }.thenReturn(StackStatus.CREATE_COMPLETE.asResponse())
            on { describeStackResources(any<DescribeStackResourcesRequest>()) }.thenReturn(
                DescribeStackResourcesResponse.builder().stackResources(resources).build()
            )
            on { describeStackEvents(any<DescribeStackEventsRequest>()) }.thenReturn(
                DescribeStackEventsResponse.builder().build()
            )
        }

        Updater(
            treeView = treeView,
            eventsTable = eventsTable,
            outputsTable = outputTable,
            resourceListener = resourceListener,
            updateInterval = Duration.ofMillis(1),
            updateIntervalOnFinalState = Duration.ofMillis(10),
            listener = updateListener,
            client = client,
            setPagesAvailable = { },
            stackId = "1234"
        ).applyFilter { it.logicalResourceId() == "L1" }

        fillResources.waitFor()

        val captor = argumentCaptor<List<StackResource>>()
        verify(treeView).fillResources(captor.capture())
        assertThat(captor.firstValue).singleElement().satisfies {
            assertThat(it.logicalResourceId()).isEqualTo("L1")
        }
    }
}


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

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

import com.intellij.openapi.Disposable
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.fileTypes.FileTypeManager
import com.intellij.openapi.fileTypes.ex.FakeFileType
import com.intellij.openapi.fileTypes.ex.FileTypeManagerEx
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.testFramework.runInEdtAndGet
import com.intellij.testFramework.runInEdtAndWait
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.jetbrains.annotations.NotNull
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import software.aws.toolkits.core.utils.test.notNull
import software.aws.toolkits.jetbrains.services.cloudformation.CloudFormationTemplate
import software.aws.toolkits.jetbrains.services.cloudformation.LambdaFunction
import software.aws.toolkits.jetbrains.services.cloudformation.SamFunction
import software.aws.toolkits.jetbrains.utils.rules.CodeInsightTestFixtureRule
import software.aws.toolkits.resources.message
import java.io.File

class YamlCloudFormationTemplateTest {
    @Rule
    @JvmField
    val projectRule = CodeInsightTestFixtureRule()

    @Rule
    @JvmField
    val folderRule = TemporaryFolder()

    @Test
    fun canListResources() {
        val template = yamlTemplate()
        runInEdtAndWait {
            assertThat(template.resources().toList()).hasSize(2)
        }
    }

    @Test
    fun canListResourcesWithGlobals() {
        val template = yamlTemplate(template = TEST_TEMPLATE_WITH_GLOBALS)
        runInEdtAndWait {
            assertThat(template.resources().toList()).hasSize(3)
        }
    }

    @Test
    fun noResourcesReturnsEmpty() {
        val template = yamlTemplate(
            """
Description: "Some description"
            """.trimIndent()
        )
        runInEdtAndWait {
            assertThat(template.resources().toList()).isEmpty()
        }
    }

    @Test
    fun emptyResourcesReturnsEmpty() {
        val template = yamlTemplate(
            """
Description: "Some description"
Resources:

            """.trimIndent()
        )
        runInEdtAndWait {
            assertThat(template.resources().toList()).isEmpty()
        }
    }

    @Test
    fun partialResourceIsIgnored() {
        val template = yamlTemplate(
            """
Description: "Some description"
Resources:
    Foo:
            """.trimIndent()
        )
        runInEdtAndWait {
            assertThat(template.resources().toList()).isEmpty()
        }
    }

    @Test
    fun canListParameters() {
        val template = yamlTemplate()
        runInEdtAndWait {
            assertThat(template.parameters().toList()).hasSize(2)
        }
    }

    @Test
    fun noParametersReturnsEmpty() {
        val template = yamlTemplate(
            """
Description: "Some description"
            """.trimIndent()
        )
        runInEdtAndWait {
            assertThat(template.parameters().toList()).isEmpty()
        }
    }

    @Test
    fun emptyParametersReturnsEmpty() {
        val template = yamlTemplate(
            """
Description: "Some description"
Parameters:

            """.trimIndent()
        )
        runInEdtAndWait {
            assertThat(template.parameters().toList()).isEmpty()
        }
    }

    @Test
    fun nullProperties() {
        val template = yamlTemplate()
        runInEdtAndWait {
            assertThat(template.parameters().toList()).hasSize(2)
            val tableTag = template.parameters().firstOrNull { it.logicalName == "TableTag" }
            assertThat(tableTag).notNull.satisfies { tag ->
                assertThat(tag.defaultValue()).isNull()
                assertThat(tag.description()).isNotNull()
                assertThat(tag.constraintDescription()).isNull()
            }
        }
    }

    @Test
    fun canUpdateAScalarValue() {
        val updatedTemplate = runInEdtAndGet {
            val template = yamlTemplate()
            val resource = template.getResourceByName("MyFunction")
            resource!!.setScalarProperty("CodeUri", "new/uri.jar")
            template.text()
        }

        assertThat(updatedTemplate).isEqualTo(
            """
Description: "Some description"
Parameters:
    TableName:
        Default: someTable
        Description: Storage for your data
        ConstraintDescription: No emojis
    TableTag:
        Description: Tag to add to the DynamoDb table
Resources:
    MyFunction:
        Type: AWS::Serverless::Function
        Properties:
            Handler: helloworld.App::handleRequest
            Runtime: java8
            CodeUri: new/uri.jar
    MyDynamoTable:
        Type: AWS::DynamoDB::Table
        Properties:
            AttributeDefinitions:
                - AttributeName: "ArtistId"
                  AttributeType: "S"
                - AttributeName: "Concert"
                  AttributeType: "S"
            KeySchema:
                - AttributeName: "ArtistId"
                  KeyType: "HASH"
                - AttributeName: "Concert"
                  KeyType: "RANGE"
            ProvisionedThroughput:
                ReadCapacityUnits: 1
                WriteCapacityUnits: 1
            """.trimIndent()
        )
    }

    @Test
    fun canUpdateAScalarValueWithGlobals() {
        val updatedTemplate = runInEdtAndGet {
            val template = yamlTemplate(template = TEST_TEMPLATE_WITH_GLOBALS)
            val resource = template.getResourceByName("MyFunction")
            resource!!.setScalarProperty("CodeUri", "new/uri.jar")
            template.text()
        }

        assertThat(updatedTemplate).isEqualTo(
            """
Description: "Some description"
Globals:
    Function:
        Runtime: java8
        CodeUri: target/out.jar
Resources:
    MyFunction:
        Type: AWS::Serverless::Function
        Properties:
            Handler: helloworld.App::handleRequest
            Runtime: java12
            CodeUri: new/uri.jar
    FooFunction:
        Type: AWS::Serverless::Function
        Properties:
            Handler: helloworld.App::handleRequest
    LambdaFunction:
        Type: AWS::Lambda::Function
        Properties:
            Handler: helloworld.App::handleRequest
            """.trimIndent()
        )
    }

    @Test
    fun canParseSequenceProperties() {
        val template = yamlTemplate()
        runInEdtAndWait {
            val resource = template.getResourceByName("MyDynamoTable")
            val attributeDefinitions = resource!!.getSequenceProperty("AttributeDefinitions")
            assertThat(attributeDefinitions.items).hasSize(2)
        }
    }

    @Test
    fun canSaveToFile() {
        val template = yamlTemplate()
        val tempFile = File.createTempFile("tempTemplate", ".yaml")

        runInEdtAndWait {
            template.saveTo(tempFile)
        }

        assertThat(tempFile).hasContent(TEST_TEMPLATE)
    }

    @Test
    fun canParseByExtension() {
        val fakeFileType = object : FakeFileType() {
            override fun isMyFileType(@NotNull file: VirtualFile): Boolean = true

            @NotNull
            override fun getName(): String = "foo"

            @NotNull
            override fun getDescription(): String = ""
        }

        runInEdtAndWait {
            val matchYaml = FileTypeManager.parseFromString("*.canParseByExtension.yaml")
            val matchYml = FileTypeManager.parseFromString("*.canParseByExtension.yml")

            runWriteAction {
                FileTypeManagerEx.getInstance().associate(fakeFileType, matchYaml)
                FileTypeManagerEx.getInstance().associate(fakeFileType, matchYml)
            }

            Disposer.register(
                projectRule.fixture.testRootDisposable,
                Disposable {
                    runWriteAction {
                        FileTypeManagerEx.getInstance().removeAssociation(fakeFileType, matchYml)
                        FileTypeManagerEx.getInstance().removeAssociation(fakeFileType, matchYaml)
                    }
                }
            )

            setOf("template.canParseByExtension.yaml", "template.canParseByExtension.yml").forEach {
                val yamlFile = projectRule.fixture.addFileToProject(it, TEST_TEMPLATE)
                assertThat(yamlFile.fileType).isEqualTo(fakeFileType)
                assertThat(CloudFormationTemplate.parse(projectRule.project, yamlFile.virtualFile)).isNotNull
            }
        }
    }

    @Test
    fun serverlessFunctionInheritsGlobalsProperties() {
        val template = yamlTemplate(template = TEST_TEMPLATE_WITH_GLOBALS)

        runInEdtAndWait {
            val samFunction = template.getResourceByName("FooFunction") as SamFunction
            assertThat(samFunction.runtime()).isEqualTo("java8")
            assertThat(samFunction.codeLocation()).isEqualTo("target/out.jar")
        }
    }

    @Test
    fun serverlessFunctionOverridesGlobalsProperties() {
        val template = yamlTemplate(template = TEST_TEMPLATE_WITH_GLOBALS)

        runInEdtAndWait {
            val samFunction = template.getResourceByName("MyFunction") as SamFunction
            assertThat(samFunction.runtime()).isEqualTo("java12")
            assertThat(samFunction.codeLocation()).isEqualTo("target/out.jar")
        }
    }

    @Test
    fun lambdaFunctionDoesNotInheritGlobalsProperties() {
        val template = yamlTemplate(template = TEST_TEMPLATE_WITH_GLOBALS)

        runInEdtAndWait {
            val lambdaFunction = template.getResourceByName("LambdaFunction") as LambdaFunction
            assertThatThrownBy { lambdaFunction.runtime() }.isInstanceOf(IllegalStateException::class.java).hasMessage(
                message("cloudformation.missing_property", "Runtime", lambdaFunction.logicalName)
            )
        }
    }

    @Test
    fun invalidTemplateIsHandledGracefully() {
        val template = yamlTemplate(
            """
            foo:
              bar
            ---
            hello:
              world
            """.trimIndent()
        )

        runInEdtAndWait {
            assertThat(template.resources().toList()).isEmpty()
            assertThat(template.parameters().toList()).isEmpty()
            assertThat(template.globals().toList()).isEmpty()
        }
    }

    @Test
    fun serverlessImageFunction() {
        val template = yamlTemplate(template = makeImageTemplate("DockerFile2"))

        runInEdtAndWait {
            val samFunction = template.getResourceByName("MyFunction") as SamFunction
            assertThat(samFunction.codeLocation()).isEqualTo("./hello-world")
            assertThat(samFunction.dockerFile()).isEqualTo("DockerFile2")
        }
    }

    @Test
    fun serverlessImageFunctionDefaultDockerfile() {
        val template = yamlTemplate(template = makeImageTemplate(null))

        runInEdtAndWait {
            val samFunction = template.getResourceByName("MyFunction") as SamFunction
            assertThat(samFunction.codeLocation()).isEqualTo("./hello-world")
            assertThat(samFunction.dockerFile()).isNull()
        }
    }

    private fun yamlTemplate(template: String = TEST_TEMPLATE): CloudFormationTemplate = runInEdtAndGet {
        val file = projectRule.fixture.addFileToProject("template.yaml", template)
        CloudFormationTemplate.parse(projectRule.project, file.virtualFile)
    }

    private companion object {
        val TEST_TEMPLATE =
            """
Description: "Some description"
Parameters:
    TableName:
        Default: someTable
        Description: Storage for your data
        ConstraintDescription: No emojis
    TableTag:
        Description: Tag to add to the DynamoDb table
Resources:
    MyFunction:
        Type: AWS::Serverless::Function
        Properties:
            Handler: helloworld.App::handleRequest
            Runtime: java8
            CodeUri: target/out.jar
    MyDynamoTable:
        Type: AWS::DynamoDB::Table
        Properties:
            AttributeDefinitions:
                - AttributeName: "ArtistId"
                  AttributeType: "S"
                - AttributeName: "Concert"
                  AttributeType: "S"
            KeySchema:
                - AttributeName: "ArtistId"
                  KeyType: "HASH"
                - AttributeName: "Concert"
                  KeyType: "RANGE"
            ProvisionedThroughput:
                ReadCapacityUnits: 1
                WriteCapacityUnits: 1
            """.trimIndent()

        val TEST_TEMPLATE_WITH_GLOBALS =
            """
Description: "Some description"
Globals:
    Function:
        Runtime: java8
        CodeUri: target/out.jar
Resources:
    MyFunction:
        Type: AWS::Serverless::Function
        Properties:
            Handler: helloworld.App::handleRequest
            Runtime: java12
    FooFunction:
        Type: AWS::Serverless::Function
        Properties:
            Handler: helloworld.App::handleRequest
    LambdaFunction:
        Type: AWS::Lambda::Function
        Properties:
            Handler: helloworld.App::handleRequest
            """.trimIndent()

        fun makeImageTemplate(dockerFile: String? = "DockerFile") =
            """
Description: "Some description"
Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      PackageType: Image
    Metadata:
      DockerTag: v1
      DockerContext: ./hello-world
      ${if (dockerFile == null) "" else "Dockerfile: $dockerFile"}
            """.trimIndent()
    }
}


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

package software.aws.toolkits.jetbrains.services.cloudwatch.logs

import com.intellij.testFramework.ProjectRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import software.amazon.awssdk.services.cloudwatchlogs.model.LogGroup
import software.aws.toolkits.jetbrains.core.MockResourceCacheRule
import software.aws.toolkits.jetbrains.core.explorer.nodes.AwsExplorerEmptyNode
import software.aws.toolkits.jetbrains.core.explorer.nodes.AwsExplorerErrorNode
import software.aws.toolkits.jetbrains.core.explorer.nodes.CloudWatchRootNode
import software.aws.toolkits.jetbrains.services.cloudwatch.logs.resources.CloudWatchResources
import java.util.concurrent.CompletableFuture

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

    @JvmField
    @Rule
    val resourceCache = MockResourceCacheRule()

    @Test
    fun logGroupsAreListed() {
        logGroups(listOf("bcd", "abc", "zzz", "AEF"))

        val children = CloudWatchLogsServiceNode(projectRule.project, CLOUDWATCH_LOGS_EXPLORER_SERVICE_NODE).children

        assertThat(children).allMatch { it is CloudWatchLogsNode }
        assertThat(children.filterIsInstance<CloudWatchLogsNode>().map { it.displayName() }).containsExactlyInAnyOrder("abc", "AEF", "bcd", "zzz")
    }

    @Test
    fun noLogGroupsShowsEmptyList() {
        logGroups(emptyList())

        val children = CloudWatchLogsServiceNode(projectRule.project, CLOUDWATCH_LOGS_EXPLORER_SERVICE_NODE).children

        assertThat(children).hasSize(1)
        assertThat(children).allMatch { it is AwsExplorerEmptyNode }
    }

    @Test
    fun exceptionLeadsToErrorNode() {
        resourceCache.addEntry(
            projectRule.project,
            CloudWatchResources.LIST_LOG_GROUPS,
            CompletableFuture<List<LogGroup>>().also {
                it.completeExceptionally(RuntimeException("Simulated error"))
            }
        )

        val children = CloudWatchLogsServiceNode(projectRule.project, CLOUDWATCH_LOGS_EXPLORER_SERVICE_NODE).children

        assertThat(children).hasSize(1)
        assertThat(children).allMatch { it is AwsExplorerErrorNode }
    }

    private fun logGroups(names: List<String>) {
        resourceCache.addEntry(
            projectRule.project,
            CloudWatchResources.LIST_LOG_GROUPS,
            CompletableFuture.completedFuture(names.map { LogGroup.builder().arn("arn:aws:logs:us-west-2:0123456789:log-group:$it").logGroupName(it).build() })
        )
    }

    private companion object {
        val CLOUDWATCH_LOGS_EXPLORER_SERVICE_NODE = CloudWatchRootNode()
    }
}


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

package software.aws.toolkits.jetbrains.services.cloudwatch.logs

import com.intellij.testFramework.ProjectRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito
import org.mockito.kotlin.whenever
import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogGroupsRequest
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogGroupsResponse
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsRequest
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsResponse
import software.amazon.awssdk.services.cloudwatchlogs.model.LogGroup
import software.amazon.awssdk.services.cloudwatchlogs.model.LogStream
import software.amazon.awssdk.services.cloudwatchlogs.model.ResourceNotFoundException
import software.aws.toolkits.jetbrains.core.MockClientManagerRule

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

    @JvmField
    @Rule
    val mockClientManagerRule = MockClientManagerRule()

    @Test
    fun checkIfLogGroupExists() {
        val client = mockClientManagerRule.create<CloudWatchLogsClient>()
        whenever(client.describeLogGroups(Mockito.any<DescribeLogGroupsRequest>()))
            .thenReturn(
                DescribeLogGroupsResponse.builder().logGroups(LogGroup.builder().logGroupName("abcdefg").build()).build()
            )
        assertThat(client.checkIfLogGroupExists("abcdefg")).isTrue()
        assertThat(client.checkIfLogGroupExists("abc")).isFalse()
        assertThat(client.checkIfLogGroupExists("def")).isFalse()
    }

    @Test
    fun checkIfLogStreamExists() {
        val client = mockClientManagerRule.create<CloudWatchLogsClient>()
        whenever(client.describeLogStreams(Mockito.any<DescribeLogStreamsRequest>()))
            .thenReturn(
                DescribeLogStreamsResponse
                    .builder()
                    .logStreams(LogStream.builder().logStreamName("abcdefg").build())
                    .build()
            )
            .thenReturn(
                DescribeLogStreamsResponse
                    .builder()
                    .logStreams(LogStream.builder().logStreamName("abcdefg").build())
                    .build()
            )
            .thenThrow(ResourceNotFoundException.builder().build())
        assertThat(client.checkIfLogStreamExists("abcdefg", "abcdefg")).isTrue()
        assertThat(client.checkIfLogStreamExists("abcdefg", "abcd")).isFalse()
        assertThat(client.checkIfLogStreamExists("abcdefg", "notFOund")).isFalse()
    }
}


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

package software.aws.toolkits.jetbrains.services.cloudwatch.logs

import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.testFramework.ProjectRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito
import org.mockito.kotlin.any
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient
import software.amazon.awssdk.services.cloudwatchlogs.model.DeleteLogGroupRequest
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsRequest
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsResponse
import software.amazon.awssdk.services.cloudwatchlogs.model.LogStream
import software.aws.toolkits.core.utils.test.retryableAssert
import software.aws.toolkits.jetbrains.core.MockClientManagerRule
import software.aws.toolkits.jetbrains.core.ToolWindowHeadlessManagerImpl
import software.aws.toolkits.jetbrains.services.cloudwatch.logs.actions.DeleteGroupAction
import software.aws.toolkits.jetbrains.services.cloudwatch.logs.toolwindow.CloudWatchLogsToolWindowFactory
import java.util.function.Consumer
import javax.swing.JPanel

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

    @JvmField
    @Rule
    val mockClientManager = MockClientManagerRule()

    private val stream = LogStream.builder().logStreamName("eman").build()
    private lateinit var cloudwatchmock: CloudWatchLogsClient

    @Before
    fun setupMocks() {
        (ToolWindowManager.getInstance(projectRule.project) as ToolWindowHeadlessManagerImpl)
            .doRegisterToolWindow(CloudWatchLogsToolWindowFactory.TOOLWINDOW_ID)
        cloudwatchmock = mockClientManager.create()
    }

    @Test
    fun deleteGroupSucceeds() {
        val mockNode = CloudWatchLogsNode(projectRule.project, "arn", "name")
        val delete = DeleteGroupAction()
        delete.performDelete(mockNode)
        verify(cloudwatchmock).deleteLogGroup(any<Consumer<DeleteLogGroupRequest.Builder>>())
    }

    @Test
    fun deleteGroupClosesWindow() {
        whenever(cloudwatchmock.describeLogStreams(Mockito.any<DescribeLogStreamsRequest>()))
            .thenReturn(DescribeLogStreamsResponse.builder().logStreams(stream).build())
        val mockNode = CloudWatchLogsNode(projectRule.project, "arn", "name")
        val toolWindow = CloudWatchLogWindow.getInstance(projectRule.project)
        toolWindow.addTab("test", JPanel(), true, "name")
        toolWindow.addTab("test", JPanel(), true, "name/eman")
        toolWindow.addTab("test", JPanel(), true, "name2")
        assertThat(toolWindow.findPrefix("name").size).isEqualTo(2)
        val delete = DeleteGroupAction()
        delete.performDelete(mockNode)
        // retryable because removing windows is done asynchronously
        retryableAssert {
            assertThat(toolWindow.find("name")).isNull()
            assertThat(toolWindow.find("name/eman")).isNull()
            assertThat(toolWindow.findPrefix("name2").size).isEqualTo(1)
        }
    }

    @Test(expected = RuntimeException::class)
    fun deleteGroupFails() {
        whenever(cloudwatchmock.deleteLogGroup(Mockito.any<DeleteLogGroupRequest>()))
            .thenThrow(RuntimeException("not found or whatever"))
        val mockNode = CloudWatchLogsNode(projectRule.project, "arn", "name")
        val delete = DeleteGroupAction()
        delete.performDelete(mockNode)
    }
}


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

package software.aws.toolkits.jetbrains.services.cloudwatch.logs

import com.intellij.ui.table.TableView
import com.intellij.util.ui.ListTableModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.ClosedSendChannelException
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
import org.mockito.kotlin.whenever
import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsRequest
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsResponse
import software.amazon.awssdk.services.cloudwatchlogs.model.LogStream
import software.aws.toolkits.jetbrains.utils.BaseCoroutineTest
import software.aws.toolkits.jetbrains.utils.waitForModelToBeAtLeast
import software.aws.toolkits.jetbrains.utils.waitForTrue
import software.aws.toolkits.resources.message

@ExperimentalCoroutinesApi
class LogGroupActorTest : BaseCoroutineTest() {
    private lateinit var client: CloudWatchLogsClient
    private lateinit var tableModel: ListTableModel<LogStream>
    private lateinit var table: TableView<LogStream>
    private lateinit var actor: CloudWatchLogsActor<LogStream>

    @Before
    fun loadVariables() {
        client = mockClientManagerRule.create()
        tableModel = ListTableModel<LogStream>()
        table = TableView(tableModel)
        actor = LogGroupActor(projectRule.project, client, table, "abc")
    }

    @Test
    fun modelIsPopulated() {
        whenever(client.describeLogStreams(Mockito.any<DescribeLogStreamsRequest>()))
            .thenReturn(DescribeLogStreamsResponse.builder().logStreams(LogStream.builder().logStreamName("name").build()).build())
        runBlocking {
            actor.channel.send(CloudWatchLogsActor.Message.LoadInitial)
            tableModel.waitForModelToBeAtLeast(1)
        }
        assertThat(tableModel.items.size).isOne()
        assertThat(tableModel.items.first().logStreamName()).isEqualTo("name")
    }

    @Test
    fun emptyTableOnExceptionThrown() {
        whenever(client.describeLogStreams(Mockito.any<DescribeLogStreamsRequest>())).then { throw IllegalStateException("network broke") }
        runBlocking {
            actor.channel.send(CloudWatchLogsActor.Message.LoadInitial)
            waitForTrue { table.emptyText.text == message("cloudwatch.logs.failed_to_load_streams", "abc") }
        }
        assertThat(tableModel.items).isEmpty()
    }

    @Test
    fun loadingForwardAppendsToTable() {
        whenever(client.describeLogStreams(Mockito.any<DescribeLogStreamsRequest>()))
            .thenReturn(DescribeLogStreamsResponse.builder().logStreams(LogStream.builder().logStreamName("name").build()).nextToken("1").build())
            .thenReturn(DescribeLogStreamsResponse.builder().logStreams(LogStream.builder().logStreamName("name2").build()).build())
        runBlocking {
            actor.channel.send(CloudWatchLogsActor.Message.LoadInitial)
            actor.channel.send(CloudWatchLogsActor.Message.LoadForward)
            tableModel.waitForModelToBeAtLeast(2)
        }
        assertThat(tableModel.items.size).isEqualTo(2)
        assertThat(tableModel.items.first().logStreamName()).isEqualTo("name")
        assertThat(tableModel.items[1].logStreamName()).isEqualTo("name2")
    }

    @Test
    fun writeChannelAndCoroutineIsDisposed() {
        val channel = actor.channel
        actor.dispose()
        assertThatThrownBy {
            runBlocking {
                channel.send(CloudWatchLogsActor.Message.LoadForward)
            }
        }.isInstanceOf(ClosedSendChannelException::class.java)
    }
}


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

package software.aws.toolkits.jetbrains.services.cloudwatch.logs

import com.intellij.ui.table.TableView
import com.intellij.util.ui.ListTableModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.ClosedSendChannelException
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
import org.mockito.kotlin.whenever
import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsRequest
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsResponse
import software.amazon.awssdk.services.cloudwatchlogs.model.LogStream
import software.aws.toolkits.jetbrains.utils.BaseCoroutineTest
import software.aws.toolkits.jetbrains.utils.waitForModelToBeAtLeast
import software.aws.toolkits.jetbrains.utils.waitForTrue
import software.aws.toolkits.resources.message
import java.time.Duration

@ExperimentalCoroutinesApi
class LogGroupSearchActorTest : BaseCoroutineTest() {
    private lateinit var client: CloudWatchLogsClient
    private lateinit var tableModel: ListTableModel<LogStream>
    private lateinit var table: TableView<LogStream>
    private lateinit var actor: CloudWatchLogsActor<LogStream>

    @Before
    fun loadVariables() {
        client = mockClientManagerRule.create()
        tableModel = ListTableModel<LogStream>()
        table = TableView(tableModel)
        actor = LogGroupSearchActor(projectRule.project, client, table, "abc")
    }

    @Test
    fun modelIsPopulated() {
        whenever(client.describeLogStreams(Mockito.any<DescribeLogStreamsRequest>()))
            .thenReturn(DescribeLogStreamsResponse.builder().logStreams(LogStream.builder().logStreamName("name-cool").build()).build())
        runBlocking {
            actor.channel.send(CloudWatchLogsActor.Message.LoadInitialFilter("name"))
            tableModel.waitForModelToBeAtLeast(1)
        }
        assertThat(tableModel.items.size).isOne()
        assertThat(tableModel.items.first().logStreamName()).isEqualTo("name-cool")
    }

    @Test
    fun loadingForwardAppendsToTable() {
        whenever(client.describeLogStreams(Mockito.any<DescribeLogStreamsRequest>()))
            .thenReturn(DescribeLogStreamsResponse.builder().logStreams(LogStream.builder().logStreamName("name-cool").build()).nextToken("token").build())
            .thenReturn(DescribeLogStreamsResponse.builder().logStreams(LogStream.builder().logStreamName("name-2").build()).build())
        runBlocking {
            actor.channel.send(CloudWatchLogsActor.Message.LoadInitialFilter("name"))
            actor.channel.send(CloudWatchLogsActor.Message.LoadForward)
            tableModel.waitForModelToBeAtLeast(2)
        }
        assertThat(tableModel.items.size).isEqualTo(2)
        assertThat(tableModel.items.first().logStreamName()).isEqualTo("name-cool")
        assertThat(tableModel.items[1].logStreamName()).isEqualTo("name-2")
    }

    @Test
    fun loadingBackwardsDoesNothing() {
        whenever(client.describeLogStreams(Mockito.any<DescribeLogStreamsRequest>()))
            .thenReturn(DescribeLogStreamsResponse.builder().logStreams(LogStream.builder().logStreamName("name-cool").build()).build())
        runBlocking {
            actor.channel.send(CloudWatchLogsActor.Message.LoadInitialFilter("name"))
            actor.channel.send(CloudWatchLogsActor.Message.LoadBackward)
            actor.channel.send(CloudWatchLogsActor.Message.LoadBackward)
            tableModel.waitForModelToBeAtLeast(1)
        }
        assertThat(tableModel.items.size).isOne()
        assertThat(tableModel.items.first().logStreamName()).isEqualTo("name-cool")
    }

    @Test
    fun writeChannelAndCoroutineIsDisposed() {
        val channel = actor.channel
        actor.dispose()
        assertThatThrownBy {
            runBlocking {
                channel.send(CloudWatchLogsActor.Message.LoadBackward)
            }
        }.isInstanceOf(ClosedSendChannelException::class.java)
    }

    @Test
    fun loadInitialThrows() {
        runBlocking {
            actor.channel.send(CloudWatchLogsActor.Message.LoadInitial)
            waitForTrue { actor.channel.isClosedForSend }
        }
    }

    @Test
    fun loadInitialRangeThrows() {
        runBlocking {
            actor.channel.send(CloudWatchLogsActor.Message.LoadInitialRange(LogStreamEntry("@@@", 0), Duration.ofMillis(0)))
            waitForTrue { actor.channel.isClosedForSend }
        }
    }

    @Test
    fun emptyTableOnExceptionThrown() {
        whenever(client.describeLogStreams(Mockito.any<DescribeLogStreamsRequest>())).thenThrow(IllegalStateException("network broke"))
        runBlocking {
            actor.channel.send(CloudWatchLogsActor.Message.LoadInitialFilter("name"))
            waitForTrue {
                println(table.emptyText.text)
                table.emptyText.text == message("cloudwatch.logs.failed_to_load_streams", "abc")
            }
        }
        assertThat(tableModel.items).isEmpty()
    }
}


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

package software.aws.toolkits.jetbrains.services.cloudwatch.logs

import com.intellij.ui.table.TableView
import com.intellij.util.ui.ListTableModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.ClosedSendChannelException
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
import org.mockito.kotlin.whenever
import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient
import software.amazon.awssdk.services.cloudwatchlogs.model.FilterLogEventsRequest
import software.amazon.awssdk.services.cloudwatchlogs.model.FilterLogEventsResponse
import software.amazon.awssdk.services.cloudwatchlogs.model.FilteredLogEvent
import software.aws.toolkits.jetbrains.utils.BaseCoroutineTest
import software.aws.toolkits.jetbrains.utils.waitForModelToBeAtLeast
import software.aws.toolkits.jetbrains.utils.waitForTrue
import java.time.Duration

@ExperimentalCoroutinesApi
class LogStreamFilterActorTest : BaseCoroutineTest() {
    private lateinit var client: CloudWatchLogsClient
    private lateinit var tableModel: ListTableModel<LogStreamEntry>
    private lateinit var table: TableView<LogStreamEntry>
    private lateinit var actor: CloudWatchLogsActor<LogStreamEntry>

    @Before
    fun loadVariables() {
        client = mockClientManagerRule.create()
        tableModel = ListTableModel<LogStreamEntry>()
        table = TableView(tableModel)
        actor = LogStreamFilterActor(projectRule.project, client, table, "abc", "def")
    }

    @Test
    fun modelIsPopulated() {
        whenever(client.filterLogEvents(Mockito.any<FilterLogEventsRequest>()))
            .thenReturn(
                FilterLogEventsResponse
                    .builder()
                    .events(FilteredLogEvent.builder().message("message").build())
                    .build()
            )
        runBlocking {
            actor.channel.send(CloudWatchLogsActor.Message.LoadInitialFilter("filter query"))
            tableModel.waitForModelToBeAtLeast(1)
        }
        assertThat(tableModel.items.size).isOne()
        assertThat(tableModel.items.first().message).isEqualTo("message")
    }

    @Test
    fun loadingForwardAppendsToTable() {
        whenever(client.filterLogEvents(Mockito.any<FilterLogEventsRequest>()))
            .thenReturn(
                FilterLogEventsResponse
                    .builder()
                    .events(FilteredLogEvent.builder().message("message").build())
                    .nextToken("2")
                    .build()
            )
            .thenReturn(
                FilterLogEventsResponse
                    .builder()
                    .events(FilteredLogEvent.builder().message("message2").timestamp(2).build())
                    .nextToken("3")
                    .build()
            )
        runBlocking {
            actor.channel.send(CloudWatchLogsActor.Message.LoadInitialFilter("filter query"))
            actor.channel.send(CloudWatchLogsActor.Message.LoadForward)
            tableModel.waitForModelToBeAtLeast(2)
        }
        assertThat(tableModel.items).hasSize(2)
        assertThat(tableModel.items.first().message).isEqualTo("message")
        assertThat(tableModel.items.first().timestamp).isZero()
        assertThat(tableModel.items[1].message).isEqualTo("message2")
        assertThat(tableModel.items[1].timestamp).isEqualTo(2)
    }

    @Test
    fun loadingBackwardsDoesNothing() {
        whenever(client.filterLogEvents(Mockito.any<FilterLogEventsRequest>()))
            .thenReturn(
                FilterLogEventsResponse
                    .builder()
                    .events(FilteredLogEvent.builder().message("message").build())
                    .build()
            )
            .thenReturn(
                FilterLogEventsResponse
                    .builder()
                    .events(FilteredLogEvent.builder().message("message2").build())
                    .build()
            )
        runBlocking {
            actor.channel.send(CloudWatchLogsActor.Message.LoadInitialFilter("filter query"))
            actor.channel.send(CloudWatchLogsActor.Message.LoadBackward)
            actor.channel.send(CloudWatchLogsActor.Message.LoadBackward)
            tableModel.waitForModelToBeAtLeast(1)
        }
        assertThat(tableModel.items).hasSize(1)
        assertThat(tableModel.items.first().message).isEqualTo("message")
    }

    @Test
    fun writeChannelAndCoroutineIsDisposed() {
        val channel = actor.channel
        actor.dispose()
        assertThatThrownBy {
            runBlocking {
                channel.send(CloudWatchLogsActor.Message.LoadForward)
            }
        }.isInstanceOf(ClosedSendChannelException::class.java)
    }

    @Test
    fun loadInitialThrows() {
        runBlocking {
            actor.channel.send(CloudWatchLogsActor.Message.LoadInitial)
            waitForTrue { actor.channel.isClosedForSend }
        }
    }

    @Test
    fun loadInitialRangeThrows() {
        runBlocking {
            actor.channel.send(CloudWatchLogsActor.Message.LoadInitialRange(LogStreamEntry("@@@", 0), Duration.ofMillis(0)))
            waitForTrue { actor.channel.isClosedForSend }
        }
    }
}