From 001aafead85bb6ba1e3193e9fccebadd9459aacc Mon Sep 17 00:00:00 2001 From: Wellyson Freitas Date: Thu, 8 Aug 2024 17:24:14 +0200 Subject: [PATCH] Fix bugs --- docker-compose.yml | 1 - pom.xml | 5 + .../messaging/config/MessagingConfig.kt | 2 +- .../fiap/payments/config/JWTSecurityConfig.kt | 6 + .../driver/database/config/DynamoDBConfig.kt | 8 +- .../consumer/RequestPaymentConsumer.kt | 1 - ...itional-spring-configuration-metadata.json | 5 - src/main/resources/application-live.yml | 10 ++ src/main/resources/application-local.yml | 12 +- src/main/resources/application-test.yml | 5 +- src/main/resources/application.yml | 24 +-- .../com/fiap/payments/TestAnnotations.kt | 8 + .../fiap/payments/it/BasicIntegrationTest.kt | 15 ++ .../it/LocalstackContainerInitializer.kt | 145 ++++++++++++++++++ terraform/secrets.tf | 8 +- terraform/sns.tf | 23 +++ terraform/sqs.tf | 29 ++++ 17 files changed, 268 insertions(+), 39 deletions(-) create mode 100644 src/main/resources/application-live.yml create mode 100644 src/test/kotlin/com/fiap/payments/it/BasicIntegrationTest.kt create mode 100644 src/test/kotlin/com/fiap/payments/it/LocalstackContainerInitializer.kt create mode 100644 terraform/sns.tf create mode 100644 terraform/sqs.tf diff --git a/docker-compose.yml b/docker-compose.yml index 49b5850..d82f197 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,6 @@ services: AWS_SECRET_ACCESS_KEY: "fakeaccesskey" AWS_DYNAMODB_ENDPOINT: http://paymentsdb:54000 AWS_REGION: us-east-2 - ADMIN_ACCESS_TOKEN: token MOCK_PAYMENT_PROVIDER: true MP_TOKEN: token MP_USER_ID: userId diff --git a/pom.xml b/pom.xml index 5f2ded1..5e77f1f 100644 --- a/pom.xml +++ b/pom.xml @@ -83,6 +83,11 @@ spring-boot-starter-test test + + org.testcontainers + localstack + test + org.springframework.boot spring-boot-starter-data-jpa diff --git a/src/main/kotlin/com/fiap/payments/adapter/messaging/config/MessagingConfig.kt b/src/main/kotlin/com/fiap/payments/adapter/messaging/config/MessagingConfig.kt index 1631848..768c35c 100644 --- a/src/main/kotlin/com/fiap/payments/adapter/messaging/config/MessagingConfig.kt +++ b/src/main/kotlin/com/fiap/payments/adapter/messaging/config/MessagingConfig.kt @@ -13,7 +13,7 @@ class MessagingConfig { @Bean fun createPaymentSender(snsTemplate: SnsTemplate, - @Value("\${topic.response-payment}") topicName: String, + @Value("\${sns.topics.response-payment}") topicName: String, objectMapper: ObjectMapper): PaymentSender { return PaymentSenderImpl(snsTemplate, topicName, objectMapper) } diff --git a/src/main/kotlin/com/fiap/payments/config/JWTSecurityConfig.kt b/src/main/kotlin/com/fiap/payments/config/JWTSecurityConfig.kt index 2e8c882..233283b 100644 --- a/src/main/kotlin/com/fiap/payments/config/JWTSecurityConfig.kt +++ b/src/main/kotlin/com/fiap/payments/config/JWTSecurityConfig.kt @@ -2,6 +2,7 @@ package com.fiap.payments.config import io.swagger.v3.oas.annotations.enums.SecuritySchemeType import io.swagger.v3.oas.annotations.security.SecurityScheme +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.http.HttpMethod @@ -19,6 +20,11 @@ import org.springframework.security.web.SecurityFilterChain bearerFormat = "JWT", scheme = "bearer" ) +@ConditionalOnProperty( + value = ["security.enable"], + havingValue = "true", + matchIfMissing = true +) class JWTSecurityConfig { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { diff --git a/src/main/kotlin/com/fiap/payments/driver/database/config/DynamoDBConfig.kt b/src/main/kotlin/com/fiap/payments/driver/database/config/DynamoDBConfig.kt index 2c005e8..c73e9c1 100644 --- a/src/main/kotlin/com/fiap/payments/driver/database/config/DynamoDBConfig.kt +++ b/src/main/kotlin/com/fiap/payments/driver/database/config/DynamoDBConfig.kt @@ -31,10 +31,10 @@ class DynamoDBConfig { * For local run. */ @Bean("amazonDynamoDB") - @ConditionalOnProperty("aws.dynamodb.local", havingValue = "true") + @ConditionalOnProperty("local", havingValue = "true") fun amazonDynamoDB( - @Value("\${aws.dynamodb.endpoint}") endpoint: String, - @Value("\${aws.dynamodb.region}") region: String, + @Value("\${spring.cloud.aws.endpoint}") endpoint: String, + @Value("\${spring.cloud.aws.region.static}") region: String, ): AmazonDynamoDB { return AmazonDynamoDBClientBuilder.standard() // using default credentials provider chain, which searches for environment variables @@ -53,7 +53,7 @@ class DynamoDBConfig { * we need to keep as v1 and include token provider to the chain. */ @Bean("amazonDynamoDB") - @ConditionalOnProperty("aws.dynamodb.local", havingValue = "false") + @ConditionalOnProperty("local", havingValue = "false") fun awsCredentialsProvider(): AmazonDynamoDB { return AmazonDynamoDBClientBuilder.standard() // AWS_WEB_IDENTITY_TOKEN_FILE is present. diff --git a/src/main/kotlin/com/fiap/payments/driver/messaging/consumer/RequestPaymentConsumer.kt b/src/main/kotlin/com/fiap/payments/driver/messaging/consumer/RequestPaymentConsumer.kt index cfaea70..461e539 100644 --- a/src/main/kotlin/com/fiap/payments/driver/messaging/consumer/RequestPaymentConsumer.kt +++ b/src/main/kotlin/com/fiap/payments/driver/messaging/consumer/RequestPaymentConsumer.kt @@ -4,7 +4,6 @@ import com.fiap.payments.driver.messaging.event.PaymentRequestEvent import com.fiap.payments.usecases.ProvidePaymentRequestUseCase import io.awspring.cloud.sqs.annotation.SqsListener import org.slf4j.LoggerFactory -import org.springframework.context.event.EventListener import org.springframework.messaging.MessageHeaders import org.springframework.messaging.handler.annotation.Headers import org.springframework.scheduling.annotation.EnableAsync diff --git a/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/src/main/resources/META-INF/additional-spring-configuration-metadata.json index b3a87de..d6d6750 100644 --- a/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -1,10 +1,5 @@ { "properties": [ - { - "name": "admin.access-token", - "type": "java.lang.String", - "description": "Description for admin-access-token." - }, { "name": "payment-provider.mock", "type": "java.lang.String", diff --git a/src/main/resources/application-live.yml b/src/main/resources/application-live.yml new file mode 100644 index 0000000..a78ab76 --- /dev/null +++ b/src/main/resources/application-live.yml @@ -0,0 +1,10 @@ +security: + enable: true + +spring: + security: + oauth2: + resourceserver: + jwt: + issuer-uri: ${COGNITO_ISSUER_URI} + jwk-set-uri: ${COGNITO_JWK_SET_URI} \ No newline at end of file diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 009f37f..607108d 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -1,8 +1,4 @@ -aws: - dynamodb: - local: true - endpoint: ${AWS_DYNAMODB_ENDPOINT} - region: ${AWS_REGION} +local: true payment-provider: mock: true @@ -21,9 +17,9 @@ sqs: queues: request-payment: request-payment_queue -topic: - response-payment: arn:aws:sns:us-east-2:000000000000:payment-response_topic - +sns: + topics: + response-payment: arn:aws:sns:us-east-2:000000000000:payment-response_topic server: port: 8081 diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 89b6211..f005990 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -1,5 +1,4 @@ -admin: - access-token: token +local: true payment-provider: - mock: true + mock: true \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f47a4cc..b376024 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,15 +1,6 @@ spring: application: name: payments - security: - oauth2: - resourceserver: - jwt: - issuer-uri: https://cognito-idp.us-east-1.amazonaws.com/us-east-1_ygM5FRn7D - jwk-set-uri: https://cognito-idp.us-east-1.amazonaws.com/us-east-1_ygM5FRn7D/.well-known/jwks.json - -admin: - access-token: ${ADMIN_ACCESS_TOKEN} payment-provider: mock: ${MOCK_PAYMENT_PROVIDER} @@ -23,9 +14,15 @@ mercadopago: posId: ${MP_POS_ID} webhookBaseUrl: ${MP_WEBHOOK_BASE_URL} -aws: - dynamodb: - local: false +local: false + +sqs: + queues: + request-payment: request-payment_queue + +sns: + topics: + response-payment: ${SNS_TOPIC_RESPONSE_PAYMENT_ARN} clients: orders-api: @@ -33,3 +30,6 @@ clients: server: port: 8082 + +security: + enable: false \ No newline at end of file diff --git a/src/test/kotlin/com/fiap/payments/TestAnnotations.kt b/src/test/kotlin/com/fiap/payments/TestAnnotations.kt index 2f5b2e8..7836345 100644 --- a/src/test/kotlin/com/fiap/payments/TestAnnotations.kt +++ b/src/test/kotlin/com/fiap/payments/TestAnnotations.kt @@ -1,9 +1,17 @@ package com.fiap.payments +import com.fiap.payments.it.LocalStackContainerInitializer import org.junit.jupiter.api.Tag +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.ContextConfiguration @Tag("IntegrationTest") @ActiveProfiles("test") @Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) annotation class IntegrationTest + +@ContextConfiguration(initializers = [LocalStackContainerInitializer::class]) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) +annotation class WithLocalstack \ No newline at end of file diff --git a/src/test/kotlin/com/fiap/payments/it/BasicIntegrationTest.kt b/src/test/kotlin/com/fiap/payments/it/BasicIntegrationTest.kt new file mode 100644 index 0000000..6a52904 --- /dev/null +++ b/src/test/kotlin/com/fiap/payments/it/BasicIntegrationTest.kt @@ -0,0 +1,15 @@ +package com.fiap.payments.it + +import com.fiap.payments.IntegrationTest +import com.fiap.payments.WithLocalstack +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest + +@IntegrationTest +@WithLocalstack +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class BasicIntegrationTest { + + @Test + fun contextLoads() { } +} \ No newline at end of file diff --git a/src/test/kotlin/com/fiap/payments/it/LocalstackContainerInitializer.kt b/src/test/kotlin/com/fiap/payments/it/LocalstackContainerInitializer.kt new file mode 100644 index 0000000..a4e943b --- /dev/null +++ b/src/test/kotlin/com/fiap/payments/it/LocalstackContainerInitializer.kt @@ -0,0 +1,145 @@ +package com.fiap.payments.it + +import org.springframework.boot.test.util.TestPropertyValues +import org.springframework.context.ApplicationContextInitializer +import org.springframework.context.ConfigurableApplicationContext +import org.testcontainers.containers.localstack.LocalStackContainer +import org.testcontainers.containers.wait.strategy.Wait.forListeningPort +import org.testcontainers.utility.DockerImageName +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.sns.SnsClient +import software.amazon.awssdk.services.sns.model.CreateTopicRequest +import software.amazon.awssdk.services.sns.model.SubscribeRequest +import software.amazon.awssdk.services.sqs.SqsClient +import software.amazon.awssdk.services.sqs.model.CreateQueueRequest +import software.amazon.awssdk.services.sqs.model.GetQueueAttributesRequest +import software.amazon.awssdk.services.sqs.model.QueueAttributeName + +class LocalStackContainerInitializer : + ApplicationContextInitializer, + LocalStackContainer( + DockerImageName.parse("localstack/localstack") + ) { + + companion object { + private val instance: LocalStackContainer = LocalStackContainerInitializer() + .withServices( + Service.DYNAMODB, + Service.SQS, + Service.SNS, + ) + .withEnv( + mapOf( + "DEBUG" to "1", + "DEFAULT_REGION" to "us-east-1", + ) + ) + .waitingFor(forListeningPort()) + } + + override fun initialize(configurableApplicationContext: ConfigurableApplicationContext) { + instance.start() + + val awsCredentials = AwsBasicCredentials.create( + instance.accessKey, + instance.secretKey + ) + + val region = Region.of(instance.region) + + val sqsClient = SqsClient.builder() + .endpointOverride(instance.getEndpointOverride(Service.SQS)) + .region(region) + .credentialsProvider(StaticCredentialsProvider.create(awsCredentials)) + .build() + + val snsClient = SnsClient.builder() + .endpointOverride(instance.getEndpointOverride(Service.SNS)) + .region(region) + .credentialsProvider(StaticCredentialsProvider.create(awsCredentials)) + .build() + + createQueues(sqsClient) + createTopics(snsClient) + subscribeToTopics(snsClient, sqsClient) + + TestPropertyValues.of( + "spring.cloud.aws.endpoint=${instance.endpoint}", + "spring.cloud.aws.credentials.access-key=${instance.accessKey}", + "spring.cloud.aws.credentials.secret-key=${instance.secretKey}", + "spring.cloud.aws.region.static=${instance.region}", + "sns.topics.response-payment=${getTopicArn(snsClient, "payment-response_topic")}", + ).applyTo(configurableApplicationContext) + } + + private fun createQueues(sqsClient: SqsClient) { + val queues = listOf("request-payment_queue", "payment-response_queue") + + queues.forEach { queue -> + val dlqName = "${queue}_dlq" + sqsClient.createQueue(CreateQueueRequest.builder().queueName(dlqName).build()) + println("DLQ queue [$dlqName] created") + + val dlqArn = getQueueArn(sqsClient, dlqName) + + val redrivePolicy = """{"deadLetterTargetArn":"$dlqArn","maxReceiveCount":"3"}""" + + sqsClient.createQueue( + CreateQueueRequest.builder() + .queueName(queue) + .attributes( + mapOf( + QueueAttributeName.DELAY_SECONDS to "5", + QueueAttributeName.REDRIVE_POLICY to redrivePolicy + ) + ) + .build() + ) + println("Queue [$queue] created") + } + } + + private fun createTopics(snsClient: SnsClient) { + val topics = listOf("request-payment_topic", "payment-response_topic") + + topics.forEach { topic -> + snsClient.createTopic(CreateTopicRequest.builder().name(topic).build()) + println("Topic [$topic] created") + } + } + + private fun getQueueArn(sqsClient: SqsClient, queueName: String) = + sqsClient.getQueueAttributes( + GetQueueAttributesRequest.builder() + .queueUrl(sqsClient.getQueueUrl { it.queueName(queueName) }.queueUrl()) + .attributeNames(QueueAttributeName.QUEUE_ARN) + .build() + ).attributes()[QueueAttributeName.QUEUE_ARN] + + private fun getTopicArn(snsClient: SnsClient, topicName: String) = + snsClient.createTopic { it.name(topicName) }.topicArn() + + private fun subscribeToTopics(snsClient: SnsClient, sqsClient: SqsClient) { + val subscriptions = listOf( + Pair("request-payment_topic", "request-payment_queue"), + Pair("payment-response_topic", "payment-response_queue") + ) + + subscriptions.forEach { (topicName, queueName) -> + val topicArn = getTopicArn(snsClient, topicName) + val queueArn = getQueueArn(sqsClient, queueName) + + snsClient.subscribe( + SubscribeRequest.builder() + .topicArn(topicArn) + .protocol("sqs") + .endpoint(queueArn) + .attributes(mapOf("RawMessageDelivery" to "true")) + .build() + ) + println("Queue [$queueName] subscribed to Topic [$topicName]") + } + } +} diff --git a/terraform/secrets.tf b/terraform/secrets.tf index 78473ce..b3c7f47 100644 --- a/terraform/secrets.tf +++ b/terraform/secrets.tf @@ -9,10 +9,10 @@ module "secrets_manager" { # For security reasons, insert values manually after apply secret_string = jsonencode({ - token = null - userId = null - posId = null - webhookBaseUrl = null + token = "" + userId = "" + posId = "" + webhookBaseUrl = "" }) tags = var.tags diff --git a/terraform/sns.tf b/terraform/sns.tf new file mode 100644 index 0000000..f8c3f53 --- /dev/null +++ b/terraform/sns.tf @@ -0,0 +1,23 @@ +resource "aws_sns_topic" "request_payment_topic" { + name = "request-payment_topic" +} + +resource "aws_sns_topic" "payment_response_topic" { + name = "payment-response_topic" +} + +resource "aws_sns_topic_subscription" "request_payment_subscription" { + topic_arn = aws_sns_topic.request_payment_topic.arn + protocol = "sqs" + endpoint = aws_sqs_queue.request_payment_queue.arn + + raw_message_delivery = true +} + +resource "aws_sns_topic_subscription" "payment_response_subscription" { + topic_arn = aws_sns_topic.payment_response_topic.arn + protocol = "sqs" + endpoint = aws_sqs_queue.payment_response_queue.arn + + raw_message_delivery = true +} \ No newline at end of file diff --git a/terraform/sqs.tf b/terraform/sqs.tf new file mode 100644 index 0000000..6c77a9d --- /dev/null +++ b/terraform/sqs.tf @@ -0,0 +1,29 @@ +resource "aws_sqs_queue" "request_payment_dlq" { + name = "request-payment_queue_dlq" +} + +resource "aws_sqs_queue" "payment_response_dlq" { + name = "payment-response_queue_dlq" +} + +resource "aws_sqs_queue" "request_payment_queue" { + name = "request-payment_queue" + + redrive_policy = jsonencode({ + deadLetterTargetArn = aws_sqs_queue.request_payment_dlq.arn + maxReceiveCount = 3 + }) + + delay_seconds = 5 +} + +resource "aws_sqs_queue" "payment_response_queue" { + name = "payment-response_queue" + + redrive_policy = jsonencode({ + deadLetterTargetArn = aws_sqs_queue.payment_response_dlq.arn + maxReceiveCount = 3 + }) + + delay_seconds = 5 +}