Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Session 1: Apply Adevinta Spain template #2

Merged
merged 3 commits into from
Feb 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[*.{kt,kts,java,gradle,scala}]
indent_size=2
ij_continuation_indent_size=6
charset=utf-8
insert_final_newline=true
trim_trailing_whitespace=true
max_line_length=120

[*.{kt,kts}]
disabled_rules=import-ordering
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Sample used in Adevinta Spain's [Factoria F5](https://factoriaf5.org/) mastercla

1. Clone/fork this repo or create your own using [spring initializr](https://start.spring.io/#!type=gradle-project&language=kotlin&platformVersion=2.6.3&packaging=jar&jvmVersion=11&groupId=com.adevinta.factoriaf5&artifactId=HelloWorld&name=HelloWorld&description=Demo%20project%20for%20Spring%20Boot&packageName=com.adevinta.factoriaf5.HelloWorld)
2. [Setup Continuous Integration using Github Actions](https://github.com/AdevintaSpain/ms-test--factoriaf5-helloworld/pull/1)

3. [Apply Adevinta Spain MicroServices Template](https://github.com/AdevintaSpain/ms-test--factoriaf5-helloworld/pull/2)

## Run

Expand Down
69 changes: 55 additions & 14 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,34 +1,75 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
import org.gradle.api.tasks.testing.logging.TestLogEvent.*

plugins {
id("org.springframework.boot") version "2.6.3"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm") version "1.6.10"
kotlin("plugin.spring") version "1.6.10"
id("org.springframework.boot") version "2.5.6"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
id("org.unbroken-dome.test-sets") version "4.0.0"
kotlin("jvm") version "1.6.10"
kotlin("plugin.spring") version "1.6.10"
}

group = "com.adevinta.factoriaf5"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11

extra["springCloudVersion"] = "2020.0.4"

repositories {
mavenCentral()
mavenCentral()
}

testSets {
create("integrationTest") {}
}

dependencies {
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("org.springframework.boot:spring-boot-starter-test")
// Spring
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.cloud:spring-cloud-starter-bootstrap")

// Kotlin
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

// Api Docs
implementation("io.springfox:springfox-boot-starter:3.0.0")

// Test
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("com.nhaarman:mockito-kotlin:1.6.0")

// Integration Test
"integrationTestImplementation"("io.rest-assured:spring-mock-mvc:4.5.0")
"integrationTestImplementation"("org.testcontainers:testcontainers:1.16.3")
}

dependencyManagement {
imports {
mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}")
}
}

tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}

tasks.withType<Test> {
useJUnitPlatform()
useJUnitPlatform()
testLogging {
events(PASSED, SKIPPED, FAILED)
exceptionFormat = FULL
showExceptions = true
showCauses = true
showStackTraces = true
}
}

tasks["check"].dependsOn("integrationTest")
tasks["integrationTest"].shouldRunAfter("test")
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
rootProject.name = "HelloWorld"
rootProject.name = "ms-test--factoriaf5-helloworld"
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.adevinta.mstestfactoriaf5helloworld.infrastructure

import com.adevinta.mstestfactoriaf5helloworld.infrastructure.helper.DockerComposeHelper
import com.adevinta.mstestfactoriaf5helloworld.infrastructure.testcases.ApplicationTestCase
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Nested

class ApplicationIntegrationTest {
companion object {

private val dockerCompose = DockerComposeHelper()

@BeforeAll
@JvmStatic
fun dockerComposeUp() {
//dockerCompose.start()
}

@AfterAll
@JvmStatic
fun dockerComposeDown() {
//dockerCompose.stop()
}
}

@Nested
inner class Application : ApplicationTestCase()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.adevinta.mstestfactoriaf5helloworld.infrastructure.helper

import org.testcontainers.containers.DockerComposeContainer
import java.io.File

class DockerComposeHelper {

private val container: DockerComposeContainer<*>

init {
container = DockerComposeContainer<Nothing>(File("docker-compose.yml"))
.apply { withLocalCompose(false) }
}

fun start() {
container.start()
}

fun stop() {
container.stop()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.adevinta.mstestfactoriaf5helloworld.infrastructure.testcases

import com.adevinta.mstestfactoriaf5helloworld.infrastructure.Application
import io.restassured.module.mockmvc.RestAssuredMockMvc.given
import io.restassured.module.mockmvc.RestAssuredMockMvc.mockMvc
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.availability.AvailabilityChangeEvent.publish
import org.springframework.boot.availability.LivenessState
import org.springframework.boot.availability.ReadinessState
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.context.ApplicationEventPublisher
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

@SpringBootTest(
classes = [Application::class],
properties = ["spring.profiles.active=integration-test"]
)
@AutoConfigureMockMvc
abstract class ApplicationTestCase {

@Autowired
private lateinit var mvc: MockMvc

@Autowired
private lateinit var eventPublisher: ApplicationEventPublisher

@BeforeEach
fun setUp() {
mockMvc(mvc)
}

@AfterEach
fun tearDown() {
publish(eventPublisher, this, LivenessState.CORRECT)
publish(eventPublisher, this, ReadinessState.ACCEPTING_TRAFFIC)
}

@Test
fun `should be healthy`() {
given()
.`when`()
.get("/health")
.then()
.assertThat(status().isOk)
.body("status", equalTo("UP"))
}

@Test
fun `should say hello`() {
given()
.`when`()
.get("/hello")
.then()
.assertThat(status().isOk)
.body(equalTo("Hello Coders!!!"))
}

@Test
fun `should expose info`() {
given()
.`when`()
.get("/info")
.then()
.assertThat(status().isOk)
}

@Test
fun `should expose swagger`() {
given()
.`when`()
.get("/schema")
.then()
.assertThat(status().isOk)
.body("paths.'/hello'.get.summary", equalTo("sayHello"))
}

@Test
fun `should not expose shutdown endpoint`() {
given()
.`when`()
.get("/shutdown")
.then()
.assertThat(status().is4xxClientError)
}

@Test
fun `should be alive`() {
given()
.`when`()
.get("/health/liveness")
.then()
.assertThat(status().isOk)
.body("status", equalTo("UP"))
}

@Test
fun `should not be alive`() {
publish(eventPublisher, this, LivenessState.BROKEN)
given()
.`when`()
.get("/health/liveness")
.then()
.body("status", equalTo("DOWN"))
.assertThat(status().isServiceUnavailable)
}

@Test
fun `should be ready`() {
given()
.`when`()
.get("/health/readiness")
.then()
.assertThat(status().isOk)
.body("status", equalTo("UP"))
}

@Test
fun `should not be ready`() {
publish(eventPublisher, this, ReadinessState.REFUSING_TRAFFIC)
given()
.`when`()
.get("/health/readiness")
.then()
.body("status", equalTo("OUT_OF_SERVICE"))
.assertThat(status().isServiceUnavailable)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.adevinta.mstestfactoriaf5helloworld.infrastructure.configuration

import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest
import org.springframework.boot.actuate.health.HealthEndpoint
import org.springframework.boot.actuate.info.InfoEndpoint
import org.springframework.boot.actuate.metrics.MetricsEndpoint
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter

@Configuration
@EnableWebSecurity
class WebSecurityConfiguration : WebSecurityConfigurerAdapter() {

override fun configure(http: HttpSecurity?) {
http!!
.authorizeRequests()
.requestMatchers(EndpointRequest.to(MetricsEndpoint::class.java)).permitAll()
.requestMatchers(EndpointRequest.to(HealthEndpoint::class.java)).permitAll()
.requestMatchers(EndpointRequest.to(InfoEndpoint::class.java)).permitAll()
.requestMatchers(EndpointRequest.toAnyEndpoint()).authenticated()
.anyRequest().permitAll()
.and().httpBasic()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.adevinta.mstestfactoriaf5helloworld.infrastructure.controller

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/hello")
class HelloWorldController {
@GetMapping
@Suppress("FunctionOnlyReturningConstant")
fun sayHello(): String = "Hello Coders!!!"
}
Empty file.
25 changes: 25 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
spring:
main:
banner-mode: "OFF"
cloud-platform: kubernetes
profiles:
active: dev
security:
user:
name: "ac92fad6c"
password: "c2712584c"

server:
port: 8000
compression:
enabled: true
mime-types: application/json

management:
endpoints:
web:
base-path: /
exposure:
include: metrics,info,health

springfox.documentation.open-api.v3.path: /schema
6 changes: 6 additions & 0 deletions src/main/resources/bootstrap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
spring:
application:
name: ms-test--factoriaf5-helloworld
cloud:
config:
enabled: false
Loading